From d76dc3c092ce0669473fe5f82ee8019fc530b608 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 30 Nov 2023 15:46:41 +0000 Subject: [PATCH 001/288] Add info about examples --- README.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 0e40f4ba..49a82709 100644 --- a/README.rst +++ b/README.rst @@ -27,17 +27,18 @@ Documentation See the READMEs for the early example applications: -`ASRC Demo `_ + * `ASRC Demo `_: an example design for an asynchronous sampling rate converter (ASRC) -`FFD `_ + * `FFD `_: two example designs for far-field voice local control, each example contains a boot image and data partition binary -`FFVA `_ + * `FFVA `_: two example designs for far-field voice assistant example design, each example contains a boot image and data partition binary -`Low Power FFD `_ + * `Low Power FFD `_: one example design for a low-power far-field voice local control, the example contains a boot image and data partition binary -`Microphone Aggregator `_ -`Speech Recognition `_ + * `Microphone Aggregator `_: an example design for a bridge between 16 PDM microphones to either TDM16 slave or USB Audio + + * `Speech Recognition `_: an example design for automatic speech recognition Getting Help ************ From ce67d67f492975653446ce6c9998ccf1a365316b Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 30 Nov 2023 15:52:10 +0000 Subject: [PATCH 002/288] Remove 'speech_recognition' from list --- README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 49a82709..ec7e7f58 100644 --- a/README.rst +++ b/README.rst @@ -37,9 +37,7 @@ See the READMEs for the early example applications: * `Microphone Aggregator `_: an example design for a bridge between 16 PDM microphones to either TDM16 slave or USB Audio - - * `Speech Recognition `_: an example design for automatic speech recognition - + Getting Help ************ From fa014ec218a1721128fde57327c71233b31c6ae0 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 30 Nov 2023 15:53:57 +0000 Subject: [PATCH 003/288] Minor changes --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index ec7e7f58..b0ccaa62 100644 --- a/README.rst +++ b/README.rst @@ -29,11 +29,11 @@ See the READMEs for the early example applications: * `ASRC Demo `_: an example design for an asynchronous sampling rate converter (ASRC) - * `FFD `_: two example designs for far-field voice local control, each example contains a boot image and data partition binary + * `FFD `_: two example designs for a far-field voice local control, each example contains a boot image and data partition binary - * `FFVA `_: two example designs for far-field voice assistant example design, each example contains a boot image and data partition binary + * `FFVA `_: two example designs for a far-field voice assistant example design, each example contains a boot image and data partition binary - * `Low Power FFD `_: one example design for a low-power far-field voice local control, the example contains a boot image and data partition binary + * `Low Power FFD `_: an example design for a low-power far-field voice local control, the example contains a boot image and data partition binary * `Microphone Aggregator `_: an example design for a bridge between 16 PDM microphones to either TDM16 slave or USB Audio From 0d92d70e28fae35f1a01cf909c4abf8aae9331cd Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 1 Dec 2023 12:03:12 +0000 Subject: [PATCH 004/288] Add extra files to firmware package and add info in QSG --- README.rst | 3 +-- doc/quick_start_guide/index.rst | 4 +++- tools/ci/build_examples.sh | 5 +++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index b0ccaa62..c6b1ee91 100644 --- a/README.rst +++ b/README.rst @@ -35,9 +35,8 @@ See the READMEs for the early example applications: * `Low Power FFD `_: an example design for a low-power far-field voice local control, the example contains a boot image and data partition binary - * `Microphone Aggregator `_: an example design for a bridge between 16 PDM microphones to either TDM16 slave or USB Audio - + Getting Help ************ diff --git a/doc/quick_start_guide/index.rst b/doc/quick_start_guide/index.rst index 762fcaf6..3afcd4a5 100644 --- a/doc/quick_start_guide/index.rst +++ b/doc/quick_start_guide/index.rst @@ -18,5 +18,7 @@ XCORE-VOICE Quick Start Guide Other examples ************** -Where no quickstart guide exists such as for :ref:`Microphone Aggregation ` and :ref:`Automatic Speech Recognition ` please constult the :ref:`Programming Guide ` which contains setup information for these applications. +Where no quickstart guide exists such as for :ref:`Microphone Aggregation `, +:ref:`Asynchronous Sample Rate Conversion ` and :ref:`Automatic Speech Recognition with Cyberon library `, +please consult the :ref:`Programming Guide ` which contains setup information for these applications. diff --git a/tools/ci/build_examples.sh b/tools/ci/build_examples.sh index 4c0106ec..d94896de 100755 --- a/tools/ci/build_examples.sh +++ b/tools/ci/build_examples.sh @@ -74,4 +74,9 @@ for ((i = 0; i < ${#examples[@]}; i += 1)); do (cd ${path}/build_${board}; log_errors $CI_BUILD_TOOL make_data_partition_${app_target} $CI_BUILD_TOOL_ARGS) (cd ${path}/build_${board}; cp ${app_target}_data_partition.bin ${DIST_DIR}) fi + echo '******************************************************' + echo '* Add extra files' + echo '******************************************************' + cd ${path}; cp LICENSE.rst ${DIST_DIR} + cd ${path}; cp README.rst ${DIST_DIR} done From a970127cde6422d81772c801550e0390163dd1b4 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 1 Dec 2023 12:44:16 +0000 Subject: [PATCH 005/288] Repackage docs --- Jenkinsfile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 056366b5..41266bd4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -285,7 +285,16 @@ pipeline { --rm \ -v ${WORKSPACE}:/build \ ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" - archiveArtifacts artifacts: 'doc/_build/**', allowEmptyArchive: true + // Zip all the generated files + zip dir: "doc/_build/", zipFile: "xcore_voice_docs_original.zip" + // Rename latex folder as pdf + sh "mv doc/_build/latex doc/_build/pdf" + // Remove linkcheck folder + sh "rm -rf doc/_build/linkcheck" + // Zip all the generated files + zip dir: "doc/_build/", zipFile: "xcore_voice_docs_release.zip" + // Archive doc files + archiveArtifacts artifacts: "xcore_voice_docs*.zip" } } } From 74be503dbd730a94c616d5bf370cbbaa2c7ee338 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 1 Dec 2023 12:48:00 +0000 Subject: [PATCH 006/288] Remove basic ASR example, as it is not maintained --- doc/programming_guide/example_designs.rst | 1 - doc/programming_guide/ffd/software_description.rst | 1 - doc/programming_guide/ffd/speech_recognition_cyberon.rst | 3 ++- doc/programming_guide/ffd/speech_recognition_sensory.rst | 5 +++-- doc/programming_guide/low_power_ffd/speech_recognition.rst | 7 ++++--- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/programming_guide/example_designs.rst b/doc/programming_guide/example_designs.rst index b7a7c66b..c33fb9b2 100644 --- a/doc/programming_guide/example_designs.rst +++ b/doc/programming_guide/example_designs.rst @@ -9,6 +9,5 @@ Example Designs ffd/ffd low_power_ffd/low_power_ffd ffva/ffva - asr/asr mic_aggregator/mic_aggregator asrc/asrc diff --git a/doc/programming_guide/ffd/software_description.rst b/doc/programming_guide/ffd/software_description.rst index bf3ae4ca..3d18d42a 100644 --- a/doc/programming_guide/ffd/software_description.rst +++ b/doc/programming_guide/ffd/software_description.rst @@ -9,7 +9,6 @@ Software Description :maxdepth: 1 software_desc/overview - ../asr/asr software_desc/bsp_config software_desc/ext software_desc/filesystem_support diff --git a/doc/programming_guide/ffd/speech_recognition_cyberon.rst b/doc/programming_guide/ffd/speech_recognition_cyberon.rst index 7f01b92a..feec43a7 100644 --- a/doc/programming_guide/ffd/speech_recognition_cyberon.rst +++ b/doc/programming_guide/ffd/speech_recognition_cyberon.rst @@ -22,7 +22,8 @@ The Cyberon DSpotter™ speech recognition engine runs proprietary models to ide One model for US English is provided. For any technical questions or additional models please contact Cyberon. -To replace the Cyberon engine with a different engine, refer to the ASR documentation on :ref:`sln_voice_asr_programming_guide` +.. TODO: Check if the line below can be removed or re-added +.. To sln_voice_asr_programming_guide the Cyberon engine with a different engine, refer to the ASR documentation on :ref:`sln_voice_asr_programming_guide` Dictionary command table ======================== diff --git a/doc/programming_guide/ffd/speech_recognition_sensory.rst b/doc/programming_guide/ffd/speech_recognition_sensory.rst index fe5e4f1e..8d7b5f5e 100644 --- a/doc/programming_guide/ffd/speech_recognition_sensory.rst +++ b/doc/programming_guide/ffd/speech_recognition_sensory.rst @@ -17,7 +17,7 @@ or 107 recognition events. Overview ======== -The Sensory THF speech recognition engine runs proprietary models to identify keywords in an audio stream. Models can be generated using `VoiceHub `__. +The Sensory THF speech recognition engine runs proprietary models to identify keywords in an audio stream. Models can be generated using `VoiceHub `__. Two models are provided - one in US English and one in Mainland Mandarin. The US English model is used by default. To modify the software to use the Mandarin model, see the comment at the top of the ``ffd_sensory.cmake`` file. Make sure run the following commands to rebuild and re-flash the data partition: @@ -26,7 +26,8 @@ Two models are provided - one in US English and one in Mainland Mandarin. The US make clean make flash_app_example_ffd_sensory -j -To replace the Sensory engine with a different engine, refer to the ASR documentation on :ref:`sln_voice_asr_programming_guide` +.. TODO: Check if the line below can be removed or re-added +.. To replace the Sensory engine with a different engine, refer to the ASR documentation on :ref:`sln_voice_asr_programming_guide` Dictionary command table ======================== diff --git a/doc/programming_guide/low_power_ffd/speech_recognition.rst b/doc/programming_guide/low_power_ffd/speech_recognition.rst index 760d1cd1..74e49f16 100644 --- a/doc/programming_guide/low_power_ffd/speech_recognition.rst +++ b/doc/programming_guide/low_power_ffd/speech_recognition.rst @@ -17,7 +17,7 @@ or 107 recognition events. Overview ======== -The Sensory THF speech recognition engine runs proprietary models to identify keywords in an audio stream. Models can be generated using `VoiceHub `__. +The Sensory THF speech recognition engine runs proprietary models to identify keywords in an audio stream. Models can be generated using `VoiceHub `__. Two models are provided for the purpose of Low Power FFD. The small wake word model running on tile 1 is approximately 67KB. The command model running on tile 0 is approximately 289KB. On tile 1, the @@ -37,13 +37,14 @@ To run with a different model, see the ``Set Sensory model variables`` section o Make sure run the following commands to rebuild and re-flash the data partition: .. code-block:: console - + make clean make flash_app_example_low_power_ffd -j You may also wish to modify the command ID-to-string lookup table which is located in the ``src/intent_engine/intent_engine_io.c`` source file. -To replace the Sensory engine with a different engine, refer to the ASR documentation on :ref:`sln_voice_asr_programming_guide` +.. TODO: Check if the line below can be removed or re-added +.. To replace the Sensory engine with a different engine, refer to the ASR documentation on :ref:`sln_voice_asr_programming_guide` Wake Word Dictionary ==================== From 0b2fe4af03b75e54da99fa721a4c51a3499a84b6 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 1 Dec 2023 14:58:42 +0000 Subject: [PATCH 007/288] Review comments --- README.rst | 4 ++-- doc/programming_guide/ffd/speech_recognition_cyberon.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index c6b1ee91..4bd74826 100644 --- a/README.rst +++ b/README.rst @@ -31,11 +31,11 @@ See the READMEs for the early example applications: * `FFD `_: two example designs for a far-field voice local control, each example contains a boot image and data partition binary - * `FFVA `_: two example designs for a far-field voice assistant example design, each example contains a boot image and data partition binary + * `FFVA `_: two example designs for a far-field voice assistant, each example contains a boot image and data partition binary * `Low Power FFD `_: an example design for a low-power far-field voice local control, the example contains a boot image and data partition binary - * `Microphone Aggregator `_: an example design for a bridge between 16 PDM microphones to either TDM16 slave or USB Audio + * `Microphone Aggregator `_: two example designs bridging 16 PDM microphones to either TDM16 slave or USB Audio Getting Help ************ diff --git a/doc/programming_guide/ffd/speech_recognition_cyberon.rst b/doc/programming_guide/ffd/speech_recognition_cyberon.rst index feec43a7..c58c06f4 100644 --- a/doc/programming_guide/ffd/speech_recognition_cyberon.rst +++ b/doc/programming_guide/ffd/speech_recognition_cyberon.rst @@ -23,7 +23,7 @@ The Cyberon DSpotter™ speech recognition engine runs proprietary models to ide One model for US English is provided. For any technical questions or additional models please contact Cyberon. .. TODO: Check if the line below can be removed or re-added -.. To sln_voice_asr_programming_guide the Cyberon engine with a different engine, refer to the ASR documentation on :ref:`sln_voice_asr_programming_guide` +.. To replace the Cyberon engine with a different engine, refer to the ASR documentation on :ref:`sln_voice_asr_programming_guide` Dictionary command table ======================== From bb7d3af67c3ddbef752c3baa437b9bff3544351c Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Dec 2023 12:09:03 +0000 Subject: [PATCH 008/288] Update readme --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 4bd74826..dba7632e 100644 --- a/README.rst +++ b/README.rst @@ -2,14 +2,14 @@ XCORE:registered:-VOICE Solution Repository ******************************************* -The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). The XCORE-VOICE design is currently based on FreeRTOS, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. +The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). Most of the XCORE-VOICE designs are based on FreeRTOS, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. The only exception is the Microphone Aggregation example which runs on bare-metal . XCORE-VOICE example designs provide turn-key solutions to enable easier product development for smart home applications such as light switches, thermostats, and home appliances. xcore.ai’s unique architecture providing powerful signal processing and accelerated AI capabilities combined with the XCORE-VOICE framework allows designers to incorporate keyword, event detection, or advanced local dictionary support to create a complete voice interface solution. -Cloning -******* +Obtaining the Source Code +************************* -Some dependent components are included as git submodules. These can be obtained by cloning this repository with the following command: +If you are interested in compiling and/or modifying the source code, you can download it from GitHub. As some dependent components are included as git submodules, the following command must be used for cloning this repository: :: From f0a150e9f1aa6627c616390189d4dbe01af07a59 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Dec 2023 12:11:25 +0000 Subject: [PATCH 009/288] Update readme --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index dba7632e..be6bd307 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ XCORE:registered:-VOICE Solution Repository ******************************************* -The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). Most of the XCORE-VOICE designs are based on FreeRTOS, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. The only exception is the Microphone Aggregation example which runs on bare-metal . +The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). Most of the XCORE-VOICE designs are based on FreeRTOS, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. The only exception is the Microphone Aggregation example which runs on bare-metal; this architecture was selected to compensate the lack of RTOS drivers for some components. XCORE-VOICE example designs provide turn-key solutions to enable easier product development for smart home applications such as light switches, thermostats, and home appliances. xcore.ai’s unique architecture providing powerful signal processing and accelerated AI capabilities combined with the XCORE-VOICE framework allows designers to incorporate keyword, event detection, or advanced local dictionary support to create a complete voice interface solution. From a9944f714c894847ddc1459a6b36e17d5d4d533d Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Dec 2023 13:34:41 +0000 Subject: [PATCH 010/288] Update sentence --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index be6bd307..8efbeda1 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ XCORE:registered:-VOICE Solution Repository ******************************************* -The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). Most of the XCORE-VOICE designs are based on FreeRTOS, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. The only exception is the Microphone Aggregation example which runs on bare-metal; this architecture was selected to compensate the lack of RTOS drivers for some components. +The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). Most of the XCORE-VOICE designs are based on FreeRTOS, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. The only exception is the Microphone Aggregation example which runs on bare-metal; this architecture was selected as it suits high-bandwidth and low latency bridging type applications. XCORE-VOICE example designs provide turn-key solutions to enable easier product development for smart home applications such as light switches, thermostats, and home appliances. xcore.ai’s unique architecture providing powerful signal processing and accelerated AI capabilities combined with the XCORE-VOICE framework allows designers to incorporate keyword, event detection, or advanced local dictionary support to create a complete voice interface solution. From 6c00eed2bd3df9543228581c08627008074127fb Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Dec 2023 13:35:15 +0000 Subject: [PATCH 011/288] Update sentence --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8efbeda1..fe30ea47 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ XCORE:registered:-VOICE Solution Repository ******************************************* -The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). Most of the XCORE-VOICE designs are based on FreeRTOS, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. The only exception is the Microphone Aggregation example which runs on bare-metal; this architecture was selected as it suits high-bandwidth and low latency bridging type applications. +The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). Most of the XCORE-VOICE designs are based on FreeRTOS, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. The only exception is the Microphone Aggregation example which runs on bare-metal; this architecture was selected as it suits high IO bandwidth and low latency bridging type applications. XCORE-VOICE example designs provide turn-key solutions to enable easier product development for smart home applications such as light switches, thermostats, and home appliances. xcore.ai’s unique architecture providing powerful signal processing and accelerated AI capabilities combined with the XCORE-VOICE framework allows designers to incorporate keyword, event detection, or advanced local dictionary support to create a complete voice interface solution. From 235448649aff47b106666cb6ab2a6b8589719867 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Dec 2023 21:14:30 +0000 Subject: [PATCH 012/288] Update links to latex folder in html docs --- Jenkinsfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 41266bd4..7738c4e7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -289,6 +289,9 @@ pipeline { zip dir: "doc/_build/", zipFile: "xcore_voice_docs_original.zip" // Rename latex folder as pdf sh "mv doc/_build/latex doc/_build/pdf" + // Update links to latex folder in html files + sh "find doc_/build/html -type f -exec sed -i -e 's/latex\/sln_voice_programming_guide_/pdf\/sln_voice_programming_guide_/g' {} \;" + sh "find doc_/build/html -type f -exec sed -i -e 's/latex\/sln_voice_quick_start_guide_/pdf\/sln_voice_quick_start_guide_/g' {} \;" // Remove linkcheck folder sh "rm -rf doc/_build/linkcheck" // Zip all the generated files From e56f72356655e1a7f2c702a661b7e94a6b395237 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Wed, 6 Dec 2023 08:04:46 +0000 Subject: [PATCH 013/288] Ret double slashes --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7738c4e7..5e3cf39a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -290,8 +290,8 @@ pipeline { // Rename latex folder as pdf sh "mv doc/_build/latex doc/_build/pdf" // Update links to latex folder in html files - sh "find doc_/build/html -type f -exec sed -i -e 's/latex\/sln_voice_programming_guide_/pdf\/sln_voice_programming_guide_/g' {} \;" - sh "find doc_/build/html -type f -exec sed -i -e 's/latex\/sln_voice_quick_start_guide_/pdf\/sln_voice_quick_start_guide_/g' {} \;" + sh "find doc_/build/html -type f -exec sed -i -e 's/latex\\/sln_voice_programming_guide_/pdf\\/sln_voice_programming_guide_/g' {} \;" + sh "find doc_/build/html -type f -exec sed -i -e 's/latex\\/sln_voice_quick_start_guide_/pdf\\/sln_voice_quick_start_guide_/g' {} \;" // Remove linkcheck folder sh "rm -rf doc/_build/linkcheck" // Zip all the generated files From 8d3f5e04d83450d88c0589e75b7c7e4712b9aa36 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Wed, 6 Dec 2023 08:06:01 +0000 Subject: [PATCH 014/288] Use double slashes --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5e3cf39a..d11a914a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -290,8 +290,8 @@ pipeline { // Rename latex folder as pdf sh "mv doc/_build/latex doc/_build/pdf" // Update links to latex folder in html files - sh "find doc_/build/html -type f -exec sed -i -e 's/latex\\/sln_voice_programming_guide_/pdf\\/sln_voice_programming_guide_/g' {} \;" - sh "find doc_/build/html -type f -exec sed -i -e 's/latex\\/sln_voice_quick_start_guide_/pdf\\/sln_voice_quick_start_guide_/g' {} \;" + sh "find doc_/build/html -type f -exec sed -i -e 's/latex\\/sln_voice_programming_guide_/pdf\\/sln_voice_programming_guide_/g' {} \\;" + sh "find doc_/build/html -type f -exec sed -i -e 's/latex\\/sln_voice_quick_start_guide_/pdf\\/sln_voice_quick_start_guide_/g' {} \\;" // Remove linkcheck folder sh "rm -rf doc/_build/linkcheck" // Zip all the generated files From b23907f29b586912b7cd143d0283af8363581cc2 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Wed, 6 Dec 2023 08:16:47 +0000 Subject: [PATCH 015/288] Fix paths --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d11a914a..dfbd5bcd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -290,8 +290,8 @@ pipeline { // Rename latex folder as pdf sh "mv doc/_build/latex doc/_build/pdf" // Update links to latex folder in html files - sh "find doc_/build/html -type f -exec sed -i -e 's/latex\\/sln_voice_programming_guide_/pdf\\/sln_voice_programming_guide_/g' {} \\;" - sh "find doc_/build/html -type f -exec sed -i -e 's/latex\\/sln_voice_quick_start_guide_/pdf\\/sln_voice_quick_start_guide_/g' {} \\;" + sh "find doc/_build/html -type f -exec sed -i -e 's/latex\\/sln_voice_programming_guide_/pdf\\/sln_voice_programming_guide_/g' {} \\;" + sh "find doc/_build/html -type f -exec sed -i -e 's/latex\\/sln_voice_quick_start_guide_/pdf\\/sln_voice_quick_start_guide_/g' {} \\;" // Remove linkcheck folder sh "rm -rf doc/_build/linkcheck" // Zip all the generated files From 74206f4643a9aede9f29393b581837968a1c5c11 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 29 Feb 2024 13:01:33 +0000 Subject: [PATCH 016/288] First changes --- .../platform/platform_conf.h | 6 +- .../XK_VOICE_L71/platform/platform_init.c | 12 ++ .../XK_VOICE_L71/platform/platform_start.c | 6 +- examples/ffva/ffva.cmake | 5 + examples/ffva/ffva_int.cmake | 147 +++++++++++++++--- examples/ffva/src/FreeRTOSConfig.h | 4 +- examples/ffva/src/app_conf.h | 66 +++++++- examples/ffva/src/main.c | 17 +- .../ffva/src/ww_model_runner/model_runner.c | 118 +++++++++++++- .../src/ww_model_runner/ww_model_runner.c | 24 ++- .../src/ww_model_runner/ww_model_runner.h | 2 + modules/asr/Cyberon/DbgTrace.c | 2 + .../reference/fixed_delay/audio_pipeline_t0.c | 8 +- .../reference/fixed_delay/audio_pipeline_t1.c | 6 +- 14 files changed, 384 insertions(+), 39 deletions(-) diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h index b1b3ea35..fd593d18 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h @@ -188,14 +188,14 @@ #ifndef BOARD_QSPI_SPEC /* Set up a default SPI spec if the app has not provided * one explicitly. - * Note: The version checks only work in XTC Tools >15.2.0 - * By default FL_QUADDEVICE_W25Q64JW is used + * Note: The version checks only work in XTC Tools >15.2.0 + * By default FL_QUADDEVICE_W25Q64JW is used */ #ifdef __XMOS_XTC_VERSION_MAJOR__ #if (__XMOS_XTC_VERSION_MAJOR__ == 15) \ && (__XMOS_XTC_VERSION_MINOR__ >= 2) \ && (__XMOS_XTC_VERSION_PATCH__ >= 0) -/* In XTC >15.2.0 some SFDP support enables a generic +/* In XTC >15.2.0 some SFDP support enables a generic * default spec */ #define BOARD_QSPI_SPEC FL_QUADDEVICE_DEFAULT diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index e549b294..36d5b168 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -25,6 +25,18 @@ static void mclk_init(chanend_t other_tile_c) static void flash_init(void) { #if ON_TILE(FLASH_TILE_NO) + rtos_qspi_flash_fast_read_init( + qspi_flash_ctx, + FLASH_CLKBLK, + PORT_SQI_CS, + PORT_SQI_SCLK, + PORT_SQI_SIO, + NULL, + qspi_fast_flash_read_transfer_nibble_swap, + 3, + QSPI_FLASH_CALIBRATION_ADDRESS); +#endif +#if 0 //ON_TILE(FLASH_TILE_NO) fl_QuadDeviceSpec qspi_spec = BOARD_QSPI_SPEC; fl_QSPIPorts qspi_ports = { .qspiCS = PORT_SQI_CS, diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index dcb10e46..7ab795a1 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -39,9 +39,11 @@ static void gpio_start(void) static void flash_start(void) { #if ON_TILE(FLASH_TILE_NO) - uint32_t flash_core_map = ~((1 << appconfUSB_INTERRUPT_CORE) | (1 << appconfUSB_SOF_INTERRUPT_CORE)); rtos_qspi_flash_start(qspi_flash_ctx, appconfQSPI_FLASH_TASK_PRIORITY); - rtos_qspi_flash_op_core_affinity_set(qspi_flash_ctx, flash_core_map); + + //uint32_t flash_core_map = ~((1 << appconfUSB_INTERRUPT_CORE) | (1 << appconfUSB_SOF_INTERRUPT_CORE)); + //rtos_qspi_flash_start(qspi_flash_ctx, appconfQSPI_FLASH_TASK_PRIORITY); + //rtos_qspi_flash_op_core_affinity_set(qspi_flash_ctx, flash_core_map); #endif } diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index fb0fd4f6..5424652d 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -1,6 +1,10 @@ #********************** # Gather Sources #********************** + +set(MODEL_LANGUAGE "english_usa") +set(CYBERON_COMMAND_NET_FILE "${CMAKE_CURRENT_LIST_DIR}/../ffd/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap") + file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src @@ -36,6 +40,7 @@ set(APP_COMPILE_DEFINITIONS set(APP_LINK_OPTIONS -lquadspi -report + -lotp3 ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope ) diff --git a/examples/ffva/ffva_int.cmake b/examples/ffva/ffva_int.cmake index 17af9838..268bd15d 100644 --- a/examples/ffva/ffva_int.cmake +++ b/examples/ffva/ffva_int.cmake @@ -1,3 +1,36 @@ +#********************** +# QSPI Flash Layout +#********************** +set(BOOT_PARTITION_SIZE 0x100000) +set(FILESYSTEM_SIZE_KB 1024) +math(EXPR FILESYSTEM_SIZE_BYTES + "1024 * ${FILESYSTEM_SIZE_KB}" + OUTPUT_FORMAT HEXADECIMAL +) + +set(CALIBRATION_PATTERN_START_ADDRESS ${BOOT_PARTITION_SIZE}) + +math(EXPR FILESYSTEM_START_ADDRESS + "${CALIBRATION_PATTERN_START_ADDRESS} + ${LIB_QSPI_FAST_READ_DEFAULT_CAL_SIZE_BYTES}" + OUTPUT_FORMAT HEXADECIMAL +) + +math(EXPR MODEL_START_ADDRESS + "${FILESYSTEM_START_ADDRESS} + ${FILESYSTEM_SIZE_BYTES}" + OUTPUT_FORMAT HEXADECIMAL +) + +set(CALIBRATION_PATTERN_DATA_PARTITION_OFFSET 0) + +math(EXPR FILESYSTEM_DATA_PARTITION_OFFSET + "${CALIBRATION_PATTERN_DATA_PARTITION_OFFSET} + ${LIB_QSPI_FAST_READ_DEFAULT_CAL_SIZE_BYTES}" + OUTPUT_FORMAT DECIMAL +) + +math(EXPR MODEL_DATA_PARTITION_OFFSET + "${FILESYSTEM_DATA_PARTITION_OFFSET} + ${FILESYSTEM_SIZE_BYTES}" + OUTPUT_FORMAT DECIMAL +) set(FFVA_INT_COMPILE_DEFINITIONS ${APP_COMPILE_DEFINITIONS} @@ -7,7 +40,11 @@ set(FFVA_INT_COMPILE_DEFINITIONS appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 - + configENABLE_DEBUG_PRINTF=1 + QSPI_FLASH_FILESYSTEM_START_ADDRESS=${FILESYSTEM_START_ADDRESS} + QSPI_FLASH_MODEL_START_ADDRESS=${MODEL_START_ADDRESS} + QSPI_FLASH_CALIBRATION_ADDRESS=${CALIBRATION_PATTERN_START_ADDRESS} + ASR_CYBERON=1 MIC_ARRAY_CONFIG_MCLK_FREQ=12288000 ) @@ -30,6 +67,7 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) ${APP_COMMON_LINK_LIBRARIES} sln_voice::app::ffva::xk_voice_l71 sln_voice::app::ffva::ap::${FFVA_AP} + sln_voice::app::asr::Cyberon ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) @@ -49,6 +87,7 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) ${APP_COMMON_LINK_LIBRARIES} sln_voice::app::ffva::xk_voice_l71 sln_voice::app::ffva::ap::${FFVA_AP} + sln_voice::app::asr::Cyberon ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) @@ -69,53 +108,115 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) #********************** set(TARGET_NAME example_ffva_int_${FFVA_AP}) set(DATA_PARTITION_FILE ${TARGET_NAME}_data_partition.bin) + set(MODEL_FILE ${TARGET_NAME}_model.bin) set(FATFS_FILE ${TARGET_NAME}_fat.fs) - set(FATFS_CONTENTS_DIR ${TARGET_NAME}_fatmktmp) - - add_custom_target( - ${FATFS_FILE} ALL - COMMAND ${CMAKE_COMMAND} -E rm -rf ${FATFS_CONTENTS_DIR}/fs/ - COMMAND ${CMAKE_COMMAND} -E make_directory ${FATFS_CONTENTS_DIR}/fs/ - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/demo.txt ${FATFS_CONTENTS_DIR}/fs/ - COMMAND fatfs_mkimage --input=${FATFS_CONTENTS_DIR} --output=${FATFS_FILE} + set(FLASH_CAL_FILE ${LIB_QSPI_FAST_READ_ROOT_PATH}/lib_qspi_fast_read/calibration_pattern_nibble_swap.bin) + #set(FATFS_CONTENTS_DIR ${TARGET_NAME}_fatmktmp) + + + add_custom_target(${MODEL_FILE} ALL + COMMAND ${CMAKE_COMMAND} -E copy ${CYBERON_COMMAND_NET_FILE} ${MODEL_FILE} COMMENT - "Create filesystem" + "Copy Cyberon NET file" VERBATIM ) - set_target_properties(${FATFS_FILE} PROPERTIES - ADDITIONAL_CLEAN_FILES ${FATFS_CONTENTS_DIR} - ) + create_filesystem_target( + #[[ Target ]] ${TARGET_NAME} + #[[ Input Directory ]] ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/${MODEL_LANGUAGE} + #[[ Image Size ]] ${FILESYSTEM_SIZE_BYTES} + ) - # The filesystem is the only component in the data partition, copy it to - # the assocated data partition file which is required for CI. add_custom_command( OUTPUT ${DATA_PARTITION_FILE} - COMMAND ${CMAKE_COMMAND} -E copy ${FATFS_FILE} ${DATA_PARTITION_FILE} + COMMAND ${CMAKE_COMMAND} -E rm -f ${DATA_PARTITION_FILE} + COMMAND datapartition_mkimage -v -b 1 + -i ${FLASH_CAL_FILE}:${CALIBRATION_PATTERN_DATA_PARTITION_OFFSET} ${FATFS_FILE}:${FILESYSTEM_DATA_PARTITION_OFFSET} ${MODEL_FILE}:${MODEL_DATA_PARTITION_OFFSET} + -o ${DATA_PARTITION_FILE} DEPENDS - ${FATFS_FILE} + ${MODEL_FILE} + make_fs_${TARGET_NAME} + ${FLASH_CAL_FILE} COMMENT "Create data partition" VERBATIM ) - list(APPEND DATA_PARTITION_FILE_LIST + set(DATA_PARTITION_FILE_LIST + ${DATA_PARTITION_FILE} + ${MODEL_FILE} ${FATFS_FILE} + ${FLASH_CAL_FILE} + ) + + set(DATA_PARTITION_DEPENDS_LIST ${DATA_PARTITION_FILE} + ${MODEL_FILE} + make_fs_${TARGET_NAME} ) + # The list of files to copy and the dependency list for populating + # the data partition folder are identical. create_data_partition_directory( #[[ Target ]] ${TARGET_NAME} #[[ Copy Files ]] "${DATA_PARTITION_FILE_LIST}" - #[[ Dependencies ]] "${DATA_PARTITION_FILE_LIST}" + #[[ Dependencies ]] "${DATA_PARTITION_DEPENDS_LIST}" ) create_flash_app_target( - #[[ Target ]] ${TARGET_NAME} - #[[ Boot Partition Size ]] 0x100000 - #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} - #[[ Dependencies ]] ${DATA_PARTITION_FILE} + #[[ Target ]] ${TARGET_NAME} + #[[ Boot Partition Size ]] ${BOOT_PARTITION_SIZE} + #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} + #[[ Dependencies ]] ${DATA_PARTITION_FILE} ) unset(DATA_PARTITION_FILE_LIST) + unset(DATA_PARTITION_DEPENDS_LIST) + + #add_custom_target( + # ${FATFS_FILE} ALL + # COMMAND ${CMAKE_COMMAND} -E rm -rf ${FATFS_CONTENTS_DIR}/fs/ + # COMMAND ${CMAKE_COMMAND} -E make_directory ${FATFS_CONTENTS_DIR}/fs/ + # COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/demo.txt ${FATFS_CONTENTS_DIR}/fs/ + # COMMAND fatfs_mkimage --input=${FATFS_CONTENTS_DIR} --output=${FATFS_FILE} + # COMMENT + # "Create filesystem" + # VERBATIM + #) + + #set_target_properties(${FATFS_FILE} PROPERTIES + # ADDITIONAL_CLEAN_FILES ${FATFS_CONTENTS_DIR} + #) + + # The filesystem is the only component in the data partition, copy it to + # the assocated data partition file which is required for CI. + #add_custom_command( + # OUTPUT ${DATA_PARTITION_FILE} + # COMMAND ${CMAKE_COMMAND} -E copy ${FATFS_FILE} ${DATA_PARTITION_FILE} + # DEPENDS + # ${FATFS_FILE} + # COMMENT + # "Create data partition" + # VERBATIM + #) + + #list(APPEND DATA_PARTITION_FILE_LIST + # ${FATFS_FILE} + # ${DATA_PARTITION_FILE} + #) + + #create_data_partition_directory( + # #[[ Target ]] ${TARGET_NAME} + # #[[ Copy Files ]] "${DATA_PARTITION_FILE_LIST}" + # #[[ Dependencies ]] "${DATA_PARTITION_FILE_LIST}" + #) + + #create_flash_app_target( + # #[[ Target ]] ${TARGET_NAME} + # #[[ Boot Partition Size ]] 0x100000 + # #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} + # #[[ Dependencies ]] ${DATA_PARTITION_FILE} + #) + + #unset(DATA_PARTITION_FILE_LIST) endforeach() diff --git a/examples/ffva/src/FreeRTOSConfig.h b/examples/ffva/src/FreeRTOSConfig.h index 5bbec8d7..97312deb 100644 --- a/examples/ffva/src/FreeRTOSConfig.h +++ b/examples/ffva/src/FreeRTOSConfig.h @@ -46,10 +46,10 @@ your application. */ #define configSUPPORT_STATIC_ALLOCATION 0 #define configSUPPORT_DYNAMIC_ALLOCATION 1 #if ON_TILE(0) -#define configTOTAL_HEAP_SIZE 128*1024 +#define configTOTAL_HEAP_SIZE 150*1024 #endif #if ON_TILE(1) -#define configTOTAL_HEAP_SIZE 128*1024 +#define configTOTAL_HEAP_SIZE 150*1024 #endif #define configAPPLICATION_ALLOCATED_HEAP 0 diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 4d9c860d..d7eedbc6 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -28,6 +28,70 @@ /* If in channel sample format, appconfAUDIO_PIPELINE_FRAME_ADVANCE == MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME*/ #define appconfAUDIO_PIPELINE_FRAME_ADVANCE MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME +/* Enable audio response output */ +#ifndef appconfAUDIO_PLAYBACK_ENABLED +#define appconfAUDIO_PLAYBACK_ENABLED 1 +#endif + +/* Intent Engine Configuration */ +#define appconfINTENT_FRAME_BUFFER_MULT (8*2) /* total buffer size is this value * MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME */ +#define appconfINTENT_SAMPLE_BLOCK_LENGTH 240 + +/* Enable inference engine */ +#ifndef appconfINTENT_ENABLED +#define appconfINTENT_ENABLED 1 +#endif + +/* Maximum delay between a wake up phrase and command phrase */ +#ifndef appconfINTENT_RESET_DELAY_MS +#if appconfAUDIO_PLAYBACK_ENABLED +#define appconfINTENT_RESET_DELAY_MS 5000 +#else +#define appconfINTENT_RESET_DELAY_MS 4000 +#endif +#endif + +/* Output raw inferences, if set to 0, a state machine requires a wake up phrase + * before a command phrase */ +#ifndef appconfINTENT_RAW_OUTPUT +#define appconfINTENT_RAW_OUTPUT 0 +#endif + +/* Maximum number of detected intents to hold */ +#ifndef appconfINTENT_QUEUE_LEN +#define appconfINTENT_QUEUE_LEN 10 +#endif + +/* External wakeup pin edge on intent found. 0 for rising edge, 1 for falling edge */ +#ifndef appconfINTENT_WAKEUP_EDGE_TYPE +#define appconfINTENT_WAKEUP_EDGE_TYPE 0 +#endif + +/* Delay between external wakeup pin edge and intent output */ +#ifndef appconfINTENT_TRANSPORT_DELAY_MS +#define appconfINTENT_TRANSPORT_DELAY_MS 50 +#endif + +#ifndef appconfINTENT_I2C_OUTPUT_ENABLED +#define appconfINTENT_I2C_OUTPUT_ENABLED 1 +#endif + +#ifndef appconfINTENT_I2C_OUTPUT_DEVICE_ADDR +#define appconfINTENT_I2C_OUTPUT_DEVICE_ADDR 0x01 +#endif + +#ifndef appconfINTENT_UART_OUTPUT_ENABLED +#define appconfINTENT_UART_OUTPUT_ENABLED 1 +#endif + +#ifndef appconfUART_BAUD_RATE +#define appconfUART_BAUD_RATE 9600 +#endif + +#ifndef appconfINTENT_ENGINE_READY_SYNC_PORT +#define appconfINTENT_ENGINE_READY_SYNC_PORT 15 +#endif /* appconfINTENT_ENGINE_READY_SYNC_PORT */ + /** * A positive delay will delay mics * A negative delay will delay ref @@ -133,7 +197,7 @@ #include "app_conf_check.h" /* WW Config */ -#define appconfWW_FRAMES_PER_INFERENCE (160) +#define appconfWW_FRAMES_PER_INFERENCE (240) /* I/O and interrupt cores for Tile 0 */ /* Note, USB and SPI are mutually exclusive */ diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 697881bd..ccf0c3c8 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -24,7 +24,7 @@ #include "audio_pipeline.h" #include "ww_model_runner/ww_model_runner.h" #include "fs_support.h" - +#include "print.h" #include "gpio_test/gpio_test.h" volatile int mic_from_usb = appconfMIC_SRC_DEFAULT; @@ -147,7 +147,7 @@ int audio_pipeline_output(void *output_app_data, size_t frame_count) { (void) output_app_data; - + printintln(777); #if appconfI2S_ENABLED #if appconfI2S_MODE == appconfI2S_MODE_MASTER #if !appconfI2S_TDM_ENABLED @@ -336,13 +336,22 @@ void startup_task(void *arg) gpio_test(gpio_ctx_t0); #endif +#if appconfINTENT_ENABLED && !ON_TILE(WW_TILE_NO) + // Wait until the intent engine is initialized before starting the + // audio pipeline. + intent_engine_ready_sync(); +#endif + audio_pipeline_init(NULL, NULL); #if ON_TILE(FS_TILE_NO) rtos_fatfs_init(qspi_flash_ctx); - rtos_dfu_image_print_debug(dfu_image_ctx); + //rtos_dfu_image_print_debug(dfu_image_ctx); + // Setup flash low-level mode + // NOTE: must call rtos_qspi_flash_fast_read_shutdown_ll to use non low-level mode calls + rtos_qspi_flash_fast_read_setup_ll(qspi_flash_ctx); #endif - +vTaskDelay(pdMS_TO_TICKS(100)); #if appconfWW_ENABLED && ON_TILE(WW_TILE_NO) ww_task_create(appconfWW_TASK_PRIORITY); #endif diff --git a/examples/ffva/src/ww_model_runner/model_runner.c b/examples/ffva/src/ww_model_runner/model_runner.c index 6ce4a6bd..2d1eb1a2 100644 --- a/examples/ffva/src/ww_model_runner/model_runner.c +++ b/examples/ffva/src/ww_model_runner/model_runner.c @@ -8,25 +8,81 @@ #include "FreeRTOS.h" #include "task.h" #include "stream_buffer.h" +#include "device_memory_impl.h" +#include "asr.h" #include "app_conf.h" #include "platform/driver_instances.h" #include "ww_model_runner/ww_model_runner.h" +#if ASR_SENSORY + #define IS_KEYWORD(id) (id == 17) + #define IS_COMMAND(id) (id > 0 && id != 17) +#elif ASR_CYBERON + #define IS_KEYWORD(id) (id == 1) + #define IS_COMMAND(id) (id >= 2) +#else +#error "Model has to be either Sensory or Cyberon" +#endif + +#define SAMPLES_PER_ASR (appconfINTENT_SAMPLE_BLOCK_LENGTH) + configSTACK_DEPTH_TYPE model_runner_manager_stack_size = 287; +// SEARCH model file is specified in the CMakeLists SENSORY_COMMAND_SEARCH_SOURCE_FILE variable +#ifdef COMMAND_SEARCH_SOURCE_FILE +extern const unsigned short gs_grammarLabel[]; +void* grammar = (void*)gs_grammarLabel; +#else +void* grammar = NULL; +#endif + +// Model file is in flash at the offset specified in the CMakeLists +// QSPI_FLASH_MODEL_START_ADDRESS variable. The XS1_SWMEM_BASE value needs +// to be added so the address in in the SwMem range. +uint16_t *model = (uint16_t *) (XS1_SWMEM_BASE + QSPI_FLASH_MODEL_START_ADDRESS); +static asr_port_t asr_ctx; +static devmem_manager_t devmem_ctx; + +#pragma stackfunction 1000 void model_runner_manager(void *args) { StreamBufferHandle_t input_queue = (StreamBufferHandle_t)args; - int16_t buf[appconfWW_FRAMES_PER_INFERENCE]; + //int16_t buf[appconfWW_FRAMES_PER_INFERENCE]; /* Perform any initialization here */ + //intent_state = STATE_EXPECTING_WAKEWORD; + + /*TimerHandle_t int_eng_tmr = xTimerCreate( + "int_eng_tmr", + pdMS_TO_TICKS(appconfINTENT_RESET_DELAY_MS), + pdFALSE, + NULL, + vIntentTimerCallback); + */ + devmem_init(&devmem_ctx); + printf("Call asr_init(). model = 0x%x, grammar = 0x%x\n", (unsigned int) model, (unsigned int) grammar); + asr_ctx = asr_init((int32_t *)model, (int32_t *)grammar, &devmem_ctx); + + int32_t buf[appconfINTENT_SAMPLE_BLOCK_LENGTH] = {0}; + int16_t buf_short[SAMPLES_PER_ASR] = {0}; + + asr_reset(asr_ctx); + + /* Alert other tile to start the audio pipeline */ + intent_engine_ready_sync(); + + size_t buf_short_index = 0; + asr_error_t asr_error; + asr_result_t asr_result; + int word_id; while (1) { /* Receive audio frames */ uint8_t *buf_ptr = (uint8_t*)buf; + printintln(111); size_t buf_len = appconfWW_FRAMES_PER_INFERENCE * sizeof(int16_t); do { size_t bytes_rxed = xStreamBufferReceive(input_queue, @@ -36,7 +92,67 @@ void model_runner_manager(void *args) buf_len -= bytes_rxed; buf_ptr += bytes_rxed; } while(buf_len > 0); + printintln(222); + for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { + buf_short[buf_short_index++] = buf[i] >> 16; + } + if (buf_short_index < SAMPLES_PER_ASR) + continue; + + buf_short_index = 0; // reset the offset into the buffer of int16s. + // Note, we do not need to overlap the window of samples. + // This is handled in the ASR ports. + + // this application does not support barge-in + // so, we need to check if an audio response is playing and skip to the next + // audio frame because the playback may trigger the ASR. + //if (intent_handler_response_playing()) continue; + + asr_error = asr_process(asr_ctx, buf_short, SAMPLES_PER_ASR); + + if (asr_error == ASR_EVALUATION_EXPIRED) { + //led_indicate_end_of_eval(); + continue; + } + if (asr_error != ASR_OK) continue; + + asr_error = asr_get_result(asr_ctx, &asr_result); + if (asr_error != ASR_OK) continue; + + word_id = asr_result.id; + + if (!IS_KEYWORD(word_id) && !IS_COMMAND(word_id)) continue; + +/* + #if appconfINTENT_RAW_OUTPUT + intent_engine_process_asr_result(word_id); + #else + if (intent_state == STATE_EXPECTING_WAKEWORD && IS_KEYWORD(word_id)) { + led_indicate_listening(); + xTimerStart(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + intent_state = STATE_EXPECTING_COMMAND; + } else if (intent_state == STATE_EXPECTING_COMMAND && IS_COMMAND(word_id)) { + xTimerReset(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + intent_state = STATE_PROCESSING_COMMAND; + } else if (intent_state == STATE_EXPECTING_COMMAND && IS_KEYWORD(word_id)) { + xTimerReset(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + // remain in STATE_EXPECTING_COMMAND state + } else if (intent_state == STATE_PROCESSING_COMMAND && IS_KEYWORD(word_id)) { + xTimerReset(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + intent_state = STATE_EXPECTING_COMMAND; + } else if (intent_state == STATE_PROCESSING_COMMAND && IS_COMMAND(word_id)) { + xTimerReset(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + // remain in STATE_PROCESSING_COMMAND state + } + #endif + } +*/ /* Perform inference here */ // rtos_printf("inference\n"); } diff --git a/examples/ffva/src/ww_model_runner/ww_model_runner.c b/examples/ffva/src/ww_model_runner/ww_model_runner.c index 49ae7107..335bd61c 100644 --- a/examples/ffva/src/ww_model_runner/ww_model_runner.c +++ b/examples/ffva/src/ww_model_runner/ww_model_runner.c @@ -32,16 +32,21 @@ void ww_audio_send(rtos_intertile_t *intertile_ctx, for (int i = 0; i < frame_count; i++) { ww_samples[i] = (uint16_t)(processed_audio_frame[i][ASR_CHANNEL] >> 16); } - + printintln(555); if(audio_stream != NULL) { if (xStreamBufferSend(audio_stream, ww_samples, sizeof(ww_samples), 0) != sizeof(ww_samples)) { rtos_printf("lost output samples for ww\n"); } } + + /*for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { + buf_short[(*buf_short_index)++] = buf[i] >> 16; + }*/ } void ww_task_create(unsigned priority) { + audio_stream = xStreamBufferCreate(2 * appconfAUDIO_PIPELINE_FRAME_ADVANCE, appconfWW_FRAMES_PER_INFERENCE); @@ -53,4 +58,21 @@ void ww_task_create(unsigned priority) NULL); } +void intent_engine_ready_sync(void) +{ + int sync = 0; +#if ON_TILE(WW_TILE_NO) + printintln(123); + size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); + xassert(len == sizeof(sync)); + rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); + printintln(124); + +#else + printintln(125); + + rtos_intertile_tx(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, &sync, sizeof(sync)); +#endif +} + #endif diff --git a/examples/ffva/src/ww_model_runner/ww_model_runner.h b/examples/ffva/src/ww_model_runner/ww_model_runner.h index f55d5b2f..756393f0 100644 --- a/examples/ffva/src/ww_model_runner/ww_model_runner.h +++ b/examples/ffva/src/ww_model_runner/ww_model_runner.h @@ -14,4 +14,6 @@ void ww_audio_send(rtos_intertile_t *intertile_ctx, void model_runner_manager(void *args); +void intent_engine_ready_sync(void); + #endif /* WW_MODEL_RUNNER_H_ */ diff --git a/modules/asr/Cyberon/DbgTrace.c b/modules/asr/Cyberon/DbgTrace.c index 617660f2..2c7ba8db 100644 --- a/modules/asr/Cyberon/DbgTrace.c +++ b/modules/asr/Cyberon/DbgTrace.c @@ -13,7 +13,9 @@ void DbgTrace(const char *lpszFormat, ...) if (n >= 0 && n < 256) { rtos_printf(szTemp); +#ifdef UART_DUMP_RECORD rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)szTemp, strlen(szTemp)); +#endif } va_end(args); } diff --git a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c index 434b426c..5c2b85a8 100644 --- a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c +++ b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c @@ -143,8 +143,8 @@ void audio_pipeline_init( void *input_app_data, void *output_app_data) { + printintln(611); const int stage_count = 3; - const pipeline_stage_t stages[] = { (pipeline_stage_t)stage_vnr_and_ic, (pipeline_stage_t)stage_ns, @@ -156,9 +156,13 @@ void audio_pipeline_init( configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(stage_ns), configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(stage_agc) + RTOS_THREAD_STACK_SIZE(audio_pipeline_output_i), }; + printintln(612); initialize_pipeline_stages(); + printintln(613); + + generic_pipeline_init((pipeline_input_t)audio_pipeline_input_i, (pipeline_output_t)audio_pipeline_output_i, input_app_data, @@ -167,6 +171,8 @@ void audio_pipeline_init( (const size_t*) stage_stack_sizes, appconfAUDIO_PIPELINE_TASK_PRIORITY, stage_count); + printintln(614); + } #endif /* ON_TILE(0)*/ diff --git a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c index 2bb96def..b8565046 100644 --- a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c +++ b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c @@ -152,7 +152,7 @@ void audio_pipeline_init( void *output_app_data) { const int stage_count = 2; - + printintln(456); const pipeline_stage_t stages[] = { (pipeline_stage_t)stage_delay, (pipeline_stage_t)stage_aec, @@ -162,8 +162,10 @@ void audio_pipeline_init( configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(stage_delay) + RTOS_THREAD_STACK_SIZE(audio_pipeline_input_i), configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(stage_aec) + RTOS_THREAD_STACK_SIZE(audio_pipeline_output_i), }; + printintln(457); initialize_pipeline_stages(); + printintln(458); generic_pipeline_init((pipeline_input_t)audio_pipeline_input_i, (pipeline_output_t)audio_pipeline_output_i, @@ -173,5 +175,7 @@ void audio_pipeline_init( (const size_t*) stage_stack_sizes, appconfAUDIO_PIPELINE_TASK_PRIORITY, stage_count); + printintln(459); + } #endif /* ON_TILE(1) */ From 51873818359adbe4c8a93d65a619f157a7e5884a Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 29 Feb 2024 13:02:04 +0000 Subject: [PATCH 017/288] Add files --- .../ffva/filesystem_support/english_usa/1.wav | Bin 0 -> 12044 bytes .../filesystem_support/english_usa/10.wav | Bin 0 -> 21644 bytes .../filesystem_support/english_usa/11.wav | Bin 0 -> 33018 bytes .../filesystem_support/english_usa/12.wav | Bin 0 -> 32986 bytes .../filesystem_support/english_usa/13.wav | Bin 0 -> 27526 bytes .../filesystem_support/english_usa/14.wav | Bin 0 -> 24524 bytes .../filesystem_support/english_usa/15.wav | Bin 0 -> 30236 bytes .../filesystem_support/english_usa/16.wav | Bin 0 -> 31484 bytes .../filesystem_support/english_usa/17.wav | Bin 0 -> 33220 bytes .../filesystem_support/english_usa/18.wav | Bin 0 -> 32326 bytes .../ffva/filesystem_support/english_usa/3.wav | Bin 0 -> 30842 bytes .../ffva/filesystem_support/english_usa/4.wav | Bin 0 -> 27884 bytes .../ffva/filesystem_support/english_usa/5.wav | Bin 0 -> 26904 bytes .../filesystem_support/english_usa/50.wav | Bin 0 -> 20204 bytes .../ffva/filesystem_support/english_usa/6.wav | Bin 0 -> 28860 bytes .../ffva/filesystem_support/english_usa/7.wav | Bin 0 -> 30218 bytes .../ffva/filesystem_support/english_usa/8.wav | Bin 0 -> 31710 bytes .../ffva/filesystem_support/english_usa/9.wav | Bin 0 -> 24618 bytes .../mandarin_mainland/1.wav | Bin 0 -> 12044 bytes .../mandarin_mainland/10.wav | Bin 0 -> 18464 bytes .../mandarin_mainland/11.wav | Bin 0 -> 31256 bytes .../mandarin_mainland/12.wav | Bin 0 -> 30796 bytes .../mandarin_mainland/13.wav | Bin 0 -> 30796 bytes .../mandarin_mainland/14.wav | Bin 0 -> 29670 bytes .../mandarin_mainland/15.wav | Bin 0 -> 30796 bytes .../mandarin_mainland/16.wav | Bin 0 -> 31716 bytes .../mandarin_mainland/17.wav | Bin 0 -> 29670 bytes .../mandarin_mainland/18.wav | Bin 0 -> 29926 bytes .../mandarin_mainland/3.wav | Bin 0 -> 30796 bytes .../mandarin_mainland/4.wav | Bin 0 -> 27418 bytes .../mandarin_mainland/5.wav | Bin 0 -> 29926 bytes .../mandarin_mainland/50.wav | Bin 0 -> 20204 bytes .../mandarin_mainland/6.wav | Bin 0 -> 30590 bytes .../mandarin_mainland/7.wav | Bin 0 -> 30232 bytes .../mandarin_mainland/8.wav | Bin 0 -> 32382 bytes .../mandarin_mainland/9.wav | Bin 0 -> 20050 bytes examples/ffva/src/device_memory_impl.c | 68 ++++++++++++++++++ examples/ffva/src/device_memory_impl.h | 10 +++ 38 files changed, 78 insertions(+) create mode 100644 examples/ffva/filesystem_support/english_usa/1.wav create mode 100644 examples/ffva/filesystem_support/english_usa/10.wav create mode 100644 examples/ffva/filesystem_support/english_usa/11.wav create mode 100644 examples/ffva/filesystem_support/english_usa/12.wav create mode 100644 examples/ffva/filesystem_support/english_usa/13.wav create mode 100644 examples/ffva/filesystem_support/english_usa/14.wav create mode 100644 examples/ffva/filesystem_support/english_usa/15.wav create mode 100644 examples/ffva/filesystem_support/english_usa/16.wav create mode 100644 examples/ffva/filesystem_support/english_usa/17.wav create mode 100644 examples/ffva/filesystem_support/english_usa/18.wav create mode 100644 examples/ffva/filesystem_support/english_usa/3.wav create mode 100644 examples/ffva/filesystem_support/english_usa/4.wav create mode 100644 examples/ffva/filesystem_support/english_usa/5.wav create mode 100644 examples/ffva/filesystem_support/english_usa/50.wav create mode 100644 examples/ffva/filesystem_support/english_usa/6.wav create mode 100644 examples/ffva/filesystem_support/english_usa/7.wav create mode 100644 examples/ffva/filesystem_support/english_usa/8.wav create mode 100644 examples/ffva/filesystem_support/english_usa/9.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/1.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/10.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/11.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/12.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/13.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/14.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/15.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/16.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/17.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/18.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/3.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/4.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/5.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/50.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/6.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/7.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/8.wav create mode 100644 examples/ffva/filesystem_support/mandarin_mainland/9.wav create mode 100644 examples/ffva/src/device_memory_impl.c create mode 100644 examples/ffva/src/device_memory_impl.h diff --git a/examples/ffva/filesystem_support/english_usa/1.wav b/examples/ffva/filesystem_support/english_usa/1.wav new file mode 100644 index 0000000000000000000000000000000000000000..ac6c951a129614064c2aa5413ea56e7ce803e70a GIT binary patch literal 12044 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 AmjD0& literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/10.wav b/examples/ffva/filesystem_support/english_usa/10.wav new file mode 100644 index 0000000000000000000000000000000000000000..e98715cb48d44d399fb60a7ba5bb312120b63e68 GIT binary patch literal 21644 zcmW(+1z1%}+nt@KgYNDYurL6-yRp05YwNX>YqwX$Yj+2BAxJ2oba%t)+A;rp|Fa)- z$2oK6jhR_%&AZ1A88~pycmO8#nK)p<@^zj{004%L9kJ+S8vr;^f_byo&7Orm!$KU8 z=-M8*0~g>60zoJU29fCKjE-m!1A2qLAPx*fe*-{2Fc^#iW575t6U+uHQDfV{Ki~>@ z3Z8>BPzbc39T1op^TvF!U@RKzgN?w3VZ*WU|9=d{2BJIhSOj`*g%MaQ_ztdvJzzZW z04?wyTn!^&Eq|FG&rA7t+&V6dYhmBA2iWOsEE~f5vw>`Pb_~0N{lMzjc-f zP52Ogfh7<_c8>wOz-u7H=3w`+Ow0vWE*Km z^`c6tUv!4xrtpoZr)0NuhK!Ll$?T<5MK1+I=<#?B6K&-6H*}jj@|sc_mo@)toLT#% zW?W-=%cbT)%}Ui1qn#z+G>KV-#|UoHJf#wOD=sTnEA)ze)`>PBto~9=koS{45ziDP zk_osANaeOOLFPrqTLw3SNq0`tnPm4pXD60dO)J_j>`-QW|X%}-)K#>ZeoGX=k<3QpH{CUjM5S*F zZkygFZy(yRRefG(ZMw?bgc5R+uu<|yKEo=(X1VQH+h`kKtI_g8aTz@Wx8q(JduaD` zNLou9AJ=cMZ)$kiByWA)@mv>XnG4sEsiGA5b?bw6MGo&AXE}D*@3fs}6(hSZB*_PG zAM?QYL$_aJPz_UESE-qPB7)LeXtMIN0Eo@mSTlfu~n#5szM;omC&MmY8P(dPcUaqHinzp3F^BY zt?dCF*&PD)F70o_b4xc^PmrQ7vRtcV+eUldVYNfGoz!Ns;+gmc6%B4$?DVkXTg&st zvif`Vj~mamF4C+r-@!a3w{35`ZT5Q@;?l_}D!-E`q{Q7teh_>&4rp0d;PTD(ebQ&o zZ+kz|AL$=&3m(?uDmVVO!oll8L`|OzjJzIZv9c8HaXq-*W-ee>WA$DHcFggul4BV&jm}u6T&}*oCw(JGu-2ZbC?|~ z+d!RT=V}|8sw@49UjOc!(Iv<;TaUCTTHkm08h>MFWvd*wdd&+k1RnM+b$lW+@c7{6aNu{tmBVP;gSQ_7|9s*H=jua}0`Eor-NI12_yZrgc! zj0hYY=^3*-)*(JQ_CT-ln9W@qq6USJ4Y2k|vAZJLVH#a?A=5AU*TW6>^X~Dt<8Qxw zB=}I97L#d{lU_DKb(QjQi|;aUNcRbQ5^58kj8_j_+htxrp?jobh%%RMX12ATFH20{ zogDe-1ik;A>@R@dGiXUgeRWoDn>K4lA=t{TGyj`kCLu7>~UUeL#F z$eoc@W4BEBH9l#~nh{{o?OyA{`@6mstkw9G9{xV|<*9pVSF6rEK63xarqd}GS0-({ z``^2o+{evTq|v=sXNP`kNBU2;pK*M~ WpOC~%T>Dx~nwZ>IVwzS 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 Lcn}(~z^nfUnXXYV literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/11.wav b/examples/ffva/filesystem_support/english_usa/11.wav new file mode 100644 index 0000000000000000000000000000000000000000..45a8c0c5b4df8c2a52958722179475df35709ef1 GIT binary patch literal 33018 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 T*KLCTZv}4f1vmhuK=1q?_=D)L literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/12.wav b/examples/ffva/filesystem_support/english_usa/12.wav new file mode 100644 index 0000000000000000000000000000000000000000..1482ef2d76b43b057feadb35dc233175dd5e93ca GIT binary patch literal 32986 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/13.wav b/examples/ffva/filesystem_support/english_usa/13.wav new file mode 100644 index 0000000000000000000000000000000000000000..e26c5621597f37593d01be32be8ba4cfb87fe06a GIT binary patch literal 27526 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/15.wav b/examples/ffva/filesystem_support/english_usa/15.wav new file mode 100644 index 0000000000000000000000000000000000000000..28b9db74899e18bdfcb725c6bad2f6395ed69eee GIT binary patch literal 30236 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/16.wav b/examples/ffva/filesystem_support/english_usa/16.wav new file mode 100644 index 0000000000000000000000000000000000000000..78c15e2cef09ea58a44c7f55f16468c478c53165 GIT binary patch literal 31484 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/17.wav b/examples/ffva/filesystem_support/english_usa/17.wav new file mode 100644 index 0000000000000000000000000000000000000000..c18b14507eb466f123da556cb62efe5f44d56cc6 GIT binary patch literal 33220 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

5=G-+(>#Q?h%hjBXMhJU(dGW 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/12.wav b/examples/ffva/filesystem_support/mandarin_mainland/12.wav new file mode 100644 index 0000000000000000000000000000000000000000..b44308a08fe225058d1cd0ec7fe2d28a447d2a2e GIT binary patch literal 30796 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 Nh8BFS^l3nU{tq;cH4Oj& literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/13.wav b/examples/ffva/filesystem_support/mandarin_mainland/13.wav new file mode 100644 index 0000000000000000000000000000000000000000..8ffec9621c4fcd6fee7909f9912228ec041910b0 GIT binary patch literal 30796 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

{+s0{=R^y$zjUj?yPcVh|6d1HYv2|FJe5!fQn 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 aV&@oeoC&S`-zfe6-?1xW&=N3xK>iPap~&R` literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/15.wav b/examples/ffva/filesystem_support/mandarin_mainland/15.wav new file mode 100644 index 0000000000000000000000000000000000000000..3c9fd9a629f62bf48038bc05a0b9f14033875e02 GIT binary patch literal 30796 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 Yd-4BYzBButUg&%Fp3i}~;N#W(A04ra(f|Me literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/16.wav b/examples/ffva/filesystem_support/mandarin_mainland/16.wav new file mode 100644 index 0000000000000000000000000000000000000000..f7389e14bfcb1aa697bfd17e6fa34999ed89c05e GIT binary patch literal 31716 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/17.wav b/examples/ffva/filesystem_support/mandarin_mainland/17.wav new file mode 100644 index 0000000000000000000000000000000000000000..df9612276d0cd70da399f425ffbcd24729c4e5e6 GIT binary patch literal 29670 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
Q8t(iiKP)OW@J>&Ugyi2~Anm`(eoftWOs8!a6 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/18.wav b/examples/ffva/filesystem_support/mandarin_mainland/18.wav new file mode 100644 index 0000000000000000000000000000000000000000..6ff1832ea1fa4d98a33539b4a55c996e897d68e5 GIT binary patch literal 29926 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
    VcpA(_mxgQ?agAFtnci$@(;2 zMC*op@Hv-*CWm^W+7=C!my2pEP!f8@OoIYn3@(5xL3y$uH91~W>~2xjDcX6{jL`bf zHiyADAySp-AijXN&12+?d}BHlR0XTin|N)m7x;kpdpq()!WHaIxKZ(4+$8=DzFY z(v9CB*K$+8%lQXG3}0=R`;R?sJCf! z_-c4gQY~kSzf5R2>Ol5#F~;P~Os$yliMY!$RWK?pgNGk3US*1MuR$@;A<~+6JJ&kO z1^xaK%9qaT>Eqt_3O=VES+3hY^SzE%Vfu3C=x_P8;$@;4xd3b>In~9N=SI`DsSbH1 zm74{NfiGftc|RHK={to5{(WM$y`!Z;@L-;7j-`I4PWOHm9%pqG(1hn7a)pM*$0;Fr zH2a1)4xi|iihUJ*$$skqZ1)f5;5*lGa}`)CHa>m_oHiFn-l*3wf@*?1{0Zq5ahEy3 zE{Ye3=5TZA`qVCZpFbt`&ho_kJ#*jNDSVXN@4A!fc-tjoj^n4ouj#2-b@CLpc~K3< z@_q=NV%MZi6Q|;D_2Dohc@~tzGf)<@U05XjN6*FGpGwQJ&FVx(b|)|0 zGV7z)34f_ZP?ESybe6giSCmS|F>1$b==#FyKwNC2uek9UdzJi&Nt0g3nn|-^Th~D@ z+pmSXPzNjpOs&Ijd~=K?%<1y}_$>AqcNFifZHn;pUe_XOga4X$4DrBCq~-d5e_I9= zcULuiMwJUJ$vtEH(^?Wd*T&*CR8{kCB@}Vl&Y6-Uw|t_syXXqjq}+E|JB^Riz357a zk#^$~l^{w6Czz(nJAp?lscXc2&8v=3pE3uqjJv^Q%L=ll-y^LzPjL42@AS7J$>jR% zy1b_HMN<#M5#JJcnWo@nc2;Py;!dp^xfHrjZZq~Y86z8_zmc2GVRfuHOlW8P(Q++# zFCImy<~)0^xYzraaLY2IK=s(w?7hkoc45-*V7li*6x!zDQ-ha?^b7-|Qr5sL{yk!6 zt~x$pUc+yRb~JUMmZBs)8kS0J4Evd=;F7Z75wM&5o8Bn(KwtBA;%7q@%QzvJwMac` z?d)c?H-Y_L(hzr^gL`AU`K6R3bXHuBJ?wbzuW~x|9bu+>k|=&$ z`-%#KIl=|Cq_GL;NgP1^BA+3LsQ|c0G2$4hH)WwK%6qj1a21$qcotbmEJIbq;fA7y zT2lE)gg7p@v!ogQ!NRDbDx(>Bv&5S4g!YATll(S3(QuGji53X2`Q22GRV9}OF9>6)-qt2W znlPK+0hV$QEFp)gr&L+^8;!KKQVn>0xmh~MHX(kJ&!L9+%Xp2Nt%&Lru#{rG+8fbaNT;k5E0TwRt{j z$sbU6qk-mML3;ETb%Wue{1Un05P3d37144h!V10fsC=XXg!w-C4W(V9TwIdD0Be?;n0gfv7X^k!&-+;b?e-NdJ zTiOw{U3@}kQacG(xQ=qc)QP(QHhb$x^Q?o+pNsBLJMAB1Z{}Og9-XGyO$XGO#3Ql? zs-rYjt5WUBY4QNcV*Hw05nmIw5ChC%;}+pzpdKj2Zlxy4JFpAs5;}`ywK;NS@lWU1`qFR^+W9eJBdU?D1yLZ>NvdraXIut%i2sD`R2kD@6^dREBc8(PphNrv zdddwVEQ(WI4t^)sGd<)&LSJBEc%q%ONbW@(qvxQ{v{`Z!IFMeZh;Rb&LW^oU&_UD@ z)F!TIFF_e)w(6mrR9U!4KB3aE74r-xtHqT0+HJBo^%iZDS}R|xTPO#4MmsALFpaJW z2Fc6R{>*RWU-%2Ywe|(ElkB1Wq}2pfhzjs;;ctuy*aE~O{xF`=yHX5$7bwaeUUx(hfgj1}|fJ;qMj8o3@S0M|ePY#>j-cgQO|QV6_L`8jby z{eW61>$L|IUV&-nl`hJ6uz@yO`Vuq;i}3f&BlQkik8X*b#oM4gc@T~k$EzdJZ^Q?( z64(ZM!J*VWc%CxRv&gbc8#Y2^5CXG={hWD3KOzSb<){l}32+|sNK?`!qz#bQ$7cvE zKQs1|xPvbqJs;l{=OUN*@{x8M zXjUBy?OEn*eDd)tH;-Lz=uKt8-@z-%DOZWuo3$}t(W`*g1^`f1#-*@n?_i0rMev{nJRVHOnx}4J5S-~>OZgkeS zG^D!Yef2QNl3NKq;|oLgeCNFOya#y`J%;zQGsb1+ybEW&5BCXG&Mli;Jlq0ZrKV_# z@Ddr#qwGhm9c(*oU7a0LCZ+FAsh!*|xmR+5PP0aooS?ia?!cSSQogM43kYk3WxeL01* z7H2$vH#Z|Y<8Ai(zz^Z`fttR5!&89S(36>l`jbcv0D|ubPjKa?fHBTMs zPD-|>6m?&;FSVo^S8_w>rX)U$08XWX_&t9$S|gH->0I+R#peZ0bB1QUe?R#B;|%Y+ zS6Q2aePdi`rRPCtDq4#D{V3upantm3QqlCesT*9+oNH5i7fvmDu0Xxyp{}W}-(4%6 zPJ5;i(bLIs)W6jC#IMRGzJ0ti-ZSqWnH8w+9qMi6tDILOYu5Yk-`D)mAv2zFBX4T7 zZfsOwqVED8bl*obWWv1b`H$;9`mgCoXcIEWzKr=;dv%{yFTmdU|z%!;=QXw zCbWe6ohlETkjqUI-S(8ZNp|-`ccYZ01*Jmu3*1hc?JDSA?Xo)`+g_QbGJdi-^(|eW z7^nOxevo#Fk!aEIzHlIXI9S0~*YkttpS&%(^Rm0Yzwz$M`x(A=(a$3#eKq~X#Z}aN z=3C-AxWml0A9fc`D(t@C`aY?5dY?jb3Jgv88f$8%yNxT;nrC{&?WTKDJ18Hy7X{=( zq9{1U#{AyMt1QWv_KJC-{P&O`XbCe^r+c~6z6e_$Gsb=0`nQ1Z(2<#znt z=+xLh(H|pUhcAblM(>Az^cVEZ^py4v%Zul23HoAOoD3ZGjui^iVR|QeC?AHcOa<`C zWer;gyV-Wv-Ye;J>YHTV@wb(8^mDzjFS1-UHe*xi=kzqXEtLv8$SZ{N(o1}9P*oTh zl_Q_=5BMKr)uR0)twZ;MBSL=%2l>`sTn7BuzciqU3zI8%x~0b8krG)pe53eq&;b-a1} zM}CGdpDz=y9Dfrn9T^mrqwOP4g1vlS`?tsXNuGH95C}ErH-h!VeYvH$Osz-G!g%3#Y)w zU_Yor4Q4BFr@2+ehUP!atIZ|NJK#^#xvV=f`T_dJ+J^3LVGl? zGF?{X70TaA3+13(M}44HRG!Ndl&~^NX)d=GSMjw4C^nK_%bSJK@eCnPydu4n+KMxz zR!T`_zA_b`QVCS;686&fiE%Vz+})BRRL-66l_53BW9B6Ocf#LU@!KOT_j+vR|pB&QgKeU+JMZ)W6kh>PU4c_V7EPo>yCGbx=L@LOY`^MBTwc zP!el73qa_EpTmXl80-dV+-n!nhPVuWg6pxjiIboRC&3WE;hps5@GtBKGYrN-HYf^R z@I5#LdV$uUCXP0EkM5!UXeOGA`k<+366%J^Avdav{kXfL+Q@{eqsAx=@0TT^nplEr zs66@x^}v2fEzo4N4J|?Aak`1@ID+90dWikrJ7T&GKr>8#0vHOWfSurXuo#TTG9LwJ z!EUe?uTV~cBj6zT6F+z1@)iuU5zGR^zz8q^G{JEc795cw;`o9?XcyXvR-$2;Ul;T} z_6Dnk{pG8mCg>~lC6=xhezrs3p*FasBkGF=p&!r)GzNRx&&TVdtvKyPC$I;726}@+ zI0nNG3gHn|1@mYD8iJ;vGx#2K0d26nU*Y>#pc<$QN_^yR!*LlTz+SdE3c~*}F5++W z5B6+7gHEC&Sgze@1Lm*+r}@~T3-Em&nuXImtdSKsHsV(-`95?Q!=Azs7SGU2l!qfU zB1n%$apRbZf>@4npc1GH8sXFsOVAXw1>b>opd~If2H$|M@x3;Ns01p2@}LaHa9~_B zzVxURd^nBe^5AHXw>asM8?SL>##{6r=QDAwAM*;KD2CG`#z-7{V!-h%dK67E?q5Mr z6coo}s1&AL1^2lczE{H$Cbd6ab@4YzRm`^AYwSy3DrQVd@yI7%i7 z(@Dh?^*9~}rtQQzJ)TLA>R~=cZ4o$9g~gX1>jH7yiXMZbN4i)ryc27o08Z)nO2t+w z01D%)AWmucnS$$6F^m;MCgE~2mRpYp(xKBaFP)nMQ%J`t8JBF>MNH?T#|Mcxl10Ql zrt!Dz2ds|>j${$BraoYuC|LhAj;qn5&mhiO@P!xe7)y^pO2iK_A7xfCj)*b!h#wWl z&gijAdR&+u)dVoC9zUq}ivWq(H4SS$7i(L$#XmUm=HbWkGi;NWxIP1AWBX*{(UghD zK`xFYdXFJ>>t*Bg4qtv8ZKsb99@`ANren@}jE&Bn!!b&F1fm7&ROhK68}6?QOY6cV z!$)1~GMlhIbd57uYx?+5u++Nz7F>#AYEeAz5cmW`#QbvcrNZ?;54EkF%6yue}Gc(70iZ9;A?Ep$7mTUj;|MZp86cM#`&grHn5{_5r;OS!AQ|4 zR2oMs9m2C$DvsQm14_U@u=E^$mwaSNrVF_g^ifKReNjikNUtUKNU734YF{Foh{BPo zM>;E?)mD(F$zGtI+D=)fl5jLpp16Y=sb=je8UZ`Qe^DPamfW)StrV#Ip%ubPfy zjbu0m{)||yGM?SrqG~V|s_H)FxzbYW1cPY0Ww|j0^?1g84Eo`3tZ`tsc0#+SEzoA8 zJ>Y@1P8+PPN53IWov8h+U02I$dD>w0GwrPQP@93OYD3i{=(5@kjUe2_TChV4Y93gT z+=Q)U#52`Q@D-6pq+^@oIRM@Q{n2Ld6aF%ui?X#@s26w%hQpFzu(lcugVm`8R4MRO zEr(7*Ct08P8EjM+D)_`3dhq{+QHv`Zw5RAQ@Zo=xMVh9W@fZ7LJiglE{jHl|FaF08 z@OcB5SCyfIq zkqxy5J&*v35N}~np9J9}>^ppuC`Pn}v(YN8HD1{O@H2)htjz>@I6CdPmWdAGf1=j# zIsPYk3~R!nV3PI&Dn%Hm2jodmLR+a_2OZ%D6jIix7hn?h(7&%{t1cW(HXc&QuFVIZ z5q|80yb6|6FDS3I9xzP&1U}QQC}y=adJaobvxrezrm_|&WF9$C`&=T`(s+gR1awvV zDXn2=9FesUFb!)vVmrw$kgFuVF3efLIhZpehiDv>j@Dc!PMP6;p1Y26#QxQmd#KwKe!% z-HryTE7a#|T%C!2hUwT3*$=$nPgn$etsa!hXCd*ML)pbHF9+Q57 zh3UfNb?`!as(h|B#kM$t*D#}%QStzV0W*mX_^kLd6cP_h72s*QBvD;P`~@jMEM>|N zqBJ2sSs6+Vz^7hcp`FSou$Sv>aBH{XF>w%%ntQH_N?~x391U-ZJLGZnRHi4~FSeJq z5i9BAT$C6pERR>$Ix{y7`-oy96_>?@)M0iz`CRTLbdeV$7tw*3L>;1vtAFvk#&ytpgv_l>k|5q$V(S`}sCUgZ1$2&WPweC2I zvpm|N%~kiSMYNK@PYl35ewU<4U;v(5U)EYZplWx+nOJ9CO1Mre5K zH&Hgo71T2drH;bqj|a$s*k`slIZFddvQ$ZHO_U`+gCCSMg;UO8FP|N-x3)yNg8Gm$ zbsx5sC(8rLEe4f2E4LAjgW~iKa+4ZWTQbMkCe#OhSMZWhovO&q)Q*b>P*HBad6e-1 z3AMv=f4*q2MW~p%mg&t6r%kLLuhklU6A45z$P{xSwwgLQ9*ngWCu-Ng8?7O|z#x*F z(Q!qO`b{OTk&D#@l0`a4m$gKgueAlyow4Qe6sd$`97IdkZyjJlYVsgxVQgMi8%$za>gqR@j#rrjvi5$x?^-PH`DJ ztQ3=mDhG%(!%@;JypHrkO)U?Mr{F7oiNIpt!u51T@HwQ3oOmP@77h~wEsf2B3PQ62 zFQjMWUb>uVtEINF08y8}9lFg2D9#o&(@HNcyDA`62|BCdQEsTepQ_BHKv-Z3muC&`j>T74t!7uv>; ziB)N*X${jui=&e4Z(Mum3y<-)QGZPvq1rJ3O$0Ds{Si( zlnc?rt<{aaEpB7WvOCdixhDRP&6R$V zzXnduV~IG5n`;WqLI;J->H(#JG)x;qU1tW8yFf8gVVBUar7h73@%4n)I>~yB-X+hI zdxCVWt(+s(RZpV3)E~?u!VS-W!D>_ChV+1#N!O&C!f%D-_)Eo3jbta%RnZf1wn(TC zn;BM+Bnrc`;2*K1ut1#vkCL~TADGMJB)ro2LFq1yMP1nzWFhfR^k0#nnlc9< zl)bURT5IMWyN&UpUD)^UXX#(;|K0#Dr8K<1GKN1OTn2sU4tNcu2>sz6CP;rl7a`rG zg{GMf1SQ|VQKNgL$LJlYF@nKiQt44@Ppv#95^bcc$l-W#^>fN-tYI!>EoJH}R}WnE z)s45L1~CVf`9dLODwWJtASSARgD;qs>@w6h(8Pa0nPF?;u0=f#z4hFVvS=BcMUtjp ztoNC|aBHg{yK?GyzVbYT@{-5HSV3l~)QI z)dJ*9I6R&gx-9*}rkYo>3-Bpu19hjcP}r?3Mi15c{AKZL=D4G>yNqE8f5X2r`m=hR zctr6;7=N|Lv42_(W|VoBafY@lJSaFooCRFOSvZC$$o4Xo<&w!fFp=0o>`;&LN8@j> zm*Gb82+2_g=+#sfXrR4T&3Ltu3Vs!+cqMTU;2DLHu~*=8p|u>+Zh%!}h}udDO3`S! zST!VY<&3$gV@wWB^gHX4o4=$3dKud&%)t`MRX6XFK>vS(3-LbnR4K@cux9^ z*uYMse}^8lPaP~}^P8m$pab)d@rtRaX{K>7(+M=^v%=Hjt;KWdaq20%h1yI&cAuda zKIbd13W6y7sMb)%3o)?(3CypIwP+HZk+P(1S_(RV_e?=-P;?*vLhhgqQHBdkg~D=7 z-A$}!j&eik#@gNZkO+H9>vv#;c;Ef#z=tRrBJjuK}xX##R~z7LvRUDpg`~jcPs8%ytuWvI|L~al8~%r zcV~S^|Fitelkhy*nYl7|XYQQ)z271JAhVHk;)vi!Z>;;B^FQbJUM##`w8`hRb`bTT z)YQn2-lZ;Cs4i9}MfO|j61o>=zRR%R*vF;;riWyHDBFEA(3Vt84Py@TXK0PuM6G4l z>+?+A3?8CHmcv%%EY(++V(7x@sZ6qnx=W8I$}72H&@9Qj)nb)bJE`f~22D^FD;}8> zhXu+Cm~hBj&KLBB{SO200|x&^UpK+!o-V|~639NaGO>=jivO)7g%^h#U`G8LV?Qnq z#pOxBOt)t%av0~LTjB-i6uO?FpQ*F{8fC?gP(64*D6Tgd7xG)FW<+oN8L^a%BaYz< zi1uUxzCatMo|4vu-2T^r%CZCIQ^O-gp;O{e<+%9Rx8J?hb-VPleRuKP;&wLLT`3?( z4ybAHhHug4!92P=GLSGby(k%3r(Q<)Qkk5arr^n34XaPHTn1lW*PE?Lg~;bjD}BCs zWK3hdgMLevV-k6TUg0~_-SNHHTjDmAMx7@*V;H&#;xL!B*YfDlbMH{EAxKEg)z6Aq zR>erDnP2wwDMhTdf_FLLk5lu{JM+Vxv?WA6wwM)o2iJvMu1$-y4GI2^{&k_HQf2%R z_qS=8rLVcQeh1r^9!BTtdEtf`;*ka;5zYWwx=Sv_$B9fYsK6Yw1D5>8OhsYGfg-U*$lt(OWzpL~b88jr>A(1#P(>X1p=tp3uio<;c(B?mn_~g)QJn^>qMmhmyd> z((AVO*4rhX!d^M%oYTes1XGDI`l_*4V}CHLrjB8AusFO4O304`YXddJW@ufuj`?2v z{`k=rgUO*w;a0+2asyR`n1$hlm5Dd5vY2E0#GEo-;hWJlvF6$k?JQCQ3t;7-#xd-8 zZW23-_)py{J__CPcN4aFvb;a}`}y~ITDd}=qCjRiJviIj&UM*#tEfrNzi&Ie`%(}R z=Ba5+hWSO@H*tQ`AKWhLF{UU~2xuqJCoh&y5EJzUu{RSJB%X=i7T3n|(a@RchIL0C zXnvHZkMi@4Hp3gkW>d|WA(qa@4Rjj_5zUj&AQo~i-G};2)FW4ue~@{&Q~O?i5KarU z^e*sJ^7i)j_iS*Tac%eJ2Fr&re}mFjw!-2^`MK}5z1ff}yJjM}`UA0b;cKPeW zyy9*6S!jT7l*3n8Gq1siMsLr4yk`4H+(ged_e~^{>c_3oZJ{a>C1|nOUs&X9?`j<= zP_n5_CU3&_q!(q%CjOl0iF;{yPNisPM3eYVo{PtEbqzM-W>aEpAZ~ihW1WYZ4so3Z z;$~RGy^M7vS5i;tQQUa$Jm~EIMDELjLLY@#_r_AjS=q72e#mts@Hp}@av^v}Sm!cEZ)5&8m+F!z0-33FRn8zy@%gw8{{!f~ zs}w_@Cp%)r>Sd{I@U(ZjXSQpvbC~_5ZKrdEkB+Pg&kBt9icV9>u)JmO#$+4wY6vVk zi%T=7Syow6V>+1@8LBc_h%U6j_0V-)_#7Ij0gv5xpkJ4#B`Hgif#u-f+D#Wx`M3F?&d1$ z-{sT9QTzzwDq86iWR8v*6T3g!++*%QJj4q;CdK{6Bq$xcjHT;6Ry>qzrR&hJ~ zX<o|zRpH{oF1nRphiQ_bksqJuXQ5l-=qW?K~2L-=-6E8;w-#uJ~n^4g4o^ z7|b{SK^tp8=x7&_!=QyN3$vkR%uwneeo<>E1p}A8_guI=z4%%25XUvomcYdbFUN*g zxR+Wd7qt15n!WO!zHqrGSIuSnn5=Q#6Q?I^v2-+CILyjFcg^Ln_8Ou<;s71 zv(J3&ThO8OSY$A9k$0HV;;JNMB@prT4G>ikr+7@mx*)~|$+eODZU{dFKF#k+{yysVkv7}C3;}2*KTT<-1 ztJR}Z^wrJtE#)klrB|$nZ;5OQT@oI-e-X+D)1`5Ui(JF)G#^T8oYJ%G^Z2Kht41#~ z6l)3bg*4Toj!^m_etN$y!%)$%+3?ap>NaubxUuYTawM!O{D!8Z|00voqxcIv7mLSN z;w+ws^niHAa$$li+jgNiXjR-@0%1SxeNg(BgL2lgEzYm<$&+7X^$B03Da7yGZzhkW zhefb7v%J@xz)B*2`8)c)_kRf-3sb5Uug^9x|Bx^vX?oJV#2Rta41VBNdtq~tF%W0# zrB2i4;(F=?i|L*EWri2JzWf=s36Qkw$SZ^&+l3@RylxPdJ>PFiPx1p>QMc0gJa&2fh*&{4 z9zP_W@XzJI{dd-1>B2X)fTH;v?mZ{t%@e0C}^8>;T`@i!XKq) z?av&`T{B$&INLbZ+uz!Hl=urP6>wh;*dCT@fgIGz?KY$sE1Jf{tc=-ZoXIZ1mPiQ! z!uP>{E$oozXe2H&_l-ZtUn}ECev+h%t86&RT&Kzru}DjJE7&U!R<9tHh05!B(SR;2Y45NE=ufF{4|Ny$FlmfjDkm^e{rGOk}6;j{A%AuI*df4adLk zG|wJKN%4`A+4kclA3t9!M4fYejipoA4yJ~#lEG`ZXS!r*ZMsCxP_rTpgUf@C(5J9F zlCOM^exXL2dL-D&v`lg)nBp46d}C(CCcnp+JUsBMEQTPN6FKwR$~KRc#5y!KU&b56zK zwT-b!R&P<)FJtU|fY2C8WODD=ojScKVse_jMvm!@SVco{rhikYuUIYeSURP>CWh$l zTRN8MoLoP-P5eXSJbhgq%Qhqjq9c`{SR^%6hQLg_32CO{=@3KeigdO3bapG7#--Bt zh|_o#Oph$k5b(XYgl4JFqy(j&x&qV*XZ=?_ZJj^Z;%zQRZ%-$oODSz1YyHvYv}ZV; zI|jHc{;J^?as#57?v)|kkfE;(-d6#(4fPN`q-D#4BQ3)bajo)BTY)cTD(W|wMN4MV8& zBIT#F`syQCrLfC{EJfCb@v!3G;vq_t99GAFrLA3ioGl&2)*p-0i`&{lj!mwAg;s$d zB@=d-tVOkBPw8@XW%*-l3X@3wi^1BgwjG?x&T5C%mgsrnKD~|mQCHV6)A-1ct+yDu z8Mf)3aG#l6@_Ql%Uk9rJG!~0r!w7s4zK9q{4PYv;d29){oink$>G$M1qB`NhE+ee^ zSeh0Y8X6H?7I+T#kq6$!p0;k(Gt@IlSR^PO)*W$7un)A)bP%P9p7Xw`p_5XEIzsz| zxPZiTGe5A8nJ_bxnMnOc7|EC9b+R03BqkEA$fG1jej?A(b}0D?V_y1 ztckf#t)V`X6Uez_XDW`?(}lq2cBej2yXkE3Km%e3nBv)JS0q!-Q(W>)=@#@~w@``y zm2a7Et8a|&inm0_5PtCdT{;rn7lzpSSTEY*oetMI;f?=Q_<{6PX4L@NgIdh|!WOV` z{9Aq!*MM0E>Wt>x8OF@iWfJKAKzCOl`%*vC-RL}0Pu-#}QSIs5v`Xt36t3e7rUl!d z6`1ABWX8$tVi&Q^n0oYg^iAqH*_~`doW<;jP0g2ANF9K*yA-qpkNLNIa|B7)Ab35$ z3RgW=SBW#d@PL>=M}W#+ON0nJ`ILMJ{!G=t^JO}9jcP?NV}`Ih*kkNHR%1ynpY04Z^$2im ziD#=Y1L)5b_(u?KtQh?p(P=*^52eD$htQ9~p#OkxmDlLa7G?{v!aNV>Np&AAo$7dC zt8e?!+Q#~e{YdFwo)*5@!FG}9l1VL(4kujn6K)sSPBz!K)fey^_}x&`+PdTXVD3Hh znjT4ciN0hHsysQ17zFeGGejv7CYw=5=zQh^QbvAg^-_k$K_f)v)X(6l?273zKwcY((rqZ|eY^z*SVEx{< z+dj}WPk8G)5S$lTA7~;V_iSCK6dZpLxYpvt0yR93n)9rgp+k0yG z=+F<5GE%Af2X=(mOULq+bP4(v@ZNbsw^na7ywXK<27P&cCp(y_Nc}~0Bi|Dr@EoEC zIgyAb4in|c7-|3wZ&gez*Nu)RPo_4Kn}{{|D9ndQumVZR z=OUS*i-Fbt(Y`_6iNa@3b>V?0+wF7>a;e~VrrKg{ob{8nxg)kT;5zM{7d#W@r6o!! z@|I{yzhx}^NJEyPjJ^r@9t89i^!s%abbsgu@J_lV^@tXdzYOE_m+`tZDua>gkC_t#Q(x;;*C*WOHe*_PQB}jw|{VKsI}MxqI|dTU+HY7 z4(~LaF$~ka(62Nu)Gq^p$G5s}{0^>&E>E5&8vzYa5r2v2;qUO7#4NlfF`x2MTPcY) zb644!>}Yl`+lQ;i&0xo}?YRh3fgVb!lNr*McXFpK>I7V;)&J~j&aUBSH!m_R2w2Cbr1y~PQ7L;>y8_$n@Wrsh8_AY#-9zh zp>{j?rQ93Z4jlIpY!1E|YlYp$uHz5zPxwc|O{}JlFtyp!>=bSq^z#)qiL-F!U~aXG z8Nh6!+fiKzKc0yX#X6z8wce^E*Oc~z_Xd9qmijfXDjX2*34TwSr?bc4QC#btbzmmA z&-$jAw*F)P=$!Aq?Ass86rU(hkUhi_2Ipt%E18~}pO_{X*BEPq=hZj5uKY8IaXhB# zfy(?WHV~X%x}sJT!_VU<@%BVRY7jk*xy4@Pw(zxeANlFr9xlSQU98r81J(D}Zy9_{AYjKS)olE|6_7}bl z%$GPc6|YU1xJSC~Msv&$;3U`6G{N9xCsFT-ao{o_U{mlVSR&R2^Fch&K!G0=JB072 z8^~wDJ!K4goEgNtr~6V12oauR9YHhm7WOM3rc*&`1!xDyl9Pz-L?&??B9=?FPx8?S zA08a~1Ri=L1AF|JyaA8Vv&)m>z3jW}uj%{Uv(M=+&M)}8!1`sNeVXTTI7=glt01}= zVdx6tr>mB0mT~4Qyh^@APb)PfULK%^kh6F{vJthAE@s#9EA)2LLzpj0x~J?IDhodW zt{|PU&iD}QC1~VMgSK!8c8pxatY;U%*q~@XQH(V~uY;yxo>rv1SDGtFr8sG_cwRgu zmPWdUzY84q#R~@42={oQwzs{vmUpvfm}7s*>Z1JoxT0Sizj@zD+v)GkZDK~mWX2_1 zl8nvu2Hr-cB8{Yiz!dLncomao+F&s0eqc%BKdp(3iyOp< z_(cW2k?zO+$)DAY(RE^L&<%--*bekhZ3V339hY~>W2MW=3p9=Rj+#jQN4bfBCQG%# z#^4=)L+?Cz7MFSsy7lhe-nzjhq0Zq|!D-%rqf*hJ+)g={3U9koB@-8pKUlU*a+ml& z%tl=yRTi(M8DKrAlHhcW6Ap&QV~S3R-(2om%9wIj6N=4wy4CbR%&PX4cZAmlP6mdD z`zx^|sV^}95<4aKvvCGf7oUtIDSwE1sg4|$0-!uq)j!ZqzU<_@k|z6zRDqMcveI&eUR2lyQC-LmrxJyd}p=dVYx3pj{a1!u$#L??!onmU7Iws zY;k-!^9$V!su40vyb&7bALu#a?H|ffpEH>zHqn~=sO-8Vv!$P56uTe8v`x}bae2fK z>ld>$H-YJ#29Ifnsi(e(E<)BSX_6@dYxfebOxH3|ln9Zh85i6$=d!8HULsAK7ugu- z<(uz)?Q;ioqFV*V4C#WrP;ZDg1Bt$xLVNID9qqhqTU8s;5_sZo%n+0cS#E#ls&Q{)gfuH;a;gM^uW3uh6eVe;~ z_$8XdH?-`CO|o3nXV4?DGRhI@C+UhfN)p5d%1UH95#cYG&%`xLxD)%GxfW-~I;az+ zaq=^zKE%;3;>RHBxQ$-R+4xlc9dnXYuq$e=NQ$JiNJ&J-W2edQn9i()tw#@_P--#p zCzh&_@~}vH@UKA4&REAXcz0y6QU&5)nN%VZ$GYjm zRBKv*H69c@qa|pg zbG5mAA#_`CJI92Eu9sti~P=vGLE#IH1{;#;NMave2Ds1$`_|bj*6mGMSDz? zVZSj_g&_W>rQhkCj@W?Db>s$Vv1Awl+T()@bg~-3UFpMOiML5FOGL z^%!D-mHZ{tar!tNN1vstlH>3)=qmNDJWV_jv5ViycIAaO2knQw250J(NPl&Rq=v=@ z>iJxrE}j?OYT=p+0q>ICq?5shg2(>iOT!|qn06fu?$_FKt1U|tKE|b+CUZydJXsf6 z6xtS=9o`d3l@}u#Ig!_lm10X{8^`>r{~2OpJ7kkMT?|QQVeX=%hO#ky2OX>H1JAQU zaDo}EI>gVBQ(|ppA=-;rLoK3%)G8{8T#X;VXsiLY7VV_9SGq}6#cCoi+qFgbQ=$&h z4ZE%FkuOA6gv*C7g;0>LrDuylJuIo zZX6I_woFRguZHS$P2{FHKR7d(8QvUuA+1-l(RWlG-5ygfaH+mv8pV$z5N)Sq6tkpz z>QC4WvM=*Hx1YNJ?~;GA^_ZjNNK6H$?Ou3%ST9}ByttR@%Wme@Fq;W4cu}4MRdz2> zV1HE86oY&ZTz%3I14Nf^Lp1HVW|WUbo`y~aKfqdK>tLDS(NIC;l0>OZw0>$>IvBhm zj4gd)O)WW8QrEdY&{*}6MY<)X>M?f=f3n|G1?YCQsoY)cCSDZROOsU(=maY9HvM7a zP2&RnM0Pac)~cxsl*OQr`+(1IBC^X)>8F|067G~DC1k!8_o>Py)tCu+yBE@TqiMS8(IW(#I2T|m7fC*mbY zb#;pTP;yCyFk%XbN7QM$C6h-T!mlD*)$Pht*(8(TJLFQVY7sD@U9b<>Eo==&qbJl> zvKr|g-VnMNx)b>0Z|=^oA_v2F%pm>}E)w^GFW}F}X|y(xMH$(yd?8=J?PVFN1NL59rnXl{YeuXZ z`2pVauhR`_6WxsJL|!Bg;^5<@l~W4kbmbS7M_Up>>J6-MSD_ja=a3nyQE4krm1oG+ zly>TE^_I2}IfUAhMx1p+i`5q|3LEDs)q0k!7Cg0eu!rRANF817d)d>LDXR!RREmQ)Pj@ zc|nzD$Af2OE8K_{t5xNy@LaipwV)rd9r=7dpFKs@!p^Ga6uW###?)!ZRIDGoTS&zB zWSD45^o6XJ$NMQSNs6fwD{ zx)nJIzP7`$I3!Lk8=e$c;MWB@1kZ+diivU)NeT4~%=F{oi}GIN6xANwgM0HDH3mPT z#)w_S7Sc84Hqsnxi}xfX@RMmxjG(qL|Io9kTi`603>r+ga!6yb6U0Kw$gF2-(6vbd z{{X7W`q~V26mSv0AzP4nsDP#7DE=ST2U`Hdr%4^7WGY$8p9-Shhwo%l&^Gr%=7Mgz zK%K1c@;EUik{gZ_eJ}z`)DNKe{Yh0NYvf4yQ>Zj_B|J9LF!CzgGDHQJ25X5!wAT1N z`Y=CTm%&YiICp!so%k+7OL0mKbOUZBHj!5#n(>m{1koTb#2Je)6IueqL?@&#){@90 zYtn1zozytu0y-7}hC&^rw%3w@)To1gfU(^OtAjPe!oZTeLso06)R#&RWvlW?Z3I+D z3@EOvYQ0oR;ndEcFn*&BSFeCx2UB0c+%*WEeJwyeen;D>JyjFcVo>!(l!M?twn#3N zD#!u3gPH?AzaP;ys7srsE?4f!zsprYDZLtqoz=jGJO^*MnV>Gl&>iSG>VP|o4Jc5n+C>`8h#qu2#Jg15mdVV{s~+Gcej zxZ?c|(P|%XJ(EGZIax^rXVOMmBhY?lz{-vR`K;`bJyI_@PtI2QYOR2!`4&kB8YZHS zQL{iZ(^DIy6@p`9P~E0BQrkiuqj^#mB59ypz5yD44mqiX;JTSWL*5yQz=}c+T7dqA z-UWSsKJX0d;5~$dw-hJT_-|weFf+Y?2}uQV1cx>BYQQB~)#ln{;H(YEQOJDJ4gT6& z3#cPhTK!3ls4L;h@6-O)GBmyBS1W=tdoqmlLqPPj*23xym~+m6Yd9YAO*o*8Cdh6r zNt0AB9Jx^Ih}=SwPy-qd)Ncmx&rN|iNCwuSA#4=TBEJA@G#r_NtVA{6;M^(pwuWU zatHiIWq7V70-@3a>UI#mC6_>dpNL$CtSQ@}PXB2&L94qM%AEqV4+Ue~2fbGjI19V> z2aGrbYOxWrz}!PxfSP>(L0yegv=kE;cStw?9of;fq#KU+OBQZZa{lFLu<|eJFpxmpXb_gZ3bMARnS+tz<^+& z!B?~xV8g~D`$3cb7Fh*ap==<;8Uj<%8AyX%tsY$C01|_0$U7joS|OjcBQV-hwFYOUkAnm2}Dhl){(vvHa;MI z@?j4G#${t*yBYvRkO(6t59;>{HY}sdMBDkDVhsO1`^u>no%d3i^v9>2lkCpRMG59Q3A{YvZijrC$Xo(wICPQ7LJrT_$_5U_U z`7s5`7hnXq;dc<~5M|aVC=o92*X&u*{9+jF75I9sqjVS#B_zSMh=UemFv|U3`z?B8 zl%t9=ZgKFx9$Lx57A41`8M&hErQxqpjxCz;iuzjP5Y#%FXDdp~MKe`JOU2-rD4`bp zQK5FxeI%$$w14AZBt^NnXojt5&qUcf=4&sQzqW~kGyVTFu*rf=gOV{Qi-DtK;Mmx& z$3~ep`D@Qd^RH>pFHu4-O2S3+c!4cA^kg(|T$DsZzV-(LHICL1g)*br*`oRAqGyh_ WF4|+!E!t1fzx+RgS~TZeSoi;jK=1_C1t{Pk5Q)5&^LQ6oahSfCHuRclWQ~jz@eMElF zg8U@(Q_#}TQ2B;IRHKGemxit;-&f$OF{)3KpQb^y|4T0@QBX@kd9_}(7PSVo<^O6U zp-(NZmMyRaEvO+~e(wM3FDRjbb*L1pRI0ro^7~iN%m4UM5K{A~sRchYKX(D=3i^%x zf92JD1zc9ks=QQ33BaHFMDl$ykYHGK5k$%N$v}ZdjS-<0KomZOe4mi-<6(#cER2r= z9$F#aRYMUN%Rx5`LePZ&_YA?92n_o{0Gc551>w{8zfb^pVTuo0HQt|}OAV`e1NmV; z@W4D?==z}R26@ouhSmd9efdu{O)XiFuDS*FsC9euV|~yqXhEe-ZO4~SiAs-3C!0@o z0sZ0p-f^&3YHw7-{y(FJBGCTVYYFm2<&gyWrE*Ko=c39l8K}H3&*0_iP~3{^T*UxCCi`8BBN1xA%jRTBS`Yc;nj59(93 z1-hd83yhjpP;Y_8{Fg(OUNx`Uet~?eTBDXzLR z52^$f$ejYYR3MjRe&5yd1)8c#r&?YO{r48(F(gpfjK(X42IudHl1r!$WOKnS~@IReW$FHE*Dur}@tp)Q=0hOw@ zQ~4UD@=oPWfqu#PvZ3mhnp>3vRW8(-5J7}|J*5A~lY*WrP`Xeg03}ia4Ae(ek_z~S zBNA*^)iNBGQ27X8UKUCz1N%!Ls@AGpr4c905rQ(Rz*GU&r1lqqr7^@0tT0{GY?Ye^ z#0L9eL3rSReM^8bGB81^ROt)+PhSxv0%?$;&Z#^N0}9q`L^w#f0O_>D(ur`UH^4f| zAcgWdF9RK{3y1ZjL#=dy53u}e@DgM|P5chtgHJHTf;1TZ2EOKNo*%>@CgdHYtzgb1 z5Is^B)~$y%qDVzZmCE}t&_F*1+xnbOfeon!IS_}Wfex^}%CLtWp>2gUh7^UtXYd`_ ziS&di<&Xu)Ph>nY2$oxqv_>YOHBmd(06mJOFdLYeL_07F{hQdvydpEfRJl4Z;Khhr z*dt{NI4Y-tfyhWuM}8^%kS{8Ql-WWb>5x(goddc^)1?kz1$F~eR31u|Ky~CNh*NOo zsuEIWf~f2RZRI913LY!N6iP~wp33i(&q{(6CpndRu*^`onYgmh`GTDU_)Mlx|o8L0k6P#@C#{!jDwskqSQjukwwUM0F(~MGPDlz3e;DEXb82T z`;Z#o5ONG@fGI|BqZBaK;9xaX6C(BTm2qz&@$C;lD#nhxZT4Fvk8|hW_ zZ*)1*PPWGVn1H?qlJdJ$UfLnpqC>f6k(H5M!K?nFfy;g>(9EatHw$zMy$E&-&0se~ zyYQ(3rSwC)5wD0ZbaP!pV-@2Llf$;jzQQrU-rElBtsI2yq&d&D&G?7z1Jjbaf&Ytc zl|P6bgo&&+lo;saAMPEW_j~SjXE*2mEMxYH+zYObxxI1+dUT-odaioQ@2quGVQ>+-96LZwjAm*wg$9}`U$J*6YO5fPfM7NcW#}}ik(J3HSJj3zq z4EDEhx;wYcA-^T7~&6)~JBLzmEp z3=2)y%x+6{du@l&vC2`)QQBVK*21>l!W$U9OP8TJOMS#9AgPE!(TEK=3!4$qhpPt9 z`HpzP?k=96c?Wa5W;e{u_Koz8&FkzA1T0(&?mfFU8kQsIAff`5Ls>PkhRK#0)>ozs z%V)=#*oBT()*H6h_Tu&<=6!}a2FlP!JDT1=ka$y622bVULj7nl?j+kf;t0y#qn@az zqi3wkpWPsUiIf^;{wp}t0HcZpc(e==LqYU^?q$!dFhKVnu%eZN5cKBnkNuau~mnWEK$*b#% zW*VG1-URQyJWbv>|Db4}XmRcaUshR0gvopKIL#B?c~fueNc%ABDSISVjw52P*&8_4 zIhr}%nC|Gd=pX2TrY5--yN?`4XMh!=6dlT*WqYz4!!`Y0&kavoZ%p1LXIkdi%oU!C zUc#lzx#<4No#(r;SJ>h5-$Ww)fc7$+F2!`xe#P*fpOO7S$OPbx)$*0S6cYVU1p5L>&@>i9E`TDD2Hf%fIrorY$bznHyD zGv)!oDM4v2Fo8s2NTe=XniC@+SkL>|lj;5|uV>DVEPGmJ)^cwZx8!tY4hqm>LxE)% zva2zlc89*ErkF0o0m5j?1rkzT_$&wm!y1XwvQ#}jZLvwm(9Qe6B{j9gSXKl7NqrbbbR9ij`X9pBnsy}3Es$Z#n zZSEACSa?v}Fk46a?D!Xj>%Y@T7T5;_IR`VdTtm1=a+YwM`!26xMwzCW>KVL-N{-Tn z{R#K%i!9NY`-M8kkFa#rUDDevXN)7L%Gd{V1-X@Sqb;Q`0-_uS!<5U>A(7o|13tjj z4&vU2o+X~@u3A|Of10xU_-5uk&w8CbB6Lq0E%o59ii3!)hMH!dVWjaNORLyOp_B2; zEm`Kyaic|ZUwtEI#k%qb`MzG zRXq(n&0PuU_rEvJdF=m@w>9fvPD=QlvKb5&Kg(~afu_l})|NEmaElVVpzyfZ;%3?W z#Bs%O!dyo)j(M-ItD8g}!$p)Nx==R>59Usxb*<+~X- zHr2^+A^ng}rjp4q=y~itd5^k`4TA48H?jE8(i|T;hK9O->Dlxe zI*~d=RKYtD2g!}ZBJvk;5g(2}LQbK>@fhShpUCy#bA>I@>HaL&^c>E4B6C6-n{CV+ z?tGQK*E=d2t0ajRg{IgggV$8du*5Lf(mGaj475!*)X_7RmzI0l{zNiff*z%xPjR3+ zI8IE_l+dIR74RV0mU%;;!?t3rupDHC5)yjz&p0NM>0j^B<-ACjQnE6xxgO^Hn|Ud# zPVlIh2ykJdc!t_)M(nZ15Bk4NYaHX^%2@|MooDm|jiPoR_7Itd_tKnUenU@#+C;K; zfu3g;69RdI5j3mlCU8x58G8)A3S-&bk@MW1&?(nHnZ?rL(&{-6JTOjCrT=u4cE! zV4Prlugxav;uWwx=o;mBZg1p$Al|3(Xmd-Xwf!+Q)syG&Q90|Klf6~J3SvFjAy2}C zhH} z;fX2D(28D-eL_znK_!cC!0rs64xIN6bD6W;zs{$P$}8+^o43o=(04}qg58iCNssVS z`Y*=8`V{(rCdw?)zM-39evv)qH6?VZkSI_sgcg|DJ_ecnsoi0E77Bvwg2Dm@{J z>vrnwRC&A)R*8zxCy1VKU0oB>Yr#(-jle9d5?z(9LDi*aXcrq>o0poWn3h-`n(fA+ z2BZG9R-`ZB=fOl-l=g|MxM{(?zB=AK*YK>z=^0L!e{gVzoAT6+u*wi(3Sgz_Xp}6e zy+)TJMxz1rJw6ogfK-x0N-mI47TE^;NE~Tn_G;GYrfMbqBU7&Rj_tF#iZRzfn64NH z>KOVrIG!Ac7wbn8qe<+cP=DX1ykgFy8E>*D__$y@PipSJ{>ySZtfTxS+F90;m&r5O z6;K%IiujSe$P9&mYwXp)gT|o6kdNpRauaic>807C&oJ3-g{*rmw{7XR6V^O)TjLi) zUA}>hh6?xo_%?5;99bWdtsoJZ?fx2-hxPuv`h4`Mz*f> z8TEj^%0)RSl~t-Mnerj{^%0Phl~(8oydP%6gVX?RRoxMt!w@zPc06$8+NayM+Q!;y z*;-rsn9AsXY8Nw$$x~>WTu!0_FB{Y=wU^AtFL#>W(m$#r1% ziB`&X+q&7DYDmy6p#~8Hh-p{}d3!XA<=8h7i;u}QJ8$Ri@Er22$Q_+i!96rsi0d1f z5@JI$xY2OLDCq?!a=#=39j}bzZwowf5kG)_LDu0<=wvrQl8R?$i)%MMFi?K7E@0v$?qA zRLsDbxsH+cMUH>$BkXUjCQH=tPTO1an3_u*z`r7W#2NgZsK^}+`toMyymQs}R?Rz= zH6*K%`<}nQU&sr&3kOR?*Fqta0MAia=!D@k|`Q6KM$1(+5FlL6JI z8}3;bI3o7$)*ANXu~TDB_DR+{)|cih##8#4x*6Ky^liKlz8br)oZ_!=OSnOy8E&_; zNiOPL+-5m(-P3`zcle(?Jt?gR&YuO-!LWQg_Jq z^kMxv<7RzGCz*Wq!m+dL4vT2Zjolf$(I!|1S>BoJ887M9Xsa-8vL@z2XtcL-fgi}e z4Q0X=aQQqAe*1)S7H21B*Ui1>S?Rsz?vVG+7sLL|H;t0)Oty?LM=1szV4G4I;jnMy z6Y4p6hI*;lX1HrurjIpDvTSpR_QJMiwr7sUF|F*otwXHkESn95bXT?YwUy`}SSp%= z9YBvs37kJ13=Il&@{G#c>pGJgle5yfF}J%r*(>FvvK_*^{M9_i^UmeTc^R(9uAIC_c}?7NJbIt(n-e%0933Jf z!z1gszl2Bf4*7_*9Gu6tkQJ!r)B*a6=CQV{p`WRS#b<73USu6=UtvFDZEm||yJ3A` zI&D~LSf}rz-A=_(o2l<4ihn?6ff{m*xGuVn)kP|WYWTCfiYEqgHp6u=ucin0SM|wo z`?OGSWoS_Na%f=aCi}b4S&S1`$SV*(T9a^+ne;GC4edZ(TVq|b#jF?)7>`)o_RF>& z=ANcbrmhydWuozi_7t<3nasp#dQ%hd80;fDANgG|$c@EGQItEu&Ip|fZ1wl_?+&~T zqW9&D!afHehixz zeioV@`NGyd#!>_})g6UzqxD5Owj*mX$I1{+tC&U^*XvH|L_-bJal;(l1YJ;{q8p&SKsTjkF%6jkWCk`4{R3@) z0emj9fZxlHlTwtDV&%x~h%6f7^ zb&$GAzJn{Te#}XF4_aKh0p8*_FbUr*T#t^8UgCR7>B13VLiA8j7cRk{i3a&4?CEf& z$o9y+P(3bD879n$y7@(LyE*1TARnD;8wD>_7#3m_F#>eLf8PgK4^n=#9PVpg%KEmuHya)zZbib z-Q?|&KcZ&-jNDe95t+}G16L(yWSg)@ZX_$|Cuk=|Yb}o+@uCgKj9ZQ7$J|Wwx1VyoHQ_0pcg*8FP}{1CoUG zkzPcc{wmTt`Vs6z9|~r?i_#itEVf_=pqG$=Xa%$u+B&=4oqc z-j|t#vf(?(NhU?1g*M0r_`P?QXs#?2kBH;(c4Ry9F114g<+`z0p)tckO9BHTQ+PH}Nv|q4l}{Y4#GKlDUM<0G@g~1$OBEbxf9u z^5d~()C6jd5cW6H)Q<;f*U;ag#bhCCy3WRT;9j9Yw2cl!8gWC|qSQmfEixzEF~p;! z_781rRF)=%ekn@~^T@UQr$7ZMg(ySaRbDDpKqam%-vl2-?jU3;p8g z+UOt@*L0w1@fb25pC>otih=i*5r)Tsqh1dYk2f{m#)tR=d7Adt%6*V3GhR`#z zxMn-PQQ5{fll}N)1CLe?x#etPK6#CbXKxA>NrUz}x*)oq_@vaM8-nZH2T0wlVh%E@fmMA1$Ztqn{$b_SvNeOcknn6b&cn^vpnitLRqz zxrQev%JsOASPZYt*wEo1KH@dQ4dfcT16&uE;7iPhmHSal`NS3n-5rIo z4f0n{F|LO_i>`*1VW$hfp+>wOZWm*f2(|;iLg-l=S03+dS*IK6UgG`@r)@J#&ZsFz z;@4Y3x@q8PWUAZ=y&&8{U2u=KyLdnPIJgvFYVm`2LKARCu7GwSv%q%nQ*g_W*WqiN&|N_WU*M1S!jGJzKNPR0=3EG3`2~Kxa!_j(nzM7 zX`vnooX<-_N*T`>Nn)(OX=s+z%M>(K2yJ#3XRMBrh|zPAjn!??4#$s1a=4d>qD|NI z2rp!LrJt#T9@StYdr@(%17AD@D~2#z@nd zLPQWf8QK}FWUQ`fWp3wwliQXlWtpb^L#gla1k0N%Gbm^%rsGxEol2C5P!yBo{tpG3(>Gck{9 zsQ((C$k#((Vwi5eGRu7ld2P(r6y@J}=HQhuji#SIC3HNur}3L^9XFDs(Ql!nv;lPp z703uOTzp9{#~j`?te5VzW>#>ykcPAZOSILo`Ju~7OJ)o2q!}WQeJO0fjD&|;iIIoK1WIo|`F#FUJNx#Rj_#2!|U1(jI&t-iRt!eyuHn|q)YmF= z26q{4O>_{}=>8Tx;r;j|;ZNgC;tqF$JI%MTvsi`DQ0%m575>oP*B$krKdemw{jCf(^uyY*Hqa3KB$DWQf>+u~{2txSZ!r=&FXv`=N0Z4~+V1!lb{ja1 zk&>kOD0Pc|##1PFWTcWqo@cKz@0hj3?!aAl1>ND;Qrd@E^>S`&7AEc_tX$AA; zR?yeErq=%gZvZZm&PrviQMF-+P`at zhRzGWLOkg;)zj_@F7nnP%IIHe`fwA%Bk*Fn@j^dkmvS5&C5Fgv_+Za42JE3$7`2j_M;vl1DPa5`1AA+^gA+?d?Sm2pTtdVAHj#d zAu6yhBXud(Pzyg6?GUB85}H2rG4wUj&$ln~MC&w-qnZVN<^D#0wa-RI@pU78=pM|n z@FMgya)jE27Y<#MZs44D60w+j2x&Y-cfd|5RY5a}7peh?Zlr6-jtQ24CkH1%M`c=| zG_#1QEiIJO7S+xd3W%b%@6F8gyw$1jP-D_}`*Q#9`_t#-o3PZ%Lb} zI>bZt3w+y##VmFdU5TLa-gwQ(GtNgUhKtISz*5;rYMEs0p)`hXDNLk`VJ_;X%te+U zlS#Mif@_7RAP2k&8D(6v7$-#|k%P8lzluAs9Md@^Ie0rVRX@x)Sosn8%8w=f!730B zWEq)&v;@86qVi=31SyQR#)?RHl-KA#J=NHWi{8!BOR|(vqdAjTJ2-)PZ+?T_ z3JznNkrg#M;E&uV7HQvuGLf)+kW7FpjIq+?@EAmkmqH&=HgPN0h}?_bM2F(vgeu%K za7A;{;1`uIC5yr_TF(e+t8_~7#P_!Yx5OpwJsK&x4wil>~&ck)oNZBW}1v5c2G?rqJ zmST5#it?wjfHDz1!6<>|dn3!?j^YLR6Wd+NB_2^V@b_XPbF=$ch1kpg&^0(w`=v=B3 zC@%HFYQcR&EeOIrpiHD0_E2_4`^hrhk*EN^OY_7eY$Dc2l8_zP6u6F@hOUw#(i^lW z%9BOWaZ#5vo_tQ%6A`Y1Qk$;Hj1*U~w?!-`{nC`ZJAw1oJ8*AqU8 zPNfJTNyn5FWChk6d%_P;(oqLm3rWUgxJO$CJ%D8+eb7hpX)KBAi=6=z6-C*KR>BYA zYmmP1yuUTX4#-GZbh+ZgdZVpSGvbr_$v2Sl#5dU@{Gkj{Fy#OUNma#VLOH3Tv`RT7 za?xHwd$A4wAgU32MHll+#qyE}&$%%1FELBmjvU2i5G`;TOCUFpf9lriz8Dhp=d@Kc zP4w-oSIl*Ftmcw#ie|sQzV7}0vo7#D_$DyHSK2o+_=W!|d!iM2gU~~12)|Jzd;v90J6wNTyH%&R z+_T5WEVDhbEVF!y`7Q36?XYQtskN!K@u+S%eUsYFbfBIiC^)S&LpDOlPKf9DO)`W= zf`-avVKTp6)QdVPTfq40+~`P&pxa-@KgV6&Wpu62d*-pZC+CiEz48nX4i0q**JYbU zdy37JUub=Dvc_t7ri(Y?*5Bi5CFI6FiFs{r8B;apWy}tH2eSyd@CUP&o<;71D@d)J zD;}2K%ax`6(qzddN97Gj8k&Gl#k-=_P!ivVUq!nqyM=jtviO6)9~t1kn`6tok(rPa zm#fe2k+C)XSJs@|E_rd@q5(%_F1J$LrgX!!x~-;v%$uxj>`mhyB>q`6l(;EzVqq?R zd+blgbGvNO8Pn)C7z@XJ3m7Cz(Z=i%_8OlhewOOVYvmlJ4YC;PNPHrv(GTei@;LDg zFN@zpNzhY#!JUcxgnO-}vh}GSel|#tWOUA$nf`C;qx9!lNv=V@xuMtWQz!?8uq0fD%G8RjuFKf!_rt3cI?i*Se z7wF@(-Dr%Eka==2ibvdm4&J%B%hDTuzx8?5*WN!~rQG{fF|BoaUS`{z_3jU$mEr(o z3*Ln`>2l4fG5rf!i%v`SCjTfptLTrSZHoR({FKnuvCw=;-;XIuyg)w4Kg2%;4Rz`jg{FwRcOPW9PsMC{^o3}MklUpZK_)@B!=B<9OrB_VtglmOACJrsq zE-|^#{rLTHD`St_ms&0vN@#ad6R}aCoV-->3sd>B(PXYA=Znr2>PhvKAee|Q$1jpP z`o5;IPSUN>#p!-%n?mr}GIAbP2w~*${CRe8I5Tj{H_$yk=g+Ld8K(5h>G2tJvKHqa za4+{9@|psFhR5@@WChtxe4@*1N9$V}ADfC=UG|_O?C9-Cw!gP-Gj}wO(zCjY+V4zv zY8ZU~od#u<$}%GFmCq@Y!3?k$>_!Ho7qE@^W1=?omafknfpf`3k|bTk5TX^HgxQc? z@?Bwd^c`D_of&Bw`4R3J@VkiU2p|3n=WrW*cE>+pBvuo?TRwnBzy`W2S|uuflkdrcA-2peZ4OT>!e z6QPYzTj(LQ5-JGQgw8?+KZY;N*WpL;<%9!*SLiP0h(NSUyCJ0Ysk~H)DjUFRM2jv$ zOJl>azc3fp0N;k!CEgNxk|ll+Pl$WOd*UXIVX3NC25niLTV+|m1yZ2T=`TNF;Ny! zF;9pUyNiRxKH?oXPcDVXFH(LX>EuRod-;RBOc9lyU@2$?=7E19if|~xBeme$WjZkz&2s)p}QVCf^EZYVo$NV*gk9~q^TTM3p2re$RF?wH5vVe zbjrt6eg!AN6L0{c>Kx!9To+V;CuoZ7f-B2ca)yj5ZW+jg;*}M-iqcp4Ls&u1=tAAK?HM6Bnc^sR6wdi`#Z$&HHPj;=*~xOA?J`Uhzq%byg@RM z56EX2x`O7hwo?kyWP&v)5Pcd0%NB!mmVi`RAvaXCu!AH6=VQ}-u%^6xYTR(ut76s*u;?P>i;5>! zd#>VuF^G^=(Z3SpF%P*`fWBAzqK<41#NevP`yfOP^Dv)U7K5#+^w?nU;~*6Y(5T#q zhgKaM2c*jkX*NPzK$VJI{_j?Rig;Hb-&G8G0g771N~ zw+Gac>Ma8lVue-QvL3dfhsF$_7ULe?+OpcV8dmwH(old4FBlzF4%9YHusQpus@dc8ha$U9R?GjZ+R8+b;PGY`%s5dS+7?Po^ zsJQni45>FW)Y}&7eG>IXi7Mafjg1hD_dvJc9!J5=5>@LsDCz3`k6dV+P&ZSchN?Q6 z3N`sB^rb;xCdh(D)hs8>>4tT9p`XpKR}DL1&0eVCs(yQ+cB^+w+)#^x(Bwcr2SdJm zYC^CzKWr%t{DAeog{bq7(9MLgIWQLw<8op7```_XeF3rW>fGRk@<&7IiGlOtZxEfX zj%*Oto&j@a!%_r9>8ov}L*)Exi0(WNHb69Xe>j)71l_mYfE{zMg{9K?Kdpt4IUFC69z3%`pg z(s9rVbK+s*CK*S@;UCez!9cmb^hz8m9+#RZJAfTc#_!;7a3jQ1?LyZh?Lms1D{YY0 zN!z3$vMA4iJE))G=+r@;!>_~lNDRdJ3_*UuKKxL|CgJH>h8c(J6kQjRDCkQDSTHVuB24n%dxX(daZEsvC|!`0g!Bp3Z1Cx|Mu zyFh*~LtRk5s)JR^KxH~y*EE0tVId%>k z32_(yzgI=(Y#o#(ifVVpO!}Y^C z$ib0HBe|~BR;(}V<XoMF5%*)+tm)3(Hs7IP_fZR}>pU~9zq4x)(`;BBDP_>sX_I=+H*QO)UG*!Deq z3OYq;CQ;&dzB!)^*W8Dq=BSbD5^fS;yw7sN8RpbT%B_@dDZSF~=G61GWKSrA$r$ZW z;~8^Xi_vo3+}M2C^wykUJ!|#YTG&6>f|jeM>G~9AJuw)4qU@CZkvra>U)ORrWuw%+EaSMMrT95B2 zj#rwaV~AT+JQJh&#hjwAQ*X%q#9ug#m&INqlNDA}q65MgeN|k0GyDBK{<;49ZSS7G zw|-WBF3X$7y}@P~tHd2IJif@xlo#hpL zOYR)oJ33xG2Daj>XpIgvc=S{B0c~}Sj|vl7qB>p!GeFG!QR!uLa(Jt6L2loSsVTQV zPku|jsr+us$0griI~TArmSmh4e>AC6u?NY!ihPeBYL7EFFqG1@*Cpz2=^kkJX*Bc{ z?4{gR{D-d$(Ygzj{ZMTu(L=kL=$}Cj|w{^1i4Oh(V0vStqbj4ZHgt^kfwP-4I?h#ui<@}s`zKL8t5+4 zY=-Y}?z+spUju$*ef{ud_oqKUR{VA@wL|tbe-q@I;Z9s;($i9VOLr=Euh4Z{O?_)> zCfWgcjbveeV>{8#XnEwda#Z{wH$yXVN>kd{$X3g--Vtk`Wgcia%(NzB@Y;9^z6EOo z&({`%mqL-qBL5TDj_h?Av(x_m74y@O^7C`;FPBqRq@T)T_)|nJkSjMR#Md z_$9(hJV%=$rKD!;JAaz{V$PQ=P3DdC@9FPSxBZ;`J^bCDo}Ak|WWfKlwNCPsj4vaW zXq;HmVkCDcbNGtU+wi@X0otLhQrfbTV_%D&Qp(s0&KPVT5Z`|Jr7vt0!j2i0)M`}!2TLV)w{Z+CO+D~aO zpF*9)Lpn>>({M(=kuHUB0usKIx7_(O>qq9I%nO;NvJ^vM!F@UZ_i4V|%LU8NCkq zP4o)u}6T%Q{l7Ygbtox{cqrt#cU&X+d$X4Mf;>4R$e=vo$lMTBp4PqL{WyCIy z30cya$I4KlAs>!zP=?X}8f)1W*xs2OOgp4$G{(2sWpGih7P&=Th1@+|^PS_e`(+kR z)ub-Zx#+(x(7MNQW0JoWZ(poRLZ*HyR)mZ5j|lt>Cq(ayJ+X7-EHZ=oOIyU!!qGY& zFBBj5!oFJD7Bt|p!jGeqBqz1a^2@O-=CS3mW-8Jq+CP}#8RE(Ee)XJoEpcIaJK$}X zChp7br#WM?={&_VG}IUQX^`ViCACX>l2Fk)gKi<&L$yNzwh?buY{V|IJYj*SJfHP? z>(7P_wH zt#;>lF1c+v#WJ7gRkub$NIuKO}sfoh>&Z2N|B)Tg26jnP!|w3>UkFzk4;VPdVP)d9G%z zxV*idfG_N?5lHmNS>>J8-2VpZiM91eVx{EyNyfxN)`8@H;ckcwHV&VUz7%H2gQ4`f zsJ^-s>%@4Wus42{eYyS-c1O^#m$?thVERwP5UXVSWFDX`f&DAj60SztgjV>Md0XY> zWLI@+b6swi-xFkm$?k?(MRJyUM}{k-Pi-xe&lVq$v>Cd@={K$nw@EsO+jJ(&Ui&ZGBGYbd4qjC{5CMLVJ11v# z_T%h3xnb|ZP$hOROLBL-Qd-}P8m@gomt4&}C@Ha6`J!&e7ez*WvGbdvP) z8F>CZS`%-s5;G`qQIVXO-p0MebU1&6AtJ3PHcK}S-fi1#e`$KDEl)H8&xDoXXa3FZ zLOHdw?`0Kob@lcLu8d}Llz(03%dAbF@BUZ(4()`*AH@zOws*|dmP4LKaBr4>18mJJ zc2-v4qp5SuBjZs=tAt90M0-U;Px6d(k6pm}<;vtq?N4(<+dNA*LxLt9p9xqo9Lez| zxtr3M9fDSu5V5k<2rsJf>Q|b&80#CR>c-Op@d0Q+wnldZ zPP(QzmuD|_esvEBzKxvVQo?3W`?UL+3I4T_JBZ#IQ)G9cuQ5X`)2Z!pD{eqwM`%0W zO1RFQk{FVuTIf&NW8>GxyJ9Mv63Br3Md-wb#C0U9e`9EGs%Pn9@@R_Fe-e$5r9z9a z;O^%1W=+qAa__GnohC-bp4@uR$JFy_W!&M=d-R+ml4L7#%of!Aft(PZ@JZ3jG7VP+ z<%Es=Yk3f=)0{DXkG&ed)iJ?v6JI4?6BCrR*da}hv8FlRaEv)jH6!cb4?)&R+G4&I!L-IbVd+#6y~2h1e2_g-4i1(>>%`kw(E`;pxIM zxF@xhh<9^cdggfSQnq$Ds8zKI*61Q6( zDgDEJ7C3N-_@p)1>~U%EW?i-}gdK(}Xc1f|)@!52+NQI*u-2Xj`&G_uE!8Hd5v{|t$;xCxLFzLv9h$D*O2<~q=08a%= z3&+7Pydve(H?Y5R+_!8qm^3}fT0|{;C)tQ8p{t?uXg+IyXwT|yY3fqrFo&#*6nC%A zE{k@mTC`(*jMNQl7{7soXrytTcsZ(oWF> z?qDdj-+02>(^lDb#yCqegYaQ8b_AX+FVj5N{c9+z*J*ocS%xClAy0*Bq08Qmo)LMz zyp6s&;YX}5QZsA}K6Bc#w7yqdEig#4HKtD!h|TbpSPhLsm!yBCk?3U%#_S^3<3*9LVzuxC-+K5NzszIx zyTV7{iC(ABSdS^=Sk}d04}Law$oLH2p!{x#r-#Z$xK_R;ED>b+FYvd}pD!sTW0y5? z2F~)pR>ii;&<}nsS0+xwjf-cR2D&=>hs+9k7TlfZAm%wv9>gyV5`#gpP^Fdw6-&!UkvN&*+|f1SAY~PC#gJfKXGu1H z)b6E^Qzzkx?>g;LT}SOw+Dy(MK2i-BCtQJ!f|%JK;mf`%p0=J=frN1B@S#Y@@D^Ws z)_P}lAVHjq&xUWLZ|1i;7d}9qEtOGdafkFCOp&j#^;t^nC@;r9Yet%FRtZm3vMT8plwTj~RbKg`*NdCW`Z9&OX~ z&>q$8)!x*c);^-s;hm&gbOU^!(pcIb{VUYR|Hc13P&TqDd^C(kx`&e88=XJ>llgU^ zKYiJB(9%|yN~VFYa%ts+v_Nqn_rbwvC3Y-#N2mZ=!>#3ijP*>i#-XumJj^id44qd$ zNxxKEm|9OVbbFd58<6|)CCX5~MR-I&4%7*_{G~&kBeg<{eJ5O@+^Inizg<~OeX^W1 zN10R9LHx0@L0l;f5~_=j#57LfUI@cwJ2*j>)3(=s&^6Wt7#E|_o!1K575eX*!}L0) zEq#D~q1mPxM9xJG$|Y`G_+SVL-U_}8w+b7=Lqo59dvYny+K4EZdi8hh!_QVr1K zAPX4*{*}fm+ax7gg`3XR5Mq=K*iPax?a=gP-cqOOxtdA341EcMOFv#mYW6{#-c5M> z7@|g<6ECNA8Bt2A6xSd1bveE>`Y=4bkp|_W>tTxmX_jpMYU# z4m>Nl3%ooTtsA|=o5lK|J`o@Q;ekycI{@mExm!d4KX552`g4xnHB96 zsv0^R+7x=rcIRrbm%{OZk8V8Bga1d0K>=MM-^2aG zZM5;-p8TNbII+Jp3Mq$g zB9@U4h-;*qq&2OyrSv=XQ}y-q4YhgnT52s_gzSy|f!vV=zCU+Ak{4dfo?#b77PHI4 zOZ;sDwb)nDX_B5WlF8&80>|s2XVF^tFxaO6a7jPJRAHb{T701_1b5K-_*EBKL9r>ID_!;gr)1F~kiq5AMCQsOISA{&joRGjz;0JKa8Hrv`O-Jw5`s#A!hWvws<*o8C$q7VVH}9;# z_tGu7pV1G0Bpw1IeuYRRKH%NB3Hck@3|-cqt54xMJ4gb@QQO3w!5w=)aK|HhyNErd#Lx+&JjC6I z0T0zgPz8nKIrvZ9iPX%MdSBpy_UZ%l&w7^m4s2s>&^xS>DfA=uDt}K{YfBQ=@%;pb z-^s?o8)GmTFi+@>)x6O3P%F7mswdZy7WxI>%;0sgqjX&T+Ux=L#GPm}#7)V-L%<$% zn0!DqHJ=#8jC-0_9|smd2lyH_;{!^dN>Xj<8(bS9S$HW75mT@UCUV}*&07?cC{rUyiQss?7wEMu^d4K_O# zc&||Z76aJXrcdt zjQ^>xhImk{Dp%1L8kLMQ#uW3ik%zY6MZ|Eb4?L}InS;;*W0yY1d}*99ClO1`rRGCi zmAr}nV&b7aj`N*`(mcgKt%+Kr_l+M)Y~g+s7>e(=x?A7^FQ{r4I05(lCBKX zqx5QE;k>KYLoOnfn2vv@N@Gw3plfETQ4u^6?ZArK6eXG=!b#4M@TWO}-_Fr&Keh^+OC`dX zx;(s-IVD>TO3Bh)@r=K!KRjpza)ZM|H#NI^;D?p2uCt5GaN(5_R!z^uqiL zEXLqjB9=oW9i3>1f5fM$Euh9Gxkd2CXl41_lM{1rNZ??`!Q_?IQTRHDeOt#W|qrSxH8bC2=Ln;h@T-4=KRM`=7Rsm8 zZu%M+j}Ab?fTzeKPm#OHL~=j)7+MlbpsqDkoRGmzJsHm-UJ>ckEBX;#o9zWXpfPuu zo6oLfvzV6D8r%lYH`}NI1*xm0mf{3)c3_Gx))yV<6Ko={2_>q#jH(cQCl<9Jr{YWG zD7p%@irkL{Tmw83@4!*N5X~kRp3)giGDH*lj`Ol(*+g~<6QXvJPjDA> zUvHq@Rn~?^%Ca~s=!SP_zLyCOlWLy`JyeW~8XY)#CDxAI0ZiMm4HCQm`TWRcy7 z)}RylL@Xr=QF|g5OXMlwYZz9wI>QBd<^mz#hGwoCGxJSNJmFCN2_Ad>(2qm#9Tn zp=!|sm=kOnE*!?ZZPvNZ@f4cyCtVrBp2aneN1vV_&iF*-|hjt-&s(+kib2 zp#}OT^<-$U{6IPul>N{B%e}YVH+?q(C^!gavEOLPI*TTlS;S0w6YZs+GcVaP^i`@Z z=vjUyk75bm#QWx65%ZCm!5&~{FlE?e<|EymzDzwQ5>PTwVU?9s`JvQZ z)B;UG;TP|{>^bI-5vR%vm95%fW1cYx_~^eNu-F`?DwoRbW#&@1sbLqiKZOT4lF0j{NMALz(O_!xQ%uFhSo?e(5$y* zZ=xf;j}0&se;$IMouyyWGCoCqBG+REU&Kd=)o2o)K<=h`LLYm<{={X#v$z%;&TgQ8 zpvsVEK&`h(C6pB~(ufTvfpvAayF}qo_Xuw{U->{?<)*$~-)8J2;;C0;F?KmuOsFC3 z<4!OfGmZWhSHWRq4KfJ6mpf<<-i%LUg}TEOKxu;PF4n<)W-Eakx(9Wb=xSWnRw{$# z(-1SKzyFoDjE5=MlfTl{-?P9UAx~F#X|K(3#37@>PbA-R4Kj4{{ zpOm*N|6SoO_k%!9rHS6zr~&Vmo)p3U&NsImwN-FT6aHcUqQ}!Jw0tLglgI~~{bX}G zcy&(V1JpGL+BS@B$>qSDX8}8!-N>|NV#z1QJME1+NsbU*A{)HsTjzP4k8%?7Te@;# z1bsxyGg#v`(H0bBqnIpVtX+3Z2&-&=!f{MAH5VTMUttP7Wj3PM##(bRYD{*8Irc(+ zpwO7x&r|>#MMGv3vy6F0deB7O9U35Kiyec_0wv*PKdx|Du8>_fk9P&V%Vp7=h1V1R zQd8($dN4oAe%vwAQP0tl-_Ah$gQ}^J7)Ojl6No3Epy-dn@kT0^OBRxB$j%5ax%*r? zW2bx32dTSgivEY%UUtE#Vq4(7?}InsE|Wj@bFJ*{dAnSkZ-{z~+DAF*bhw)4+yG&K zW4YryM-x7ht&E2f@j9nhG^-PX@oBK~=MpnW5pQ4;goE}JdlmZy8x1wskWFGrQ`M-= z*kP0nt%K{iPFy)u^Ts=N)rxTj8yqiqFHl^96dLWQ)h7 z+tT0iP3m`v`6>*V?|^G1B%o{{vT*c|i^(4^ZPEQqODr$b6?z2cFXBnZMWsh|h2ne;^uCEt%SERr(sWk?5zef$E-W zZndD3i}l^|CHQU@PR=cp-6Q{pKuxs?_jlyUm;+8$c*kU$H?%66HFBz{?*mG&dZ>i* zLMNDIj=ItLQ9nmk3!fnTLAN7E(0$p{+yVYi{yz9lJ1}M0`wV6WfbI5^av)g2*QoG9 zLAGn1Z;UV8^SyVTyHw8XjN18EL`h#lcZ*Dg2t%Woh3LBREkxZ@v?LgDKG1r~vqR_1 z+Bk(B5k4TMYSbayW9|lBnmmby)1_>hgAO|+m;zYKY#zS0zzYugwO%?DAvFq2^qut0 z34RDn3DoluzC3r=Jn@q&lh1GMxg;&c9ULViRttUk1@u0%zuHoZF$Rn~5(oRh< z3m}R}*YMAgAEQc#b3!URo-9uQD**u_=fha!AYWEk4nD^@d_DdV7fWBzDyqM#tK@gV z*3x*Txb#^}k_Jn4{oM;PKZ_aVaxNA=73bpWjz7Ye@h01vI%UiU$x}%+Jk(ykB^889 zsb|c5D$aJznHq^B-a6bu3$m8sF&bkTx1a~vJof$eW{w>2UoWz~5jfUP#+diDaP?kj zj*_W()N*P?B}pkCIvcFz?Nw0f6Y-%``jOnq!Dhrd$DU{@ya}H|M(TaV%YkunA6b+* zd0Z$(DQQ%uqivre{NR&*8CFS{NA6b#sac3%_OLelgYeH`UpWqiA!ijw7hxtFPwvsK z$$v|>&}KCk<{Z`0aPzo&OuieOz(d8koQMU z)lc_6K6*3nb%V^u1xu9_J}h=-iIj*twtzWMeB-LypDzOps&UvQI>ivP8Dj{YfjXz@$Y9V3^7*K-u-%W`YzaI{JL9aP6x^f|;? zHccpI8^T=2$CU){!rW({rf0YKY-B&rng4lMdcudy&mPzD&}w!>^uw4D&Zz>4=J`GO zcMHL}EoypOay}i!G!ll|>V?I@71obhZzCA5eogvQoFZe~$aXktRBZ3KMsW?I{{l*` z1V4m64>Q+OFv}WZbO&ysD~ycqvpwj^C`IY+o0Pxv)BVi!tme6=3QN1+xJu^V%vqc} z!4)0&MV08IcG}UI74Z;lTVStutDg>)*Bp2>Gn5-*>t{dd=p1$kA}#I|lG!&{F(&AJ zj0-rC{}6T}vRAR@#a9(u5|QT6_^V7WJlxFHZ)lo^&9-E5`VBLbU(a!Lk~viw9GFuu zB`5g#XwI_yZ(OlHm-n?k}alo_ZkY7lL(z4@6O zNv~%63m4#y;vHk5e|v;nwkEX`$eBCT7?!qga$bqp5K+Qe+nMUPAnX7hu`D};d<+o> zG;h+$%i|d`)~~3)6C5Wi|TTGjESO zL+#6)u`9Ms+;Jw~NDVa+yGTF5{Ah=kXEvu&=>1f6AwR5Y#2cp)mS!Kw+OP^E@tV|m zZjqxyME9uTQLUZ*!W8>iA%>k!)geoxp`cejj>eHB!-A)-r=uHx3BT9U#r3{)_bgAg zx3aIA@071bu!Fc=oEzNk>*S)dOaHg+?XZs%@{apc(SWeCF-M}suw$Hn{*?;7i+z)W zW2Bbqc(7>fq)+lM!X`%k5_2dfDYBD2flf2+pz4l82bp?~DiMdHYJ$Y`r?7u*vmmzQ zTdFp0guXTkv^ikMyG+01s@NLZJzOy=O-~FB5SIpa1P_RlrAN|rvAh4IySGa%yyM>E zZRpSNbj|>@md}M@9F5|9LDdFJ)_VSX zd*AROk&JVay(Rw(Gl2XKMmrDLNTH%o$2QYmC`<+sXA5Dk5Y5N10@VulMHm#)xx`AU z7k!%U4KtJ<}pDOK?ktUl(%uo$^}b%*vBI zF=AOQ38xE6_$9|4h;%pCoEN(09}>t4UXUl56g7i;B9ynU2*Zwj_E<+b8|J6cy{KE{ zRa}PJ&G`9O_6?4!j{OeGp3A+1sI<3HU1Ob2oA(JjRf747ZzJ^Ldor70Ml)PH5h^YJ zBkzHTR28*bzu!zG|MC?#RFnxwtk4O`+TJ z&)5{YIgn)C!7p-H8K+l5(WF4tq~Eb+_!`1`;iT=LJwxcpVWuXoZQ@G{ zO>Nt3lfX(;4D2cgsU1WT+;3@G9i1_6;e$*DSC>D|&Z7>S7nN)B3%N-sK~2@$gJ!+A zQBrTN71#D?s&)!Inu$^^|0(xKSHP9w(fn(~o8pVWZSM@vet&wXK2n))!kR|(bF`p$ z>KT&HKf}LRDrp=eN3yB>S$-Z56goG7^YI&n#oPkA7x@4kFfJKs#1-b8(8#ed?6RX! z7|nhr%V3BH0;*IeaA`i2h29b6$yU@=sw_2|%p(3WoyJuCmhRGrK`dJg7R8C6rpQuy zs7UJ!5z?fh}koCp81{TbA|0vq@{nT9hIj0nE^4-Y# z`d9KEajT@N#Rv&cqh`^|U|#&5xx=rpakdxyX!b2R*3^K8J7(@DHI}ni43Bn-jw@h= zZj9d;ZS;@YGkqFR1*tGk_>sIpy`?`e1DT(wW;o3()OV@Fm4RwIEne?x+=FCoL5OQ^kGK&`=#USM8vdQwpUxaed&O|B)}*yT-H7P57LlvuGlHof9GA z-!^_eC7Y(!TKNO4EZ>+f2sgQv(&%%{Ps~!bB6o(r4zW7^reBb)h*M?|q7WyL>*;OW zV4mai+0OI?G7`@s?t{8-59&ZDXfui@w%{~8AC?rnjo58w=%=+7T8wr=OEfl_*U?I# zH6Ec^Wui3fIt@6RW|R~3Ql-#%&{4ie1TH}a$W)+h29huiApQXj|4wr>_`Rot z^7x@y88p~~OpmeBc%y%#Q@TT+u9elgXl1ma+BNN{j*Jw&guYy>tEPq8%2y>?IxYSy z%?}mR2ACD`eQGgih#xb3sfl3CTCJDW-fF)aSIjM_5lPUc=?J(m|8M6yw zNwgy_0$b4u{Xk46d(vx|?Pvt>EnkCQh01KlfYP6Q1Zc^$2yHrUPwDa0KeHHLGs=m@# zY#sm~Lk?ImI)Q$yI{JvZftBbDPNwS7Y1G%$pQIl4hkqOaiTH-x85-eaBa5gRv z_QTcWb@CSJBrAdas1RZTdd;0cs?-4uY$95PR-g>1zskms`gSczYYs7tih*_5rZ>}n z)7(l|m=iygoYHjZbm*X_7)Q}IVllo$B%&KeWxW+>q8V+Mz6_#_-@+cyOk-*-S&lsa z#V+jVp`ZB$@K?4Ky1k}NY&3ncMJrCXiq&{4aHLeP{((ToklyO zg;56XD@9*oU{E28<{GeNbo~=K_MT2KC>CF|2}AQIao7<_)OFTPD&;Q6Kg;- zyb{Q!xuA3JfnKB9;E&5j&(Rvt``-jL^I>xy=+xt(P0tydjg6pFn*glEujna~AvQS$ ztayge-*}>*1Ia7x_s4Xa+A3$&U2F*kUsM%){ z6Je_dpvUfF)-r2=M?*6=BR8~jCOQD6PXxNEh-1+}n_m)`pK9>k*e};L5%mV<;3a79 zUjqlV6{xA@<}mX|V5bVeFFzkj=K>}8R?t^}1fuLavpZbhd$S7a4m#^GCr)2WN2sn>89DzSo9w-G{4=13oMX*2kkBU#?*q)Zsv| zn|uIvYr44@Z1#rP5me*(Q1{2sQn=DWIQluzmH!E>fz@sp6#(zx0gq2?Q4TUgxMW1vBr!+D26OD;i^p^ruZA93G2 z3dcAP+yw_DS0{-4{|(%&1hiiotXXOP3jOyC&=Wq>31_YVY*PiOJqCKuDR?eU0-9nJ zsPaz%wGjhXI~kVmp#%l+44DV@I}=KN6X=Z!s1fM#>m!H_1S5AEhz%=y0tPb8%3bgh z=nyOaKm#~;ALs|MP-~~*s1u=XHb6NHvl^7EKlBbOYr!+~80h_%o4-SExCCT}2n0tN zAko^wIpBE@Owc=E@oqz(e+);K;kZ@cTH8VoXa^;$1Wb(=cod6Y5g|`P12}3A=+$ju zD+hAJzR%5PP%1yD>77u@ve44ifru*&_p1v1APwsICTx)l)KM(7R2}%W2)ZbEH_)($ z#atDGHBoRLJKRUr;GAusJ$k@-nn2w-VLJoJlyEp^edw31U}+4;C<9c2#gD~8zp^MK zJ6x%?FwoNBU+5x>8nVMD9_rBIhr;1l)?)EpMLbgs>~9fgQSiCMm)5F^YJ6EL0s9vR z$BuzD#o>P?|Nm@pUs}TA(-99y8E5D5d`&tZ{MY-8weHiR-;PSI}> zLuGwuZR>+;FFJEkW*w{KMVW4_Og=>%p_PNj;;F3iSm(Bm`#-|a+TS9tiZbyOW!$mO zP*gsv_APSDVzsR7KSf!9tZys|&}ub{#;K2fSzB65QGT5w ze$FDuEV|F)`mBs97S(7i7GYPE>&D^>Ee@?HgN>D!#iHwY_|78Jti|e?))v-Ml=;bG p)vPQeR(=$V4z&0{i(a$x!&p3D(eYuz_2tK6FaLK|i)ple{tv(C(d7UD literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/5.wav b/examples/ffva/filesystem_support/mandarin_mainland/5.wav new file mode 100644 index 0000000000000000000000000000000000000000..7f859ea7b9e669d986a08565fb63179d894d0434 GIT binary patch literal 29926 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/50.wav b/examples/ffva/filesystem_support/mandarin_mainland/50.wav new file mode 100644 index 0000000000000000000000000000000000000000..386c60b9966b7c292057f644c245c1a71a8bdd2a GIT binary patch literal 20204 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 JxVy``%j~!u>3%ir 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 NrR%eFuW?+{{~yd`TlW9} literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/7.wav b/examples/ffva/filesystem_support/mandarin_mainland/7.wav new file mode 100644 index 0000000000000000000000000000000000000000..9cf623873807377202f5c6e666968c1d4ba291f3 GIT binary patch literal 30232 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/8.wav b/examples/ffva/filesystem_support/mandarin_mainland/8.wav new file mode 100644 index 0000000000000000000000000000000000000000..d1879b378c103c1e326c5c6252e88ed26ed9aa40 GIT binary patch literal 32382 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 literal 0 HcmV?d00001 diff --git a/doc/programming_guide/ffva/diagrams/dfu_reboot.plantuml b/doc/programming_guide/ffva/diagrams/dfu_reboot.plantuml new file mode 100644 index 00000000..172c7cae --- /dev/null +++ b/doc/programming_guide/ffva/diagrams/dfu_reboot.plantuml @@ -0,0 +1,10 @@ +@startuml +participant Host as H +participant Device as D +H -> D : SET_INTERFACE (ALT=0) +D --> H +H -> D : DFU_GETSTATUS +D --> H : Status=OK, State=dfuIDLE, Timeout=0ms +H -> D : DFU_DETACH +D --> H +@enduml diff --git a/doc/programming_guide/ffva/diagrams/dfu_reboot.plantuml.png b/doc/programming_guide/ffva/diagrams/dfu_reboot.plantuml.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d1b407f0472ed0d2b6cc7c70f0e6ed005ae7ee GIT binary patch literal 9725 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/programming_guide/ffva/diagrams/dfu_state.drawio.png b/doc/programming_guide/ffva/diagrams/dfu_state.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e1f8c55bb3419c4f5d635f1265f8693ab0ba04 GIT binary patch literal 124186 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 literal 0 HcmV?d00001 diff --git a/doc/programming_guide/ffva/diagrams/dfu_upload.plantuml b/doc/programming_guide/ffva/diagrams/dfu_upload.plantuml new file mode 100644 index 00000000..461a60f2 --- /dev/null +++ b/doc/programming_guide/ffva/diagrams/dfu_upload.plantuml @@ -0,0 +1,14 @@ +@startuml +participant Host as H +participant Device as D +H -> D : SET_INTERFACE (ALT=0) +D --> H +H -> D : DFU_GETSTATUS +D --> H : Status=OK, State=dfuIDLE, Timeout=0ms +loop until image is complete: n from 0 to N-1 + H -> D : DFU_UPLOAD (BlockNum=n) + D --> H : Data, Size>0 +end +H -> D : DFU_UPLOAD (BlockNum=N) +D --> H : Data, Size=0 +@enduml diff --git a/doc/programming_guide/ffva/diagrams/dfu_upload.plantuml.png b/doc/programming_guide/ffva/diagrams/dfu_upload.plantuml.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b69ae3d6e68abfd68c626cc49ad96171c1d4c3 GIT binary patch literal 17438 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 Date: Tue, 26 Mar 2024 08:43:56 +0000 Subject: [PATCH 128/288] Update test steps --- test/device_firmware_update/check_dfu_i2c.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index f0049ce5..03a1c42d 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -9,11 +9,22 @@ # 2. Prepare the hardware # a. Attach an XK-VOICE-L71 board to the Raspberry-Pi expander # b. Connect an xTAG to XK-VOICE-L71 board and to a host machine -# 3. Build the application example_ffva_int_fixed_delay and flash it to the board -# 4. Generate an upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download1.bin -# 5. Generate a different upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download2.bin +# 3. Build the application example_ffva_int_fixed_delay and flash it to the board: +# cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake +# cd build +# make flash_app_example_ffva_int_fixed_delay +# 4. Change the value of APP_VERSION_MAJOR in app_conf.h and generate an upgrade image: +# cd build +# make create_upgrade_img_example_ffva_int_fixed_delay +# mv example_ffva_int_fixed_delay_upgrade.bin download1.bin +# 5. Change the value again of APP_VERSION_MAJOR in app_conf.h and generate an upgrade image: +# cd build +# make create_upgrade_img_example_ffva_int_fixed_delay +# mv example_ffva_int_fixed_delay_upgrade.bin download2.bin # 6. Copy the files download1.bin, download2.bin, emptyfile.bin and check_dfu_i2c.sh to the host_xvf_control/build folder on the Raspberry-Pi -# 7. On the Raspberry-Pi, set the desired value of ITERATION_NUM in check_dfu_i2c.sh and run this script: `source check_dfu_i2c.sh` +# 7. On the Raspberry-Pi, set the desired value of ITERATION_NUM in check_dfu_i2c.sh +# 8. Run this script: +# source check_dfu_i2c.sh ITERATION_NUM=2 UPGRADE_FILE_1="download1.bin" From 7a1b90288a6b1838c7b4d243d88c181e3702bfab Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 26 Mar 2024 11:04:41 +0000 Subject: [PATCH 129/288] Fix paths --- doc/programming_guide/ffva/design.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index 944aa587..145b6e6f 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -109,7 +109,7 @@ The rest of this section describes the message sequence charts of the supported A message sequence chart of the download operation is below: -.. figure:: images/dfu_download.plantuml.png +.. figure:: diagrams/dfu_download.plantuml.png :width: 75% Message sequence chart of the download operation @@ -128,7 +128,7 @@ A message sequence chart of the download operation is below: A message sequence chart of the reboot operation is below: -.. figure:: images/dfu_reboot.plantuml.png +.. figure:: diagrams/dfu_reboot.plantuml.png Message sequence chart of the reboot operation @@ -142,7 +142,7 @@ A message sequence chart of the reboot operation is below: A message sequence chart of the upload operation is below: -.. figure:: images/dfu_upload.plantuml.png +.. figure:: diagrams/dfu_upload.plantuml.png :width: 75% Message sequence chart of the upload operation @@ -154,7 +154,7 @@ A message sequence chart of the upload operation is below: .. _dfu_usb_interface_design: DFU over USB implementation ---------------------------- +*************************** The UA variant of the device make use of a USB connection for handling DFU operations. This interface is a relatively standard, specification-compliant implementation. @@ -163,7 +163,7 @@ The implementation is encapsulated within the tinyUSB library, which provides a .. _dfu_i2c_interface_design: DFU over |I2C| implementation ------------------------------ +***************************** The INT variant of the device presents a DFU interface that may be controlled over |I2C|. From af01bb24b57ffc7305a1e732dc951dec9c646283 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 26 Mar 2024 13:06:10 +0000 Subject: [PATCH 130/288] Add control info --- doc/programming_guide/ffva/design.rst | 63 +++++++++++++++--- .../control_plane_packet_diagram.drawio | 1 + .../control_plane_packet_diagram.drawio.png | Bin 0 -> 53003 bytes 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 doc/programming_guide/ffva/diagrams/control_plane_packet_diagram.drawio create mode 100644 doc/programming_guide/ffva/diagrams/control_plane_packet_diagram.drawio.png diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index 145b6e6f..10c3b198 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -168,11 +168,59 @@ DFU over |I2C| implementation The INT variant of the device presents a DFU interface that may be controlled over |I2C|. -The INT DFU state machine is driven by use of control commands, as described in -:ref:`Control Plane Detailed Design`. The DFU state -machine has its own servicer, which then interacts with a separate RTOS task in +:numref:`fig_control_plane_components` shows the modules involved in +processing the DFU commands. The *I2C* has a dedicated logical core so that it is always ready +to receive and send control messages. The DFU state machine is driven by use of control commands. The DFU state +machine interacts with a separate RTOS task in order to asynchronously perform flash read/write operations. +.. _fig_control_plane_components: +.. figure:: diagrams/control_plane_components_diagram.png + :width: 100% + + |project| Control Plane Components Diagram + +.. raw:: latex + + \newpage + +:numref:`fig_control_plane_dc_servicer_flow_chart` shows the interaction +between the Device Control module and the DFU Servicer. +In this diagram, boxes with the same colour reside in the same RTOS task. + +.. _fig_control_plane_dc_servicer_flow_chart: +.. figure:: diagrams/control_plane_device_control_servicer_flow_chart.png + :width: 100% + + |project| Device Control -- Servicer Flow Chart + +This diagram shows a critical aspect of Control Plane operation. +The Device Control module, having placed a command on a Servicer's command +queue, waits on the Gateway queue for a response. +As a result, it ensures processing of a single control command at a time. +Limiting Control Plane operation to a single command in-flight reduces the +complexity of the control protocol and eliminates several potential error +cases. + +The FFVA-INT uses a packet protocol to receive control commands and send each +corresponding response. +Because packet transmission occurs over a very short-haul transport, as in +|I2C|, the protocol does not include fields for error detection or correction such as start-of-frame and +end-of-frame symbols, a cyclical redundancy check or an error correcting code. +:numref:`fig_control_plane_packet` depicts the structure of each packet. + +.. _fig_control_plane_packet: +.. figure:: diagrams/control_plane_packet_diagram.drawio.png + :width: 100% + + |project| Control Plane Packet Diagram + +Packets containing a response from the FFVA-INT to the host application place +a status value in the first byte of the payload. + +INT DFU implementation +---------------------- + Mirroring the USB DFU specification, the INT implementation supports a set of 9 control commands intended to drive the state machine, along with an additional 2 utility commands: @@ -189,15 +237,14 @@ These commands are then used to drive the state machine described in the When writing a custom compliant host application, the use of XMOS' **fwk_rtos** library is advised; the **device_control** library provided there gives a host -API that can communicate effectively with the |project|, as demonstrated in -the *xvf_host* application. However, a description of the |I2C| bus activity +API that can communicate effectively with the FFVA-INT. A description of the |I2C| bus activity during the execution of the above DFU commands is provided below, in the instance that usage of the **device_control** library is inconvenient or impossible. -The |project|'s |I2C| address is set by default as 0x42. This may be +The FFVA-INT |I2C| address is set by default as 0x42. This may be confirmed by examination of the ``appconf_CONTROL_I2C_DEVICE_ADDR`` define in the -``platform_conf.h`` file. The |project|'s |I2C| address may also be altered by editing this file. +``platform_conf.h`` file. The |I2C| address may also be altered by editing this file. The DFU resource has an internal "resource ID" of 0xF0. This maps to the register that read/write operations on the DFU resource should target - therefore, the register to write to will always be 0xF0. @@ -244,7 +291,7 @@ To issue a read command (e.g. DFU_GETSTATUS): bytes. ACK each received byte. After the last expected byte, issue a STOP. It is heavily advised that those wishing to write a custom host application to -drive the DFU process for the |project| over |I2C| familiarise themselves with +drive the DFU process for the FFVA-INT over |I2C| familiarise themselves with `version 1.1 of the Universal Serial Bus Device Class Specification for Device Firmware Upgrade `_. diff --git a/doc/programming_guide/ffva/diagrams/control_plane_packet_diagram.drawio b/doc/programming_guide/ffva/diagrams/control_plane_packet_diagram.drawio new file mode 100644 index 00000000..1e0217dc --- /dev/null +++ b/doc/programming_guide/ffva/diagrams/control_plane_packet_diagram.drawio @@ -0,0 +1 @@ +7Zldb5swFIZ/TaTtohVfIXCZJu3WqZW6VtO63UweOEAHmBkngf76mWC+jGnSFLpU6U1rH5vj4/O8NuhkpM6C5BMGkXuNbOiPFMlORup8pCiGqdO/mSHNDaph5AYHe3ZukivDnfcImVFi1qVnw7gxkSDkEy9qGi0UhtAiDRvAGK2b0xbIb64aAQe2DHcW8NvW755NXGbVx1o18Bl6jlssLetmPhKAYjbbSuwCG61rJvV8pM4wQiRvBckM+lnyisTkz110jJaRYRiSXR5YKCtwnz58fYx+/rq0rh6+Xd9+OWFeVsBfsh2PFN2n/s4WiLqlUZOU5UL/u0TFwEm8ITWlExQtSqpB2nKy/7cwRktsQTrhcl64/I2L4Q90/HL+sRigQefL5aMsX+XKCkbL0IbZPiQ6vHY9Au8iYGWjayo7anNJ4NOeXD69gpjApDNVcgmAKheiABKc0inFAzpjxlSraKy/rmuA2dw6/sIImO6c0ndFhjYYnGeAUgYCNUNBAEK7i9MssA+Yk6ocHCe1k1McgXB/Tjcg9RHIOF3B0KGbq5Dkjg8EiWYcHBJNgITLEgztafa2oL0QhbCZFZh45L7W/sGSmbXnSb2Tsk7uH9qtdwuXRRpDfktu0VM727VsjgXJLGwY+oB4q2YYogSzFW6Qt7lMOu5BbcIxysNnT9XfPZwjQ+pQReGIAOxA0nK04V1ue38JjA9WAv8Lra5xaKWe0LYcDYxWf0fLETG7vl6ei1aWu96vr8R28s52C9u9j22L7WufW+OFbA+d1PYEv5ljaG5HZS3xavOVuTlqT57J0L7wsvXfxHmbmFzq+a/UnV+TMudI2Y0hTSRIa9OibELcHXBrHV16Mq7WBptVBNrII+hVUEWMx6gocyz3oyhzIgvJ9a2o1jpbFNXa4KsoSlTIOhJFTcxmwjVeCLvfUU1HqjHUHSUOuPuOEsc1rKKU41WUOZaEgPa4oyQhuf7vKHHA3XeUOK5hFdVdG3xZDbesDe5WpSUwIU11xgSjP3CGfIQr9S6oXjkT8D0npF2LyhRS+1lWKvQs4E/ZQODZdraMsNDYLEWyXLyk1mio3LdL8WtTvdYoCY4J//3cW6lRFtUa+0B8enp6fHj5OobcxqsJ6KqD0RWVEd/p9kNXnQxGl3arn1nz27z6sVo9/wc= \ No newline at end of file diff --git a/doc/programming_guide/ffva/diagrams/control_plane_packet_diagram.drawio.png b/doc/programming_guide/ffva/diagrams/control_plane_packet_diagram.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..7588cd356f10e26fbee86d44766bc8603e3127c7 GIT binary patch literal 53003 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%0Dn z9Et$uFp10PTxD*;x36A{-Rd9SMejQ{NNkc=2!SQ zg-HP!k|~n`%oyN=8&eGD;C}it95qh-pa!772H6PulpNNlmjcvfwi>*_uTBCa;@nL$ zu!khw>v(Z{ljXM6nNG}HVeQaMH{(h-QCt8gIr^BjrO&!Bsjknt8RuFA!M5S!?6@1u zg6?-b2QEMbG$2uw6ANg_0^pZ06(Qt&TXAm2`bJKmThhCtw>|LvjP4aBLiMk8U}2dqdomt_<2xu>RE! zejy2h_VaC*zzyybpI_a%S`1YfntjL~x!%9B{GQ(4=z!4aN2<~s^0m>TL}f5bJfM%n z?8#9CIM;YkGcm0=Yk-a@pu3eF<02Ru6qnqt^-KO9Tm~FR$AA5EEd-Bvq@;vk12mtH z=DB>)gz8&TFc~4RMZWt}cVN5;Fh;TBhs1%gI1usYKTiQqj1WBCA_C2Q2u`lol4Ftb z5CNS4%u~gscLguN22Aej4z&UHKR@~Tv78T3VQyv220r`)K9oeN$09zaH1 zp7h_9gWFsCz(Ea`CrO$**7Fa$9E%pGFr zV;iv4>%hCn85@FW)%KO>@Lu^+>=i`!Lb*^hb>}^ z;t-$y`XPlpNDB!snF3ybH^91QOY0eKz&aBM?mGV($^7!wCObHl!u6M>@XJd$r$YW4 zr}CG1{_ox8UqtdlIf!JaICB~Jo3N8$7#w4bh#@!44!Wu9ailqN;UaAba&dke_e=#}I*x$3h9rj`+eED7aXi0!$y4 zUoQ=EO$ondi{2uEG16d+pKt%NMG;&QElr^U%<5fTnDJl4b_LO&B^Y-B!O4RUi&dM# z{+9P`fJ`C7;ShOX(l8i^$x5GW#vetw{cli|`!$L13+H|*j%iciB7A_9|Hu^lMJQ!p zW9u(svRQHR-p(@J&~OQaMIKB&8GQ^l3r;n@wf(blAjA)0#2D#^1+e5}0PJiM+4TU_ z*nrBswn{)e{IeGTYcc?ZI?$8N9tq_F5ZYhlh54xoi06zg{UTJ4DRGgKnS%`3Ap%WS zFfFQD4y>?{A|TGsx4$?Bx##SlgE#A2A$u=Jhcjk(avjv+*Fo@WVsjbT^k+^OzA+melXg&7XIrNi2i@*7XIrB_K^Q| z1;0Y#|3(PEq9Fhd|KCOkZx0C6q}Q}@O-xi0rGsuao!g{0-gYtrwhLsgP_YR}*&XLV ze2V6Ju%Z~wVD*rco_a^BCV9CyWv}HTOlAhylVdY&F zfVc1ILi-_7m?SQFBflIXP;!X8O>%27Y^M z828$p!ATYea{^7sNS9ZTcFu104D>m!i9m!~Q0NM!;;GEyCw^=6KN1SR#6tnA$J5WT zcz0=BjTgN0zKgeXJW?4I&UgVurK?|OQ&e~KrA1pZ0uteZgO%=uTd2gxa!g0|p=g*e z=+>W~g)By3ayE-KXA_mg7W&Y_(CbxV;4u+{LFVS`ZpIJ5e=Xd(d69;D9_wHaqxP7V1N0MtO%}gX&#BI2_v8hil?f0eQ~gVzG|g)sHQlQB7BdCDhcQy zBb%hUn>+lJ=T|)e)SPb0EKWS))CFCW*^*O<&Mk-p+t~f?uVg2#$-(>%8OUji?@#5) zh$G{H0r&7l-3}hJT_6x$afht^B(BQ4H)1EdT09m5ee;W#Rw+j%?Gl3s_z)jwlf6i!XFG>XFfNE(usb8ZYw5C zwk4^aYL2`Eu9Ux2y&(que)%K({mH4l2#aKomy(mu?$9|&^WO(}?|qG^Oi1P&)xqaY z8?2{#m>SWX4E0SLL0lJDf^e%uT6h1a0S`#qSPQ`tw;t5;%nqCStm*90j2-)%ei5Z> zGoy*k_#|^_gZLF{tNd{JxcssqAyAA&59q5=P8M_c`u3J0(-Fw<(Zx^<`D_gK^X7B+ z;5zDf^^4x(8eZ$##^-yB1AQEtW8lv{Mj$*t+7Ud8+5Vu2m+^Vi3(Qe!J*InGk8!Nu zt}@@i86z54egA^XG(|&MoT+c`kDgce0mnyMJ+=EfpOMq;FY4aX9K-iG^+EHpM94S6 zC!29)Q$i>T6R38!#NM651U=JH70F>#qwuw%5)e`HG6xgh8S zQ&@rSoCa#`tNmjUvUaVq2i|E7dL_HxBK%aap$%WC#t8^!Pg!bTzV|+qwr=PxHaIdK zMeGIbq4A)?-Moe+Sr{&#opn*zejzTQjBXZPTS^#Yk#JuexW#pY{nJ9~m1SrFwSz9Q z^jqp5krU9xLxOm!njn^sd0{By`K8DV%U?Il`cyK2mp!QX^qB<41EGz#k^>GREO}G{ ztI4#P(@8qH<|-(WY>d^P)%{T;{-~x%um`>W(H@NRv)DwLIyO>GLN6ST;eIZ3upn9! zC3zW>_AR0=*hI>GCN`_2LlL_!R!ki|EG825g&(D}2E-TD=P%aDh-bWiu(#xgIhSB_ zJK1A0K6Tc0aiAC4F#QCf3}KRowC-Nx%1pf7Dm9mvx)N=qo2vih7_f`-!=1|#s^4D4 ze-lt|dtn@>$QJZ&E(}@&p+&I#F%pv7qQVxfEqfPufJMHS|MMQCSa^N;PUcToE_@%4 zQb(CoduG(Smdg{MnA<-EIp{NDj&^Hybm-vy*_bN&qT4Rc2Jz@Cl!utu92Q8av-GyuV)7n!mEYvy=d-YT;`| zmToEQKAkE}xJ)y&=L`!%3vFyKm>G=Pg@wD(U$ZlCm5w^0KQY^0$ zUs@&fRihRMD-)pQseb;=T!ARZQ?YlM%9_x>pFR#*OK4*k%ZMFH?;aaoG zo?Ok~k6zyi87iL5{*o_|k#eH(n$Oh_nma1i+279WekGi2lHGUwnz@&09NcQ(Q(!#O zo)Kpvb)GZ?k(!&d+6HYIwv%lsXBu>UEb?l$XX}!C2$}9Z-|C3)9slmA&}1nvk81|M zRK1$?BIRTw9_iEeH8&{uq*dQrv&q_wq_ETRw$a@U*}HXjI!KWt2wXN})m-lcG(CxM zB;UKOL$#L&Mar}~nU&yNfvV;E(Eo91c_r^&L5hKKrTfwlBE@Gt`wjO4-~IV~J#D>( zJEqFLc9jdkN4aJW)|}78dQ#SVs&{YHfrX~&GF|f9R?|q-H0s)TR%5YN^I~Ha&H`k| zaz9ht(S7seDw9Wp%Cqxz)5F_)a!Y?8<*TjdDsxS1eJ|yaA159`p-QINMosq~8bKNA zSJhzN!>>>$$|a7zfxb{4L!zr$Zm6N@0Ig1w;MR{zVRK>*c4`lfQ$8}R$GW~xp=_R= z*19jBq{^@Ggw6FWk6_Lfe`OEo{|J=6xdLrTW=AD^_G3`dx;{GY z1J*AE*F^W?5t9&j0H3EH(8?J>MN%Z9tUAgem0#wz!KHXSz30RQa>~DPLi!vZNNQZN z7iP4q`4S;x7viBd-&)>8BEpqu9{}C)WUWfeb0x{OiqSY7n}p&PQKUuRyU*qomZaY8 z^S99fk@sPV1XzV9JxFu#0i^2>$ePDLMg@|2uqU~uC>_=1Mor{h5~918ma69GVby*$ zBSo+A>fV60`lJ&3!UoMyncgwib8RS};YmZsOiE1K@hf`clguRtTqa$mBMazbLy|8ZDJH}oM=(?|G ze9C5}C3v@MBo@oZa!djb*PMV-s>t84+$Qr^h0FTI*4U4f)a=i@9F4QH4l6U70R#%$ zW5{w=d_sZg&e3*JNA+gL=c7+yQoC#(Ba883w0rGq_kt0(-}oj=y~mD}ZcQe{Nwb5L zP`*_<_DJEt2awg#ViOT(KEqG!L83hR!c3yg{AzzR4Z088LL+MD_bHB6pZ=zoTr7Ea zd!X-JzbiiXZY<)1wV2lD3hKHT;J*R z;knLpxG~W%cs)<|tV!F=jMVf>X)oi0{3Z6rYjJfqM$n?CFAa;w1p7{ke&Mvyi}U1q zg6sSyJP184nChT3bh7?>Qxl?5HcqT{RuFTI$a*;tG4@DdDHXMLv%vD`t3BrvO`5p_ zmgXo;o_fn$k9Cn9?mGJ67r$QMAf&=eg95cRj8y0rNdcAuQ=QTASMRREl{;Yk{u^D)2p)& zXWefUAA@}VU#bK1#N#q7d1QR95BrN#M35#)>=VI=lh)^qT;2!7l63QOy1uq1enT#^ z$$afQXWdKoN7zeL)a=D2dX5>rXp|#e(qOvzaCFv<`&vCW4qKheMbJM`cI(^7A6v+{ zo#y)4hl?vUe4>c+MVfggnk{!xJtZyPLJ4nLIMpj6r&G;UgU~QL^6q7Q;Y!it=Xg;I z`DCgXU$PKHgB7s>yHHo_NaW{{;oMHur1A~|Cjf7O2Ja2e>J^j3@M`T?ew3-S)O`|r zCzL;mktwQcMa#PUy4GcV*VD4T-sf-C@rw^Q$e-_*nfV+_c8O1(DeSP4T~1UL)dV!J zktO@_9k-=nM`E+{GDOkoFC*`-Ok$2l{vkf>?VMoHK&O2&Y0%bG;LSi*Y;6}FDIaV8 zGaP^>Jgi!hp6Pooro0OJW&2w8eE^3C_BIGP0VT3ToSCT3elafuPPAGNgUCzu4+^Sz>1MOXy}#@aed53o%v}hU%9?Bdd10Ih^ZU zcKznii+l_*%>6OPd~i)IeM6^uZ024*rx}f{;PV*zc>>$b4J8pC7CP$co!P?wa$9c^XSc>$F`eyC$t1{) zY&Gj}MaQL92bpB=bbgKp%*d(K%4wAy{>@S&QelUqe0uqEg|+-Ur^sW@lY~gkpq=Ws z=L*m8KFXGkNmEcZ;r?bRdz`L3Rn5sY$XXOHpYjGb+1{jhtzevy)AbJacjh_DSe}Iq z4rhbjK#%BWI`>nJ@Z|-Sh;MnV=OZJ@jMSluYfNp+3W1{bbC9EEvL9P2= zt(Kjc8p^Ollao#sjXkSQ@xB)(L2Txu&iT=1II6YBKh;Utmnuz!LZP{}U!nOb;L=9+ z+{hsN%{n1$Ww!AuQvv&nw56gEL8W7JgO?ARq`mqhsHnNqz?K^=r2}#ix`w`=!XTJD0Kp8~eKZo06LK(zVLzw&>Ggv1}*nX^dSoG%_}&#}$z;85EPow4g&`S#*RdG%FV0%!NiqyLB-8tBMbnOt(dP`WhuiIW?v8 zvE=P^;T`dMn2AW^IT7t*rTFM8bo$<})?7%P5E7MOok_itN15kV0T?;-sIE2-Y9`O^ zRg?#+Ld0$UOJwH$_qbXc;at_EZq#5p!KI4SdoM3t=tvD;()43uDzofJy`}rCx~*V6 z0X>%IgDDwKp%haZ2vTT1XYE!NSr!w_P|{YR8%q>YkWn4iOqKuoxxst4j+N|tFKH`d zf=5$F(|l4;Vfx(E);D5wJ%~bTqC|u>uIDQ$5JKT7k$l8@)!JL7*k-WMe>0pXZUE=j}4R55r$-9~KT>Gpfm+4^f zQ6ZVNF0BXsJ;zSx+(Ao!MVGCzJYx9#vX3n&SDM)NAo+h|PUavaJZ$NqRQbhAGZx8@ zsi}sOe#iVWT4neO_4qnYCsQogXj&v83>8A89J>BENzM1s9byq>^v17phxpSYk>n zP5!(Tt8sCyh;RVDDCPWcuQZcPYAR2mw3wR{2#G$&Kz8dCK8ga!?bmL`A zhoUv7tk+9prO+V=@wmPyZRJknlcgeU;<3sZ&o(|aM%88F0g5z7KPx3gNkMo&P0+A8s;{{!?t<)_@Aua^yL0exEEVuVM8l6Twg4_}4pR)GXT%FQ>GH)5FzY~Y_u&m^yR%`L<+^xtWMF^n#y@L>w z);fe<{v#Dr$@IJ`jtX!beSYZl!d`~OY!ibm~m`nsdY!Nkl~^X zuTqxch7P}LK4ObIyYSLQvyRGA-FxjjIm%CJa_1N*>-n7;njatr zc0JmbJ3?i8Qps2` zs3HqIZb3%lZhPp~R7kP{M@BpXM{kJ^SN@q0-0pC;YS(k-a^u9Q|M zYL>kkZ&h21bj@Gg=-A^#2j52=#PD6k$VFY0B5c>y?O`rYj>NQ-k9(f{zk% ze>+XhXO*{8ppDCEi_q&IesqdniK-`PIl^!E&dTOk1P%Wy$nBrX-yXf4W=lEGULL1^ zn>cNWaj#N?J>1?!;SqGD67aZ}Q~N~ZO3z=u{6%LeVeGNrH;e@CSotp~joAx7Ta`#W zQd5|ytkC05+2c-VFD(vqb%|5<#B*0;OvC2xO55?cPN9mvI~v5KaT%N3 zgH4cKu_%c%(nron^i*4M>F+QpV>oSGFMn}8Hv`!qqiAi-v-LyvC5Dx{J3DMCDaZ}Y znWfHW6g7~7Yk}N?p-cZZEf>1bRNA%B%}!W~08{`Y*56_oKbRh}{uI4&xu>l(@?Jr) z+Pj^w)_dI~>e5;rl&a;6a1jCS5ObEHE#_9YiclqIGVk}Q@^~s(x+F3^CKGn?V8hTH6*MdAXG`R;dNw@5Wi&PRN zI_7J37TF7zA}PAv19e%S8tWskd&_Ko4XmTwb$e2IUrlsQqDFS7w=G!rwS(~q)+)jZ z%NyC%O`d%G*xBffT8{mR>Id0p{;Glg1Wrj5x=k7f`+I@abxM31G(H5W!pz+eQr?=u zl$&>DX-q<1o;UHOkG1FpNyBJPCc)cR5@hw#vb5B$8@n2=E%f)4SoiRE+gchJE{vUy zE#D!0G?ia5iBjC@d7$7`YegWmF=ALGhiz}S%X1ji&iZ*k zUlV`9lYK)*rKFxvvN}r(`wTX7);Y@R?yq%$m@lvH9QQMt^SQE74poiJj+$^BSKsh2 z*!c-Q=;;!}>+oS!HW+kj=2H}YUr))wl32PE(yjj9ko3}%_cSK=Se|{$HLzW$+#Hcq zBAH&Qrxa)5ZGv;G&0CH}<}o_Kx{aLR8}sgvwj2wY3w7K%Ct`W!V@FY!PaiFDu1dpm|w;igPPB z=0?4yRqn}Z@!35=UOJal9pooWy({3J3C2dc%7$K9iHqtx9qy3)oR-2BTzaiCleoo9 zi@il3-+}_cgSPI33iUX44=K0SOr7U4pKJepsv@)B`iEjgdATE`z$D%P)?8ZRT%nAcThAgD;$ zsbSwH4nnmSLoq2{lvM#A(P;GabEbbo6^Vs}#yj-!6x|z1u{z!RRUJNEJiV5FwtF_y zQ(UOcuseo)EDTB61()Is>#MuG+*}lbk%#S!DC;xUrQHUF8r(QVr{!~%r!TzLwkoxE zp?|2QCfPXc9w?!3*hZvTl9w%LY$&e9m+zZll(-AZ5cj?IiiVgJ!P~ zEm^RZ&mD0#vdKX0JFccVD@Pn6g=K<6H_$7YX~|V7yEmimsOBhD+DG}Sk6e}bCf}VY z&5}o`Sl|2--WQa3Yy7#HUQZT#N~e;&P_nxtLE zbX$~1n$cMhaZsH}IB|gVFe!FX%O@TIjNalh|D|KvSdWKx+SnS-9SUzRhG>=al*PK2 zm90N^uJN%!2QAk>I3Bp5vbt1cxkjIV|0JKMxYk)wUTo}Lw;77AJu6ydrS{FzH_;D{ zCMZ~NTbr8Yr=H-uzNt9e6Qk0Dmm#50Ycai%Tm%Pwt}gjCQT~D=)`-fh4zSL9RVm7s zC6@S9_P|v^iGTboOCGzh*Xp!}NT5=25SCWQ&f|?{x{@1mt3r{cCp{zf5SD}+bFSN5^9(gVr2t0K0CMk+KUn)BdoCV=uNj{r>eNbGtKdg;eYdv%ZXu*Hy(0qwxLEOcp3e@7ixt=Ac`N5%+YUV#n|HJxzT-0EzA%?> z@Ww4(+N%@}UwDl~+`Zdo!lz>qM|P>2=4u&PyZ@*1Kv{IUuM0i3k2*P>NY-?B3UC5@#!|CXv=~(M{ z7A^aonO!hsIjlyED{dWmW(&trcRP3sa5ot(|ACe{kbg)bh)V;ENKM4ra)1W88iOfh zNH+*XeWZ?UO}aoxtV8OSqYKU>T!tLY+w0eG;wphx?!%1ObSKfu684~|&W>SEY!78= z8%YsR(e%Kfmegh(Fh%6%m*(gWrq=cheVToY4jB;nD#&|%8|i1BPe&OGHb4nThXjdJLdpN_$D6BxDDAtx!bz2K@KYC$$nf7 z!$~xLjwO$70j~E@WLz{efIJNbS3eW`_|j>?uSKxw*-D#i?Q)OO==840Dc2w?s~T`u zU$TUo`7@**Ct=NTjWYOHxE%BdUIyl2B)S}L5O!iYRdDf#;=!~%qch>Nz$E(H1NAn@yA2j`n{bEak#d_ z=TM{T1j$XoH{`F#iAUI=YCUQ?j~Dgoq#?iv|2oUFF3{0}JCCY%e_Q7BvBPfJrFUKK z<<%rmMbry7|e>?}ry=`w#Hh3MVAjhpYE6|Raa-DWdL-LZM!D=$%{&UZ6sLKg=m z>+^XGUa&`CUzp?@L9>E_@_VckIaVfZs8;XMv)EwWCS(1sj+d>{mEne-KX4&~7Sf;= zxsl2~AAPMTj+GbPW|$pR6!`&eT=PUn5bc|MOrN&=fM5#}&Fx6eNDco4S0qT>+p@dz zv3@1Z6Y4y>fG(fe4Zev#a-8}UN-pW6n}suL-}^N>%q#`2#jBs>!;foMzJFjfw$Gn+ zgaj|qdIB~wLx~ef>r;tq*CC-7%Uix`_*!Wvw8>{bt@%S-pPtc~%a_Y-qbk5LzOUVE z6lk$68`NE+B_eOv>@;+e;X)ElaG-s=uMOpF!T;1@$Fr5;)^FB0ni3y{dEMldJopc31w!}`SVqQCz z+qs$^rIzFAvx8?#mHNeuhP`Ovkofg&#W*iqqYm!Qtfaa0jm+t8LAbza>lop`{n^)C zxz#=Oz{;*+-Rx=U$#>+kBu5>rmn(G%cy(4UMCmH8yZ~>v_4S4|<8`7Q?k0~P(60i; zk~i5!(q|c|@Z>1~tT#yOIKw`)(e2l>ji`HX;mF~ATD03|nZEdpiAiX+>#8@-D!y@j ztfUVtWR4E@N+#j4gR#Q+(B1&*P65@SE2L)PQFUw`3O@Y3lo zE;hGn)BX5qCF&)Z(Rf?*yBIwas}glH9;IR_g0+?GD`mz~F4MP?!FNUqkBoI2HzRTa zFN6~3G~w>R(>YoH9Zsdv?|r^S<2q!WeZA*9M12Ip)g=R#`q18I5N%0zkQn4}Q>Q1W zDE5)>M2y}sns&-OrSFy07=|vAvLXN`-niqKfNHa{jw#(kevMJ?_52{D+BB6og z^S4(9-`X;Kiak|wDeDOt9Q-T3y{A8J(o9{}hU?Yx=41KW;`vzTJ*@!{K z4H<|5`va@L(4*2`K0A?OkXe<#uO;r?7wp1 z#dV^}Ss!PHf9IfUj){!!srzVoa&U<{EEZHQ3SF2ll3(;N9zJj)*SK>8ct+G@^@C@& zx*tBRl9-`0DXCHU%K#E-Q0_Asab!|@smcus{ia?Iv)>u8Zq$#WR0`G%KJyx3rXAwz zZ2TV1rlwozmqaW>@0RZ7fu=BX$wmU=Y&kWis@Q!KL z!-35t86>XLa8{&5S(vgOMOp=CQE6OnC%bkVC<bY^3Q)9nX%;GUGzuSBtEn)6Q7XuTM>p_erTFi`|0X_amNI zQPvlzzY}jd@h9vAHDw(>P*M$V&tZqgsZh~)))3o~?ftmMH|&mWY(Y^!PP@UGv}5CT z4~tMUPdJj#LsuH#EQPzV&=&3a{FotQ=LH&erj3>yHAh7`k5=&0=byeUyJEXsQ-+4rED!qrwwn(DaOy{?Su z9i^MioVpJcmKr;-$0JvQ12Z z+k5IaWfgX2MoGGwkr&Eq4$RTMm>PS^dJOgCHfXz??-8eX3SDPXxR5VPzL8(g)CVmI zewL^7Wvm1CI9z_^f>emh>sG18`F4y6U=jNJclGsGJ^H4;xW?Xcc^T8%NkYv2Vj;D+ z8Z|*r&F&M;`MC8n_mLk!!n0pt(Y z7z%!LNph@eS7crcPS-UbtP2gIdb4-Rl%aZAJ(3jz)yut1gDV{=s#q@4oe7aj`5RA| zFyq10d>r{-URbnbOiOmzoL^A2S<1|E77f|^tSrFi5wF=+JI775?a?hq@3pL$p=Lo@ zZ=3K|l%)zKNM#Bz48fsp zVaU>^Q8{wQ5T87EnzW()o8b$^gCK>S=&KeZah0*7VMkT^c(9ymy@6t*HrFXKck9lz*t?9N@6wIs{EaEZ~!k;5Bo zLB@WOHNOHS_YI9ToK(+K${WV4nWP`8rn~#{(AajhdX#9FY?eEt z@l79p6vGKdeVRT0X>P8Z+)~Bdj4OVb*n|F)yrWeI3bLlTX4$f8OUXI4z8g9zS?3t0 zlWy?jTfR-&))Q$rxH*m0q+7T-!&EYxHiTWXSKaRjHiNb=8f5#+?LX%EmZ4Mgb7~^) zh&(fMzyK8JC$^wJbhrvG6nzle-TV|mlT}=$$rYo*Bxa-k)QB&x1RL@Q;mAd7%|VKl zEKCnGE^*0~@|$NjiP7(BshqYMo%WeE*VuVAqaeUL%h?kmUt&4=7T1C!;&nr~M1=m8 zk*5AefwYnNEdj}#xF%dI#Cua@EAKGRf>$<~1ZKKjc>tRKZ1!OpG#=1RkhjB)zJl)4 zP0GzcC~*s>ZTIgG&OK;J#@v1OidTa_clloCd($^twZ4QoYBrkkn`gPpM>V0MG^d7EbVpCsw z#W1q`^DSEGxi_VZ#UZnWhw0kh_24Dv%VzVBO zUo-a*63{OBp|0H~pT;gtqS`gH2jI4mx~0(d9V4`uhF_rX#u9{?ALZ9B+;Pd^WDiP< zLge^_`^xIn=I+%{8#nT{?xgH_Y**_qUBcNnqihu%2FA|zGsiF7!U5B~v z=b4%Jeb@T_o0T<@oPGAb_EmnRrpbJ0>F2$pNuAelfl|S(O~-vck9HTjyw246`j&x! zj^zKCj>>3j%8vTv*5vGoWiM(ye+%W8q}rN(&eU`!dNU!|@yD;5Ut<}H$M<%e*DFov z6xQ=B)UtFg+^qmG?aPmRK6TqiMV)TkEJzNZ-NIfBM<#l=f3zGCl6gDzzdWrC#J zwIsZzw;8v+ZNXnyuy(BJMrfCPX;;s3x;~Ky>YGP0KHEA4uAwHW!VBz4z7k#nWd+lIp7|{q z&dGVQPKDx!<=dz0heP|9MB#2O)jV9#k=ZvYeLHzAtxwMsQquT(>@ClxG&1kS^!m)v zoT{b?K0cz7<%4PMh=w-pIJ9DM$z|(nGl2FVaPItd0`&*kJ5MUN)gygK$6%q)vne1P zK>n`k#-=s?RtN5o-|0V3GC5l(7|RwHUpcQsHL2V5NdkoyU*Csu7IVA`$?>_QFocNn zT0x~7640!yaoeTWzCx|mlh`nW_IbyKyazJ+v^FwV(P#AeVXKY9PIFBOEkVas^PMzH z_{_|o%#htrCRvtVyYd_Xd+rw>U3Y>$T)kdPPWs8s%%X)cADmJ&a!_Mtj=WUu#dT1)`ctgW8%> zkCZ#_sYV+MI6P0tI<_3!vSn0t3{@jQOnF1f?o}PWeRL?jJqx>}1)GS?tie~7N@}UC z@6B>kHwA>qe{_T7&y>lF^lsxjbp4WL?l+XF5|5;rB^Y1+;0OepLwRbuRQ`(v=tYsn z6SyU;q)!rGrwxj(ei1rpCk-N|cmyridnYclUSAA<%8gQcCnoswt^lP2uUAhJyq;{2L9lY=+>KTNR>f9N0z=2J z$FBPbpG?d4wEEN4Dr484lQ*p#MU)jKwi+(&V4TVhBKS>~Gbm9iYYsM8nN0>~Q8NCx z@lYnHvRyDxY%mcjX#Rp{^*M>lzLhBixY~69_yz}&zXzRE7ddtLl7_Iekn)kQ>|S~c{ahOput&R51kIMF3*U`l1n4akm;cIO! z8qI7^Jf{}!X2+Yauezr{9f%B1>Uhy(vwkSND9$2rd_+&A;_^KbR_jVj^Vr&Ids@E@ zOt%aZ0U#Dje3-P<1|9l9%-hE0u+oL}Ypk~KJtSOy%9caM+Vea#E1F99X-Kr<&J9;@ zbN9bi)8!R|!4{QMTw#L~7%PY`LsY(&W4n0OM??XQp|N@SgW{eoynw7yb|Uls)?=xq zPG2^inU_W9bqU{-GKxCFvWBUN0A`nN#nq^ z&dUXDy-zd*CYAH01Ln6EM7KW`@B4i{h z21Ro;MEIv5Xx=u9HEF%PNB3cLGU133DwYiP2b4Ohh|$!WmY~pZ(>65|LB^!yORhdY z)^0Lft6@joB!>|!Quv$j!%DV%Cl99Nan>7vmY=_g`21<&AnK(WK+*P! zEb>k;9uVaXFMYO7q=?yoGuzP3c|`aO6uivEbOkR$3h zFzZ9iU#LyEKX+RYO8YcCnl9T;ohTd zt<9e jSCY5jiZReReZfhzXWNrC!Nk5)BLNt?ZyajFptJZ+g|CUUrc`+Ro2Rn=ZW zmW&47F6)%C@HsuT`)rT!4P2bv7+D8J1O1!$>mp-|s%7CXFcsQebXL~pRXT@twY>Ss z9HF6HShB)LgND#+?h{^SpC0W}7O8z3!)ZqtUDqgY&2aEMvN*}-khVv`NwMw*VUW{k zzi}TOR$Jgac8Q>Si9iuC9h-iOrb)dhn|Eoet;K?PB_A$Qr-`IxX4lhW>sO~Ao}5ft zMwfmWlfD{j2?S2R8V$u#TmEaK((-iz6Vis$GM<_(abMa3_iv}~X<@&d&P5#1j$uf> zdco7UO;2FoC>wUVd2Q;DWheh{K8(%G3rtm`51(%}pUQehl1^?Gk9ssyruKUcJs%zc zmFzz81&2(k?WCSmz3lqr?ML?vF7qzpgMyxc4x;AJtWNB-)kNNOH zyUN?ilNt9%Zh>f$`Ny^bOJqVTx*I5(wmE_OmphdWui&C*37<*Pl9}djMBgY(-(GWH zk-?~N6*1*7B%Qo$WV34K6I_bI1-H4`Vv{qm0o`@gi0qn`3oy`_G&=Yt&~yAMR4i#Y4zur25JN_j4^GvXaH|YGZYv$}!RkUX zrl2X~zL0XAk1ZWHg3o(1cwz@01hVkIC6TH4zoqcVZH9G9wjS1kxmnFw#>KZ+OSRd2 z$v(R+IbX*>A4Squ7DSQgzZJ=!W2m5HSOb*H-eSM4EuB4q(O9wQm<3wG=9G9bFJH!x zRXW~3i6P`pQ)qS3G};^(Ov<^B$<(ksZazID#tO=%8-@p-nFEjKB7*EcABqsUt!N$W z`*vJlx|l^XDrfaB{p>x5HB_*kVClHzwKRF#_5|`2ZG#}N1i$`2I&Wx8jmJ`t zu^gwGyFpL7-R)w%VnP+j@f;Wbg9G%PXv}V$iKd@wC%$W9FK(;FD+fKfm4M!`ws0Vg z7LOw43G38+Q?hE?!t%*Vme|J`J`cSX$Mxnslb6J{5Gg+(zUVB?gOn!QB=uBy03%N{ z&|yl4W`$_`h09hd9*j1A{w8#u%q0tHhQd4$@k&NzNv7G996)bPH~21zAYzX!vYtR* zyx<4K-B?FuA?&!JNa2K&)0X@ZB?M*v4F`f$Mxs18(8u%yp`Xm_AugcXR0of7KHnr~ z*%vtUk;ZPF$sbN9Sm;}^R=|Lc%j=x?$=!!Ruu_yJv<{gcvC}9qR=i4^L*$!*8lde0 zBiQPq7>s2hkavF|B7rajbR<0qvMA$!Y$JS?7&N|(6eaixeKgI4JjyRs{T)Wa@;_3o zsA;WrcH70s4?C1hq_f$1s{kGv-e5~+PA$K3Jd5S_ubE0@PWS^ZVoP_>2?*@{G1qqH ziKNSKCRxL}yCEk<`7vGaT?(Qoc|Jx*6@R7ufb#_eF}qS{Uy2of?k zJ(AYfF&UkR%4V37H>5cfF5kfyx~ep$Uv$lF<{;YS0lW7vEkEy7Pg|9% zRKe8xa>QQ%sLFh!4@p5wS6aTmK}8rb$p1q z&AYhd5%h!U251sogubk6f5>HN{oDss2n}{EAWd4qQS&XX6q`{GTM`jvwkw&fR!)`M zS{Y1n>?`|tg52Ry>_!c&2b+LQR%jGA#iND^tnKmMb1MD0wkp3Ja%#ALL;pW*;^aqq zNl<146+CfJ1#@W$(*>)KaS-?VV4_eDDubCIwU$G1{!u)Qh9tVZk=+5}v_tPAKBqV|T9uFZ1Y@ALPU0^CJ!7Xip5k z5cIZl37c-zx#w~lpC)o(;EC`ziO2tllY_0+J^%p7_WLuw4E4Qp%Q_kN zB&taD{73&5qUJj>Bs!*!8o+4NVuM0^o5++jk^VjM;upBdlI|P-z_ce+*i@l7H{h0z z0?JuERFnsv)x|nhv;IaV%5JOp@^T=i{Q%o0!v#z9ZQE*BgV2RnLdzxVQ^oJ#(va$~ z1`*uHLB`-wHjj9eo%vM?zk8Zv*B`L)KoJm>j{omcds({yg2HkC)2`ppzdhfbvUIpT z6Vu_0NPZtXca4c{3VNq=)mT16>0U5K&ayt{b~_bjbC&gBYE}{72UzmY0ErD$RW#@T z_YuMG;FG0WU?k`z2GMzEI)DE#^z3zc_Hw-=$4b1ABGvY3nHQ=l)6qK zC{Y$AKwOXhf27-I$e?3~c(naZyzak>>Gb!RcTR&NLYQT7$c5l(0YOwFf3wpvJ_-R8 zAlJs|Ls)PPkf|;G?oZ2ja7192r1#4G7P4(e#oSkHwLxxp`6ks-zf`ybCfdIpo*!ZR z4vGPc=hdm$uq%2DqUYDPA?+?7UEuP$4gxg6GD|-zMkZv2{v(Hv*TIpJRUaT@LX8K1 zwK{LVzr{nG#Umme z2fx<#LkfNg+U*|w4Pj|lwm0Tuvqqxn<3w%4Z-E+cU{*R1G@B;0FTX8>6yi5rz{00} ze~5NOB2#kYhsS&TX3cH7c8b|&3y`!w5)__hV8#%9LFi00*&eJi^u{ER!1*^zJo%_c zG)Twl7wFp_GsQ-Gf{LLoVw(WUn(!$k0eR zF=Eb{Em)pWmkT&~E6pY;DgrG{kVfxrp|E(Vh04Rnd(1>RJ4ankz~|mt5YQ z)lt@Es`+so0v84j}Sh7vBx0sEoeLgi`H`E<;pi`)sE;!iwJq`dA z;E#Asf8^450b*YuFAve2bti@I+pR`(i3x5gkh+d1$Y>VtxJ;Zta$qHWX3iagC;HYf0r%sm%))DXQqwh7jzY5<25E3Gp#uB>y;^u0 zY^schMYPtBuwJSp#Fz7a-ukrXs9CoZc0?SZ*`>_C)(gA21%K-;2M_1z2SZMv-U;^% z?VW1mEzjit!T975NG0D#RJUy!S{xA7?Td&H!>+wg4z@z+y{N;m08$xh={Bhvv6*lw z_U3&@Q&%>K}+62KlJV zpznJXoK1`%gDj-Xlfc=8p4TXWo3Vn!cO{W*-_;JGF#i*CA9Ru@XTNeR=bdF2wb8}p zx-g1o7#HUWnv_UP`y7=TP2jg=0f(FMJn*>^e#}Z@f(YvaQXyz$y7ff%?64F{3&&*& zP|_SJKlErzq+g z99Q~X+Q@xF>C_1?8X&-iQ7gz&1_Xb;oyDdZ zVg%V?%@^@-zly*?l4@zY49d#QkQE%F{m1^3Pq_a$T7!mA4dy{)@WZ2kP;mTN16hK)6tmRKgzs=!2W#mgqeynwib#4EM~ zCW!f?f@=^(-_TfYwvrK99WTQtey{_2kN2XR#qBArkY=(>Kw)T2cE=k5AE`I0BET82 zc|Qg7yR~mh7w#m5pVXoBbXbxmg2TWeB#xZ_zsC{zGm)Q4JmyQdl0zf;-`V8^xMOC~ z0z{_C{YKA_w-4J^6cH-Bw(0?L)s-rbXg^CV?stWPj~BX7XNu1}YuQm!bi_7D@`X?75L;T`4SQFx|w$C*VChJNAcgWY8wK%N0N&ET8^^`E;f$f ze=?K{6}LG^HtD}=efD2NJ^4lWb@~lNY>eA@mxsTsP4E)7e${b>-}?9opHP+RdcylqP<+$xTw`@Pa)(TDYVOgQ5=_SqZkZ+X_y>gaA2k^;T}kO~l7SaK+&NNNJ~t_D1D;Rd6%qT$#CZZ{v2&BZ z+AxMcWLJk6Z2|570QXB}=TShgngkU*p7v?aFLH-TF8rpi5V|#|_aKA#Qpf=@z_j2O zUjb2^JK&5&=Qemqzb#$oYwKG2EMA6*FBvMK1(44MigRwD|AB=+I`3PJ zqdTZh!^mOG%G%kbvJqr)=zX?Bhx?y^UAs1aKTg{jmI%coRI}%WAL`EVUp}VHXMw%{ z(DBX_lV%Mjw-1EZ^~rw`VCta(6`WAVB8W9Z>;v+f)y)C9nFABUDo8%^Uup`AamI|L zpt;z6SNeOGTH%p{6WV6Il+qd53$d9je*kdC8u8kSxFGbNcUR{7xj<%tk`Pfi!2pWg zL(tDQ;mq%+`~in^zdXA5ed9{_7%hk2#z$kA?5E@hGY*zCMvpFMKn~g)uZOvn+!&(Y zz38M0&o?Mn`NFSh;TUbdRm>O9z`S0hJ}K=&#Jjwjg!KW59qvt9=G;=A_|fv$nG{H< z;=`8US5p8G`xDZ%NkQ~XM7W?K#n7?T+&1&$HSbf3h+!I%fSgH!Jwda-8cD{%s8L;3 zB_ITB8e}Kls=lGuxS~dQ{`~N%-^3IbVyl^=#2q5lVSRae8dZL~oi-!o(4x9OS}80^TR z4I<~rgE)idjL_^KAZuos2{`YXR4dmk%$H%$w?Z;bg^(3t*O#b!+H^1HT?W+z3L{|BZW4!cUHjB$yPNsn9IAwX>h{Hb--|d7E9|5IYSqld-t2!M5&+~ zA9cEtY}dUuwY*g~e61s%A+DRg!1&{j@4NryFcr${nLK-Ua;uVJpcL2W`t)-Ibt!$|b{Kxf__teJ3t2zvY0S)}0lkI78QIRe2i2>gjHLqM; zjR)?Iq2t6T;CUrYT~|EhA$i!N7# z3L8O|fI>UTV)Nh2`9E=-J`2w5eVMoJIt*^0VEF+$nOxglo6CKUUr)QJ_451~OW(c+ z7pAL-4S1_`3y`7+nn7Und{+rBWOkn@j$Y$6@2%v$ILwjtIBjkl5mMy#cI{XsrW!sN;X1vQIV|J@F7yw~2 z6S-wYaE2tSJ4kWP24-g%C_v;J(#QNi@E3v}UrV9D7R$bcKG&3aNW$qCvUf)@4m&){dk`dO;JQhgt}!l5(U33*8=@$jOL*7bd(UU*6(oAbhMGd%B+2~>$)U!q%| zk9&5RLQq-K+O+WXZ8}Ya6UGMZq+U3zL1npP(phfLwa$0+iLaHEb&Yj;jyXPObE8>% zm_?@ozHkg&`8(yvUpE?Z++UF4gfO^b2dLPEmnYq{YZ2z0I=b^>X z7wYuOw$^NQPC*f9j%AC?w!&+6m5;|XLiRFT+`V2{kCg>|ETrW}_n=fr`|KJ3@KW8Y zRmzz2oNmw%>d_AcXtPy5De;*lPb;$+n{5NuIW*oLw5Jp-9y_s>yr%tpJJzIUCGWwQ zb{BY^mY7-KN2`tzojazTPYjPHg)F%#HFiz}+Aof`)jc#UEtmsgKrX)}C>tKum{IWw zL;mAogwbbA>f-|K=~j5ti`s@JBY|(AYREw38~M8IM?8ZW_V1(}3*Ypm+$Kla_9V>L zw5;E_c(rE5?%7JDX8xR?#zs&|h%HbWMcP51Xfigx3}jwP4!3pQ? ztvRlVJE5m2c4MQtP7EEs)-d^F(WtaCDR(kOGd?cY6P?!!(*_eS-8|THVGX0xyHKAQ zQIq~3Opa(XVEyt(t75$u^<;GQ*=QLS)?LcB`bSowXjpw!@Gtw^gbk<+A`*_06%|yD zqG%YD8Ra3vGEPVZCyva$UonREIPK?Wx(twBWs1^Mik?-|@hrp`J0E*azbQi;!Q9_&*;{-5bKazM-rE@{G58e-q?32t|;(nXe<2eVa_!z}`su@6-Bf z-d@lW1;VUf6CE4kj=pu^x%V?SmPT};Qd7Aj-21(GEnCbhh3tfaq`iLzGBAQ47LAcA zQ5G{Gsoyj%LtJZfIHXVqJkK#=MGNZ>v=SG(kPGs11uvy%BMeu;G@M7(?wu5_py zj9RtbtUXqTBT7BF(LZVX;aC1pog5#Q*{-{}I!@hQNA;06N*+QDKKp0)zm*!dAcm?o zUWfc=F>jxGn8lB1=F5qu8U7!*laWIhc3ciRBfZBPI%`2&poDsZMc5?9Ie;O8!r%q1 zv?cB-(r^OiObAH3uBVodPTo~l$^wZ_R*odvOFh?Beke8ZOA%*UG7hTKkF#R62yM=k zq=;SCTG;*Emqo1i;o!#6BdZ71^W;Dgv!>Wa+Je~KCe{qwGbN$`6Dltd zfs1TAc(*Ecp{*RvJ!<~0ays6ms42Yp$2;-myeOmzRZ5H_NB=NADef?dLOGXK;^(Vy zD>E$igOpTF4##(TZR%Y({(iif*WFXPC_fS!+f~7hmPEn(<2<_bDF58nK7anX9A20O zN8kFYYjxnbU(+nRREnaLQt@RN00|?-h{r{(F5zs0k7qp!F+az>BFgG41jc4{g-5OP zpFkGez5c>2b*#HEce(VsVUO)8-zud|pVwPw6WqKJB2liaY9BMRX^XxTAr?x(4?rnv z?@I9l3ZIGBROzzG^mleW<0Nyrh#zRN>E$zp_wkXm7$?ca`DM>oG&$!{Hsg-~|dJK+WviNV>|K79z zt@4GT%HQjgpdze3z*QS%8D|ZARN9UA+9-c$R)EvAx?fXeF#}^%Aa~{EfRVmljjn9| z!Z!i|SL}XXkRE&~qGnl)LT8y#KRw<Io#g*MT#sDVc@Z?4Qe#}xaa+zX+^4{hH1wmd=SMAp$i>Z@!mXoKircEep0qcl zubT0ruMmO*@GAs==d92;`u3$w&s&y24<|oo_xvXPTvn&4?|R_sRzxsKTJvmroS&JK z@MwLnZ<**cw z4q+G>una9yq77(|*{4Qik7h~{=WZ~mPg-Bn5F1k_JYp2+TF8ZxSH}FAYnCM+>!92_ z4a?Jr7Avb9L%#ogQ1vLC-`G$lSmdBcn;q6wLFKU&e79U;bhh7$hXrK$^C2(CtHp`C zRplD=1&kMWtEh(|IS|P{x12X1qb+aErxp`-=meOU;y473y&Eg%5i-g#3&=mKMrdGA zmOeRZ9;K!%JhO`A=3@UU}%!_Ad;g zTBYn8vhCOAnM*TMraiHrqj?MS3LBU-arfI*#n3Ci`2*jLsbHx5NfE!BgUZg0O`q{= zUH~X#E);6QBO;_){eYipBRmmOoSA{v6ZVVO%jP3nOqW`Mg0ZEswmbpeJ~a(b!Q|c7H>h-4K zFmq1xt@sYH#w{E(+Sf)8?b#&ee+kWGZ%=d)c^Xz3{uJwxC?{o@57{iM+(}sR6y`^i z@QYpJ$NZ8m#U6;YCA-tBW_=tQ$1m8Q`nVb|e{t6S_xGq?3~V<*To^#QVe2ohv-_&> zXWme_{5bqyBT>U9@kIjPOE+|NS*I*5?2+1mDBMvRDxuoOC11h@dA@ zrGpY*d}3OLpHA~+{p!d5!Gx)KzD0G7PS{gRtMBgemOPN@Dx2i<#l87czjFV*Vz zLKlgjfFtAbgU=>}VIHmMV@;?2ZOL{N$~4a9rx=1wn+@Jd*(imXwvhc4M$w=v+J+!W z+#e-si>W<_i`1IC*kzN8463E&T1>+Zp*6LKY@XL+rNK5JG{UTlUCc+dOi^F=0JWk3 zV(&iPGIK3Kocf6f$ znm7FFg^DNdf;`o=*Vlhemv2u|n-dJig_FStK!U#1h}8_2UDjf)_o1Bkc#0p#(bn5S z{5N0)=~-C*<){{x4w>(3=_$xjJ|fU{j}D~q0|HnFJ<(A23s||b8@|RXCFWVQ*@zPN zuENBd;H~!7gR&;s!CDH4k5XfBk*#SM#}a1*-t*E@{r=qlF0V}IY>R2D*!H+Nir~s^ z**J8)QK|~02#MA>uH^{D(Egl7o;C8?Fi=sC)_!(nMe;3~^AOrSB~hEt_Sy4fhCovO*u4}f#s8M5Ge;!|3e&J@(7$ml(?SRDUvNtET z8Up^xtC-;I<5I@Ez~iC1t4PACX}JdE7A_3aFjYAbnKXpq7Va~IR(wlB$lR90OgxS- zh=xgx?(qDWFuloxCgt8jszOj+1iQ4GUF|Te>;YrG*fd>`RMM%JXb&=w+7qRXg~ZC* zr%?J|!4_#kKkq&oUD=$M6yOZFooI~DaqVWth-cnnePs06@zVthW(sQ~q^bo7eW4o- z0Fusp#VW$xN7eDos;Lz1FlsTxz2S|@d&(~E5X0v3{UTp{TV4Z}vl%Wcegf)XhX3>c zY*g}HE}zAhFU{HBTWK%BR7A}rLj+RT+3GVK!_nuFVw3$O)S7Cv{fMq~ZAO3EeoN2Y zNie8L$t|2`jx{c<hQOTcyA)pzrOgH^~U;!VyXG7rm(=2(7?(43)lbbN? z2+bGTIq-cq4sAmpc}88N=VBln_&RB+;wA9+IA8D>CS)`9yEMXpiBAy%OJM$Gm5|i6 zDKX!Tl}`;V8ppP%^P1As%W(U4Y0G9mOlixdY;tQo*!ocKcoa)oEX!+NiqPiqCsUC} zIZX;QX$CTIxuyxngW|DIM}Jkkgn7XJf9bhBqJt#%>eMh4=$!!JcwIuM|Jo`U^(;&q zx>0Sp+&>>20f8%Gc)k>cyU}ldGWRm6Hn9=U739aDEieSLBAoLnlyF%0f~FL=P-A#I zI(QO#S9wrNbU_&#e`SQ+SX2pW9q>!MEPd8g8wHaVS=BEDpSXB-b#|PKN++7RFcC%n zz2{3+r8pIx(09i#y(VC4Ved6g%lAChDtJMux)Lya@bHsqHubFur`M3vm+7)8G&jO;pKpN<*Rh`S;=z5H_Fp(ldS z$}2mHZEnzS2&0da^D7et#L93uMco3hcrx4$TDvWP$K*I{Sh zTlik7dAq6Z!hLzN+rxVf93lB`uIUxtpB<>`tu-h&PAMMqZU7*)7p)nwOvub|40b2( zkB;$Eep=0TKy>(i)cUu*dX}DiKy#tXNSI#FV}}Z5zMUOPzq3Qjz-v6!F7fp6eKpP7 z3)6jlFj6j(*Sn!f(*T$iQkzd1!(~oHrMC+ZLJ=jg^=$3Iw9lAI?at0C@gY0&hM{#W zMhxHCc7ROQ9WL-UT10W4CW(J31;yYj9(TkC{6DSlokl?_5-!)3NAAcjce~USA31U1 z6X@^NK8XvBD>!dfd=sT=3h{7g{=rt)a}HlCgm$gqY5PHd<4PDv0 zq3amB?1ml7N}XGN6-uFj0RX`2vnK}*3Pq- zAHS`5Vfh;`ciGsL`2`k<1?{JP%!Xlum+Aqd8&qUg6Uz16w*mJ-=)F~2Ox~zwm@_gI zpc|v!&q8fI8;qP5(gMA!kj-!UsR?rWMok&r3$R)bfjrZmXWtceeWLj8TGOdQN{(IU zrIvb;Ek#8Mt7-iu*UIwRDN`)iJN|v&%*Y}4Z-~DemC^~PKDvYu`VLX4QPAY$<^(i8 zhZh00VR?PL4?ZrEFRt?R@Q7|u;6XP0jkxyQxSvn@J$MUOoNABY^} zIx!iL>EItl;@eq74?t5a9ga5=WQNEpMhQzc!?*EPldBDEhrQ1UsU3v!(CY%zslH}? zSt?|0OVIOc{YLZBMX>-w#PV5YkEs5(*xd~pXKIGGc+h2D^c=QkC#uI2E6c5l&LJTz zs^kt?t@sC`!ma1P0*e8qcpkuc-(NB8I|>cA`AC0}ZoQ`@K&Kt|iK4i*7J%rm?UFF}JVH`+#Ul$N;Y8alj(}ap;u*{Ktr~)4xB7 z4^{9xev=R7ziH>K+UJZFcJYQWa0>BPk`Q!=#qEAJ!rC^0t|*x^iH)0S^eB`P&ws6Lr}z-G<$90TqTh4Z0;0$wTEY;( zZ+rBTArSHjEJKZ9bPC{$2@T5?^DZ6?mR1vXSW$I#)A}avCi*$M z?JET%YZ&07_z$w}nJuXGjd!=1`Jr z$_g~U4RiKG97cKsgv|Z*xbHt&4cHPxeJg2b`$G>0wx^1t`7Kq ziT3CCWP(@45+Gl={JKG%LWki!vL*%%o}lzQneg-o+G7$%5D}LYA_*E})JBg7(7)w< z&x$ICF|3Uq(GO*u_gvyWY#5f?T9A#Km|&)KM-+gYFJ? zK?Y_IRs$*?Z?4|+U7GCFt9Q+u`lIbQ9?J&tbHa0E3Uq@721^HH=r#JL3L967p_#`& zQXQd#^mBWfOuI*3_*Jd$+k61a062*jfQG-#40XF>P;mcFBmZ%4W7rdH=~^)A4J&%Z zA=g{{XahPGKKj8ba;trDn;+69}F7@JCT- z7!sf<>>yFbr`PIH0a`eGFY;mf$Y%%!JIwA1uU>RkL9R3P`oe%=O0FRJl8L}jkRZjz zz;JNAp}7~fU`fxh<_4A!hzZw2*u}uZhU+;)x6Rc?d@MtA2n}xpvL~#BpZl@Fpua`U z{pH!68~+`&rGngEHBIeF=7p%KH@)C8I$uRz$Q$362)jgjSne&oP}K|GaxaGM+U$n3 zi~k6YkL3FyH=z@+Tct&bl5mdr(UM&2mK5fNFqk2M9%8b6>0*1{!`XqM`TQ;iX~!;k z;*B4ABtgWL3i@oa5wf2RZho0F+JJKV7L2gziiTkf&`*?5V**VErdr*B*E^Uz>%~5Z zqtTi8JaXF!YXFog*n_UivTB%OevHuQonWKya8QDeoGY`=w3QxN=+h~cNMf-HmtHG;lFgBiI&=uN9kl{N?~ zLMe~`etp29vACuvF?1&(fX`1m3EZ7hsM2(NcToO|YO$QDTmj`qfM;+6o0K79XlM;U z^|a=sh&H8w|DN*|a{3H-A*<*T=mz)L!E7`i5DKZaZhl}tQfw{y4wGvYs^NDzfR*Gu zSHqJB7zdPa16lB{I?okSh2{|m>+0xvp`y{VEs`0}j-whP&ff~%h^&s|zRuUi0(We_ z@rhY=&BnlA;8rF$e8=;mB4LQGb4$eODYF3P=c8SFGB()l0iU-7d7|YNRf&4qe!wVw zf@uJ5K*a9nhcLn0xr<>YV%AVi)ne9)#HNr6WpNN=5YM&s3>CF5i)^W*m$ zzeDrc0RlA%W5Jz( z>5M`bzwM=7Q&~WI@^1@v-1`Cy1l$YE;#&TxHnRkV4@i$>jIhfU0X0zpyk0}d^ImnH z04T>MOocT}2!ffL+lF$Dm6UI9&b{rh+U)}7Z64F{1!sIvFw~@KV?tIOb#*}1+g}&p zWa)B`2}NU>-{Fu8-K*edQS>}()DiA1{p}N*_HUL;oRP(QzPxbseByg_0sn#_ehbeG zQXCm9m?vs86S)*x1xvCfB*eoeYb9=?R_&R$s;^Z^a%LWM%2L>WC?YzV_?_4G6hiBHz*`4cvA zC7tZpF4ZcQLPP~w)B;HMPOP=Mp z^%^8W%9BjMi*U#Xss{IIztrv6A-@YSHunvw@I5#yY^OE#uOyECI;6CyQ5@i4y4gYD zaVQq|s{~wy_Q>(U@QU5LS%s$)dZcf)4-mH+ZE}p11`P1g#!5;;hBK2ocl654Cp>$Wl4S~%fp2*+qtXH8 zqh5OX4m~9j>5=IXb_?23c|SRzsgcj{)0AYq0264O=&_TaAzYl1RywBql{||23|PhH zuHRVuhoIwS4|>okEEWqg+QJNcS^L##Cf18h#^+gl{91As4*4{+EbIDRiuS*+b+xcs zR&9fD(UbkhOd1EWhF_>cL}IB-Ic7 z=EnvkzE@xC%vb1g0hprah`xmYn%j2qPwv$WL((RhTfl{kdg*`Nbs}OV1~X*jyE>f0 zEQNN^%ePuLuzwuMd(s71JXurJm2L!{{hQ-pRdxOXj}?BaRv?=M@aoT`DxZtVE+h4B z08reIb>)B+KP|hpa<3(j1UR()7EN>l(7yT8-Ho-3VE$h$zy`2ya2wr#x#=ga)L~wP zJPr_8QUt(+@w+(sBZKLlW17YJX8dctsnZ5y!cF^p5Y^oY6bARl4-8(7hHxa%a!rVt zG@8a#dO2x2hw_mdR9ahv#Ks!b`f_0D8chNK{aF5R@c{W>b8+5R$kG)c$fMr8CV+bo zNo^VaXOOj%(KjR@sb%s(q(aH3h?zE_W!1~9aZZizUtx|4)FfFB3FI^vx=Argw6HpM z%4YAKV6}`nHu}9OTJ2$LwrQVXGI4&LMEMwr-&>le21!kLn_OF`XHlf5?tmqgQ0L#5a7$1KU2FgVZU7gB(A0L!? zWihyd!BbP-THtyRi*Wrd@zM)vdKK6jg7ys{nOI^`lYVDt`_CltZFPXJK;QLN`hPSA z}g7ka1g1);}`~G%Q8VM>6 z2{>t4@kRK5pd|m?1PQ8#hIedneUN5NR4EgGwm9sf)S7ga+X%EMHAzlN=}2gWwW{>3 zwK`t&KL22^Uhg7u>5>4b6%f9OOho}d=GE5^;*`cMbh4hbnm(r4CNjctym@XwJ6&gw zpvLq+NRNh>dxY}abMog$lXn&kaa8hk`=-8QQhp|j&2IGpY`2(H7pJ#!-z0+dM3=QSu52MZ0%EJc4-)iUD|gdevMm>6@-IZSl zN&=vInjKrA8&y6Tt2!-AiMDms%ueE@0gikS{H9$coq$Gy8Gi*7Ss1EKLlDL1_7epl)>9i-<7 zn&;kP9{HL&C=9@W%Z_^4YLHP}r;%#f0e^esB0+D09v;f9G2xp>(XSBN` zT_^Q;$t!1C!Z%{w4S5v_d)qWYI!44p7eT09vQaJ})cW6^+2SnQmp@S3Ob35c9R8-` zH?{%63wJANq@i{27T2ok3OJ%$B?9Wyvta9|T=D(tzn0g=0MZ{?>0eNy%+=q0C+T7_ z$0s$RW+|gh&#QYIf_raO^M0KJ$cIko5XF>`pu6n`slonL-VWUYAR<&q zNB;hoL-#`j`EZ3EY;9R50HZj^2PO1J0Be40H@NqTK`yFGKlBr{+U@HE^GSdOztowD z9M&a3C!lRCUgbZd{ajJ>Xay6=q91YX(aMQnfmz|x*Qd0l9z$thFj)Ocr;H8(6pC3l zzb`oo1f%N+??kn53S4RPU}PybZCRKw2ipVJ_m%XoI0CE1bx&S^9z2KNdbjUmF}e*U zz7JQ!Z%!QN{d&ofpFn?!v3ps63>-8bm|qsUM!c=~VW$2}8M8w*4}dsj{~T1=p!@o! z7*+~cXk?H?mKf#)WLr4gBCfYg^J1=en`Bp1`H3zXRZC z2Y{Em-yJVr6X7&?7(w56P<)WFpO&8EOD>c>mNl_xo)5qpLeOgnP?ql~LX%m9oajN! zwWMpW>^gJId-Iy1nH>q-E>*q`n}|5zi#+W6!AAoVtgjOPJiPSx6=1=XN;lw9J3&I? z9#VELR|1r!6LQmgixV9oY5T9FoZbhf>lw<{Kh9FiF$L7W_TcfMkP#C*b`4U<5}jyR22Ddvq^vNsE1&|XV5 z#QyouY-45{;7ZpeP9lXFw0NE80uT>a>r>Li6Den23(dgWyaiX4`Ur`;7V^k@GG0r2 z2}+PHU^v28Fh0__ zlU@2;h_)2Ow4hv684^#P!a09xxUV#Hg+(`TVt^|_uL5Cvxii&$j}o!(uXvVXpG;;b zP@I36ZroX@lZgvF}j3xfV^y&8_sp%;)zAz zHhloL%^ekI4nW(A!yN6ePyZg7>C=KCZjGnSvhJl%y8*vaAji;}X*UhR3fm_?z_9b_ zfT^;Zz^i}NTsPn^et(T@l`UwOzfOckLl!Weji??JAU{;f?a1FDDl3(N|L_=I@xe*mXi$`U3_td9WC6`TexH28cLbZ}aX{M#NOzh*f0;R7$hey( zECRw#$yyKvSWyq*-}vj(zaKAoZ}{TZe8)7_-84$)VNEK3T>&EM-E;CALF)Q9j{zjn z02cG2M99Ae5NXFC%@D1qzY0nv1 z^m(wBACwEhjU|i-NE-7lSKZUQ_6CAV2{Y}tugByQ?BI+4`SkBGJORfwz$-uf4uZZh7e(U#vQRiKq7|)HGBB~;Pd>?j{Wrn zm}D%TQDU%WXW(7$x#)BZ{$c{G(jsgMtTI|Jxe`h0|)pR-387QJB$7JC!D3bEzAgC z{_JtM(GJ!ByRm7b1R>|uhxwcEl9FIS{4(FD=C8eLhXp+X3(Cs=kCz1j|IpuerbkPZQit(L{?T)$qHqZq8!U6m8FLc8COKMqME5KS{++$NbBe+$0IDc zM#r)n(V;|-FxFZsMYK9pPlxCI8LX+Sp4aR7xBbh`tKapx&-XV1v{Byis248fuST43 zJjGC2_sy`g*Q^VI^b(sl1{do7un8^V5=Xpg*@|QK(j3PlJ!l^1>mOd=fpuscEz5Va zDuw7l;y`yb_+s7#j^@u7R*Dt9xbBj%}DTjIRCHv7#(xU8J5U z(gr~@UMJ@Pk8++s&SKY_%z^?9lls@h4Fim6g&d3KYr)E?K$w+O)$yvbuw8mti}@<_ zD{SA&X3f87ArTbebalMmolmTvz$}=7+2?a^;nN*ffFKXsrg}pQ;eva9mHawj6Yy6T zt$qCd^D$3wHygs-OYDffP#GgPi~0D(m7qIkQrc^sq~(lP3(IhR?Cq_1y?pRI%I~HliT+oh`y`GAMCcTDT@UyED(&or zGI*l8pfm{=){(yq>8UaS%!~mfIrvTMBBclZSkwwRS;U7B-co`q_~T7xeQ*UITmdBi zgDa3$(GRZRgDd#p3gA*`w(LVz@PB3n4ynZlwQYPcSAaB{ebjv0)`!=y7U&K{!<<*> zMKmU0ulc%j`ZCA69fg43Qi_OU1WpZco4XV1{MLxkdt>8NHYI>5Q~5w)9HWEa{z~LY zL~iSmmmRK-&PBNXs~Tn&j$w@QLbC0Vl^BZsc?%l9LJb7nTs3tBZhKe}q0`z%rfp}) zXj1^G;_Q!z`ZfiSv%1G`02l(7Qc~UZqY;l#4Bz+bSMqzX<6^_2b(qig*I_QXL!;+e z_`L*7cSSu-!600tIWQM|u$T4dbvjBTN$HYUD<_LoK>C`*PYt*W2qq`+kT zle){BgW{2Eb|I53J+6@|iwtfmGPuScC*wU|vT|h~qwe63bt0}GGvgFCLBv29;Sc{L z_F!(^s1hh9K*ki2EyCZ+1aW>_vn+9m;1@$OHTovp2vUI1>X< zpsd4$#Zi`{$}=pFoE7F4zM9$1^f48NxKm zIPkJk8HaVE1oom*4-*PGs0%CyC4Qkcf}XQLv^3kz<~(9ZK)n3Huy(~u`(ttc?gI1b zMM&Z&UXj4nsK|u%tkQ?NMap1NIvZduwLnkuM9m@#^@{q1ljCk_%KCwxR^Z>hV8aT$ z$dCBkk2wgQi~k-6=O*iiG52b-kDtRX4z>U5Pzv)RATOVhx#~<7TZPj(>qecS8WVBl z3<8-Eau8O2*vpgXo=45_$!&+(hjU=9Zt z6hlnxAdl(vnQY1tdLeR*4R~`)x|qC2$^j@3v<9?R2ea5?YQr8?bf9)$0EC1anWAR- z;j&)S!6Y4CLr_K;e=6ia1;lVv5mi@S|?d zOz#`zKfiovQ|9s|ARCM4Gn?Zh&)FXZ!qB(eRg@Nu;6P^>lMIldQ2}Ae>Vhg=`W0E8 zL}nb6Wvo8kbiyu%`wMNa(r(@S+}#WC9WsSr;vOg%kF!Le_Uj%Lb9hbbC_N?6t$|uX zZ+Z#~d^vh*fyst88glms7IrqKOGK5vRH#u@PkOaW-2nM zK=d))j{Cmb1z@bT7Sg#IBMy0iEf;3pSRV^}k}1Cj$OafOvS*g}Qft)^6vn9x1JIzd z`%dmKiG+Pk=_HW8q%^TNoow39L;Ktp`}0QYZ&|M^Wy^vdslhbbQ~Z%4+q&W8<9*3n zkVAbBo!l-G0T4!SIgs5*|3WP&Q@! zSpd$Us7LjDcc^{$oV}1%Uy{}dLrc9n3!?_CGbkDR0gH~%IZjuuCT{LixO+Pq70&&l z)@R0v&yg_$Stdpq=^8ANCrTB97MV$wYz>B?1~VM=Q~YL*omKHfVc+~rXGTvA&Uhs}XPnobPQ=Pkw_P#h(jly_F7+ zIrFSJvIBq(At$?X$1+(zj&Vqzz;$N?Uc1W_aJw zRgblu096WP#vN%qZZ*0&zNH1yDd43+vlqbHa`3SPakCEvioh@ixXniOX;#B?!%vvu ztYrH*Z$EK<9-4S!!1T8EOYWbTJrdiSc3Ad{S~;3oF~au}4ZgP;E8et8oP)lzNx$SO zJ?$%z7LuWu^BxV*$-?AiJ;q1Yq6tzg!W(RU4&(Tg;> zn2R^P6SL|*K#~doyDF;ktp`ZDqQV{;Vhrh6)DX0BWMwdvN)1Y-^&X}At{5P@z68fa z8~M~s_!>}Iu6&aYVBK2DM(Hnaj*GKz2nuFY`a<0_@tGFy;ev&bxCs~lch$-Exd3=C zv0%ocM4l-}@C)5&#;QJbZQocQ$p}@;;V~GBTNYYcIQ?(7Nte62TuB1VhIVZ(ZYS=) zH#poLh{K0sN6ANX=vHg6jxRcFk!j`{AvW$9qsT94+#D%t zbuOqz@Ka8DEpwnR5$T`@#1NONw>N+I1;G1}^tgvmwV@xuv5qH(+ z`G}iKBZC^fVVr6(%khH`sU83ou0Efoy%#G5acA#fg7?(LLz9=L)Q4SgWcnTWX_`$x zzjr6uuy99F;Kn)f&j>wen5tY%qrNGab{`SfcQ8yt|OefIWci;roW#KE= zVEQ0y*Qg2gfCEd%SiQmY$~&5nAa!Axm5(JaaNSYA{Ok=k-J(THwPqJnb3zd^(Nd{! z0~}a65z&JFY2hNg>L^sny%}*494_If+5b5;iu=Ne~Hv(UmD2|7ZDn zOc1+XJ=g008^(%wp>Q`5p`D6MmwkdqxOtq03qsyK`36?}n;ORvPin={>v~>-F!n!&`s~&{!CYgQNnFY|qyc z2()BMW?PxWdg7xN2-D>}&XVDLvqDS%n>byzv#4h)!L!ZeSCtSb<&nK7mcU^OzI4Z+ z10BETL4m#T=UFRD<$rqBv(~fc#TR`5ffNNw6*fmWoe<~@J3R6=M(S)As%VFQhJV0I zQZV#ysJ(M7;UD)ZPQc643t3KIX8y8B?RAgrIO*#LZs&BO82Dk{YvVU1=lRxEp^fW& z!0&`3z1KekqOy(jF|Q{>u@1cX27PxB43i;nJiubq(R>;dS+oK8oI>YnODNlNfv9!; zgX$2Rg9F#*X*Td!slS>cis@iaoEmwg15^LqFayR_-m$RP1_<(8JGay$$djYs4(O1B z(3Y)S+2)?1Qj=B)$Xg2KTCFehbtKVILtkg)8O3xE@Ie?8V4aAm2qUbjSDQZ&wJ3(W z*e`~!h9<=K0&8+6;^VUi|M+<_Gd!XV34dFHFvvl0%08zi~9tW}82+btqP5&z3 zPXRPEImxl_(2Zm$z^SQkXZA7zFv(IQdr4v)l9+>t5|bK}GX~<6cRn7mEcS}?uyEyq z>81O?`U!H~fp8G{PRZ=PCg^E;b5*JL9k`3JUgD!2K8W9dU>XgG&sy()r`=9vRer$Wb1aIRe^xk57RXE`GbY)psv<9H^W{JEHzSKld~RWR8+VUsgb0 zy4-AsfoXQg3<-yG5M^bL#fq2deH1}*5D4GI!tSjsmL1002R?jAnNy(Sn+bdm&OIM1 zho<@xi9zZfKV`O^=|GA*$g}Tb-uvOfUtF}quyQa_Qh_;(3QY5vn2#jk4)~?=H}v|x z*5nDn-~F;<_&ZM=%DSAFp>y`!umw?5v~3%0^su86??K2=`nh&EJ~?g(A!Epd;0$;I z^z(I@b`v4AB?v0~4mGJIVd~X9M82wseCNFdx6@}I!)0T?Sm#fOgAmS8On6KQ*Y+}j za%e(1l2Z7S-=iGv*h8twyp5gtqzB zBT@fb2{qQQBx2)WRK%X1-5A<3d0R?2ZdAPZ8xd8-ajT=#H?-GuW7vm=(tP0>f@}qM zG^K4DeCxN|mbWl@`l-(vtG~=0&~t|>fO3smf(LATaOx8(R=JJ=0czWGKkhH)4Cc`s zc&)MzP~!voSHf~}zF!CS_Nk0wtZQ)y268~$IniWhGzJ%24ws&`w*;wo~kI)mw}QmVTU*;g$AzaYqX=(4HSBb?AW)3NQN*5DnBnuC;& zKSBoAVDKApnQ^1d0LMAOBsjGqezs}dX=sxepx}!=GLmND3j~{5|Ec@bAIQ~#JrGmQxEfgk)O>amy0C~{I_tvy=CTH_wD}$R`m*U literal 0 HcmV?d00001 From cd4bb01487dbdce6bf2cf430208b6caf9a5c6d78 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 26 Mar 2024 13:26:05 +0000 Subject: [PATCH 131/288] Fix paths --- .../ffd/speech_recognition_cyberon.rst | 1 - doc/programming_guide/ffva/design.rst | 2 +- .../diagrams/control_plane_components.drawio | 140 ++++++++++++++++++ .../control_plane_components.drawio.png | Bin 0 -> 70960 bytes ..._device_control_servicer_flow_chart.drawio | 90 +++++++++++ ...ice_control_servicer_flow_chart.drawio.png | Bin 0 -> 35124 bytes 6 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 doc/programming_guide/ffva/diagrams/control_plane_components.drawio create mode 100644 doc/programming_guide/ffva/diagrams/control_plane_components.drawio.png create mode 100644 doc/programming_guide/ffva/diagrams/control_plane_device_control_servicer_flow_chart.drawio create mode 100644 doc/programming_guide/ffva/diagrams/control_plane_device_control_servicer_flow_chart.drawio.png diff --git a/doc/programming_guide/ffd/speech_recognition_cyberon.rst b/doc/programming_guide/ffd/speech_recognition_cyberon.rst index c58c06f4..191f1361 100644 --- a/doc/programming_guide/ffd/speech_recognition_cyberon.rst +++ b/doc/programming_guide/ffd/speech_recognition_cyberon.rst @@ -1,5 +1,4 @@ .. include:: -.. include:: ../../substitutions.rst .. _sln_voice_ffd_speech_recognition_cyberon: diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index 10c3b198..a33cb701 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -175,7 +175,7 @@ machine interacts with a separate RTOS task in order to asynchronously perform flash read/write operations. .. _fig_control_plane_components: -.. figure:: diagrams/control_plane_components_diagram.png +.. figure:: diagrams/control_plane_components.drawio.png :width: 100% |project| Control Plane Components Diagram diff --git a/doc/programming_guide/ffva/diagrams/control_plane_components.drawio b/doc/programming_guide/ffva/diagrams/control_plane_components.drawio new file mode 100644 index 00000000..53f3cf3d --- /dev/null +++ b/doc/programming_guide/ffva/diagrams/control_plane_components.drawio @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/programming_guide/ffva/diagrams/control_plane_components.drawio.png b/doc/programming_guide/ffva/diagrams/control_plane_components.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..f7860fe10803a917db235d680e43b64d87efab53 GIT binary patch literal 70960 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 literal 0 HcmV?d00001 diff --git a/doc/programming_guide/ffva/diagrams/control_plane_device_control_servicer_flow_chart.drawio b/doc/programming_guide/ffva/diagrams/control_plane_device_control_servicer_flow_chart.drawio new file mode 100644 index 00000000..82c783c2 --- /dev/null +++ b/doc/programming_guide/ffva/diagrams/control_plane_device_control_servicer_flow_chart.drawio @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/programming_guide/ffva/diagrams/control_plane_device_control_servicer_flow_chart.drawio.png b/doc/programming_guide/ffva/diagrams/control_plane_device_control_servicer_flow_chart.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..42e360f659668822040c8b5401e150c624c8c525 GIT binary patch literal 35124 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 literal 0 HcmV?d00001 From 683cd1c269ee4b8e24c6e39c8281c0e7fd875381 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 26 Mar 2024 15:10:38 +0000 Subject: [PATCH 132/288] Fix errors --- doc/programming_guide/ffva/design.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index a33cb701..d9b4508d 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -154,7 +154,7 @@ A message sequence chart of the upload operation is below: .. _dfu_usb_interface_design: DFU over USB implementation -*************************** +--------------------------- The UA variant of the device make use of a USB connection for handling DFU operations. This interface is a relatively standard, specification-compliant implementation. @@ -163,7 +163,7 @@ The implementation is encapsulated within the tinyUSB library, which provides a .. _dfu_i2c_interface_design: DFU over |I2C| implementation -***************************** +----------------------------- The INT variant of the device presents a DFU interface that may be controlled over |I2C|. @@ -189,7 +189,7 @@ between the Device Control module and the DFU Servicer. In this diagram, boxes with the same colour reside in the same RTOS task. .. _fig_control_plane_dc_servicer_flow_chart: -.. figure:: diagrams/control_plane_device_control_servicer_flow_chart.png +.. figure:: diagrams/control_plane_device_control_servicer_flow_chart.drawio.png :width: 100% |project| Device Control -- Servicer Flow Chart @@ -219,7 +219,7 @@ Packets containing a response from the FFVA-INT to the host application place a status value in the first byte of the payload. INT DFU implementation ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ Mirroring the USB DFU specification, the INT implementation supports a set of 9 control commands intended to drive the state machine, along with an additional 2 From 2ca14f7bf6ce983307d784d977845c8a0b1fb339 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 26 Mar 2024 16:27:57 +0000 Subject: [PATCH 133/288] Minor fixes --- doc/programming_guide/ffva/deploying/linux_macos.rst | 10 +++++----- doc/programming_guide/ffva/design.rst | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/programming_guide/ffva/deploying/linux_macos.rst b/doc/programming_guide/ffva/deploying/linux_macos.rst index 5709beaa..173eb358 100644 --- a/doc/programming_guide/ffva/deploying/linux_macos.rst +++ b/doc/programming_guide/ffva/deploying/linux_macos.rst @@ -80,7 +80,7 @@ To create an upgrade image from the build folder run: make create_upgrade_img_example_ffva_ua_adec_altarch -Once the application is running, a USB DFU v1.1 tool can be used to perform various actions. This example will demonstrate with dfu-util commands. Installation instructions for respective operating system can be found `here `__. +Once the application is running, a USB DFU v1.1 tool can be used to perform various actions. This example will demonstrate with dfu-util commands. Installation instructions for the respective operating systems can be found `here `__. To verify the device is running run: @@ -118,7 +118,7 @@ The upgrade image can be read back by running: dfu-util -e -d ,20b1:4001 -a 1 -U readback_upgrade_img.bin -On system reboot, the upgrade image will always be loaded if valid. If the upgrade image is invalid, the factory image will be loaded. To revert back to the factory image, you can upload an file containing the word 0xFFFFFFFF. +On system reboot, the upgrade image will always be loaded if valid. If the upgrade image is invalid, the factory image will be loaded. To revert back to the factory image, you can upload a file containing the word 0xFFFFFFFF. The data partition image can be read back by running: @@ -145,7 +145,7 @@ To create an upgrade image from the build folder run: make create_upgrade_img_example_ffva_int_fixed_delay -Once the application is running, the *xvf_dfu* tool can be used to perform various actions. Installation instructions for respective operating system can be found `here `__. +Once the application is running, the *xvf_dfu* tool can be used to perform various actions. Installation instructions for Raspbian OS can be found `here `__. Before running the *xvf_dfu* host application, the ``I2C_ADDRESS`` value in the file ``transport_config.yaml`` located in the same folder as the binary file ``xvf_dfu`` must be updated. This value must match the one set for ``appconf_CONTROL_I2C_DEVICE_ADDR`` in the ``platform_conf.h`` file. @@ -177,7 +177,7 @@ The device can be rebooted remotely by running xvf_dfu --reboot -On system reboot, the upgrade image will always be loaded if valid. If the upgrade image is invalid, the factory image will be loaded. To revert back to the factory image, you can upload an file containing the word 0xFFFFFFFF. +On system reboot, the upgrade image will always be loaded if valid. If the upgrade image is invalid, the factory image will be loaded. To revert back to the factory image, you can upload a file containing the word 0xFFFFFFFF. The FFVA-INT variants include some version numbers: @@ -185,7 +185,7 @@ The FFVA-INT variants include some version numbers: - *APP_VERSION_MINOR* - *APP_VERSION_PATCH* -These values are defined in the ``app_conf.h`` file and they can read by running: +These values are defined in the ``app_conf.h`` file, and they can read by running: .. code-block:: console diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index d9b4508d..13661373 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -169,14 +169,14 @@ The INT variant of the device presents a DFU interface that may be controlled over |I2C|. :numref:`fig_control_plane_components` shows the modules involved in -processing the DFU commands. The *I2C* has a dedicated logical core so that it is always ready -to receive and send control messages. The DFU state machine is driven by use of control commands. The DFU state +processing the DFU commands. The *I2C* task has a dedicated logical core so that it is always ready +to receive and send control messages. The DFU state machine is driven by the control commands. The DFU state machine interacts with a separate RTOS task in order to asynchronously perform flash read/write operations. .. _fig_control_plane_components: .. figure:: diagrams/control_plane_components.drawio.png - :width: 100% + :width: 50% |project| Control Plane Components Diagram @@ -190,15 +190,15 @@ In this diagram, boxes with the same colour reside in the same RTOS task. .. _fig_control_plane_dc_servicer_flow_chart: .. figure:: diagrams/control_plane_device_control_servicer_flow_chart.drawio.png - :width: 100% + :width: 50% |project| Device Control -- Servicer Flow Chart -This diagram shows a critical aspect of Control Plane operation. +This diagram shows a critical aspect of the DFU control operation. The Device Control module, having placed a command on a Servicer's command queue, waits on the Gateway queue for a response. As a result, it ensures processing of a single control command at a time. -Limiting Control Plane operation to a single command in-flight reduces the +Limiting DFU control operation to a single command in-flight reduces the complexity of the control protocol and eliminates several potential error cases. From ca634f014bf7df422b141b90e8702388023e2e6b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 26 Mar 2024 16:46:07 +0000 Subject: [PATCH 134/288] Minor fixes --- doc/programming_guide/ffva/design.rst | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index 13661373..0cdb643d 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -154,16 +154,16 @@ A message sequence chart of the upload operation is below: .. _dfu_usb_interface_design: DFU over USB implementation ---------------------------- +=========================== -The UA variant of the device make use of a USB connection for handling DFU operations. +The UA variant of the device makes use of a USB connection for handling DFU operations. This interface is a relatively standard, specification-compliant implementation. The implementation is encapsulated within the tinyUSB library, which provides a USB stack for the |project|. .. _dfu_i2c_interface_design: DFU over |I2C| implementation ------------------------------ +============================= The INT variant of the device presents a DFU interface that may be controlled over |I2C|. @@ -218,10 +218,7 @@ end-of-frame symbols, a cyclical redundancy check or an error correcting code. Packets containing a response from the FFVA-INT to the host application place a status value in the first byte of the payload. -INT DFU implementation -^^^^^^^^^^^^^^^^^^^^^^ - -Mirroring the USB DFU specification, the INT implementation supports a set of 9 +Mirroring the USB DFU specification, the INT DFU implementation supports a set of 9 control commands intended to drive the state machine, along with an additional 2 utility commands: From 49b615d514ac4c56966b6ec6346ba6a5694b283c Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 8 Apr 2024 09:01:52 +0100 Subject: [PATCH 135/288] Add comment --- test/device_firmware_update/check_dfu_i2c.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index 03a1c42d..0abe8ce0 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -1,5 +1,7 @@ #!/bin/bash +# The steps below are meant to be run on a Linux/macOS platform. +# On Windows the `make` command must be replaced by `ninja` # To run this test do the following: # 1. Configure a Rapsberry-Pi: # a. Follow the instructions for XVF3800-INT on https://github.com/xmos/vocalfusion-rpi-setup?tab=readme-ov-file#setup From 01bb9669bb114659d9c5044b3e8469d102ca12ee Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 8 Apr 2024 09:15:45 +0100 Subject: [PATCH 136/288] Add comment --- test/device_firmware_update/check_dfu_i2c.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index 0abe8ce0..f498bf38 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -25,7 +25,7 @@ # mv example_ffva_int_fixed_delay_upgrade.bin download2.bin # 6. Copy the files download1.bin, download2.bin, emptyfile.bin and check_dfu_i2c.sh to the host_xvf_control/build folder on the Raspberry-Pi # 7. On the Raspberry-Pi, set the desired value of ITERATION_NUM in check_dfu_i2c.sh -# 8. Run this script: +# 8. On the Raspberry-Pi, run this script from the host_xvf_control build folder: # source check_dfu_i2c.sh ITERATION_NUM=2 From ce15f8de0784420ac1b8e728ca354b602cbc2454 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 8 Apr 2024 09:54:25 +0100 Subject: [PATCH 137/288] Add instructios for Windows --- test/device_firmware_update/check_dfu_i2c.sh | 35 ++++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index f498bf38..2a9f9fa2 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -7,22 +7,37 @@ # a. Follow the instructions for XVF3800-INT on https://github.com/xmos/vocalfusion-rpi-setup?tab=readme-ov-file#setup # b. Clone on the Raspberry Pi the repo host_xvf_control using `git clone https://github.com/xmos/host_xvf_control` # c. In the file host_xvf_control/src/dfu/transport_config.yaml update the value of I2C_ADDRESS from 0x2C to 0x42 -# d. Build the xvf_dfu host application on a Raspberry-Pi using the instructions in https://github.com/xmos/host_xvf_control/blob/main/README.rst +# d. Build the xvf_dfu host application on the Raspberry-Pi using the instructions in https://github.com/xmos/host_xvf_control/blob/main/README.rst # 2. Prepare the hardware # a. Attach an XK-VOICE-L71 board to the Raspberry-Pi expander # b. Connect an xTAG to XK-VOICE-L71 board and to a host machine # 3. Build the application example_ffva_int_fixed_delay and flash it to the board: -# cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake -# cd build -# make flash_app_example_ffva_int_fixed_delay +# - on Linux/macOS: +# cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake +# cd build +# make flash_app_example_ffva_int_fixed_delay +# - on Windows: +# cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake +# cd build +# ninja flash_app_example_ffva_int_fixed_delay # 4. Change the value of APP_VERSION_MAJOR in app_conf.h and generate an upgrade image: -# cd build -# make create_upgrade_img_example_ffva_int_fixed_delay -# mv example_ffva_int_fixed_delay_upgrade.bin download1.bin +# - on Linux/macOS: +# cd build +# make create_upgrade_img_example_ffva_int_fixed_delay +# mv example_ffva_int_fixed_delay_upgrade.bin download1.bin +# - on Windows: +# cd build +# ninja create_upgrade_img_example_ffva_int_fixed_delay +# MOVE example_ffva_int_fixed_delay_upgrade.bin download1.bin # 5. Change the value again of APP_VERSION_MAJOR in app_conf.h and generate an upgrade image: -# cd build -# make create_upgrade_img_example_ffva_int_fixed_delay -# mv example_ffva_int_fixed_delay_upgrade.bin download2.bin +# - on Linux/macOS: +# cd build +# make create_upgrade_img_example_ffva_int_fixed_delay +# mv example_ffva_int_fixed_delay_upgrade.bin download2.bin +# - on Windows: +# cd build +# ninja create_upgrade_img_example_ffva_int_fixed_delay +# MOVE example_ffva_int_fixed_delay_upgrade.bin download2.bin # 6. Copy the files download1.bin, download2.bin, emptyfile.bin and check_dfu_i2c.sh to the host_xvf_control/build folder on the Raspberry-Pi # 7. On the Raspberry-Pi, set the desired value of ITERATION_NUM in check_dfu_i2c.sh # 8. On the Raspberry-Pi, run this script from the host_xvf_control build folder: From cbbbc7c9a11c42a5e752383c63ff7b1d3e229c95 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 8 Apr 2024 09:55:37 +0100 Subject: [PATCH 138/288] Remove first lines --- test/device_firmware_update/check_dfu_i2c.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index 2a9f9fa2..a07ce8bd 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -1,7 +1,5 @@ #!/bin/bash -# The steps below are meant to be run on a Linux/macOS platform. -# On Windows the `make` command must be replaced by `ninja` # To run this test do the following: # 1. Configure a Rapsberry-Pi: # a. Follow the instructions for XVF3800-INT on https://github.com/xmos/vocalfusion-rpi-setup?tab=readme-ov-file#setup From e0a8dfb4bcab20bfaea899eb6f968d155a00a5de Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 8 Jul 2024 15:54:44 +0100 Subject: [PATCH 139/288] Update empy pipeline in reference group --- .../audio_pipelines/reference/CMakeLists.txt | 9 +- .../reference/empty/audio_pipeline_dsp.h | 20 ++++ .../{audio_pipeline.c => audio_pipeline_t0.c} | 11 +-- .../reference/empty/audio_pipeline_t1.c | 95 +++++++++++++++++++ 4 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 modules/audio_pipelines/reference/empty/audio_pipeline_dsp.h rename modules/audio_pipelines/reference/empty/{audio_pipeline.c => audio_pipeline_t0.c} (86%) create mode 100644 modules/audio_pipelines/reference/empty/audio_pipeline_t1.c diff --git a/modules/audio_pipelines/reference/CMakeLists.txt b/modules/audio_pipelines/reference/CMakeLists.txt index b2b11e87..159317bf 100644 --- a/modules/audio_pipelines/reference/CMakeLists.txt +++ b/modules/audio_pipelines/reference/CMakeLists.txt @@ -1,5 +1,5 @@ ##****************************************** -## Create fixed_delay AEC+IC+NS+AGC +## Create fixed_delay AEC+IC+NS+AGC ## 2 mic input channels # 2 reference input channels ##****************************************** @@ -30,7 +30,7 @@ target_link_libraries(fixed_delay_aec_ic_ns_agc_2mic_2ref ) ##****************************************** -## Create ADEC "prevarch" AEC+IC+NS+AGC +## Create ADEC "prevarch" AEC+IC+NS+AGC ## 2 mic input channels # 2 reference input channels ##****************************************** @@ -66,7 +66,7 @@ target_link_libraries(adec_aec_ic_ns_agc_2mic_2ref ) ##****************************************** -## Create ADEC altarch AEC+IC+NS+AGC +## Create ADEC altarch AEC+IC+NS+AGC ## 2 mic input channels # 2 reference input channels ##****************************************** @@ -108,7 +108,8 @@ target_link_libraries(adec_altarch_aec_ic_ns_agc_2mic_2ref add_library(empty_2mic_2ref INTERFACE) target_sources(empty_2mic_2ref INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/empty/audio_pipeline.c + ${CMAKE_CURRENT_LIST_DIR}/empty/audio_pipeline_t0.c + ${CMAKE_CURRENT_LIST_DIR}/empty/audio_pipeline_t1.c ) target_include_directories(empty_2mic_2ref INTERFACE diff --git a/modules/audio_pipelines/reference/empty/audio_pipeline_dsp.h b/modules/audio_pipelines/reference/empty/audio_pipeline_dsp.h new file mode 100644 index 00000000..3d5f0822 --- /dev/null +++ b/modules/audio_pipelines/reference/empty/audio_pipeline_dsp.h @@ -0,0 +1,20 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef AUDIO_PIPELINE_DSP_H_ +#define AUDIO_PIPELINE_DSP_H_ + +#include +#include "app_conf.h" + + +/* Note: Changing the order here will effect the channel order for + * audio_pipeline_input() and audio_pipeline_output() + */ +typedef struct { + int32_t samples[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; + int32_t aec_reference_audio_samples[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; + int32_t mic_samples_passthrough[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; +} frame_data_t; + +#endif /* AUDIO_PIPELINE_DSP_H_ */ diff --git a/modules/audio_pipelines/reference/empty/audio_pipeline.c b/modules/audio_pipelines/reference/empty/audio_pipeline_t0.c similarity index 86% rename from modules/audio_pipelines/reference/empty/audio_pipeline.c rename to modules/audio_pipelines/reference/empty/audio_pipeline_t0.c index 03f5039a..fbe44f45 100644 --- a/modules/audio_pipelines/reference/empty/audio_pipeline.c +++ b/modules/audio_pipelines/reference/empty/audio_pipeline_t0.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ @@ -19,16 +19,13 @@ /* App headers */ #include "app_conf.h" #include "audio_pipeline.h" +#include "audio_pipeline_dsp.h" #if appconfAUDIO_PIPELINE_FRAME_ADVANCE != 240 #error This pipeline is only configured for 240 frame advance #endif -typedef struct { - int32_t samples[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; - int32_t aec_reference_audio_samples[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; - int32_t mic_samples_passthrough[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; -} frame_data_t; +#if ON_TILE(0) static void *audio_pipeline_input_i(void *input_app_data) { @@ -99,3 +96,5 @@ void audio_pipeline_init( appconfAUDIO_PIPELINE_TASK_PRIORITY, stage_count); } + +#endif /* ON_TILE(0)*/ diff --git a/modules/audio_pipelines/reference/empty/audio_pipeline_t1.c b/modules/audio_pipelines/reference/empty/audio_pipeline_t1.c new file mode 100644 index 00000000..ddf31c8d --- /dev/null +++ b/modules/audio_pipelines/reference/empty/audio_pipeline_t1.c @@ -0,0 +1,95 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* STD headers */ +#include +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" +#include "task.h" +#include "timers.h" +#include "queue.h" +#include "stream_buffer.h" + +/* Library headers */ +#include "generic_pipeline.h" + +/* App headers */ +#include "app_conf.h" +#include "audio_pipeline.h" +#include "audio_pipeline_dsp.h" + +#if appconfAUDIO_PIPELINE_FRAME_ADVANCE != 240 +#error This pipeline is only configured for 240 frame advance +#endif + +#if ON_TILE(1) + +static void *audio_pipeline_input_i(void *input_app_data) +{ + frame_data_t *frame_data; + frame_data = pvPortMalloc(sizeof(frame_data_t)); + memset(frame_data, 0x00, sizeof(frame_data_t)); + + audio_pipeline_input(input_app_data, + (int32_t **)frame_data->aec_reference_audio_samples, + 4, + appconfAUDIO_PIPELINE_FRAME_ADVANCE); + + memcpy(frame_data->samples, frame_data->mic_samples_passthrough, sizeof(frame_data->samples)); + + return frame_data; +} + +static int audio_pipeline_output_i(frame_data_t *frame_data, + void *output_app_data) +{ + + rtos_intertile_tx(intertile_ctx, + appconfAUDIOPIPELINE_PORT, + frame_data, + sizeof(frame_data_t)); + + return AUDIO_PIPELINE_FREE_FRAME; +} + +void empty_stage(void) +{ + ; +} + +static void initialize_pipeline_stages(void) +{ + ; +} + +void audio_pipeline_init( + void *input_app_data, + void *output_app_data) +{ + const int stage_count = 2; + const pipeline_stage_t stages[] = { + (pipeline_stage_t)empty_stage, + (pipeline_stage_t)empty_stage, + }; + + const configSTACK_DEPTH_TYPE stage_stack_sizes[] = { + configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(empty_stage) + RTOS_THREAD_STACK_SIZE(audio_pipeline_input_i), + configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(empty_stage) + RTOS_THREAD_STACK_SIZE(audio_pipeline_output_i), + }; + + initialize_pipeline_stages(); + + generic_pipeline_init((pipeline_input_t)audio_pipeline_input_i, + (pipeline_output_t)audio_pipeline_output_i, + input_app_data, + output_app_data, + stages, + (const size_t*) stage_stack_sizes, + appconfAUDIO_PIPELINE_TASK_PRIORITY, + stage_count); + +} +#endif /* ON_TILE(1)*/ From 2943925f82204e04f0851ba8a7c2c8f1068f1192 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 8 Jul 2024 15:54:44 +0100 Subject: [PATCH 140/288] Update empty pipeline in reference group --- .../audio_pipelines/reference/CMakeLists.txt | 9 +- .../reference/empty/audio_pipeline_dsp.h | 20 ++++ .../{audio_pipeline.c => audio_pipeline_t0.c} | 11 +-- .../reference/empty/audio_pipeline_t1.c | 95 +++++++++++++++++++ 4 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 modules/audio_pipelines/reference/empty/audio_pipeline_dsp.h rename modules/audio_pipelines/reference/empty/{audio_pipeline.c => audio_pipeline_t0.c} (86%) create mode 100644 modules/audio_pipelines/reference/empty/audio_pipeline_t1.c diff --git a/modules/audio_pipelines/reference/CMakeLists.txt b/modules/audio_pipelines/reference/CMakeLists.txt index b2b11e87..159317bf 100644 --- a/modules/audio_pipelines/reference/CMakeLists.txt +++ b/modules/audio_pipelines/reference/CMakeLists.txt @@ -1,5 +1,5 @@ ##****************************************** -## Create fixed_delay AEC+IC+NS+AGC +## Create fixed_delay AEC+IC+NS+AGC ## 2 mic input channels # 2 reference input channels ##****************************************** @@ -30,7 +30,7 @@ target_link_libraries(fixed_delay_aec_ic_ns_agc_2mic_2ref ) ##****************************************** -## Create ADEC "prevarch" AEC+IC+NS+AGC +## Create ADEC "prevarch" AEC+IC+NS+AGC ## 2 mic input channels # 2 reference input channels ##****************************************** @@ -66,7 +66,7 @@ target_link_libraries(adec_aec_ic_ns_agc_2mic_2ref ) ##****************************************** -## Create ADEC altarch AEC+IC+NS+AGC +## Create ADEC altarch AEC+IC+NS+AGC ## 2 mic input channels # 2 reference input channels ##****************************************** @@ -108,7 +108,8 @@ target_link_libraries(adec_altarch_aec_ic_ns_agc_2mic_2ref add_library(empty_2mic_2ref INTERFACE) target_sources(empty_2mic_2ref INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/empty/audio_pipeline.c + ${CMAKE_CURRENT_LIST_DIR}/empty/audio_pipeline_t0.c + ${CMAKE_CURRENT_LIST_DIR}/empty/audio_pipeline_t1.c ) target_include_directories(empty_2mic_2ref INTERFACE diff --git a/modules/audio_pipelines/reference/empty/audio_pipeline_dsp.h b/modules/audio_pipelines/reference/empty/audio_pipeline_dsp.h new file mode 100644 index 00000000..3d5f0822 --- /dev/null +++ b/modules/audio_pipelines/reference/empty/audio_pipeline_dsp.h @@ -0,0 +1,20 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef AUDIO_PIPELINE_DSP_H_ +#define AUDIO_PIPELINE_DSP_H_ + +#include +#include "app_conf.h" + + +/* Note: Changing the order here will effect the channel order for + * audio_pipeline_input() and audio_pipeline_output() + */ +typedef struct { + int32_t samples[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; + int32_t aec_reference_audio_samples[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; + int32_t mic_samples_passthrough[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; +} frame_data_t; + +#endif /* AUDIO_PIPELINE_DSP_H_ */ diff --git a/modules/audio_pipelines/reference/empty/audio_pipeline.c b/modules/audio_pipelines/reference/empty/audio_pipeline_t0.c similarity index 86% rename from modules/audio_pipelines/reference/empty/audio_pipeline.c rename to modules/audio_pipelines/reference/empty/audio_pipeline_t0.c index 03f5039a..fbe44f45 100644 --- a/modules/audio_pipelines/reference/empty/audio_pipeline.c +++ b/modules/audio_pipelines/reference/empty/audio_pipeline_t0.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ @@ -19,16 +19,13 @@ /* App headers */ #include "app_conf.h" #include "audio_pipeline.h" +#include "audio_pipeline_dsp.h" #if appconfAUDIO_PIPELINE_FRAME_ADVANCE != 240 #error This pipeline is only configured for 240 frame advance #endif -typedef struct { - int32_t samples[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; - int32_t aec_reference_audio_samples[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; - int32_t mic_samples_passthrough[appconfAUDIO_PIPELINE_CHANNELS][appconfAUDIO_PIPELINE_FRAME_ADVANCE]; -} frame_data_t; +#if ON_TILE(0) static void *audio_pipeline_input_i(void *input_app_data) { @@ -99,3 +96,5 @@ void audio_pipeline_init( appconfAUDIO_PIPELINE_TASK_PRIORITY, stage_count); } + +#endif /* ON_TILE(0)*/ diff --git a/modules/audio_pipelines/reference/empty/audio_pipeline_t1.c b/modules/audio_pipelines/reference/empty/audio_pipeline_t1.c new file mode 100644 index 00000000..ddf31c8d --- /dev/null +++ b/modules/audio_pipelines/reference/empty/audio_pipeline_t1.c @@ -0,0 +1,95 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* STD headers */ +#include +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" +#include "task.h" +#include "timers.h" +#include "queue.h" +#include "stream_buffer.h" + +/* Library headers */ +#include "generic_pipeline.h" + +/* App headers */ +#include "app_conf.h" +#include "audio_pipeline.h" +#include "audio_pipeline_dsp.h" + +#if appconfAUDIO_PIPELINE_FRAME_ADVANCE != 240 +#error This pipeline is only configured for 240 frame advance +#endif + +#if ON_TILE(1) + +static void *audio_pipeline_input_i(void *input_app_data) +{ + frame_data_t *frame_data; + frame_data = pvPortMalloc(sizeof(frame_data_t)); + memset(frame_data, 0x00, sizeof(frame_data_t)); + + audio_pipeline_input(input_app_data, + (int32_t **)frame_data->aec_reference_audio_samples, + 4, + appconfAUDIO_PIPELINE_FRAME_ADVANCE); + + memcpy(frame_data->samples, frame_data->mic_samples_passthrough, sizeof(frame_data->samples)); + + return frame_data; +} + +static int audio_pipeline_output_i(frame_data_t *frame_data, + void *output_app_data) +{ + + rtos_intertile_tx(intertile_ctx, + appconfAUDIOPIPELINE_PORT, + frame_data, + sizeof(frame_data_t)); + + return AUDIO_PIPELINE_FREE_FRAME; +} + +void empty_stage(void) +{ + ; +} + +static void initialize_pipeline_stages(void) +{ + ; +} + +void audio_pipeline_init( + void *input_app_data, + void *output_app_data) +{ + const int stage_count = 2; + const pipeline_stage_t stages[] = { + (pipeline_stage_t)empty_stage, + (pipeline_stage_t)empty_stage, + }; + + const configSTACK_DEPTH_TYPE stage_stack_sizes[] = { + configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(empty_stage) + RTOS_THREAD_STACK_SIZE(audio_pipeline_input_i), + configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(empty_stage) + RTOS_THREAD_STACK_SIZE(audio_pipeline_output_i), + }; + + initialize_pipeline_stages(); + + generic_pipeline_init((pipeline_input_t)audio_pipeline_input_i, + (pipeline_output_t)audio_pipeline_output_i, + input_app_data, + output_app_data, + stages, + (const size_t*) stage_stack_sizes, + appconfAUDIO_PIPELINE_TASK_PRIORITY, + stage_count); + +} +#endif /* ON_TILE(1)*/ From dc16faa1ec1ebd8d04233e4698d0dc565486b9e2 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 16 Jul 2024 15:35:18 +0100 Subject: [PATCH 141/288] Remove QE bit and register information from XN files --- examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 2 -- .../bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn | 4 +--- examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 2 -- .../low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 2 -- examples/mic_aggregator/src/XCORE-AI-EXPLORER.xn | 8 +++----- examples/speech_recognition/XCORE-AI-EXPLORER.xn | 4 +--- examples/speech_recognition/XK_VOICE_L71.xn | 2 -- test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 2 -- 8 files changed, 5 insertions(+), 21 deletions(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index b927e064..d003d7e1 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -81,8 +81,6 @@ - - diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn index 754092ec..a50f5738 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn @@ -74,7 +74,7 @@ - + @@ -101,8 +101,6 @@ - - diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index 3b666f18..d7b548e1 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -85,8 +85,6 @@ - - diff --git a/examples/low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/examples/low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index b927e064..d003d7e1 100644 --- a/examples/low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/examples/low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -81,8 +81,6 @@ - - diff --git a/examples/mic_aggregator/src/XCORE-AI-EXPLORER.xn b/examples/mic_aggregator/src/XCORE-AI-EXPLORER.xn index e0f33013..bea83b53 100644 --- a/examples/mic_aggregator/src/XCORE-AI-EXPLORER.xn +++ b/examples/mic_aggregator/src/XCORE-AI-EXPLORER.xn @@ -48,13 +48,13 @@ - + - + - + @@ -111,8 +111,6 @@ - - diff --git a/examples/speech_recognition/XCORE-AI-EXPLORER.xn b/examples/speech_recognition/XCORE-AI-EXPLORER.xn index 44e033b4..e3a0db39 100644 --- a/examples/speech_recognition/XCORE-AI-EXPLORER.xn +++ b/examples/speech_recognition/XCORE-AI-EXPLORER.xn @@ -74,7 +74,7 @@ - + @@ -101,8 +101,6 @@ - - diff --git a/examples/speech_recognition/XK_VOICE_L71.xn b/examples/speech_recognition/XK_VOICE_L71.xn index b927e064..d003d7e1 100644 --- a/examples/speech_recognition/XK_VOICE_L71.xn +++ b/examples/speech_recognition/XK_VOICE_L71.xn @@ -81,8 +81,6 @@ - - diff --git a/test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index b927e064..d003d7e1 100644 --- a/test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -81,8 +81,6 @@ - - From 7c9d1d6c8d0af999a466ca2095c72cbd5c14887b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 16 Jul 2024 15:39:37 +0100 Subject: [PATCH 142/288] Remove flash type info from XN files --- examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 2 +- .../ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn | 2 +- examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 2 +- .../low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 2 +- examples/speech_recognition/XCORE-AI-EXPLORER.xn | 2 +- test/asr/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 4 +--- test/ffd_gpio/XCORE-AI-EXPLORER.xn | 4 +--- test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 2 +- 8 files changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index d003d7e1..b1284ff2 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -77,7 +77,7 @@ - + diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn index a50f5738..839a2974 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/XCORE-AI-EXPLORER.xn @@ -97,7 +97,7 @@ - + diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index d7b548e1..44199fd4 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -81,7 +81,7 @@ - + diff --git a/examples/low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/examples/low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index d003d7e1..b1284ff2 100644 --- a/examples/low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/examples/low_power_ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -77,7 +77,7 @@ - + diff --git a/examples/speech_recognition/XCORE-AI-EXPLORER.xn b/examples/speech_recognition/XCORE-AI-EXPLORER.xn index e3a0db39..7370eee4 100644 --- a/examples/speech_recognition/XCORE-AI-EXPLORER.xn +++ b/examples/speech_recognition/XCORE-AI-EXPLORER.xn @@ -97,7 +97,7 @@ - + diff --git a/test/asr/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/test/asr/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index b927e064..b1284ff2 100644 --- a/test/asr/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/test/asr/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -77,12 +77,10 @@ - + - - diff --git a/test/ffd_gpio/XCORE-AI-EXPLORER.xn b/test/ffd_gpio/XCORE-AI-EXPLORER.xn index db710c9e..7331171b 100644 --- a/test/ffd_gpio/XCORE-AI-EXPLORER.xn +++ b/test/ffd_gpio/XCORE-AI-EXPLORER.xn @@ -100,12 +100,10 @@ - + - - diff --git a/test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index d003d7e1..b1284ff2 100644 --- a/test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/test/pipeline/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -77,7 +77,7 @@ - + From 0cdb37a6a3e5e7ca8eb0b201114d8852edd12cdf Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 17 Jul 2024 08:56:33 +0100 Subject: [PATCH 143/288] Add entry --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bccd9f63..b49b7265 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,8 @@ XCORE-VOICE change log * ADDED: Support for DFU over I2C for FFVA INT example. * CHANGED: Updated submodule fwk_rtos to version 3.1.0 from 3.0.5. * ADDED: lib_sw_pll submodule v1.1.0. + * REMOVED: flash settings in .xn files, as they are not used by XMOS Tools + 15.2.x. 2.2.0 ----- From 7d7e41becdbea490919c3a4ada0ab0a87d11115f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 17 Jul 2024 10:37:35 +0100 Subject: [PATCH 144/288] Improve text --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b49b7265..2deda70a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,8 +12,8 @@ XCORE-VOICE change log * ADDED: Support for DFU over I2C for FFVA INT example. * CHANGED: Updated submodule fwk_rtos to version 3.1.0 from 3.0.5. * ADDED: lib_sw_pll submodule v1.1.0. - * REMOVED: flash settings in .xn files, as they are not used by XMOS Tools - 15.2.x. + * REMOVED: flash settings in .xn files, as they are not required by XMOS + Tools 15.2.x. 2.2.0 ----- From ac3c48e9daf608fb3507e27047e00214ce44407f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 24 Jul 2024 13:42:52 +0100 Subject: [PATCH 145/288] Add new FFD example with I2S input --- examples/examples.cmake | 2 + .../bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 4 + .../XK_VOICE_L71/platform/driver_instances.c | 4 + .../XK_VOICE_L71/platform/driver_instances.h | 20 + .../XK_VOICE_L71/platform/platform_init.c | 83 +- .../XK_VOICE_L71/platform/platform_start.c | 2 + examples/ffd/ffd_cyberon_i2s_input.cmake | 204 +++++ examples/ffd/src/app_conf.h | 19 + examples/ffd/src/fractions_1000ppm.h | 796 ++++++++++++++++++ examples/ffd/src/main.c | 151 +++- examples/ffd/src/register_setup_1000ppm.h | 17 + tools/ci/main_examples.txt | 1 + 12 files changed, 1301 insertions(+), 2 deletions(-) create mode 100644 examples/ffd/ffd_cyberon_i2s_input.cmake create mode 100644 examples/ffd/src/fractions_1000ppm.h create mode 100644 examples/ffd/src/register_setup_1000ppm.h diff --git a/examples/examples.cmake b/examples/examples.cmake index 413503ca..c3d0c635 100644 --- a/examples/examples.cmake +++ b/examples/examples.cmake @@ -6,6 +6,8 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) include(${CMAKE_CURRENT_LIST_DIR}/asrc_demo/asrc_demo.cmake) include(${CMAKE_CURRENT_LIST_DIR}/ffd/ffd_sensory.cmake) include(${CMAKE_CURRENT_LIST_DIR}/ffd/ffd_cyberon.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/ffd/ffd_cyberon_i2s_input.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/low_power_ffd/low_power_ffd_sensory.cmake) include(${CMAKE_CURRENT_LIST_DIR}/mic_aggregator/mic_aggregator.cmake) else() diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index b1284ff2..5427e8df 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -56,6 +56,10 @@ + + + + diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c index e7fae7be..53677abf 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c @@ -29,3 +29,7 @@ rtos_i2s_t *i2s_ctx = &i2s_ctx_s; static rtos_uart_tx_t uart_tx_ctx_s; rtos_uart_tx_t *uart_tx_ctx = &uart_tx_ctx_s; +#if appconfRECOVER_MCLK_I2S_APP_PLL +static sw_pll_ctx_t sw_pll_ctx_s; +sw_pll_ctx_t *sw_pll_ctx = &sw_pll_ctx_s; +#endif \ No newline at end of file diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.h b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.h index 496dbfa1..cee3b29f 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.h +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.h @@ -21,6 +21,13 @@ extern "C" { #include "rtos_mic_array.h" +#if appconfRECOVER_MCLK_I2S_APP_PLL +/* Config headers for sw_pll */ +#include "sw_pll.h" +#include "fractions_1000ppm.h" +#include "register_setup_1000ppm.h" +#endif + /* Tile specifiers */ #define FLASH_TILE_NO 0 #define I2C_TILE_NO 0 @@ -55,4 +62,17 @@ extern rtos_i2c_master_t *i2c_master_ctx; extern rtos_i2s_t *i2s_ctx; extern rtos_uart_tx_t *uart_tx_ctx; +#if appconfRECOVER_MCLK_I2S_APP_PLL +typedef struct { + port_t p_mclk_count; // Used for keeping track of MCLK output for sw_pll + port_t p_bclk_count; // Used for keeping track of BCLK input for sw_pll + sw_pll_state_t *sw_pll; // Pointer to sw_pll state (if used) + +}sw_pll_ctx_t; + +static sw_pll_state_t sw_pll = {0}; + +extern sw_pll_ctx_t *sw_pll_ctx; +#endif + #endif /* DRIVER_INSTANCES_H_ */ diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index af3e77fd..772820f7 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -96,6 +96,64 @@ static void i2c_init(void) #endif } +#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL +static int *p_lock_status = NULL; +/// @brief Save the pointer to the pll lock_status variable +static void set_pll_lock_status_ptr(int* p) +{ + p_lock_status = p; +} +#endif + +static void platform_sw_pll_init(void) +{ +#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL + + port_t p_bclk = PORT_I2S_BCLK; + port_t p_mclk = PORT_MCLK; + port_t p_mclk_count = PORT_MCLK_COUNT; // Used internally by sw_pll + port_t p_bclk_count = PORT_BCLK_COUNT; // Used internally by sw_pll + xclock_t ck_bclk = I2S_CLKBLK; + + port_enable(p_mclk); + port_enable(p_bclk); + // NOTE: p_lrclk does not need to be enabled by the caller + + set_pll_lock_status_ptr(&sw_pll.lock_status); + // Create clock from mclk port and use it to clock the p_mclk_count port which will count MCLKs. + port_enable(p_mclk_count); + port_enable(p_bclk_count); + + // Allow p_mclk_count to count mclks + xclock_t clk_mclk = MCLK_CLKBLK; + clock_enable(clk_mclk); + clock_set_source_port(clk_mclk, p_mclk); + port_set_clock(p_mclk_count, clk_mclk); + clock_start(clk_mclk); + + // Allow p_bclk_count to count bclks + port_set_clock(p_bclk_count, ck_bclk); + sw_pll_init(&sw_pll, + SW_PLL_15Q16(0.0), + SW_PLL_15Q16(1.0), + PLL_CONTROL_LOOP_COUNT_INT, + PLL_RATIO, + (appconfBCLK_NOMINAL_HZ / appconfLRCLK_NOMINAL_HZ), + frac_values_90, + SW_PLL_NUM_LUT_ENTRIES(frac_values_90), + APP_PLL_CTL_REG, + APP_PLL_DIV_REG, + SW_PLL_NUM_LUT_ENTRIES(frac_values_90) / 2, + PLL_PPM_RANGE); + + debug_printf("Using SW PLL to track I2S input\n"); + sw_pll_ctx->sw_pll = &sw_pll; + sw_pll_ctx->p_mclk_count = p_mclk_count; + sw_pll_ctx->p_bclk_count = p_bclk_count; + +#endif +} + static void mics_init(void) { #if ON_TILE(MICARRAY_TILE_NO) @@ -109,8 +167,11 @@ static void mics_init(void) static void i2s_init(void) { #if appconfI2S_ENABLED +#if appconfI2S_MODE == appconfI2S_MODE_MASTER static rtos_driver_rpc_t i2s_rpc_config; +#endif #if ON_TILE(I2S_TILE_NO) +#if appconfI2S_MODE == appconfI2S_MODE_MASTER rtos_intertile_t *client_intertile_ctx[1] = {intertile_ctx}; port_t p_i2s_dout[1] = { PORT_I2S_DAC_DATA @@ -137,13 +198,33 @@ static void i2s_init(void) &i2s_rpc_config, client_intertile_ctx, 1); +#elif appconfI2S_MODE == appconfI2S_MODE_SLAVE + port_t p_i2s_dout[1] = { + PORT_I2S_ADC_DATA + }; + port_t p_i2s_din[1] = { + PORT_I2S_DAC_DATA + }; + rtos_i2s_slave_init( + i2s_ctx, + (1 << appconfI2S_IO_CORE), + p_i2s_dout, + 1, + p_i2s_din, + 1, + PORT_I2S_BCLK, + PORT_I2S_LRCLK, + I2S_CLKBLK); +#endif #else +#if appconfI2S_MODE == appconfI2S_MODE_MASTER rtos_i2s_rpc_client_init( i2s_ctx, &i2s_rpc_config, intertile_ctx); #endif #endif +#endif } static void uart_init(void) @@ -166,7 +247,7 @@ void platform_init(chanend_t other_tile_c) { rtos_intertile_init(intertile_ctx, other_tile_c); rtos_intertile_init(intertile_ap_ctx, other_tile_c); - + platform_sw_pll_init(); mclk_init(other_tile_c); gpio_init(); flash_init(); diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index 476003de..f09943a7 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -75,7 +75,9 @@ static void mics_start(void) static void i2s_start(void) { #if appconfI2S_ENABLED +#if appconfI2S_MODE == appconfI2S_MODE_MASTER rtos_i2s_rpc_config(i2s_ctx, appconfI2S_RPC_PORT, appconfI2S_RPC_PRIORITY); +#endif #if ON_TILE(I2S_TILE_NO) if (appconfI2S_AUDIO_SAMPLE_RATE == 3*appconfAUDIO_PIPELINE_SAMPLE_RATE) { diff --git a/examples/ffd/ffd_cyberon_i2s_input.cmake b/examples/ffd/ffd_cyberon_i2s_input.cmake new file mode 100644 index 00000000..ff84458e --- /dev/null +++ b/examples/ffd/ffd_cyberon_i2s_input.cmake @@ -0,0 +1,204 @@ +set(FFD_SRC_ROOT ${CMAKE_CURRENT_LIST_DIR}) + +set(MODEL_LANGUAGE "english_usa") +set(CYBERON_COMMAND_NET_FILE "${FFD_SRC_ROOT}/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap") + +#********************** +# Gather Sources +#********************** +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ) + + +set(APP_INCLUDES + ${CMAKE_CURRENT_LIST_DIR}/src + ${CMAKE_CURRENT_LIST_DIR}/src/gpio_ctrl + ${CMAKE_CURRENT_LIST_DIR}/src/intent_engine + ${CMAKE_CURRENT_LIST_DIR}/src/power +) +set(RTOS_CONF_INCLUDES + ${CMAKE_CURRENT_LIST_DIR}/src/rtos_conf +) + +#********************** +# QSPI Flash Layout +#********************** +set(BOOT_PARTITION_SIZE 0x100000) +set(FILESYSTEM_SIZE_KB 1024) +math(EXPR FILESYSTEM_SIZE_BYTES + "1024 * ${FILESYSTEM_SIZE_KB}" + OUTPUT_FORMAT HEXADECIMAL +) + +set(CALIBRATION_PATTERN_START_ADDRESS ${BOOT_PARTITION_SIZE}) + +math(EXPR FILESYSTEM_START_ADDRESS + "${CALIBRATION_PATTERN_START_ADDRESS} + ${LIB_QSPI_FAST_READ_DEFAULT_CAL_SIZE_BYTES}" + OUTPUT_FORMAT HEXADECIMAL +) + +math(EXPR MODEL_START_ADDRESS + "${FILESYSTEM_START_ADDRESS} + ${FILESYSTEM_SIZE_BYTES}" + OUTPUT_FORMAT HEXADECIMAL +) + +set(CALIBRATION_PATTERN_DATA_PARTITION_OFFSET 0) + +math(EXPR FILESYSTEM_DATA_PARTITION_OFFSET + "${CALIBRATION_PATTERN_DATA_PARTITION_OFFSET} + ${LIB_QSPI_FAST_READ_DEFAULT_CAL_SIZE_BYTES}" + OUTPUT_FORMAT DECIMAL +) + +math(EXPR MODEL_DATA_PARTITION_OFFSET + "${FILESYSTEM_DATA_PARTITION_OFFSET} + ${FILESYSTEM_SIZE_BYTES}" + OUTPUT_FORMAT DECIMAL +) + + +#********************** +# Flags +#********************** +set(APP_COMPILER_FLAGS + -Os + -g + -report + -fxscope + -mcmodel=large + -Wno-xcore-fptrgroup + ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope +) + +set(APP_COMPILE_DEFINITIONS + configENABLE_DEBUG_PRINTF=1 + PLATFORM_USES_TILE_0=1 + PLATFORM_USES_TILE_1=1 + QSPI_FLASH_FILESYSTEM_START_ADDRESS=${FILESYSTEM_START_ADDRESS} + QSPI_FLASH_MODEL_START_ADDRESS=${MODEL_START_ADDRESS} + QSPI_FLASH_CALIBRATION_ADDRESS=${CALIBRATION_PATTERN_START_ADDRESS} + ASR_CYBERON=1 + appconfUSE_I2S_INPUT=1 + appconfAUDIO_PLAYBACK_ENABLED=0 + appconfI2S_MODE=appconfI2S_MODE_SLAVE + appconfI2S_AUDIO_SAMPLE_RATE=48000 + appconfI2S_ENABLED=1 + appconfRECOVER_MCLK_I2S_APP_PLL=1 +) + +set(APP_LINK_OPTIONS + -report + -lotp3 + ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope +) + +set(APP_COMMON_LINK_LIBRARIES + sln_voice::app::ffd::ap + sln_voice::app::asr::Cyberon + sln_voice::app::asr::device_memory + sln_voice::app::asr::gpio_ctrl + sln_voice::app::asr::intent_engine + sln_voice::app::asr::intent_handler + sln_voice::app::ffd::xk_voice_l71 + lib_src + lib_sw_pll +) + +#********************** +# Tile Targets +#********************** +set(TARGET_NAME tile0_example_ffd_cyberon_i2s_input) +add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL) +target_sources(${TARGET_NAME} PUBLIC ${APP_SOURCES}) +target_include_directories(${TARGET_NAME} PUBLIC ${APP_INCLUDES} ${RTOS_CONF_INCLUDES}) +target_compile_definitions(${TARGET_NAME} PUBLIC ${APP_COMPILE_DEFINITIONS} THIS_XCORE_TILE=0) +target_compile_options(${TARGET_NAME} PRIVATE ${APP_COMPILER_FLAGS}) +target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) +target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) +unset(TARGET_NAME) + +set(TARGET_NAME tile1_example_ffd_cyberon_i2s_input) +add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL) +target_sources(${TARGET_NAME} PUBLIC ${APP_SOURCES}) +target_include_directories(${TARGET_NAME} PUBLIC ${APP_INCLUDES} ${RTOS_CONF_INCLUDES}) +target_compile_definitions(${TARGET_NAME} PUBLIC ${APP_COMPILE_DEFINITIONS} THIS_XCORE_TILE=1) +target_compile_options(${TARGET_NAME} PRIVATE ${APP_COMPILER_FLAGS}) +target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) +target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS} ) +unset(TARGET_NAME) + +#********************** +# Merge binaries +#********************** +merge_binaries(example_ffd_cyberon_i2s_input tile0_example_ffd_cyberon_i2s_input tile1_example_ffd_cyberon_i2s_input 1) + +#********************** +# Create run and debug targets +#********************** +create_run_target(example_ffd_cyberon_i2s_input) +create_debug_target(example_ffd_cyberon_i2s_input) + +#********************** +# Create data partition support targets +#********************** +set(TARGET_NAME example_ffd_cyberon_i2s_input) +set(DATA_PARTITION_FILE ${TARGET_NAME}_data_partition.bin) +set(MODEL_FILE ${TARGET_NAME}_model.bin) +set(FATFS_FILE ${TARGET_NAME}_fat.fs) +set(FLASH_CAL_FILE ${LIB_QSPI_FAST_READ_ROOT_PATH}/lib_qspi_fast_read/calibration_pattern_nibble_swap.bin) + +add_custom_target(${MODEL_FILE} ALL + COMMAND ${CMAKE_COMMAND} -E copy ${CYBERON_COMMAND_NET_FILE} ${MODEL_FILE} + COMMENT + "Copy Cyberon NET file" + VERBATIM +) + +create_filesystem_target( + #[[ Target ]] ${TARGET_NAME} + #[[ Input Directory ]] ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/${MODEL_LANGUAGE} + #[[ Image Size ]] ${FILESYSTEM_SIZE_BYTES} +) + +add_custom_command( + OUTPUT ${DATA_PARTITION_FILE} + COMMAND ${CMAKE_COMMAND} -E rm -f ${DATA_PARTITION_FILE} + COMMAND datapartition_mkimage -v -b 1 + -i ${FLASH_CAL_FILE}:${CALIBRATION_PATTERN_DATA_PARTITION_OFFSET} ${FATFS_FILE}:${FILESYSTEM_DATA_PARTITION_OFFSET} ${MODEL_FILE}:${MODEL_DATA_PARTITION_OFFSET} + -o ${DATA_PARTITION_FILE} + DEPENDS + ${MODEL_FILE} + make_fs_${TARGET_NAME} + ${FLASH_CAL_FILE} + COMMENT + "Create data partition" + VERBATIM +) + +set(DATA_PARTITION_FILE_LIST + ${DATA_PARTITION_FILE} + ${MODEL_FILE} + ${FATFS_FILE} + ${FLASH_CAL_FILE} +) + +set(DATA_PARTITION_DEPENDS_LIST + ${DATA_PARTITION_FILE} + ${MODEL_FILE} + make_fs_${TARGET_NAME} +) + +# The list of files to copy and the dependency list for populating +# the data partition folder are identical. +create_data_partition_directory( + #[[ Target ]] ${TARGET_NAME} + #[[ Copy Files ]] "${DATA_PARTITION_FILE_LIST}" + #[[ Dependencies ]] "${DATA_PARTITION_DEPENDS_LIST}" +) + +create_flash_app_target( + #[[ Target ]] ${TARGET_NAME} + #[[ Boot Partition Size ]] ${BOOT_PARTITION_SIZE} + #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} + #[[ Dependencies ]] ${DATA_PARTITION_FILE} +) + +unset(DATA_PARTITION_FILE_LIST) +unset(DATA_PARTITION_DEPENDS_LIST) diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 9696d08e..bfc33fa7 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -11,6 +11,7 @@ #define appconfI2C_MASTER_RPC_PORT 4 #define appconfI2S_RPC_PORT 5 #define appconfINTENT_ENGINE_READY_SYNC_PORT 16 +#define appconfI2S_OUTPUT_SLAVE_PORT 8 /* Application tile specifiers */ #include "platform/driver_instances.h" @@ -31,6 +32,10 @@ #define appconfAUDIO_PLAYBACK_ENABLED 1 #endif +#if appconfUSE_I2S_INPUT==1 && appconfAUDIO_PLAYBACK_ENABLED==1 +#error "I2S audio input cannot be used with audio playback" +#endif + /* Intent Engine Configuration */ #define appconfINTENT_FRAME_BUFFER_MULT (8*2) /* total buffer size is this value * MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME */ #define appconfINTENT_SAMPLE_BLOCK_LENGTH 240 @@ -106,6 +111,9 @@ #define appconfI2S_AUDIO_SAMPLE_RATE appconfAUDIO_PIPELINE_SAMPLE_RATE #endif +#define appconfI2S_MODE_MASTER 0 +#define appconfI2S_MODE_SLAVE 1 + /* I/O and interrupt cores for Tile 0 */ /* I/O and interrupt cores for Tile 1 */ @@ -124,6 +132,17 @@ #define appconfQSPI_FLASH_TASK_PRIORITY (configMAX_PRIORITIES - 1) #define appconfLED_TASK_PRIORITY (configMAX_PRIORITIES / 2 - 1) +/* Software PLL settings for mclk recovery configurations */ +/* see fractions.h and register_setup.h for other pll settings */ +#define appconfLRCLK_NOMINAL_HZ appconfI2S_AUDIO_SAMPLE_RATE +#define appconfBCLK_NOMINAL_HZ (appconfLRCLK_NOMINAL_HZ * 64) +#define PLL_RATIO (MIC_ARRAY_CONFIG_MCLK_FREQ / appconfLRCLK_NOMINAL_HZ) +#define PLL_CONTROL_LOOP_COUNT_INT 512 // How many refclk ticks (LRCLK) per control loop iteration. Aim for ~100Hz +#define PLL_PPM_RANGE 1000 // Max allowable diff in clk count. For the PID constants we + // have chosen, this number should be larger than the number + // of elements in the look up table as the clk count diff is + // added to the LUT index with a multiplier of 1. Only used for INT mclkless + #include "app_conf_check.h" #endif /* APP_CONF_H_ */ diff --git a/examples/ffd/src/fractions_1000ppm.h b/examples/ffd/src/fractions_1000ppm.h new file mode 100644 index 00000000..9c9ed1b8 --- /dev/null +++ b/examples/ffd/src/fractions_1000ppm.h @@ -0,0 +1,796 @@ +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +// Header file listing fraction options searched +// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. +static short frac_values_90[790] = { +0x1832, // Index: 0 Fraction: 25/51 = 0.4902 +0x1934, // Index: 1 Fraction: 26/53 = 0.4906 +0x1A36, // Index: 2 Fraction: 27/55 = 0.4909 +0x1B38, // Index: 3 Fraction: 28/57 = 0.4912 +0x1C3A, // Index: 4 Fraction: 29/59 = 0.4915 +0x1D3C, // Index: 5 Fraction: 30/61 = 0.4918 +0x1E3E, // Index: 6 Fraction: 31/63 = 0.4921 +0x1F40, // Index: 7 Fraction: 32/65 = 0.4923 +0x2042, // Index: 8 Fraction: 33/67 = 0.4925 +0x2144, // Index: 9 Fraction: 34/69 = 0.4928 +0x2246, // Index: 10 Fraction: 35/71 = 0.4930 +0x2348, // Index: 11 Fraction: 36/73 = 0.4932 +0x244A, // Index: 12 Fraction: 37/75 = 0.4933 +0x254C, // Index: 13 Fraction: 38/77 = 0.4935 +0x264E, // Index: 14 Fraction: 39/79 = 0.4937 +0x2750, // Index: 15 Fraction: 40/81 = 0.4938 +0x2852, // Index: 16 Fraction: 41/83 = 0.4940 +0x2954, // Index: 17 Fraction: 42/85 = 0.4941 +0x2A56, // Index: 18 Fraction: 43/87 = 0.4943 +0x2B58, // Index: 19 Fraction: 44/89 = 0.4944 +0x0001, // Index: 20 Fraction: 1/2 = 0.5000 +0x2C58, // Index: 21 Fraction: 45/89 = 0.5056 +0x2B56, // Index: 22 Fraction: 44/87 = 0.5057 +0x2A54, // Index: 23 Fraction: 43/85 = 0.5059 +0x2952, // Index: 24 Fraction: 42/83 = 0.5060 +0x2850, // Index: 25 Fraction: 41/81 = 0.5062 +0x274E, // Index: 26 Fraction: 40/79 = 0.5063 +0x264C, // Index: 27 Fraction: 39/77 = 0.5065 +0x254A, // Index: 28 Fraction: 38/75 = 0.5067 +0x2448, // Index: 29 Fraction: 37/73 = 0.5068 +0x2346, // Index: 30 Fraction: 36/71 = 0.5070 +0x2244, // Index: 31 Fraction: 35/69 = 0.5072 +0x2142, // Index: 32 Fraction: 34/67 = 0.5075 +0x2040, // Index: 33 Fraction: 33/65 = 0.5077 +0x1F3E, // Index: 34 Fraction: 32/63 = 0.5079 +0x1E3C, // Index: 35 Fraction: 31/61 = 0.5082 +0x1D3A, // Index: 36 Fraction: 30/59 = 0.5085 +0x1C38, // Index: 37 Fraction: 29/57 = 0.5088 +0x1B36, // Index: 38 Fraction: 28/55 = 0.5091 +0x1A34, // Index: 39 Fraction: 27/53 = 0.5094 +0x1932, // Index: 40 Fraction: 26/51 = 0.5098 +0x1830, // Index: 41 Fraction: 25/49 = 0.5102 +0x172E, // Index: 42 Fraction: 24/47 = 0.5106 +0x162C, // Index: 43 Fraction: 23/45 = 0.5111 +0x2C57, // Index: 44 Fraction: 45/88 = 0.5114 +0x152A, // Index: 45 Fraction: 22/43 = 0.5116 +0x2A53, // Index: 46 Fraction: 43/84 = 0.5119 +0x1428, // Index: 47 Fraction: 21/41 = 0.5122 +0x284F, // Index: 48 Fraction: 41/80 = 0.5125 +0x1326, // Index: 49 Fraction: 20/39 = 0.5128 +0x264B, // Index: 50 Fraction: 39/76 = 0.5132 +0x1224, // Index: 51 Fraction: 19/37 = 0.5135 +0x2447, // Index: 52 Fraction: 37/72 = 0.5139 +0x1122, // Index: 53 Fraction: 18/35 = 0.5143 +0x2243, // Index: 54 Fraction: 35/68 = 0.5147 +0x1020, // Index: 55 Fraction: 17/33 = 0.5152 +0x203F, // Index: 56 Fraction: 33/64 = 0.5156 +0x0F1E, // Index: 57 Fraction: 16/31 = 0.5161 +0x1E3B, // Index: 58 Fraction: 31/60 = 0.5167 +0x2D58, // Index: 59 Fraction: 46/89 = 0.5169 +0x0E1C, // Index: 60 Fraction: 15/29 = 0.5172 +0x2B54, // Index: 61 Fraction: 44/85 = 0.5176 +0x1C37, // Index: 62 Fraction: 29/56 = 0.5179 +0x2A52, // Index: 63 Fraction: 43/83 = 0.5181 +0x0D1A, // Index: 64 Fraction: 14/27 = 0.5185 +0x284E, // Index: 65 Fraction: 41/79 = 0.5190 +0x1A33, // Index: 66 Fraction: 27/52 = 0.5192 +0x274C, // Index: 67 Fraction: 40/77 = 0.5195 +0x0C18, // Index: 68 Fraction: 13/25 = 0.5200 +0x2548, // Index: 69 Fraction: 38/73 = 0.5205 +0x182F, // Index: 70 Fraction: 25/48 = 0.5208 +0x2446, // Index: 71 Fraction: 37/71 = 0.5211 +0x0B16, // Index: 72 Fraction: 12/23 = 0.5217 +0x2E59, // Index: 73 Fraction: 47/90 = 0.5222 +0x2242, // Index: 74 Fraction: 35/67 = 0.5224 +0x162B, // Index: 75 Fraction: 23/44 = 0.5227 +0x2140, // Index: 76 Fraction: 34/65 = 0.5231 +0x2C55, // Index: 77 Fraction: 45/86 = 0.5233 +0x0A14, // Index: 78 Fraction: 11/21 = 0.5238 +0x2A51, // Index: 79 Fraction: 43/82 = 0.5244 +0x1F3C, // Index: 80 Fraction: 32/61 = 0.5246 +0x1427, // Index: 81 Fraction: 21/40 = 0.5250 +0x1E3A, // Index: 82 Fraction: 31/59 = 0.5254 +0x284D, // Index: 83 Fraction: 41/78 = 0.5256 +0x0912, // Index: 84 Fraction: 10/19 = 0.5263 +0x2649, // Index: 85 Fraction: 39/74 = 0.5270 +0x1C36, // Index: 86 Fraction: 29/55 = 0.5273 +0x1223, // Index: 87 Fraction: 19/36 = 0.5278 +0x2E58, // Index: 88 Fraction: 47/89 = 0.5281 +0x1B34, // Index: 89 Fraction: 28/53 = 0.5283 +0x2445, // Index: 90 Fraction: 37/70 = 0.5286 +0x2D56, // Index: 91 Fraction: 46/87 = 0.5287 +0x0810, // Index: 92 Fraction: 9/17 = 0.5294 +0x2B52, // Index: 93 Fraction: 44/83 = 0.5301 +0x2241, // Index: 94 Fraction: 35/66 = 0.5303 +0x1930, // Index: 95 Fraction: 26/49 = 0.5306 +0x2A50, // Index: 96 Fraction: 43/81 = 0.5309 +0x101F, // Index: 97 Fraction: 17/32 = 0.5312 +0x294E, // Index: 98 Fraction: 42/79 = 0.5316 +0x182E, // Index: 99 Fraction: 25/47 = 0.5319 +0x203D, // Index: 100 Fraction: 33/62 = 0.5323 +0x284C, // Index: 101 Fraction: 41/77 = 0.5325 +0x070E, // Index: 102 Fraction: 8/15 = 0.5333 +0x2E57, // Index: 103 Fraction: 47/88 = 0.5341 +0x2648, // Index: 104 Fraction: 39/73 = 0.5342 +0x1E39, // Index: 105 Fraction: 31/58 = 0.5345 +0x162A, // Index: 106 Fraction: 23/43 = 0.5349 +0x2546, // Index: 107 Fraction: 38/71 = 0.5352 +0x0E1B, // Index: 108 Fraction: 15/28 = 0.5357 +0x2444, // Index: 109 Fraction: 37/69 = 0.5362 +0x1528, // Index: 110 Fraction: 22/41 = 0.5366 +0x1C35, // Index: 111 Fraction: 29/54 = 0.5370 +0x2342, // Index: 112 Fraction: 36/67 = 0.5373 +0x2A4F, // Index: 113 Fraction: 43/80 = 0.5375 +0x060C, // Index: 114 Fraction: 7/13 = 0.5385 +0x2F58, // Index: 115 Fraction: 48/89 = 0.5393 +0x284B, // Index: 116 Fraction: 41/76 = 0.5395 +0x213E, // Index: 117 Fraction: 34/63 = 0.5397 +0x1A31, // Index: 118 Fraction: 27/50 = 0.5400 +0x2E56, // Index: 119 Fraction: 47/87 = 0.5402 +0x1324, // Index: 120 Fraction: 20/37 = 0.5405 +0x203C, // Index: 121 Fraction: 33/61 = 0.5410 +0x2D54, // Index: 122 Fraction: 46/85 = 0.5412 +0x0C17, // Index: 123 Fraction: 13/24 = 0.5417 +0x2C52, // Index: 124 Fraction: 45/83 = 0.5422 +0x1F3A, // Index: 125 Fraction: 32/59 = 0.5424 +0x1222, // Index: 126 Fraction: 19/35 = 0.5429 +0x2B50, // Index: 127 Fraction: 44/81 = 0.5432 +0x182D, // Index: 128 Fraction: 25/46 = 0.5435 +0x1E38, // Index: 129 Fraction: 31/57 = 0.5439 +0x2443, // Index: 130 Fraction: 37/68 = 0.5441 +0x2A4E, // Index: 131 Fraction: 43/79 = 0.5443 +0x3059, // Index: 132 Fraction: 49/90 = 0.5444 +0x050A, // Index: 133 Fraction: 6/11 = 0.5455 +0x2E55, // Index: 134 Fraction: 47/86 = 0.5465 +0x284A, // Index: 135 Fraction: 41/75 = 0.5467 +0x223F, // Index: 136 Fraction: 35/64 = 0.5469 +0x1C34, // Index: 137 Fraction: 29/53 = 0.5472 +0x1629, // Index: 138 Fraction: 23/42 = 0.5476 +0x2748, // Index: 139 Fraction: 40/73 = 0.5479 +0x101E, // Index: 140 Fraction: 17/31 = 0.5484 +0x2C51, // Index: 141 Fraction: 45/82 = 0.5488 +0x1B32, // Index: 142 Fraction: 28/51 = 0.5490 +0x2646, // Index: 143 Fraction: 39/71 = 0.5493 +0x0A13, // Index: 144 Fraction: 11/20 = 0.5500 +0x3058, // Index: 145 Fraction: 49/89 = 0.5506 +0x2544, // Index: 146 Fraction: 38/69 = 0.5507 +0x1A30, // Index: 147 Fraction: 27/49 = 0.5510 +0x2A4D, // Index: 148 Fraction: 43/78 = 0.5513 +0x0F1C, // Index: 149 Fraction: 16/29 = 0.5517 +0x2442, // Index: 150 Fraction: 37/67 = 0.5522 +0x1425, // Index: 151 Fraction: 21/38 = 0.5526 +0x2E54, // Index: 152 Fraction: 47/85 = 0.5529 +0x192E, // Index: 153 Fraction: 26/47 = 0.5532 +0x1E37, // Index: 154 Fraction: 31/56 = 0.5536 +0x2340, // Index: 155 Fraction: 36/65 = 0.5538 +0x2849, // Index: 156 Fraction: 41/74 = 0.5541 +0x2D52, // Index: 157 Fraction: 46/83 = 0.5542 +0x0408, // Index: 158 Fraction: 5/9 = 0.5556 +0x3057, // Index: 159 Fraction: 49/88 = 0.5568 +0x2B4E, // Index: 160 Fraction: 44/79 = 0.5570 +0x2645, // Index: 161 Fraction: 39/70 = 0.5571 +0x213C, // Index: 162 Fraction: 34/61 = 0.5574 +0x1C33, // Index: 163 Fraction: 29/52 = 0.5577 +0x172A, // Index: 164 Fraction: 24/43 = 0.5581 +0x2A4C, // Index: 165 Fraction: 43/77 = 0.5584 +0x1221, // Index: 166 Fraction: 19/34 = 0.5588 +0x203A, // Index: 167 Fraction: 33/59 = 0.5593 +0x2E53, // Index: 168 Fraction: 47/84 = 0.5595 +0x0D18, // Index: 169 Fraction: 14/25 = 0.5600 +0x2441, // Index: 170 Fraction: 37/66 = 0.5606 +0x1628, // Index: 171 Fraction: 23/41 = 0.5610 +0x1F38, // Index: 172 Fraction: 32/57 = 0.5614 +0x2848, // Index: 173 Fraction: 41/73 = 0.5616 +0x3158, // Index: 174 Fraction: 50/89 = 0.5618 +0x080F, // Index: 175 Fraction: 9/16 = 0.5625 +0x3056, // Index: 176 Fraction: 49/87 = 0.5632 +0x2746, // Index: 177 Fraction: 40/71 = 0.5634 +0x1E36, // Index: 178 Fraction: 31/55 = 0.5636 +0x1526, // Index: 179 Fraction: 22/39 = 0.5641 +0x223D, // Index: 180 Fraction: 35/62 = 0.5645 +0x2F54, // Index: 181 Fraction: 48/85 = 0.5647 +0x0C16, // Index: 182 Fraction: 13/23 = 0.5652 +0x2A4B, // Index: 183 Fraction: 43/76 = 0.5658 +0x1D34, // Index: 184 Fraction: 30/53 = 0.5660 +0x2E52, // Index: 185 Fraction: 47/83 = 0.5663 +0x101D, // Index: 186 Fraction: 17/30 = 0.5667 +0x2542, // Index: 187 Fraction: 38/67 = 0.5672 +0x1424, // Index: 188 Fraction: 21/37 = 0.5676 +0x2D50, // Index: 189 Fraction: 46/81 = 0.5679 +0x182B, // Index: 190 Fraction: 25/44 = 0.5682 +0x1C32, // Index: 191 Fraction: 29/51 = 0.5686 +0x2039, // Index: 192 Fraction: 33/58 = 0.5690 +0x2440, // Index: 193 Fraction: 37/65 = 0.5692 +0x2847, // Index: 194 Fraction: 41/72 = 0.5694 +0x2C4E, // Index: 195 Fraction: 45/79 = 0.5696 +0x3055, // Index: 196 Fraction: 49/86 = 0.5698 +0x0306, // Index: 197 Fraction: 4/7 = 0.5714 +0x3258, // Index: 198 Fraction: 51/89 = 0.5730 +0x2E51, // Index: 199 Fraction: 47/82 = 0.5732 +0x2A4A, // Index: 200 Fraction: 43/75 = 0.5733 +0x2643, // Index: 201 Fraction: 39/68 = 0.5735 +0x223C, // Index: 202 Fraction: 35/61 = 0.5738 +0x1E35, // Index: 203 Fraction: 31/54 = 0.5741 +0x1A2E, // Index: 204 Fraction: 27/47 = 0.5745 +0x3156, // Index: 205 Fraction: 50/87 = 0.5747 +0x1627, // Index: 206 Fraction: 23/40 = 0.5750 +0x2948, // Index: 207 Fraction: 42/73 = 0.5753 +0x1220, // Index: 208 Fraction: 19/33 = 0.5758 +0x213A, // Index: 209 Fraction: 34/59 = 0.5763 +0x3054, // Index: 210 Fraction: 49/85 = 0.5765 +0x0E19, // Index: 211 Fraction: 15/26 = 0.5769 +0x2846, // Index: 212 Fraction: 41/71 = 0.5775 +0x192C, // Index: 213 Fraction: 26/45 = 0.5778 +0x243F, // Index: 214 Fraction: 37/64 = 0.5781 +0x2F52, // Index: 215 Fraction: 48/83 = 0.5783 +0x0A12, // Index: 216 Fraction: 11/19 = 0.5789 +0x3257, // Index: 217 Fraction: 51/88 = 0.5795 +0x2744, // Index: 218 Fraction: 40/69 = 0.5797 +0x1C31, // Index: 219 Fraction: 29/50 = 0.5800 +0x2E50, // Index: 220 Fraction: 47/81 = 0.5802 +0x111E, // Index: 221 Fraction: 18/31 = 0.5806 +0x2A49, // Index: 222 Fraction: 43/74 = 0.5811 +0x182A, // Index: 223 Fraction: 25/43 = 0.5814 +0x1F36, // Index: 224 Fraction: 32/55 = 0.5818 +0x2642, // Index: 225 Fraction: 39/67 = 0.5821 +0x2D4E, // Index: 226 Fraction: 46/79 = 0.5823 +0x060B, // Index: 227 Fraction: 7/12 = 0.5833 +0x3358, // Index: 228 Fraction: 52/89 = 0.5843 +0x2C4C, // Index: 229 Fraction: 45/77 = 0.5844 +0x2540, // Index: 230 Fraction: 38/65 = 0.5846 +0x1E34, // Index: 231 Fraction: 31/53 = 0.5849 +0x1728, // Index: 232 Fraction: 24/41 = 0.5854 +0x2845, // Index: 233 Fraction: 41/70 = 0.5857 +0x101C, // Index: 234 Fraction: 17/29 = 0.5862 +0x2B4A, // Index: 235 Fraction: 44/75 = 0.5867 +0x1A2D, // Index: 236 Fraction: 27/46 = 0.5870 +0x243E, // Index: 237 Fraction: 37/63 = 0.5873 +0x2E4F, // Index: 238 Fraction: 47/80 = 0.5875 +0x0910, // Index: 239 Fraction: 10/17 = 0.5882 +0x3459, // Index: 240 Fraction: 53/90 = 0.5889 +0x2A48, // Index: 241 Fraction: 43/73 = 0.5890 +0x2037, // Index: 242 Fraction: 33/56 = 0.5893 +0x1626, // Index: 243 Fraction: 23/39 = 0.5897 +0x233C, // Index: 244 Fraction: 36/61 = 0.5902 +0x3052, // Index: 245 Fraction: 49/83 = 0.5904 +0x0C15, // Index: 246 Fraction: 13/22 = 0.5909 +0x2946, // Index: 247 Fraction: 42/71 = 0.5915 +0x1C30, // Index: 248 Fraction: 29/49 = 0.5918 +0x2C4B, // Index: 249 Fraction: 45/76 = 0.5921 +0x0F1A, // Index: 250 Fraction: 16/27 = 0.5926 +0x3255, // Index: 251 Fraction: 51/86 = 0.5930 +0x223A, // Index: 252 Fraction: 35/59 = 0.5932 +0x121F, // Index: 253 Fraction: 19/32 = 0.5938 +0x2844, // Index: 254 Fraction: 41/69 = 0.5942 +0x1524, // Index: 255 Fraction: 22/37 = 0.5946 +0x2E4E, // Index: 256 Fraction: 47/79 = 0.5949 +0x1829, // Index: 257 Fraction: 25/42 = 0.5952 +0x3458, // Index: 258 Fraction: 53/89 = 0.5955 +0x1B2E, // Index: 259 Fraction: 28/47 = 0.5957 +0x1E33, // Index: 260 Fraction: 31/52 = 0.5962 +0x2138, // Index: 261 Fraction: 34/57 = 0.5965 +0x243D, // Index: 262 Fraction: 37/62 = 0.5968 +0x2742, // Index: 263 Fraction: 40/67 = 0.5970 +0x2A47, // Index: 264 Fraction: 43/72 = 0.5972 +0x2D4C, // Index: 265 Fraction: 46/77 = 0.5974 +0x3051, // Index: 266 Fraction: 49/82 = 0.5976 +0x3356, // Index: 267 Fraction: 52/87 = 0.5977 +0x0204, // Index: 268 Fraction: 3/5 = 0.6000 +0x3457, // Index: 269 Fraction: 53/88 = 0.6023 +0x3152, // Index: 270 Fraction: 50/83 = 0.6024 +0x2E4D, // Index: 271 Fraction: 47/78 = 0.6026 +0x2B48, // Index: 272 Fraction: 44/73 = 0.6027 +0x2843, // Index: 273 Fraction: 41/68 = 0.6029 +0x253E, // Index: 274 Fraction: 38/63 = 0.6032 +0x2239, // Index: 275 Fraction: 35/58 = 0.6034 +0x1F34, // Index: 276 Fraction: 32/53 = 0.6038 +0x1C2F, // Index: 277 Fraction: 29/48 = 0.6042 +0x192A, // Index: 278 Fraction: 26/43 = 0.6047 +0x3050, // Index: 279 Fraction: 49/81 = 0.6049 +0x1625, // Index: 280 Fraction: 23/38 = 0.6053 +0x2A46, // Index: 281 Fraction: 43/71 = 0.6056 +0x1320, // Index: 282 Fraction: 20/33 = 0.6061 +0x243C, // Index: 283 Fraction: 37/61 = 0.6066 +0x3558, // Index: 284 Fraction: 54/89 = 0.6067 +0x101B, // Index: 285 Fraction: 17/28 = 0.6071 +0x2F4E, // Index: 286 Fraction: 48/79 = 0.6076 +0x1E32, // Index: 287 Fraction: 31/51 = 0.6078 +0x2C49, // Index: 288 Fraction: 45/74 = 0.6081 +0x0D16, // Index: 289 Fraction: 14/23 = 0.6087 +0x3456, // Index: 290 Fraction: 53/87 = 0.6092 +0x263F, // Index: 291 Fraction: 39/64 = 0.6094 +0x1828, // Index: 292 Fraction: 25/41 = 0.6098 +0x233A, // Index: 293 Fraction: 36/59 = 0.6102 +0x2E4C, // Index: 294 Fraction: 47/77 = 0.6104 +0x0A11, // Index: 295 Fraction: 11/18 = 0.6111 +0x3354, // Index: 296 Fraction: 52/85 = 0.6118 +0x2842, // Index: 297 Fraction: 41/67 = 0.6119 +0x1D30, // Index: 298 Fraction: 30/49 = 0.6122 +0x304F, // Index: 299 Fraction: 49/80 = 0.6125 +0x121E, // Index: 300 Fraction: 19/31 = 0.6129 +0x2D4A, // Index: 301 Fraction: 46/75 = 0.6133 +0x1A2B, // Index: 302 Fraction: 27/44 = 0.6136 +0x2238, // Index: 303 Fraction: 35/57 = 0.6140 +0x2A45, // Index: 304 Fraction: 43/70 = 0.6143 +0x3252, // Index: 305 Fraction: 51/83 = 0.6145 +0x070C, // Index: 306 Fraction: 8/13 = 0.6154 +0x3455, // Index: 307 Fraction: 53/86 = 0.6163 +0x2C48, // Index: 308 Fraction: 45/73 = 0.6164 +0x243B, // Index: 309 Fraction: 37/60 = 0.6167 +0x1C2E, // Index: 310 Fraction: 29/47 = 0.6170 +0x3150, // Index: 311 Fraction: 50/81 = 0.6173 +0x1421, // Index: 312 Fraction: 21/34 = 0.6176 +0x3658, // Index: 313 Fraction: 55/89 = 0.6180 +0x2136, // Index: 314 Fraction: 34/55 = 0.6182 +0x2E4B, // Index: 315 Fraction: 47/76 = 0.6184 +0x0C14, // Index: 316 Fraction: 13/21 = 0.6190 +0x2B46, // Index: 317 Fraction: 44/71 = 0.6197 +0x1E31, // Index: 318 Fraction: 31/50 = 0.6200 +0x304E, // Index: 319 Fraction: 49/79 = 0.6203 +0x111C, // Index: 320 Fraction: 18/29 = 0.6207 +0x2841, // Index: 321 Fraction: 41/66 = 0.6212 +0x1624, // Index: 322 Fraction: 23/37 = 0.6216 +0x3251, // Index: 323 Fraction: 51/82 = 0.6220 +0x1B2C, // Index: 324 Fraction: 28/45 = 0.6222 +0x2034, // Index: 325 Fraction: 33/53 = 0.6226 +0x253C, // Index: 326 Fraction: 38/61 = 0.6230 +0x2A44, // Index: 327 Fraction: 43/69 = 0.6232 +0x2F4C, // Index: 328 Fraction: 48/77 = 0.6234 +0x3454, // Index: 329 Fraction: 53/85 = 0.6235 +0x0407, // Index: 330 Fraction: 5/8 = 0.6250 +0x3352, // Index: 331 Fraction: 52/83 = 0.6265 +0x2E4A, // Index: 332 Fraction: 47/75 = 0.6267 +0x2942, // Index: 333 Fraction: 42/67 = 0.6269 +0x243A, // Index: 334 Fraction: 37/59 = 0.6271 +0x1F32, // Index: 335 Fraction: 32/51 = 0.6275 +0x1A2A, // Index: 336 Fraction: 27/43 = 0.6279 +0x304D, // Index: 337 Fraction: 49/78 = 0.6282 +0x1522, // Index: 338 Fraction: 22/35 = 0.6286 +0x263D, // Index: 339 Fraction: 39/62 = 0.6290 +0x3758, // Index: 340 Fraction: 56/89 = 0.6292 +0x101A, // Index: 341 Fraction: 17/27 = 0.6296 +0x2D48, // Index: 342 Fraction: 46/73 = 0.6301 +0x1C2D, // Index: 343 Fraction: 29/46 = 0.6304 +0x2840, // Index: 344 Fraction: 41/65 = 0.6308 +0x3453, // Index: 345 Fraction: 53/84 = 0.6310 +0x0B12, // Index: 346 Fraction: 12/19 = 0.6316 +0x3656, // Index: 347 Fraction: 55/87 = 0.6322 +0x2A43, // Index: 348 Fraction: 43/68 = 0.6324 +0x1E30, // Index: 349 Fraction: 31/49 = 0.6327 +0x314E, // Index: 350 Fraction: 50/79 = 0.6329 +0x121D, // Index: 351 Fraction: 19/30 = 0.6333 +0x2C46, // Index: 352 Fraction: 45/71 = 0.6338 +0x1928, // Index: 353 Fraction: 26/41 = 0.6341 +0x2033, // Index: 354 Fraction: 33/52 = 0.6346 +0x273E, // Index: 355 Fraction: 40/63 = 0.6349 +0x2E49, // Index: 356 Fraction: 47/74 = 0.6351 +0x3554, // Index: 357 Fraction: 54/85 = 0.6353 +0x060A, // Index: 358 Fraction: 7/11 = 0.6364 +0x324F, // Index: 359 Fraction: 51/80 = 0.6375 +0x2B44, // Index: 360 Fraction: 44/69 = 0.6377 +0x2439, // Index: 361 Fraction: 37/58 = 0.6379 +0x1D2E, // Index: 362 Fraction: 30/47 = 0.6383 +0x3452, // Index: 363 Fraction: 53/83 = 0.6386 +0x1623, // Index: 364 Fraction: 23/36 = 0.6389 +0x263C, // Index: 365 Fraction: 39/61 = 0.6393 +0x3655, // Index: 366 Fraction: 55/86 = 0.6395 +0x0F18, // Index: 367 Fraction: 16/25 = 0.6400 +0x3858, // Index: 368 Fraction: 57/89 = 0.6404 +0x283F, // Index: 369 Fraction: 41/64 = 0.6406 +0x1826, // Index: 370 Fraction: 25/39 = 0.6410 +0x2134, // Index: 371 Fraction: 34/53 = 0.6415 +0x2A42, // Index: 372 Fraction: 43/67 = 0.6418 +0x3350, // Index: 373 Fraction: 52/81 = 0.6420 +0x080D, // Index: 374 Fraction: 9/14 = 0.6429 +0x3756, // Index: 375 Fraction: 56/87 = 0.6437 +0x2E48, // Index: 376 Fraction: 47/73 = 0.6438 +0x253A, // Index: 377 Fraction: 38/59 = 0.6441 +0x1C2C, // Index: 378 Fraction: 29/45 = 0.6444 +0x304B, // Index: 379 Fraction: 49/76 = 0.6447 +0x131E, // Index: 380 Fraction: 20/31 = 0.6452 +0x324E, // Index: 381 Fraction: 51/79 = 0.6456 +0x1E2F, // Index: 382 Fraction: 31/48 = 0.6458 +0x2940, // Index: 383 Fraction: 42/65 = 0.6462 +0x3451, // Index: 384 Fraction: 53/82 = 0.6463 +0x0A10, // Index: 385 Fraction: 11/17 = 0.6471 +0x3857, // Index: 386 Fraction: 57/88 = 0.6477 +0x2D46, // Index: 387 Fraction: 46/71 = 0.6479 +0x2235, // Index: 388 Fraction: 35/54 = 0.6481 +0x1724, // Index: 389 Fraction: 24/37 = 0.6486 +0x2438, // Index: 390 Fraction: 37/57 = 0.6491 +0x314C, // Index: 391 Fraction: 50/77 = 0.6494 +0x0C13, // Index: 392 Fraction: 13/20 = 0.6500 +0x3552, // Index: 393 Fraction: 54/83 = 0.6506 +0x283E, // Index: 394 Fraction: 41/63 = 0.6508 +0x1B2A, // Index: 395 Fraction: 28/43 = 0.6512 +0x2A41, // Index: 396 Fraction: 43/66 = 0.6515 +0x3958, // Index: 397 Fraction: 58/89 = 0.6517 +0x0E16, // Index: 398 Fraction: 15/23 = 0.6522 +0x2E47, // Index: 399 Fraction: 47/72 = 0.6528 +0x1F30, // Index: 400 Fraction: 32/49 = 0.6531 +0x304A, // Index: 401 Fraction: 49/75 = 0.6533 +0x1019, // Index: 402 Fraction: 17/26 = 0.6538 +0x3450, // Index: 403 Fraction: 53/81 = 0.6543 +0x2336, // Index: 404 Fraction: 36/55 = 0.6545 +0x3653, // Index: 405 Fraction: 55/84 = 0.6548 +0x121C, // Index: 406 Fraction: 19/29 = 0.6552 +0x3A59, // Index: 407 Fraction: 59/90 = 0.6556 +0x273C, // Index: 408 Fraction: 40/61 = 0.6557 +0x141F, // Index: 409 Fraction: 21/32 = 0.6562 +0x2B42, // Index: 410 Fraction: 44/67 = 0.6567 +0x1622, // Index: 411 Fraction: 23/35 = 0.6571 +0x2F48, // Index: 412 Fraction: 48/73 = 0.6575 +0x1825, // Index: 413 Fraction: 25/38 = 0.6579 +0x334E, // Index: 414 Fraction: 52/79 = 0.6582 +0x1A28, // Index: 415 Fraction: 27/41 = 0.6585 +0x3754, // Index: 416 Fraction: 56/85 = 0.6588 +0x1C2B, // Index: 417 Fraction: 29/44 = 0.6591 +0x1E2E, // Index: 418 Fraction: 31/47 = 0.6596 +0x2031, // Index: 419 Fraction: 33/50 = 0.6600 +0x2234, // Index: 420 Fraction: 35/53 = 0.6604 +0x2437, // Index: 421 Fraction: 37/56 = 0.6607 +0x263A, // Index: 422 Fraction: 39/59 = 0.6610 +0x283D, // Index: 423 Fraction: 41/62 = 0.6613 +0x2A40, // Index: 424 Fraction: 43/65 = 0.6615 +0x2C43, // Index: 425 Fraction: 45/68 = 0.6618 +0x2E46, // Index: 426 Fraction: 47/71 = 0.6620 +0x3049, // Index: 427 Fraction: 49/74 = 0.6622 +0x324C, // Index: 428 Fraction: 51/77 = 0.6623 +0x344F, // Index: 429 Fraction: 53/80 = 0.6625 +0x3652, // Index: 430 Fraction: 55/83 = 0.6627 +0x3855, // Index: 431 Fraction: 57/86 = 0.6628 +0x3A58, // Index: 432 Fraction: 59/89 = 0.6629 +0x0102, // Index: 433 Fraction: 2/3 = 0.6667 +0x3A57, // Index: 434 Fraction: 59/88 = 0.6705 +0x3854, // Index: 435 Fraction: 57/85 = 0.6706 +0x3651, // Index: 436 Fraction: 55/82 = 0.6707 +0x344E, // Index: 437 Fraction: 53/79 = 0.6709 +0x324B, // Index: 438 Fraction: 51/76 = 0.6711 +0x3048, // Index: 439 Fraction: 49/73 = 0.6712 +0x2E45, // Index: 440 Fraction: 47/70 = 0.6714 +0x2C42, // Index: 441 Fraction: 45/67 = 0.6716 +0x2A3F, // Index: 442 Fraction: 43/64 = 0.6719 +0x283C, // Index: 443 Fraction: 41/61 = 0.6721 +0x2639, // Index: 444 Fraction: 39/58 = 0.6724 +0x2436, // Index: 445 Fraction: 37/55 = 0.6727 +0x2233, // Index: 446 Fraction: 35/52 = 0.6731 +0x2030, // Index: 447 Fraction: 33/49 = 0.6735 +0x1E2D, // Index: 448 Fraction: 31/46 = 0.6739 +0x3B58, // Index: 449 Fraction: 60/89 = 0.6742 +0x1C2A, // Index: 450 Fraction: 29/43 = 0.6744 +0x3752, // Index: 451 Fraction: 56/83 = 0.6747 +0x1A27, // Index: 452 Fraction: 27/40 = 0.6750 +0x334C, // Index: 453 Fraction: 52/77 = 0.6753 +0x1824, // Index: 454 Fraction: 25/37 = 0.6757 +0x2F46, // Index: 455 Fraction: 48/71 = 0.6761 +0x1621, // Index: 456 Fraction: 23/34 = 0.6765 +0x2B40, // Index: 457 Fraction: 44/65 = 0.6769 +0x141E, // Index: 458 Fraction: 21/31 = 0.6774 +0x3C59, // Index: 459 Fraction: 61/90 = 0.6778 +0x273A, // Index: 460 Fraction: 40/59 = 0.6780 +0x3A56, // Index: 461 Fraction: 59/87 = 0.6782 +0x121B, // Index: 462 Fraction: 19/28 = 0.6786 +0x3650, // Index: 463 Fraction: 55/81 = 0.6790 +0x2334, // Index: 464 Fraction: 36/53 = 0.6792 +0x344D, // Index: 465 Fraction: 53/78 = 0.6795 +0x1018, // Index: 466 Fraction: 17/25 = 0.6800 +0x3047, // Index: 467 Fraction: 49/72 = 0.6806 +0x1F2E, // Index: 468 Fraction: 32/47 = 0.6809 +0x2E44, // Index: 469 Fraction: 47/69 = 0.6812 +0x0E15, // Index: 470 Fraction: 15/22 = 0.6818 +0x3954, // Index: 471 Fraction: 58/85 = 0.6824 +0x2A3E, // Index: 472 Fraction: 43/63 = 0.6825 +0x1B28, // Index: 473 Fraction: 28/41 = 0.6829 +0x283B, // Index: 474 Fraction: 41/60 = 0.6833 +0x354E, // Index: 475 Fraction: 54/79 = 0.6835 +0x0C12, // Index: 476 Fraction: 13/19 = 0.6842 +0x3148, // Index: 477 Fraction: 50/73 = 0.6849 +0x2435, // Index: 478 Fraction: 37/54 = 0.6852 +0x3C58, // Index: 479 Fraction: 61/89 = 0.6854 +0x1722, // Index: 480 Fraction: 24/35 = 0.6857 +0x3A55, // Index: 481 Fraction: 59/86 = 0.6860 +0x2232, // Index: 482 Fraction: 35/51 = 0.6863 +0x2D42, // Index: 483 Fraction: 46/67 = 0.6866 +0x3852, // Index: 484 Fraction: 57/83 = 0.6867 +0x0A0F, // Index: 485 Fraction: 11/16 = 0.6875 +0x344C, // Index: 486 Fraction: 53/77 = 0.6883 +0x293C, // Index: 487 Fraction: 42/61 = 0.6885 +0x1E2C, // Index: 488 Fraction: 31/45 = 0.6889 +0x3249, // Index: 489 Fraction: 51/74 = 0.6892 +0x131C, // Index: 490 Fraction: 20/29 = 0.6897 +0x3046, // Index: 491 Fraction: 49/71 = 0.6901 +0x1C29, // Index: 492 Fraction: 29/42 = 0.6905 +0x2536, // Index: 493 Fraction: 38/55 = 0.6909 +0x2E43, // Index: 494 Fraction: 47/68 = 0.6912 +0x3750, // Index: 495 Fraction: 56/81 = 0.6914 +0x080C, // Index: 496 Fraction: 9/13 = 0.6923 +0x3C57, // Index: 497 Fraction: 61/88 = 0.6932 +0x334A, // Index: 498 Fraction: 52/75 = 0.6933 +0x2A3D, // Index: 499 Fraction: 43/62 = 0.6935 +0x2130, // Index: 500 Fraction: 34/49 = 0.6939 +0x3A54, // Index: 501 Fraction: 59/85 = 0.6941 +0x1823, // Index: 502 Fraction: 25/36 = 0.6944 +0x283A, // Index: 503 Fraction: 41/59 = 0.6949 +0x3851, // Index: 504 Fraction: 57/82 = 0.6951 +0x0F16, // Index: 505 Fraction: 16/23 = 0.6957 +0x364E, // Index: 506 Fraction: 55/79 = 0.6962 +0x2637, // Index: 507 Fraction: 39/56 = 0.6964 +0x3D58, // Index: 508 Fraction: 62/89 = 0.6966 +0x1620, // Index: 509 Fraction: 23/33 = 0.6970 +0x344B, // Index: 510 Fraction: 53/76 = 0.6974 +0x1D2A, // Index: 511 Fraction: 30/43 = 0.6977 +0x2434, // Index: 512 Fraction: 37/53 = 0.6981 +0x2B3E, // Index: 513 Fraction: 44/63 = 0.6984 +0x3248, // Index: 514 Fraction: 51/73 = 0.6986 +0x3952, // Index: 515 Fraction: 58/83 = 0.6988 +0x0609, // Index: 516 Fraction: 7/10 = 0.7000 +0x3C56, // Index: 517 Fraction: 61/87 = 0.7011 +0x354C, // Index: 518 Fraction: 54/77 = 0.7013 +0x2E42, // Index: 519 Fraction: 47/67 = 0.7015 +0x2738, // Index: 520 Fraction: 40/57 = 0.7018 +0x202E, // Index: 521 Fraction: 33/47 = 0.7021 +0x3A53, // Index: 522 Fraction: 59/84 = 0.7024 +0x1924, // Index: 523 Fraction: 26/37 = 0.7027 +0x2C3F, // Index: 524 Fraction: 45/64 = 0.7031 +0x121A, // Index: 525 Fraction: 19/27 = 0.7037 +0x3146, // Index: 526 Fraction: 50/71 = 0.7042 +0x1E2B, // Index: 527 Fraction: 31/44 = 0.7045 +0x2A3C, // Index: 528 Fraction: 43/61 = 0.7049 +0x364D, // Index: 529 Fraction: 55/78 = 0.7051 +0x0B10, // Index: 530 Fraction: 12/17 = 0.7059 +0x344A, // Index: 531 Fraction: 53/75 = 0.7067 +0x2839, // Index: 532 Fraction: 41/58 = 0.7069 +0x1C28, // Index: 533 Fraction: 29/41 = 0.7073 +0x2D40, // Index: 534 Fraction: 46/65 = 0.7077 +0x3E58, // Index: 535 Fraction: 63/89 = 0.7079 +0x1017, // Index: 536 Fraction: 17/24 = 0.7083 +0x374E, // Index: 537 Fraction: 56/79 = 0.7089 +0x2636, // Index: 538 Fraction: 39/55 = 0.7091 +0x3C55, // Index: 539 Fraction: 61/86 = 0.7093 +0x151E, // Index: 540 Fraction: 22/31 = 0.7097 +0x3044, // Index: 541 Fraction: 49/69 = 0.7101 +0x1A25, // Index: 542 Fraction: 27/38 = 0.7105 +0x3A52, // Index: 543 Fraction: 59/83 = 0.7108 +0x1F2C, // Index: 544 Fraction: 32/45 = 0.7111 +0x2433, // Index: 545 Fraction: 37/52 = 0.7115 +0x293A, // Index: 546 Fraction: 42/59 = 0.7119 +0x2E41, // Index: 547 Fraction: 47/66 = 0.7121 +0x3348, // Index: 548 Fraction: 52/73 = 0.7123 +0x384F, // Index: 549 Fraction: 57/80 = 0.7125 +0x3D56, // Index: 550 Fraction: 62/87 = 0.7126 +0x0406, // Index: 551 Fraction: 5/7 = 0.7143 +0x3E57, // Index: 552 Fraction: 63/88 = 0.7159 +0x3950, // Index: 553 Fraction: 58/81 = 0.7160 +0x3449, // Index: 554 Fraction: 53/74 = 0.7162 +0x2F42, // Index: 555 Fraction: 48/67 = 0.7164 +0x2A3B, // Index: 556 Fraction: 43/60 = 0.7167 +0x2534, // Index: 557 Fraction: 38/53 = 0.7170 +0x202D, // Index: 558 Fraction: 33/46 = 0.7174 +0x3C54, // Index: 559 Fraction: 61/85 = 0.7176 +0x1B26, // Index: 560 Fraction: 28/39 = 0.7179 +0x3246, // Index: 561 Fraction: 51/71 = 0.7183 +0x161F, // Index: 562 Fraction: 23/32 = 0.7188 +0x3F58, // Index: 563 Fraction: 64/89 = 0.7191 +0x2838, // Index: 564 Fraction: 41/57 = 0.7193 +0x3A51, // Index: 565 Fraction: 59/82 = 0.7195 +0x1118, // Index: 566 Fraction: 18/25 = 0.7200 +0x3043, // Index: 567 Fraction: 49/68 = 0.7206 +0x1E2A, // Index: 568 Fraction: 31/43 = 0.7209 +0x2B3C, // Index: 569 Fraction: 44/61 = 0.7213 +0x384E, // Index: 570 Fraction: 57/79 = 0.7215 +0x0C11, // Index: 571 Fraction: 13/18 = 0.7222 +0x3B52, // Index: 572 Fraction: 60/83 = 0.7229 +0x2E40, // Index: 573 Fraction: 47/65 = 0.7231 +0x212E, // Index: 574 Fraction: 34/47 = 0.7234 +0x364B, // Index: 575 Fraction: 55/76 = 0.7237 +0x141C, // Index: 576 Fraction: 21/29 = 0.7241 +0x3144, // Index: 577 Fraction: 50/69 = 0.7246 +0x1C27, // Index: 578 Fraction: 29/40 = 0.7250 +0x2432, // Index: 579 Fraction: 37/51 = 0.7255 +0x2C3D, // Index: 580 Fraction: 45/62 = 0.7258 +0x3448, // Index: 581 Fraction: 53/73 = 0.7260 +0x3C53, // Index: 582 Fraction: 61/84 = 0.7262 +0x070A, // Index: 583 Fraction: 8/11 = 0.7273 +0x3A50, // Index: 584 Fraction: 59/81 = 0.7284 +0x3245, // Index: 585 Fraction: 51/70 = 0.7286 +0x2A3A, // Index: 586 Fraction: 43/59 = 0.7288 +0x222F, // Index: 587 Fraction: 35/48 = 0.7292 +0x3D54, // Index: 588 Fraction: 62/85 = 0.7294 +0x1A24, // Index: 589 Fraction: 27/37 = 0.7297 +0x2D3E, // Index: 590 Fraction: 46/63 = 0.7302 +0x4058, // Index: 591 Fraction: 65/89 = 0.7303 +0x1219, // Index: 592 Fraction: 19/26 = 0.7308 +0x3042, // Index: 593 Fraction: 49/67 = 0.7313 +0x1D28, // Index: 594 Fraction: 30/41 = 0.7317 +0x2837, // Index: 595 Fraction: 41/56 = 0.7321 +0x3346, // Index: 596 Fraction: 52/71 = 0.7324 +0x3E55, // Index: 597 Fraction: 63/86 = 0.7326 +0x0A0E, // Index: 598 Fraction: 11/15 = 0.7333 +0x394E, // Index: 599 Fraction: 58/79 = 0.7342 +0x2E3F, // Index: 600 Fraction: 47/64 = 0.7344 +0x2330, // Index: 601 Fraction: 36/49 = 0.7347 +0x3C52, // Index: 602 Fraction: 61/83 = 0.7349 +0x1821, // Index: 603 Fraction: 25/34 = 0.7353 +0x3F56, // Index: 604 Fraction: 64/87 = 0.7356 +0x2634, // Index: 605 Fraction: 39/53 = 0.7358 +0x3447, // Index: 606 Fraction: 53/72 = 0.7361 +0x0D12, // Index: 607 Fraction: 14/19 = 0.7368 +0x3A4F, // Index: 608 Fraction: 59/80 = 0.7375 +0x2C3C, // Index: 609 Fraction: 45/61 = 0.7377 +0x1E29, // Index: 610 Fraction: 31/42 = 0.7381 +0x2F40, // Index: 611 Fraction: 48/65 = 0.7385 +0x4057, // Index: 612 Fraction: 65/88 = 0.7386 +0x1016, // Index: 613 Fraction: 17/23 = 0.7391 +0x3548, // Index: 614 Fraction: 54/73 = 0.7397 +0x2431, // Index: 615 Fraction: 37/50 = 0.7400 +0x384C, // Index: 616 Fraction: 57/77 = 0.7403 +0x131A, // Index: 617 Fraction: 20/27 = 0.7407 +0x3E54, // Index: 618 Fraction: 63/85 = 0.7412 +0x2A39, // Index: 619 Fraction: 43/58 = 0.7414 +0x4158, // Index: 620 Fraction: 66/89 = 0.7416 +0x161E, // Index: 621 Fraction: 23/31 = 0.7419 +0x3041, // Index: 622 Fraction: 49/66 = 0.7424 +0x1922, // Index: 623 Fraction: 26/35 = 0.7429 +0x3649, // Index: 624 Fraction: 55/74 = 0.7432 +0x1C26, // Index: 625 Fraction: 29/39 = 0.7436 +0x3C51, // Index: 626 Fraction: 61/82 = 0.7439 +0x1F2A, // Index: 627 Fraction: 32/43 = 0.7442 +0x4259, // Index: 628 Fraction: 67/90 = 0.7444 +0x222E, // Index: 629 Fraction: 35/47 = 0.7447 +0x2532, // Index: 630 Fraction: 38/51 = 0.7451 +0x2836, // Index: 631 Fraction: 41/55 = 0.7455 +0x2B3A, // Index: 632 Fraction: 44/59 = 0.7458 +0x2E3E, // Index: 633 Fraction: 47/63 = 0.7460 +0x3142, // Index: 634 Fraction: 50/67 = 0.7463 +0x3446, // Index: 635 Fraction: 53/71 = 0.7465 +0x374A, // Index: 636 Fraction: 56/75 = 0.7467 +0x3A4E, // Index: 637 Fraction: 59/79 = 0.7468 +0x3D52, // Index: 638 Fraction: 62/83 = 0.7470 +0x4056, // Index: 639 Fraction: 65/87 = 0.7471 +0x0203, // Index: 640 Fraction: 3/4 = 0.7500 +0x4258, // Index: 641 Fraction: 67/89 = 0.7528 +0x3F54, // Index: 642 Fraction: 64/85 = 0.7529 +0x3C50, // Index: 643 Fraction: 61/81 = 0.7531 +0x394C, // Index: 644 Fraction: 58/77 = 0.7532 +0x3648, // Index: 645 Fraction: 55/73 = 0.7534 +0x3344, // Index: 646 Fraction: 52/69 = 0.7536 +0x3040, // Index: 647 Fraction: 49/65 = 0.7538 +0x2D3C, // Index: 648 Fraction: 46/61 = 0.7541 +0x2A38, // Index: 649 Fraction: 43/57 = 0.7544 +0x2734, // Index: 650 Fraction: 40/53 = 0.7547 +0x2430, // Index: 651 Fraction: 37/49 = 0.7551 +0x212C, // Index: 652 Fraction: 34/45 = 0.7556 +0x4055, // Index: 653 Fraction: 65/86 = 0.7558 +0x1E28, // Index: 654 Fraction: 31/41 = 0.7561 +0x3A4D, // Index: 655 Fraction: 59/78 = 0.7564 +0x1B24, // Index: 656 Fraction: 28/37 = 0.7568 +0x3445, // Index: 657 Fraction: 53/70 = 0.7571 +0x1820, // Index: 658 Fraction: 25/33 = 0.7576 +0x2E3D, // Index: 659 Fraction: 47/62 = 0.7581 +0x151C, // Index: 660 Fraction: 22/29 = 0.7586 +0x3E52, // Index: 661 Fraction: 63/83 = 0.7590 +0x2835, // Index: 662 Fraction: 41/54 = 0.7593 +0x3B4E, // Index: 663 Fraction: 60/79 = 0.7595 +0x1218, // Index: 664 Fraction: 19/25 = 0.7600 +0x3546, // Index: 665 Fraction: 54/71 = 0.7606 +0x222D, // Index: 666 Fraction: 35/46 = 0.7609 +0x3242, // Index: 667 Fraction: 51/67 = 0.7612 +0x4257, // Index: 668 Fraction: 67/88 = 0.7614 +0x0F14, // Index: 669 Fraction: 16/21 = 0.7619 +0x3C4F, // Index: 670 Fraction: 61/80 = 0.7625 +0x2C3A, // Index: 671 Fraction: 45/59 = 0.7627 +0x1C25, // Index: 672 Fraction: 29/38 = 0.7632 +0x2936, // Index: 673 Fraction: 42/55 = 0.7636 +0x3647, // Index: 674 Fraction: 55/72 = 0.7639 +0x4358, // Index: 675 Fraction: 68/89 = 0.7640 +0x0C10, // Index: 676 Fraction: 13/17 = 0.7647 +0x3D50, // Index: 677 Fraction: 62/81 = 0.7654 +0x303F, // Index: 678 Fraction: 49/64 = 0.7656 +0x232E, // Index: 679 Fraction: 36/47 = 0.7660 +0x3A4C, // Index: 680 Fraction: 59/77 = 0.7662 +0x161D, // Index: 681 Fraction: 23/30 = 0.7667 +0x3748, // Index: 682 Fraction: 56/73 = 0.7671 +0x202A, // Index: 683 Fraction: 33/43 = 0.7674 +0x2A37, // Index: 684 Fraction: 43/56 = 0.7679 +0x3444, // Index: 685 Fraction: 53/69 = 0.7681 +0x3E51, // Index: 686 Fraction: 63/82 = 0.7683 +0x090C, // Index: 687 Fraction: 10/13 = 0.7692 +0x4256, // Index: 688 Fraction: 67/87 = 0.7701 +0x3849, // Index: 689 Fraction: 57/74 = 0.7703 +0x2E3C, // Index: 690 Fraction: 47/61 = 0.7705 +0x242F, // Index: 691 Fraction: 37/48 = 0.7708 +0x3F52, // Index: 692 Fraction: 64/83 = 0.7711 +0x1A22, // Index: 693 Fraction: 27/35 = 0.7714 +0x2B38, // Index: 694 Fraction: 44/57 = 0.7719 +0x3C4E, // Index: 695 Fraction: 61/79 = 0.7722 +0x1015, // Index: 696 Fraction: 17/22 = 0.7727 +0x394A, // Index: 697 Fraction: 58/75 = 0.7733 +0x2834, // Index: 698 Fraction: 41/53 = 0.7736 +0x4053, // Index: 699 Fraction: 65/84 = 0.7738 +0x171E, // Index: 700 Fraction: 24/31 = 0.7742 +0x3646, // Index: 701 Fraction: 55/71 = 0.7746 +0x1E27, // Index: 702 Fraction: 31/40 = 0.7750 +0x4458, // Index: 703 Fraction: 69/89 = 0.7753 +0x2530, // Index: 704 Fraction: 38/49 = 0.7755 +0x2C39, // Index: 705 Fraction: 45/58 = 0.7759 +0x3342, // Index: 706 Fraction: 52/67 = 0.7761 +0x3A4B, // Index: 707 Fraction: 59/76 = 0.7763 +0x4154, // Index: 708 Fraction: 66/85 = 0.7765 +0x0608, // Index: 709 Fraction: 7/9 = 0.7778 +0x4255, // Index: 710 Fraction: 67/86 = 0.7791 +0x3B4C, // Index: 711 Fraction: 60/77 = 0.7792 +0x3443, // Index: 712 Fraction: 53/68 = 0.7794 +0x2D3A, // Index: 713 Fraction: 46/59 = 0.7797 +0x2631, // Index: 714 Fraction: 39/50 = 0.7800 +0x1F28, // Index: 715 Fraction: 32/41 = 0.7805 +0x3848, // Index: 716 Fraction: 57/73 = 0.7808 +0x181F, // Index: 717 Fraction: 25/32 = 0.7812 +0x4356, // Index: 718 Fraction: 68/87 = 0.7816 +0x2A36, // Index: 719 Fraction: 43/55 = 0.7818 +0x3C4D, // Index: 720 Fraction: 61/78 = 0.7821 +0x1116, // Index: 721 Fraction: 18/23 = 0.7826 +0x4052, // Index: 722 Fraction: 65/83 = 0.7831 +0x2E3B, // Index: 723 Fraction: 47/60 = 0.7833 +0x1C24, // Index: 724 Fraction: 29/37 = 0.7838 +0x4457, // Index: 725 Fraction: 69/88 = 0.7841 +0x2732, // Index: 726 Fraction: 40/51 = 0.7843 +0x3240, // Index: 727 Fraction: 51/65 = 0.7846 +0x3D4E, // Index: 728 Fraction: 62/79 = 0.7848 +0x0A0D, // Index: 729 Fraction: 11/14 = 0.7857 +0x4558, // Index: 730 Fraction: 70/89 = 0.7865 +0x3A4A, // Index: 731 Fraction: 59/75 = 0.7867 +0x2F3C, // Index: 732 Fraction: 48/61 = 0.7869 +0x242E, // Index: 733 Fraction: 37/47 = 0.7872 +0x3E4F, // Index: 734 Fraction: 63/80 = 0.7875 +0x1920, // Index: 735 Fraction: 26/33 = 0.7879 +0x4254, // Index: 736 Fraction: 67/85 = 0.7882 +0x2833, // Index: 737 Fraction: 41/52 = 0.7885 +0x3746, // Index: 738 Fraction: 56/71 = 0.7887 +0x4659, // Index: 739 Fraction: 71/90 = 0.7889 +0x0E12, // Index: 740 Fraction: 15/19 = 0.7895 +0x3F50, // Index: 741 Fraction: 64/81 = 0.7901 +0x303D, // Index: 742 Fraction: 49/62 = 0.7903 +0x212A, // Index: 743 Fraction: 34/43 = 0.7907 +0x3442, // Index: 744 Fraction: 53/67 = 0.7910 +0x1217, // Index: 745 Fraction: 19/24 = 0.7917 +0x3C4C, // Index: 746 Fraction: 61/77 = 0.7922 +0x2934, // Index: 747 Fraction: 42/53 = 0.7925 +0x4051, // Index: 748 Fraction: 65/82 = 0.7927 +0x161C, // Index: 749 Fraction: 23/29 = 0.7931 +0x313E, // Index: 750 Fraction: 50/63 = 0.7937 +0x1A21, // Index: 751 Fraction: 27/34 = 0.7941 +0x3948, // Index: 752 Fraction: 58/73 = 0.7945 +0x1E26, // Index: 753 Fraction: 31/39 = 0.7949 +0x4152, // Index: 754 Fraction: 66/83 = 0.7952 +0x222B, // Index: 755 Fraction: 35/44 = 0.7955 +0x2630, // Index: 756 Fraction: 39/49 = 0.7959 +0x2A35, // Index: 757 Fraction: 43/54 = 0.7963 +0x2E3A, // Index: 758 Fraction: 47/59 = 0.7966 +0x323F, // Index: 759 Fraction: 51/64 = 0.7969 +0x3644, // Index: 760 Fraction: 55/69 = 0.7971 +0x3A49, // Index: 761 Fraction: 59/74 = 0.7973 +0x3E4E, // Index: 762 Fraction: 63/79 = 0.7975 +0x4253, // Index: 763 Fraction: 67/84 = 0.7976 +0x4658, // Index: 764 Fraction: 71/89 = 0.7978 +0x0304, // Index: 765 Fraction: 4/5 = 0.8000 +0x4455, // Index: 766 Fraction: 69/86 = 0.8023 +0x4050, // Index: 767 Fraction: 65/81 = 0.8025 +0x3C4B, // Index: 768 Fraction: 61/76 = 0.8026 +0x3846, // Index: 769 Fraction: 57/71 = 0.8028 +0x3441, // Index: 770 Fraction: 53/66 = 0.8030 +0x303C, // Index: 771 Fraction: 49/61 = 0.8033 +0x2C37, // Index: 772 Fraction: 45/56 = 0.8036 +0x2832, // Index: 773 Fraction: 41/51 = 0.8039 +0x242D, // Index: 774 Fraction: 37/46 = 0.8043 +0x4556, // Index: 775 Fraction: 70/87 = 0.8046 +0x2028, // Index: 776 Fraction: 33/41 = 0.8049 +0x3D4C, // Index: 777 Fraction: 62/77 = 0.8052 +0x1C23, // Index: 778 Fraction: 29/36 = 0.8056 +0x3542, // Index: 779 Fraction: 54/67 = 0.8060 +0x181E, // Index: 780 Fraction: 25/31 = 0.8065 +0x4657, // Index: 781 Fraction: 71/88 = 0.8068 +0x2D38, // Index: 782 Fraction: 46/57 = 0.8070 +0x4252, // Index: 783 Fraction: 67/83 = 0.8072 +0x1419, // Index: 784 Fraction: 21/26 = 0.8077 +0x3A48, // Index: 785 Fraction: 59/73 = 0.8082 +0x252E, // Index: 786 Fraction: 38/47 = 0.8085 +0x3643, // Index: 787 Fraction: 55/68 = 0.8088 +0x4758, // Index: 788 Fraction: 72/89 = 0.8090 +0x1014, // Index: 789 Fraction: 17/21 = 0.8095 +}; diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index 23c435cc..e9a8bf6c 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -15,6 +15,9 @@ /* Library headers */ #include "rtos_printf.h" +#if appconfI2S_ENABLED +#include "src.h" +#endif /* App headers */ #include "app_conf.h" @@ -28,17 +31,41 @@ #include "gpio_ctrl/leds.h" #include "intent_handler/intent_handler.h" +#if appconfRECOVER_MCLK_I2S_APP_PLL +/* Config headers for sw_pll */ +#include "sw_pll.h" +#endif + #ifndef MEM_ANALYSIS_ENABLED #define MEM_ANALYSIS_ENABLED 0 #endif +#if appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) +void i2s_slave_intertile(void *args) { + (void) args; + while(1) { +#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL + sw_pll_ctx_t* i2s_callback_args = (sw_pll_ctx_t*) args; + port_clear_buffer(i2s_callback_args->p_bclk_count); + port_in(i2s_callback_args->p_bclk_count); // Block until BCLK transition to synchronise. Will consume up to 1/64 of a LRCLK cycle + uint16_t mclk_pt = port_get_trigger_time(i2s_callback_args->p_mclk_count); // Immediately sample mclk_count + uint16_t bclk_pt = port_get_trigger_time(i2s_callback_args->p_bclk_count); // Now grab bclk_count (which won't have changed) + + sw_pll_do_control(i2s_callback_args->sw_pll, mclk_pt, bclk_pt); +#endif + + } +} +#endif + void audio_pipeline_input(void *input_app_data, int32_t **input_audio_frames, size_t ch_count, size_t frame_count) { (void) input_app_data; - + +#if !appconfUSE_I2S_INPUT static int flushed; while (!flushed) { size_t received; @@ -59,6 +86,26 @@ void audio_pipeline_input(void *input_app_data, input_audio_frames, frame_count, portMAX_DELAY); + +#else + xassert(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); + int32_t tmp[appconfAUDIO_PIPELINE_FRAME_ADVANCE][appconfAUDIO_PIPELINE_CHANNELS]; + int32_t *tmpptr = (int32_t *)input_audio_frames; + /* I2S provides sample channel format */ + size_t rx_count = + rtos_i2s_rx(i2s_ctx, + (int32_t *) tmp, + frame_count, + portMAX_DELAY); + + + for (int i=0; i= 2) { + i2s_frame[0] = src_us3_voice_input_sample(src_data[0], src_ff3v_fir_coefs[2], send_buf[0]); + i2s_frame[1] = src_us3_voice_input_sample(src_data[1], src_ff3v_fir_coefs[2], send_buf[1]); + return 2; + } else { + i2s_frame[0] = src_us3_voice_input_sample(src_data[0], src_ff3v_fir_coefs[2], 0); + i2s_frame[1] = src_us3_voice_input_sample(src_data[1], src_ff3v_fir_coefs[2], 0); + return 0; + } + case 1: + i = 2; + i2s_frame[0] = src_us3_voice_get_next_sample(src_data[0], src_ff3v_fir_coefs[1]); + i2s_frame[1] = src_us3_voice_get_next_sample(src_data[1], src_ff3v_fir_coefs[1]); + return 0; + case 2: + i = 0; + i2s_frame[0] = src_us3_voice_get_next_sample(src_data[0], src_ff3v_fir_coefs[0]); + i2s_frame[1] = src_us3_voice_get_next_sample(src_data[1], src_ff3v_fir_coefs[0]); + return 0; + default: + xassert(0); + return 0; + } +} + +RTOS_I2S_APP_RECEIVE_FILTER_CALLBACK_ATTR +size_t i2s_send_downsample_cb(rtos_i2s_t *ctx, void *app_data, int32_t *i2s_frame, size_t i2s_frame_size, int32_t *receive_buf, size_t sample_spaces_free) +{ + static int i; + static int64_t sum[2]; + static int32_t src_data[2][SRC_FF3V_FIR_NUM_PHASES][SRC_FF3V_FIR_TAPS_PER_PHASE] __attribute__((aligned (8))); + + xassert(i2s_frame_size == 2); + + switch (i) { + case 0: + i = 1; + sum[0] = src_ds3_voice_add_sample(0, src_data[0][0], src_ff3v_fir_coefs[0], i2s_frame[0]); + sum[1] = src_ds3_voice_add_sample(0, src_data[1][0], src_ff3v_fir_coefs[0], i2s_frame[1]); + return 0; + case 1: + i = 2; + sum[0] = src_ds3_voice_add_sample(sum[0], src_data[0][1], src_ff3v_fir_coefs[1], i2s_frame[0]); + sum[1] = src_ds3_voice_add_sample(sum[1], src_data[1][1], src_ff3v_fir_coefs[1], i2s_frame[1]); + return 0; + case 2: + i = 0; + if (sample_spaces_free >= 2) { + receive_buf[0] = src_ds3_voice_add_final_sample(sum[0], src_data[0][2], src_ff3v_fir_coefs[2], i2s_frame[0]); + receive_buf[1] = src_ds3_voice_add_final_sample(sum[1], src_data[1][2], src_ff3v_fir_coefs[2], i2s_frame[1]); + return 2; + } else { + (void) src_ds3_voice_add_final_sample(sum[0], src_data[0][2], src_ff3v_fir_coefs[2], i2s_frame[0]); + (void) src_ds3_voice_add_final_sample(sum[1], src_data[1][2], src_ff3v_fir_coefs[2], i2s_frame[1]); + return 0; + } + default: + xassert(0); + return 0; + } +} + +void i2s_rate_conversion_enable(void) +{ +#if !appconfI2S_TDM_ENABLED + rtos_i2s_send_filter_cb_set(i2s_ctx, i2s_send_upsample_cb, NULL); +#endif + rtos_i2s_receive_filter_cb_set(i2s_ctx, i2s_send_downsample_cb, NULL); +} +#endif // appconfUSE_I2S_INPUT void vApplicationMallocFailedHook(void) { @@ -96,6 +225,26 @@ void startup_task(void *arg) platform_start(); +#if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) + +// Use sw_pll_ctx only if the MCLK recovery is enabled +#if appconfRECOVER_MCLK_I2S_APP_PLL + xTaskCreate((TaskFunction_t) i2s_slave_intertile, + "i2s_slave_intertile", + RTOS_THREAD_STACK_SIZE(i2s_slave_intertile), + sw_pll_ctx, + appconfAUDIO_PIPELINE_TASK_PRIORITY, + NULL); +#else + xTaskCreate((TaskFunction_t) i2s_slave_intertile, + "i2s_slave_intertile", + RTOS_THREAD_STACK_SIZE(i2s_slave_intertile), + NULL, + appconfAUDIO_PIPELINE_TASK_PRIORITY, + NULL); +#endif +#endif + #if ON_TILE(0) led_task_create(appconfLED_TASK_PRIORITY, NULL); #endif diff --git a/examples/ffd/src/register_setup_1000ppm.h b/examples/ffd/src/register_setup_1000ppm.h new file mode 100644 index 00000000..25646bcd --- /dev/null +++ b/examples/ffd/src/register_setup_1000ppm.h @@ -0,0 +1,17 @@ +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +/* Autogenerated by sw_pll_sim.py using command: + /Users/ed/sandboxes/sw_xvf3800/lib_sw_pll/python/sw_pll/pll_calc.py -i 24.0 -a -m 90 -t 12.288 -p 6.0 -e 5 -r --fracmin 0.49 --fracmax 0.81 --header + Picked output solution #83 + F: 154 + R: 1 + f: 56 + p: 87 + OD: 1 + ACD: 18 + Output freq: 12287978.0 + VCO freq: 1867770000.0 */ + +#define APP_PLL_CTL_REG 0x08809A01 +#define APP_PLL_DIV_REG 0x80000012 +#define APP_PLL_FRAC_REG 0x80003857 diff --git a/tools/ci/main_examples.txt b/tools/ci/main_examples.txt index 2c13fea4..448beb1a 100644 --- a/tools/ci/main_examples.txt +++ b/tools/ci/main_examples.txt @@ -4,6 +4,7 @@ ffva_int_cyberon_fixed_delay example_ffva_int_cyberon_fixed_delay Yes XK_ low_lower_ffd_sensory example_low_power_ffd_sensory Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake ffd_sensory example_ffd_sensory Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake ffd_cyberon example_ffd_cyberon Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +ffd_cyberon_i2s_input example_ffd_cyberon_i2s_input Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake mic_aggregator_TDM example_mic_aggregator_tdm No XCORE-AI-EXPLORER xmos_cmake_toolchain/xs3a.cmake mic_aggregator_USB example_mic_aggregator_usb No XCORE-AI-EXPLORER xmos_cmake_toolchain/xs3a.cmake asrc example_asrc_demo No XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake From b3dfe72b9b4364ff148c4d01f2c74547854e320a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 24 Jul 2024 13:47:47 +0100 Subject: [PATCH 146/288] Rename build configuration --- examples/examples.cmake | 2 +- ...n_i2s_input.cmake => ffd_i2s_input_cyberon.cmake} | 12 ++++++------ tools/ci/main_examples.txt | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename examples/ffd/{ffd_cyberon_i2s_input.cmake => ffd_i2s_input_cyberon.cmake} (94%) diff --git a/examples/examples.cmake b/examples/examples.cmake index c3d0c635..517a507e 100644 --- a/examples/examples.cmake +++ b/examples/examples.cmake @@ -6,7 +6,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) include(${CMAKE_CURRENT_LIST_DIR}/asrc_demo/asrc_demo.cmake) include(${CMAKE_CURRENT_LIST_DIR}/ffd/ffd_sensory.cmake) include(${CMAKE_CURRENT_LIST_DIR}/ffd/ffd_cyberon.cmake) - include(${CMAKE_CURRENT_LIST_DIR}/ffd/ffd_cyberon_i2s_input.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/ffd/ffd_i2s_input_cyberon.cmake) include(${CMAKE_CURRENT_LIST_DIR}/low_power_ffd/low_power_ffd_sensory.cmake) include(${CMAKE_CURRENT_LIST_DIR}/mic_aggregator/mic_aggregator.cmake) diff --git a/examples/ffd/ffd_cyberon_i2s_input.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake similarity index 94% rename from examples/ffd/ffd_cyberon_i2s_input.cmake rename to examples/ffd/ffd_i2s_input_cyberon.cmake index ff84458e..0ce98024 100644 --- a/examples/ffd/ffd_cyberon_i2s_input.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -104,7 +104,7 @@ set(APP_COMMON_LINK_LIBRARIES #********************** # Tile Targets #********************** -set(TARGET_NAME tile0_example_ffd_cyberon_i2s_input) +set(TARGET_NAME tile0_example_ffd_i2s_input_cyberon) add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL) target_sources(${TARGET_NAME} PUBLIC ${APP_SOURCES}) target_include_directories(${TARGET_NAME} PUBLIC ${APP_INCLUDES} ${RTOS_CONF_INCLUDES}) @@ -114,7 +114,7 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) -set(TARGET_NAME tile1_example_ffd_cyberon_i2s_input) +set(TARGET_NAME tile1_example_ffd_i2s_input_cyberon) add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL) target_sources(${TARGET_NAME} PUBLIC ${APP_SOURCES}) target_include_directories(${TARGET_NAME} PUBLIC ${APP_INCLUDES} ${RTOS_CONF_INCLUDES}) @@ -127,18 +127,18 @@ unset(TARGET_NAME) #********************** # Merge binaries #********************** -merge_binaries(example_ffd_cyberon_i2s_input tile0_example_ffd_cyberon_i2s_input tile1_example_ffd_cyberon_i2s_input 1) +merge_binaries(example_ffd_i2s_input_cyberon tile0_example_ffd_i2s_input_cyberon tile1_example_ffd_i2s_input_cyberon 1) #********************** # Create run and debug targets #********************** -create_run_target(example_ffd_cyberon_i2s_input) -create_debug_target(example_ffd_cyberon_i2s_input) +create_run_target(example_ffd_i2s_input_cyberon) +create_debug_target(example_ffd_i2s_input_cyberon) #********************** # Create data partition support targets #********************** -set(TARGET_NAME example_ffd_cyberon_i2s_input) +set(TARGET_NAME example_ffd_i2s_input_cyberon) set(DATA_PARTITION_FILE ${TARGET_NAME}_data_partition.bin) set(MODEL_FILE ${TARGET_NAME}_model.bin) set(FATFS_FILE ${TARGET_NAME}_fat.fs) diff --git a/tools/ci/main_examples.txt b/tools/ci/main_examples.txt index 448beb1a..be629b99 100644 --- a/tools/ci/main_examples.txt +++ b/tools/ci/main_examples.txt @@ -4,7 +4,7 @@ ffva_int_cyberon_fixed_delay example_ffva_int_cyberon_fixed_delay Yes XK_ low_lower_ffd_sensory example_low_power_ffd_sensory Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake ffd_sensory example_ffd_sensory Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake ffd_cyberon example_ffd_cyberon Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake -ffd_cyberon_i2s_input example_ffd_cyberon_i2s_input Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +ffd_i2s_input_cyberon example_ffd_i2s_input_cyberon Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake mic_aggregator_TDM example_mic_aggregator_tdm No XCORE-AI-EXPLORER xmos_cmake_toolchain/xs3a.cmake mic_aggregator_USB example_mic_aggregator_usb No XCORE-AI-EXPLORER xmos_cmake_toolchain/xs3a.cmake asrc example_asrc_demo No XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake From 7de7d04113799cd325e4949f3f2ff81e24d41cdc Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 24 Jul 2024 14:00:28 +0100 Subject: [PATCH 147/288] Add debug info sent over UART --- examples/ffd/ffd_i2s_input_cyberon.cmake | 1 + modules/asr/Cyberon/DSpotter_asr.c | 9 +++++++++ modules/asr/intent_engine/intent_engine_io.c | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 0ce98024..907fc491 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -81,6 +81,7 @@ set(APP_COMPILE_DEFINITIONS appconfI2S_AUDIO_SAMPLE_RATE=48000 appconfI2S_ENABLED=1 appconfRECOVER_MCLK_I2S_APP_PLL=1 + appconfINTENT_UART_CMD_INFO=1 ) set(APP_LINK_OPTIONS diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index 868c9c64..44c23912 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -32,7 +32,9 @@ #define VOLUME_SCALE_RECONG 800 // The AGC volume scale percentage for recognition. It depends on original microphone data. static uint8_t *g_lpbyDSpotterMem = NULL; +#ifndef UART_DUMP_RECORD static size_t g_nRecordFrameCount = 0; +#endif devmem_manager_t *devmem_ctx = NULL; @@ -174,6 +176,13 @@ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result) result->start_index = -1; result->end_index = -1; result->duration = -1; + #if appconfINTENT_UART_CMD_INFO + static char res_info[128]; + snprintf(res_info, sizeof(res_info)-1, "ID:%d,Sc:%d,SGD:%d,En:%d\r\n", nCmdID, nCmdScore, nCmdSG, nCmdEnergy); + // Enable the printout below to see the information sent over UART + // rtos_printf(res_info); + rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)&res_info, strlen(res_info)); +#endif return ASR_OK; } else diff --git a/modules/asr/intent_engine/intent_engine_io.c b/modules/asr/intent_engine/intent_engine_io.c index 63069968..bbe91be2 100644 --- a/modules/asr/intent_engine/intent_engine_io.c +++ b/modules/asr/intent_engine/intent_engine_io.c @@ -96,6 +96,13 @@ void intent_engine_process_asr_result(int word_id) } } rtos_printf("RECOGNIZED: 0x%x, %s\n", (int) word_id, (char*)text); +#if appconfINTENT_UART_CMD_INFO + static char res_info[128]; + snprintf(res_info, sizeof(res_info)-1, "Cmd:%s\r\n", text); + // Enable the printout below to see the information sent over UART + // rtos_printf(res_info); + rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)&res_info, strlen(res_info)); +#endif intent_engine_play_response(wav_id); } From ab8617e7ae9826de89c61129fb6f1959280a3091 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 25 Jul 2024 12:27:04 +0100 Subject: [PATCH 148/288] Add support for I2S slave in ASR audio playback --- examples/ffd/ffd_i2s_input_cyberon.cmake | 2 +- examples/ffd/src/app_conf.h | 19 ++++--- examples/ffd/src/main.c | 53 ++++++++++++++----- .../XK_VOICE_L71/platform/platform_conf.h | 10 ---- examples/ffva/src/app_conf.h | 18 +++++++ .../audio_response/audio_response.c | 26 ++++++--- 6 files changed, 88 insertions(+), 40 deletions(-) diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 907fc491..ea5f3373 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -76,7 +76,7 @@ set(APP_COMPILE_DEFINITIONS QSPI_FLASH_CALIBRATION_ADDRESS=${CALIBRATION_PATTERN_START_ADDRESS} ASR_CYBERON=1 appconfUSE_I2S_INPUT=1 - appconfAUDIO_PLAYBACK_ENABLED=0 + appconfAUDIO_PLAYBACK_ENABLED=1 appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 appconfI2S_ENABLED=1 diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index bfc33fa7..82653ef7 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -32,10 +32,6 @@ #define appconfAUDIO_PLAYBACK_ENABLED 1 #endif -#if appconfUSE_I2S_INPUT==1 && appconfAUDIO_PLAYBACK_ENABLED==1 -#error "I2S audio input cannot be used with audio playback" -#endif - /* Intent Engine Configuration */ #define appconfINTENT_FRAME_BUFFER_MULT (8*2) /* total buffer size is this value * MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME */ #define appconfINTENT_SAMPLE_BLOCK_LENGTH 240 @@ -95,6 +91,18 @@ #define appconfI2S_ENABLED 1 #endif +#ifndef appconfI2S_MODE_MASTER +#define appconfI2S_MODE_MASTER 0 +#endif + +#ifndef appconfI2S_MODE_SLAVE +#define appconfI2S_MODE_SLAVE 1 +#endif + +#ifndef appconfI2S_MODE +#define appconfI2S_MODE appconfI2S_MODE_MASTER +#endif + #ifndef appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR #define appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR 0 #endif @@ -111,9 +119,6 @@ #define appconfI2S_AUDIO_SAMPLE_RATE appconfAUDIO_PIPELINE_SAMPLE_RATE #endif -#define appconfI2S_MODE_MASTER 0 -#define appconfI2S_MODE_SLAVE 1 - /* I/O and interrupt cores for Tile 0 */ /* I/O and interrupt cores for Tile 1 */ diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index e9a8bf6c..3fe1f704 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -41,10 +41,39 @@ #endif #if appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) -void i2s_slave_intertile(void *args) { - (void) args; +void i2s_slave_intertile() +{ + int32_t tmp[appconfAUDIO_PIPELINE_FRAME_ADVANCE][appconfAUDIO_PIPELINE_CHANNELS]; while(1) { -#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL + memset(tmp, 0x00, sizeof(tmp)); + + size_t bytes_received = 0; + bytes_received = rtos_intertile_rx_len( + intertile_ctx, + appconfI2S_OUTPUT_SLAVE_PORT, + portMAX_DELAY); + + xassert(bytes_received == sizeof(tmp)); + + rtos_intertile_rx_data( + intertile_ctx, + tmp, + bytes_received); + + rtos_i2s_tx(i2s_ctx, + (int32_t*) tmp, + appconfAUDIO_PIPELINE_FRAME_ADVANCE, + portMAX_DELAY); + } +} +#endif + +#if appconfRECOVER_MCLK_I2S_APP_PLL +void sw_pll(void *args) +{ + + while(1) + { sw_pll_ctx_t* i2s_callback_args = (sw_pll_ctx_t*) args; port_clear_buffer(i2s_callback_args->p_bclk_count); port_in(i2s_callback_args->p_bclk_count); // Block until BCLK transition to synchronise. Will consume up to 1/64 of a LRCLK cycle @@ -52,8 +81,6 @@ void i2s_slave_intertile(void *args) { uint16_t bclk_pt = port_get_trigger_time(i2s_callback_args->p_bclk_count); // Now grab bclk_count (which won't have changed) sw_pll_do_control(i2s_callback_args->sw_pll, mclk_pt, bclk_pt); -#endif - } } #endif @@ -91,6 +118,7 @@ void audio_pipeline_input(void *input_app_data, xassert(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); int32_t tmp[appconfAUDIO_PIPELINE_FRAME_ADVANCE][appconfAUDIO_PIPELINE_CHANNELS]; int32_t *tmpptr = (int32_t *)input_audio_frames; + /* I2S provides sample channel format */ size_t rx_count = rtos_i2s_rx(i2s_ctx, @@ -100,7 +128,6 @@ void audio_pipeline_input(void *input_app_data, for (int i=0; i Date: Thu, 25 Jul 2024 13:49:08 +0100 Subject: [PATCH 149/288] Update task name --- examples/ffd/src/main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index 3fe1f704..780782b4 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -69,7 +69,7 @@ void i2s_slave_intertile() #endif #if appconfRECOVER_MCLK_I2S_APP_PLL -void sw_pll(void *args) +void sw_pll_control(void *args) { while(1) @@ -261,9 +261,9 @@ void startup_task(void *arg) appconfAUDIO_PIPELINE_TASK_PRIORITY, NULL); #if appconfRECOVER_MCLK_I2S_APP_PLL - xTaskCreate((TaskFunction_t) sw_pll, - "sw_pll", - RTOS_THREAD_STACK_SIZE(sw_pll), + xTaskCreate((TaskFunction_t) sw_pll_control, + "sw_pll_control", + RTOS_THREAD_STACK_SIZE(sw_pll_control), sw_pll_ctx, appconfAUDIO_PIPELINE_TASK_PRIORITY, NULL); From a779601792e94933e2e0944d0f3ff58ba7272fef Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 25 Jul 2024 15:19:01 +0100 Subject: [PATCH 150/288] Add some of Michael's comments --- .../XK_VOICE_L71/platform/platform_init.c | 2 ++ examples/ffd/src/app_conf.h | 3 ++- .../XK_VOICE_L71/platform/platform_init.c | 2 ++ examples/ffva/src/app_conf.h | 15 ++------------- examples/ffva/src/main.c | 2 ++ 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 772820f7..e75a2d1e 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -215,6 +215,8 @@ static void i2s_init(void) PORT_I2S_BCLK, PORT_I2S_LRCLK, I2S_CLKBLK); +#else + #error "Invalid I2S mode" #endif #else #if appconfI2S_MODE == appconfI2S_MODE_MASTER diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 82653ef7..f24b5997 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -137,6 +137,7 @@ #define appconfQSPI_FLASH_TASK_PRIORITY (configMAX_PRIORITIES - 1) #define appconfLED_TASK_PRIORITY (configMAX_PRIORITIES / 2 - 1) +#if appconfI2S_MODE==appconfI2S_MODE_SLAVE /* Software PLL settings for mclk recovery configurations */ /* see fractions.h and register_setup.h for other pll settings */ #define appconfLRCLK_NOMINAL_HZ appconfI2S_AUDIO_SAMPLE_RATE @@ -147,7 +148,7 @@ // have chosen, this number should be larger than the number // of elements in the look up table as the clk count diff is // added to the LUT index with a multiplier of 1. Only used for INT mclkless - +#endif #include "app_conf_check.h" #endif /* APP_CONF_H_ */ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index e153cd8e..04620821 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -285,6 +285,8 @@ static void i2s_init(void) PORT_I2S_BCLK, PORT_I2S_LRCLK, I2S_CLKBLK); +#else + #error "Invalid I2S mode" #endif #else #if appconfI2S_MODE == appconfI2S_MODE_MASTER diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 30830b5f..c42e40f5 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -238,18 +238,7 @@ #define appconfINTENT_MODEL_RUNNER_TASK_PRIORITY (configMAX_PRIORITIES - 2) #define appconfLED_TASK_PRIORITY (configMAX_PRIORITIES / 2 - 1) -#ifndef appconfI2S_MODE_MASTER -#define appconfI2S_MODE_MASTER 0 -#endif - -#ifndef appconfI2S_MODE_SLAVE -#define appconfI2S_MODE_SLAVE 1 -#endif - -#ifndef appconfI2S_MODE -#define appconfI2S_MODE appconfI2S_MODE_MASTER -#endif - +#if appconfI2S_MODE==appconfI2S_MODE_SLAVE /* Software PLL settings for mclk recovery configurations */ /* see fractions.h and register_setup.h for other pll settings */ #define appconfLRCLK_NOMINAL_HZ appconfI2S_AUDIO_SAMPLE_RATE @@ -260,6 +249,6 @@ // have chosen, this number should be larger than the number // of elements in the look up table as the clk count diff is // added to the LUT index with a multiplier of 1. Only used for INT mclkless - +#endif #endif /* APP_CONF_H_ */ diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 62843874..1caceb76 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -226,6 +226,8 @@ int audio_pipeline_output(void *output_app_data, appconfI2S_OUTPUT_SLAVE_PORT, tmp, sizeof(tmp)); +#else + #error "Invalid I2S mode" #endif #endif From c752cfc5ffc92c103d35992cee092be94d36ab05 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 25 Jul 2024 15:40:10 +0100 Subject: [PATCH 151/288] Add blank lines --- .../ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c index 53677abf..4ba009e3 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c @@ -29,7 +29,8 @@ rtos_i2s_t *i2s_ctx = &i2s_ctx_s; static rtos_uart_tx_t uart_tx_ctx_s; rtos_uart_tx_t *uart_tx_ctx = &uart_tx_ctx_s; + #if appconfRECOVER_MCLK_I2S_APP_PLL static sw_pll_ctx_t sw_pll_ctx_s; sw_pll_ctx_t *sw_pll_ctx = &sw_pll_ctx_s; -#endif \ No newline at end of file +#endif From d6e92811acdec829e622cea566ed322ec0d72fc0 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 25 Jul 2024 16:36:20 +0100 Subject: [PATCH 152/288] Update info about FFD examples --- .../ffd/deploying/configuration.rst | 21 +++++++++++++++++++ .../ffd/deploying/linux_macos.rst | 5 +++++ .../ffd/deploying/native_windows.rst | 8 +++++++ doc/programming_guide/ffd/overview.rst | 2 +- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 905994cd..c14b8046 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -31,6 +31,9 @@ If options are changed, the application firmware must be rebuilt. * - appconfINTENT_UART_OUTPUT_ENABLED - Enables/disables the UART intent message - 1 + * - appconfINTENT_UART_CMD_INFO + - Enables/disables the UART intent debug inform + - 1 * - appconfINTENT_I2C_OUTPUT_ENABLED - Enables/disables the |I2C| intent message - 1 @@ -58,3 +61,21 @@ If options are changed, the application firmware must be rebuilt. * - appconfAUDIO_PIPELINE_SKIP_AGC - Enables/disables the AGC - 0 + +.. list-table:: Additional Compile Options for FFD with I2S audio source + :widths: 90 85 20 + :header-rows: 1 + :align: left + + * - appconfUSE_I2S_INPUT + - Replace I2S audio source instead of the mic array audio source. + - 1 + * - appconfI2S_MODE + - Select I2S mode, supported values are appconfI2S_MODE_MASTER and appconfI2S_MODE_SLAVE + - appconfI2S_MODE_SLAVE + * - appconfI2S_AUDIO_SAMPLE_RATE + - Select the sample rate of the I2S interface, supported values are 16000 and 48000 + - 48000 + * - appconfRECOVER_MCLK_I2S_APP_PLL + - Enables/disables the recovery of the MCLK from the Software PLL application; this removes the need to use an external MCLK. + - 1 diff --git a/doc/programming_guide/ffd/deploying/linux_macos.rst b/doc/programming_guide/ffd/deploying/linux_macos.rst index 04eee990..d6531b45 100644 --- a/doc/programming_guide/ffd/deploying/linux_macos.rst +++ b/doc/programming_guide/ffd/deploying/linux_macos.rst @@ -11,6 +11,11 @@ This document explains how to deploy the software using *CMake* and *Make*. In the commands below ```` can be either ``sensory`` or ``cyberon``, depending on the choice of the speech recognition engine and model. +.. note:: + + The Cyberon speech recognition engine is integrated in two examples. The ``example_ffd_cyberon`` use the mic array as the audio source, and the ``example_ffd_i2s_input_cyberon`` uses the I2S interface as the audio source. + In the rest of this section, we use only the ``example_ffd_`` as an example. + Building the Host Applications ============================== diff --git a/doc/programming_guide/ffd/deploying/native_windows.rst b/doc/programming_guide/ffd/deploying/native_windows.rst index bed339b5..c5b6b2c1 100644 --- a/doc/programming_guide/ffd/deploying/native_windows.rst +++ b/doc/programming_guide/ffd/deploying/native_windows.rst @@ -17,6 +17,14 @@ install with ``winget`` by running the following commands in *PowerShell*: # Reload user Path $env:Path=[System.Environment]::GetEnvironmentVariable("Path","User") +.. note:: + In the commands below ```` can be either ``sensory`` or ``cyberon``, depending on the choice of the speech recognition engine and model. + +.. note:: + + The Cyberon speech recognition engine is integrated in two examples. The ``example_ffd_cyberon`` use the mic array as the audio source, and the ``example_ffd_i2s_input_cyberon`` uses the I2S interface as the audio source. + In the rest of this section, we use only the ``example_ffd_`` as an example. + Building the Host Applications ============================== diff --git a/doc/programming_guide/ffd/overview.rst b/doc/programming_guide/ffd/overview.rst index d5a5b279..62f1a4ef 100644 --- a/doc/programming_guide/ffd/overview.rst +++ b/doc/programming_guide/ffd/overview.rst @@ -5,7 +5,7 @@ Overview ******** -This is the far-field voice local command (FFD) example design. Two examples are provided: both examples include speech recognition and a local dictionary. One example uses the Sensory TrulyHandsfree™ (THF) libraries, and the other one uses the Cyberon DSPotter™ libraries. +This is the far-field voice local command (FFD) example design. Three examples are provided: all examples include speech recognition and a local dictionary. One example uses the Sensory TrulyHandsfree™ (THF) libraries, and the other ones use the Cyberon DSPotter™ libraries. The two examples with the the Cyberon DSPotter™ libraries differ in the audio source fed into the intent engine. One example uses the audio source from the microphone array, and the other uses the audio source from the |I2S| interface. When a wakeword phrase is detected followed by a command phrase, the application will output an audio response and a discrete message over |I2C| and UART. From 6cff8dddcf4e7ec167d6dad0c0640ebef85cf062 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 26 Jul 2024 11:12:22 +0100 Subject: [PATCH 153/288] Add info about new builds --- .../ffd/deploying/linux_macos.rst | 2 +- .../ffd/deploying/native_windows.rst | 2 +- examples/ffd/README.rst | 7 ++++++- examples/ffva/README.rst | 19 +++++++++++++------ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/linux_macos.rst b/doc/programming_guide/ffd/deploying/linux_macos.rst index d6531b45..22afbe7f 100644 --- a/doc/programming_guide/ffd/deploying/linux_macos.rst +++ b/doc/programming_guide/ffd/deploying/linux_macos.rst @@ -13,7 +13,7 @@ This document explains how to deploy the software using *CMake* and *Make*. .. note:: - The Cyberon speech recognition engine is integrated in two examples. The ``example_ffd_cyberon`` use the mic array as the audio source, and the ``example_ffd_i2s_input_cyberon`` uses the I2S interface as the audio source. + The Cyberon speech recognition engine is integrated in two examples. The ``example_ffd_cyberon`` use the microphone array as the audio source, and the ``example_ffd_i2s_input_cyberon`` uses the |I2S| interface as the audio source. In the rest of this section, we use only the ``example_ffd_`` as an example. Building the Host Applications diff --git a/doc/programming_guide/ffd/deploying/native_windows.rst b/doc/programming_guide/ffd/deploying/native_windows.rst index c5b6b2c1..fe9f401d 100644 --- a/doc/programming_guide/ffd/deploying/native_windows.rst +++ b/doc/programming_guide/ffd/deploying/native_windows.rst @@ -22,7 +22,7 @@ install with ``winget`` by running the following commands in *PowerShell*: .. note:: - The Cyberon speech recognition engine is integrated in two examples. The ``example_ffd_cyberon`` use the mic array as the audio source, and the ``example_ffd_i2s_input_cyberon`` uses the I2S interface as the audio source. + The Cyberon speech recognition engine is integrated in two examples. The ``example_ffd_cyberon`` use the microphone array as the audio source, and the ``example_ffd_i2s_input_cyberon`` uses the |I2S| interface as the audio source. In the rest of this section, we use only the ``example_ffd_`` as an example. Building the Host Applications diff --git a/examples/ffd/README.rst b/examples/ffd/README.rst index e1e27b0c..f3600992 100644 --- a/examples/ffd/README.rst +++ b/examples/ffd/README.rst @@ -2,7 +2,7 @@ Far-field Voice Local Command ***************************** -This is the XCORE-VOICE far-field voice local control firmware. Two examples are provided: one example uses the Sensory TrulyHandsfree™ (THF) speech recognition, and the other one uses the Cyberon DSPotter™ speech recognition. +This is the XCORE-VOICE far-field voice local control firmware. Three examples are provided: all examples include speech recognition and a local dictionary. One example uses the Sensory TrulyHandsfree™ (THF) libraries, and the other ones use the Cyberon DSPotter™ libraries. The two examples with the the Cyberon DSPotter™ libraries differ in the audio source fed into the intent engine. One example uses the audio source from the microphone array, and the other uses the audio source from the I2S interface. This software is an evaluation version only. It includes a mechanism that limits the maximum number of recognitions. You can reset the counter to 0 by restarting or rebooting the application. @@ -88,6 +88,11 @@ This application requires a host application to create the flash data partition. In the commands below ```` can be either ``sensory`` or ``cyberon``, depending on the choice of the speech recognition engine and model. +.. note:: + + The Cyberon speech recognition engine is integrated in two examples. The ``example_ffd_cyberon`` use the microphone array as the audio source, and the ``example_ffd_i2s_input_cyberon`` uses the I2S interface as the audio source. + In the rest of this file, we use only the ``example_ffd_`` as an example. + .. note:: Permissions may be required to install the host applications. diff --git a/examples/ffva/README.rst b/examples/ffva/README.rst index 4a9c0760..efa01100 100644 --- a/examples/ffva/README.rst +++ b/examples/ffva/README.rst @@ -87,8 +87,9 @@ On Linux and Mac run: cmake -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build - make example_ffva_int_fixed_delay make example_ffva_ua_adec_altarch + make example_ffva_int_fixed_delay + make example_ffva_int_cyberon_fixed_delay On Windows run: @@ -97,8 +98,9 @@ On Windows run: cmake -G Ninja -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build - ninja example_ffva_int_fixed_delay ninja example_ffva_ua_adec_altarch + ninja example_ffva_int_fixed_delay + ninja example_ffva_int_cyberon_fixed_delay From the build folder, create the data partition containing the filesystem and flash the device with the appropriate command to the desired configuration: @@ -107,15 +109,17 @@ On Linux and Mac run: :: - make flash_app_example_ffva_int_fixed_delay make flash_app_example_ffva_ua_adec_altarch + make flash_app_example_ffva_int_fixed_delay + make flash_app_example_ffva_int_cyberon_fixed_delay On Windows run: :: - ninja flash_app_example_ffva_int_fixed_delay ninja flash_app_example_ffva_ua_adec_altarch + ninja flash_app_example_ffva_int_fixed_delay + ninja flash_app_example_ffva_int_cyberon_fixed_delay Once flashed, the application will run. @@ -129,8 +133,10 @@ Run the following commands in the build folder: :: - xrun --xscope example_ffva_int_fixed_delay.xe xrun --xscope example_ffva_ua_adec_altarch.xe + xrun --xscope example_ffva_int_fixed_delay.xe + xrun --xscope example_ffva_int_cyberon_fixed_delay.xe + Debugging the firmware with `xgdb` ================================= @@ -139,8 +145,9 @@ Run the following commands in the build folder: :: - xgdb -ex "conn --xscope" -ex "r" example_ffva_int_fixed_delay.xe xgdb -ex "conn --xscope" -ex "r" example_ffva_ua_adec_altarch.xe + xgdb -ex "conn --xscope" -ex "r" example_ffva_int_cyberon_fixed_delay.xe + Running the Firmware With WAV Files =================================== From b5eb72b7e59dcab57daf8c425d83d1d1b10e184b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 26 Jul 2024 11:53:53 +0100 Subject: [PATCH 154/288] Add debug info over UART for Sensory --- examples/ffd/src/app_conf.h | 4 ++++ examples/ffva/src/app_conf.h | 4 ++++ modules/asr/sensory/sensory_asr.c | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index f24b5997..6133e0b8 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -83,6 +83,10 @@ #define appconfINTENT_UART_OUTPUT_ENABLED 1 #endif +#ifndef appconfINTENT_UART_CMD_INFO +#define appconfINTENT_UART_CMD_INFO 0 +#endif + #ifndef appconfUART_BAUD_RATE #define appconfUART_BAUD_RATE 9600 #endif diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index c42e40f5..afdf9c00 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -99,6 +99,10 @@ #define appconfINTENT_UART_OUTPUT_ENABLED 1 #endif +#ifndef appconfINTENT_UART_CMD_INFO +#define appconfINTENT_UART_CMD_INFO 0 +#endif + #ifndef appconfUART_BAUD_RATE #define appconfUART_BAUD_RATE 9600 #endif diff --git a/modules/asr/sensory/sensory_asr.c b/modules/asr/sensory/sensory_asr.c index 98cad2ce..b07588e2 100644 --- a/modules/asr/sensory/sensory_asr.c +++ b/modules/asr/sensory/sensory_asr.c @@ -200,6 +200,13 @@ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result) result->start_index = sensory_asr->start_index; result->end_index = sensory_asr->end_index; result->duration = sensory_asr->duration; +#if appconfINTENT_UART_CMD_INFO + static char res_info[128]; + snprintf(res_info, sizeof(res_info)-1, "score:%d,start:%d,end:%d,dur:%d\r\n", result->score, result->start_index, result->end_index, result->duration); + // Enable the printout below to see the information sent over UART + // rtos_printf(res_info); + rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)&res_info, strlen(res_info)); +#endif } else { result->id = 0; result->score = 0; From 27a387580ae998996511cd8f601ef77de70e6f44 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 26 Jul 2024 14:22:23 +0100 Subject: [PATCH 155/288] Skip pipeline stages for new FFD example --- doc/programming_guide/ffd/overview.rst | 6 ++++++ examples/ffd/ffd_i2s_input_cyberon.cmake | 3 +++ .../referenceless/audio_pipeline.c | 18 ++++++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/doc/programming_guide/ffd/overview.rst b/doc/programming_guide/ffd/overview.rst index 62f1a4ef..6d23c9f7 100644 --- a/doc/programming_guide/ffd/overview.rst +++ b/doc/programming_guide/ffd/overview.rst @@ -7,6 +7,12 @@ Overview This is the far-field voice local command (FFD) example design. Three examples are provided: all examples include speech recognition and a local dictionary. One example uses the Sensory TrulyHandsfree™ (THF) libraries, and the other ones use the Cyberon DSPotter™ libraries. The two examples with the the Cyberon DSPotter™ libraries differ in the audio source fed into the intent engine. One example uses the audio source from the microphone array, and the other uses the audio source from the |I2S| interface. +The examples using the microphone array as the audio source include an audio pipeline with the following stages: + + #. Interference Canceler (IC) + Voice To Noise Ratio Estimator (VNR) + #. Noise Suppressor (NS) + #. Adaptive Gain Control (AGC) + When a wakeword phrase is detected followed by a command phrase, the application will output an audio response and a discrete message over |I2C| and UART. Sensory's THF and Cyberon's DSpotter™ libraries ship with an expiring development license. The Sensory one will suspend recognition after 11.4 hours or 107 recognition events, and the Cyberon one will suspend recognition after 100 recognition events. After the maximum number of recognitions is reached, a device reset is required to resume normal operation. To perform a reset, either power cycle the device or press the SW2 button. diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index ea5f3373..8ef83611 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -82,6 +82,9 @@ set(APP_COMPILE_DEFINITIONS appconfI2S_ENABLED=1 appconfRECOVER_MCLK_I2S_APP_PLL=1 appconfINTENT_UART_CMD_INFO=1 + appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR=1 + appconfAUDIO_PIPELINE_SKIP_NS=1 + appconfAUDIO_PIPELINE_SKIP_AGC=1 ) set(APP_LINK_OPTIONS diff --git a/modules/audio_pipelines/referenceless/audio_pipeline.c b/modules/audio_pipelines/referenceless/audio_pipeline.c index 8f90ca23..1c95b653 100644 --- a/modules/audio_pipelines/referenceless/audio_pipeline.c +++ b/modules/audio_pipelines/referenceless/audio_pipeline.c @@ -60,10 +60,16 @@ typedef struct agc_stage_ctx { agc_state_t state; } agc_stage_ctx_t; +#if !appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR static ic_stage_ctx_t DWORD_ALIGNED ic_stage_state = {}; static vnr_pred_stage_ctx_t DWORD_ALIGNED vnr_pred_stage_state = {}; +#endif +#if !appconfAUDIO_PIPELINE_SKIP_NS static ns_stage_ctx_t DWORD_ALIGNED ns_stage_state = {}; +#endif +#if !appconfAUDIO_PIPELINE_SKIP_AGC static agc_stage_ctx_t DWORD_ALIGNED agc_stage_state = {}; +#endif static trace_data_t* trace_data = 0; @@ -162,19 +168,23 @@ static void stage_agc(frame_data_t *frame_data) } static void initialize_pipeline_stages(void) { +#if !appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR ic_init(&ic_stage_state.state); // Set some VNR parameters - ic_stage_state.state.ic_adaption_controller_state.adaption_controller_config.input_vnr_threshold = + ic_stage_state.state.ic_adaption_controller_state.adaption_controller_config.input_vnr_threshold = f64_to_float_s32(IC_INPUT_VNR_THRESHOLD); - ic_stage_state.state.ic_adaption_controller_state.adaption_controller_config.input_vnr_threshold_high = + ic_stage_state.state.ic_adaption_controller_state.adaption_controller_config.input_vnr_threshold_high = f64_to_float_s32(IC_INPUT_VNR_THRESHOLD_HIGH); - +#endif +#if !appconfAUDIO_PIPELINE_SKIP_NS ns_init(&ns_stage_state.state); - +#endif +#if !appconfAUDIO_PIPELINE_SKIP_AGC agc_init(&agc_stage_state.state, &AGC_PROFILE_ASR); agc_stage_state.md.aec_ref_power = AGC_META_DATA_NO_AEC; agc_stage_state.md.aec_corr_factor = AGC_META_DATA_NO_AEC; +#endif } void audio_pipeline_init( From bab1dd3dc0e8123c2eab7fce16ac76883f42ccff Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 26 Jul 2024 14:58:27 +0100 Subject: [PATCH 156/288] Add first I2C slave calls --- .../XK_VOICE_L71/platform/driver_instances.c | 3 +++ .../XK_VOICE_L71/platform/driver_instances.h | 3 +++ .../XK_VOICE_L71/platform/platform_conf.h | 16 +++++++++++++++ .../XK_VOICE_L71/platform/platform_init.c | 10 ++++++++++ .../XK_VOICE_L71/platform/platform_start.c | 20 ++++++++++++++++++- examples/ffd/ffd_i2s_input_cyberon.cmake | 3 +++ examples/ffd/src/app_conf.h | 12 +++++++++++ 7 files changed, 66 insertions(+), 1 deletion(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c index 4ba009e3..49c86aaf 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.c @@ -27,6 +27,9 @@ rtos_i2c_master_t *i2c_master_ctx = &i2c_master_ctx_s; static rtos_i2s_t i2s_ctx_s; rtos_i2s_t *i2s_ctx = &i2s_ctx_s; +static rtos_i2c_slave_t i2c_slave_ctx_s; +rtos_i2c_slave_t *i2c_slave_ctx = &i2c_slave_ctx_s; + static rtos_uart_tx_t uart_tx_ctx_s; rtos_uart_tx_t *uart_tx_ctx = &uart_tx_ctx_s; diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.h b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.h index cee3b29f..ebfb3180 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.h +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/driver_instances.h @@ -12,6 +12,7 @@ extern "C" { #include "rtos_qspi_flash.h" #include "rtos_gpio.h" #include "rtos_i2c_master.h" +#include "rtos_i2c_slave.h" #include "rtos_i2s.h" #include "rtos_uart_tx.h" @@ -31,6 +32,7 @@ extern "C" { /* Tile specifiers */ #define FLASH_TILE_NO 0 #define I2C_TILE_NO 0 +#define I2C_CTRL_TILE_NO I2C_TILE_NO #define MICARRAY_TILE_NO 1 #define I2S_TILE_NO 1 #define UART_TILE_NO 0 @@ -59,6 +61,7 @@ extern rtos_gpio_t *gpio_ctx_t0; extern rtos_gpio_t *gpio_ctx_t1; extern rtos_mic_array_t *mic_array_ctx; extern rtos_i2c_master_t *i2c_master_ctx; +extern rtos_i2c_slave_t *i2c_slave_ctx; extern rtos_i2s_t *i2s_ctx; extern rtos_uart_tx_t *uart_tx_ctx; diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h index 23d382de..bd44a308 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -54,6 +54,14 @@ #define appconfI2S_IO_CORE 2 /* Must be kept off core 0 with the RTOS tick ISR */ #endif /* appconfI2S_IO_CORE */ +#ifndef appconfI2C_IO_CORE +#define appconfI2C_IO_CORE 3 /* Must be kept off core 0 with the RTOS tick ISR */ +#endif /* appconfI2C_IO_CORE */ + +#ifndef appconfI2C_INTERRUPT_CORE +#define appconfI2C_INTERRUPT_CORE 0 /* Must be kept off I/O cores. */ +#endif /* appconfI2C_INTERRUPT_CORE */ + #ifndef appconfPDM_MIC_INTERRUPT_CORE #define appconfPDM_MIC_INTERRUPT_CORE 3 /* Must be kept off I/O cores. Best kept off core 0 with the tick ISR. */ #endif /* appconfPDM_MIC_INTERRUPT_CORE */ @@ -77,6 +85,14 @@ #define appconfPIPELINE_AUDIO_SAMPLE_RATE 16000 #endif /* appconfPIPELINE_AUDIO_SAMPLE_RATE */ +/*****************************************/ +/* Other required defines */ +/*****************************************/ + +#ifndef appconf_CONTROL_I2C_DEVICE_ADDR +#define appconf_CONTROL_I2C_DEVICE_ADDR 0x42 +#endif /* appconf_CONTROL_I2C_DEVICE_ADDR*/ + /*****************************************/ /* I/O Task Priorities */ /*****************************************/ diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index e75a2d1e..fe4c8fd5 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -74,6 +74,15 @@ static void i2c_init(void) { static rtos_driver_rpc_t i2c_rpc_config; +#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) + rtos_i2c_slave_init(i2c_slave_ctx, + (1 << appconfI2C_IO_CORE), + PORT_I2C_SCL, + PORT_I2C_SDA, + appconf_CONTROL_I2C_DEVICE_ADDR); +#endif + +#if appconfI2C_MASTER_ENABLED #if ON_TILE(I2C_TILE_NO) rtos_intertile_t *client_intertile_ctx[1] = {intertile_ctx}; rtos_i2c_master_init( @@ -94,6 +103,7 @@ static void i2c_init(void) &i2c_rpc_config, intertile_ctx); #endif +#endif } #if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index f09943a7..7f2a4496 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -39,16 +39,32 @@ static void flash_start(void) static void i2c_master_start(void) { +#if appconfI2C_MASTER_ENABLED rtos_i2c_master_rpc_config(i2c_master_ctx, appconfI2C_MASTER_RPC_PORT, appconfI2C_MASTER_RPC_PRIORITY); #if ON_TILE(I2C_TILE_NO) rtos_i2c_master_start(i2c_master_ctx); #endif +#endif +} + +static void i2c_slave_start(void) +{ +#if 0 //appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) + rtos_i2c_slave_start(i2c_slave_ctx, + device_control_i2c_ctx, + (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, + (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, + (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, + (rtos_i2c_slave_tx_done_cb_t) NULL, + appconfI2C_INTERRUPT_CORE, + appconfI2C_TASK_PRIORITY); +#endif } static void audio_codec_start(void) { -#if appconfI2S_ENABLED +#if appconfI2S_ENABLED && appconfI2C_MASTER_ENABLED int ret = 0; #if ON_TILE(I2C_TILE_NO) if (dac3101_init(appconfI2S_AUDIO_SAMPLE_RATE) != 0) { @@ -114,4 +130,6 @@ void platform_start(void) mics_start(); i2s_start(); uart_start(); + // I2C slave can be started only after i2c_master_start() is completed + i2c_slave_start(); } diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 8ef83611..a2810afd 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -80,6 +80,9 @@ set(APP_COMPILE_DEFINITIONS appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 appconfI2S_ENABLED=1 + appconfI2C_SLAVE_ENABLED=0 + appconfI2C_MASTER_ENABLED=0 + appconfINTENT_I2C_OUTPUT_ENABLED=0 appconfRECOVER_MCLK_I2S_APP_PLL=1 appconfINTENT_UART_CMD_INFO=1 appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR=1 diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 6133e0b8..fc0be1d0 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -107,6 +107,18 @@ #define appconfI2S_MODE appconfI2S_MODE_MASTER #endif +#ifndef appconfI2C_MASTER_ENABLED +#define appconfI2C_MASTER_ENABLED 1 +#endif + +#ifndef appconfI2C_SLAVE_ENABLED +#define appconfI2C_SLAVE_ENABLED 0 +#endif + +#if appconfINTENT_I2C_OUTPUT_ENABLED==1 && appconfI2C_MASTER_ENABLED==0 +#error "I2C master must be enabled for intent I2C output" +#endif + #ifndef appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR #define appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR 0 #endif From 8a3dccb337b2a7d7274c559ea86d25b6abb8bc2f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 29 Jul 2024 18:26:33 +0100 Subject: [PATCH 157/288] Fix I2C defines --- examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c | 4 ++-- examples/ffd/ffd_i2s_input_cyberon.cmake | 2 +- examples/ffd/src/app_conf.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index fe4c8fd5..7ab6d2ea 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -72,8 +72,6 @@ static void gpio_init(void) static void i2c_init(void) { - static rtos_driver_rpc_t i2c_rpc_config; - #if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_init(i2c_slave_ctx, (1 << appconfI2C_IO_CORE), @@ -83,6 +81,8 @@ static void i2c_init(void) #endif #if appconfI2C_MASTER_ENABLED + static rtos_driver_rpc_t i2c_rpc_config; + #if ON_TILE(I2C_TILE_NO) rtos_intertile_t *client_intertile_ctx[1] = {intertile_ctx}; rtos_i2c_master_init( diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index a2810afd..ca14917b 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -80,7 +80,7 @@ set(APP_COMPILE_DEFINITIONS appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 appconfI2S_ENABLED=1 - appconfI2C_SLAVE_ENABLED=0 + appconfI2C_SLAVE_ENABLED=1 appconfI2C_MASTER_ENABLED=0 appconfINTENT_I2C_OUTPUT_ENABLED=0 appconfRECOVER_MCLK_I2S_APP_PLL=1 diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index fc0be1d0..69480075 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -108,7 +108,7 @@ #endif #ifndef appconfI2C_MASTER_ENABLED -#define appconfI2C_MASTER_ENABLED 1 +#define appconfI2C_MASTER_ENABLED 1 #endif #ifndef appconfI2C_SLAVE_ENABLED From ae76aadbcf2f54b27df986af26150fe3698278f2 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 29 Jul 2024 18:29:06 +0100 Subject: [PATCH 158/288] Add entry --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2deda70a..fc8aa04b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,8 @@ XCORE-VOICE change log intent_handler from examples/ffd/src to folder modules/asr. * CHANGED: Remove need to use external MCLK in FFVA INT examples * ADDED: Support for DFU over I2C for FFVA INT example. + * ADDED: FFD example with I2S audio input to Cyberon speech recognition + engine and model. * CHANGED: Updated submodule fwk_rtos to version 3.1.0 from 3.0.5. * ADDED: lib_sw_pll submodule v1.1.0. * REMOVED: flash settings in .xn files, as they are not required by XMOS From 3b72cbd0b9ad02f41f11938dba5bab0c7a28da1e Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 29 Jul 2024 18:29:06 +0100 Subject: [PATCH 159/288] Add entry --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2deda70a..fc8aa04b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,8 @@ XCORE-VOICE change log intent_handler from examples/ffd/src to folder modules/asr. * CHANGED: Remove need to use external MCLK in FFVA INT examples * ADDED: Support for DFU over I2C for FFVA INT example. + * ADDED: FFD example with I2S audio input to Cyberon speech recognition + engine and model. * CHANGED: Updated submodule fwk_rtos to version 3.1.0 from 3.0.5. * ADDED: lib_sw_pll submodule v1.1.0. * REMOVED: flash settings in .xn files, as they are not required by XMOS From a062e8ef41b8c3c7f71ba4b9c190a10c8160f662 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 30 Jul 2024 08:56:33 +0100 Subject: [PATCH 160/288] Use replacements --- .../ffd/deploying/configuration.rst | 8 +++--- .../mic_aggregator/mic_aggregator.rst | 28 +++++++++---------- doc/shared/introduction.rst | 4 +-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index c14b8046..4db91f3f 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -62,19 +62,19 @@ If options are changed, the application firmware must be rebuilt. - Enables/disables the AGC - 0 -.. list-table:: Additional Compile Options for FFD with I2S audio source +.. list-table:: Additional Compile Options for FFD with |I2S| audio source :widths: 90 85 20 :header-rows: 1 :align: left * - appconfUSE_I2S_INPUT - - Replace I2S audio source instead of the mic array audio source. + - Replace |I2S| audio source instead of the mic array audio source. - 1 * - appconfI2S_MODE - - Select I2S mode, supported values are appconfI2S_MODE_MASTER and appconfI2S_MODE_SLAVE + - Select |I2S| mode, supported values are appconfI2S_MODE_MASTER and appconfI2S_MODE_SLAVE - appconfI2S_MODE_SLAVE * - appconfI2S_AUDIO_SAMPLE_RATE - - Select the sample rate of the I2S interface, supported values are 16000 and 48000 + - Select the sample rate of the |I2S| interface, supported values are 16000 and 48000 - 48000 * - appconfRECOVER_MCLK_I2S_APP_PLL - Enables/disables the recovery of the MCLK from the Software PLL application; this removes the need to use an external MCLK. diff --git a/doc/programming_guide/mic_aggregator/mic_aggregator.rst b/doc/programming_guide/mic_aggregator/mic_aggregator.rst index 478974e4..ff7b556c 100644 --- a/doc/programming_guide/mic_aggregator/mic_aggregator.rst +++ b/doc/programming_guide/mic_aggregator/mic_aggregator.rst @@ -7,10 +7,10 @@ Operation ========= The design consists of a number of tasks connected via the xcore-ai silicon communication channels. -The decimators in the microphone array are configured to produce a 48 kHz PCM output. +The decimators in the microphone array are configured to produce a 48 kHz PCM output. The 16 output channels are loaded into a 16 slot TDM slave peripheral running at 24.576 MHz bit clock or a USB Audio Class 2 asynchronous interface and are optionally -amplified. The TDM build also provides a simple I2C slave interface to allow +amplified. The TDM build also provides a simple |I2C| slave interface to allow gains to be controlled at run-time. The USB build supports USB Audio Class 2 compliant volume controls. For the TDM build, a simple TDM16 master peripheral is included as well as a local @@ -42,13 +42,13 @@ xcore-ai architecture. The thread diagrams are shown in :numref:`agg_tdm` and :n PDM Capture ----------- -Both the TDM and USB aggregator examples share a common PDM front end. This consists of an 8 bit port +Both the TDM and USB aggregator examples share a common PDM front end. This consists of an 8 bit port with each data line connected to two PDM microphones each configured to provide data on a different clock edge. The 3.072 MHz clock for the PDM microphones is provided by the xcore-ai device on a 1 bit port and clocks all PDM microphones. The PDM clock is divided down from the 24.576 MHz local MCLK. -The data collected by the 8 bit port is sent to the lib_mic_array block which de-interleaves +The data collected by the 8 bit port is sent to the lib_mic_array block which de-interleaves the PDM data streams and performs decimation of the PDM data down to 48 kHz 32 bit PCM samples. Due to the large number of microphones the PDM capture stage uses four hardware threads on tile[0]; one for the microphone capture and three for decimation. This is needed to divide the processing workload and meet timing comfortably. @@ -62,11 +62,11 @@ Audio Hub The 16 channels of 48 kHz PCM streams are collected by `Hub` and are amplified using a saturated gain stage. The initial gain is set to 100, since a gain of 1 sounds very quiet due to the mic_array output being scaled to allow acoustic -overload of the microphones without clipping within the decimators. This value can be +overload of the microphones without clipping within the decimators. This value can be overridden using the ``MIC_GAIN_INIT`` define in `app_conf.h`. Additionally for the TDM configuration, the `Hub` task also checks for control packets -from I2C which may be used to dynamically update the individual gains at runtime. +from |I2C| which may be used to dynamically update the individual gains at runtime. A single hardware thread contains the task and a triple buffer scheme is used to ensure there is always a free buffer available to write into regardless of the relative phase between the production @@ -80,9 +80,9 @@ TDM Host Connection ------------------- The TDM build supports a 16-slot TDM slave Tx peripheral from the fwk_io sub-module. In this application -it runs at 24.576 MHz bit clock which supports 16 channels of 32 bit, 48 kHz samples per frame. +it runs at 24.576 MHz bit clock which supports 16 channels of 32 bit, 48 kHz samples per frame. -The TDM component uses a single hardware thread. +The TDM component uses a single hardware thread. For the purpose of debugging a simple TDM 16 Master Rx component is provided. This allows the transmitted TDM frames from the application to be received and checked without having to connect an external @@ -113,7 +113,7 @@ Resource Usage ============== The xcore-ai device has a total resource count of 2 x 524288 Bytes of memory and 2 x 8 hardware threads across two tiles. -This application uses around half of the processing resources and a tiny fraction of the available memory +This application uses around half of the processing resources and a tiny fraction of the available memory meaning there is plenty of space inside the chip for additional functionality if needed. TDM Build @@ -124,7 +124,7 @@ Tile Memory Threads ===== ======== ======= 0 25996 5 1 22812 2* -Total 48808 7 +Total 48808 7 ===== ======== ======= * An additional debug TDM Master thread is used on Tile[1] by default which is not needed in a practical deployment. @@ -137,7 +137,7 @@ Tile Memory Threads ===== ======== ======= 0 24252 4 1 52116 5 -Total 76368 9 +Total 76368 9 ===== ======== ======= @@ -171,7 +171,7 @@ Mic pair J14 pin 7, 15 21 ======== ======= -For I2C control, make the following connections: +For |I2C| control, make the following connections: =============== ================ Host Connection Board Connection @@ -181,7 +181,7 @@ SDA IOL Your I2C host SDA. GND Your I2C host ground. =============== ================ -The I2C slave is tested at 100 kHz SCL. +The |I2C| slave is tested at 100 kHz SCL. I2C Controlled Volume ===================== @@ -233,7 +233,7 @@ Register Value 31 Channel 15 lower gain byte ======== ========================== -If using a raspberry Pi as the I2C host you may use the following +If using a raspberry Pi as the |I2C| host you may use the following commands: :: diff --git a/doc/shared/introduction.rst b/doc/shared/introduction.rst index ccb23e39..a99be59e 100644 --- a/doc/shared/introduction.rst +++ b/doc/shared/introduction.rst @@ -9,7 +9,7 @@ XCORE-VOICE example designs include turn-key solutions to enable easier product The C SDK is composed of the following components: -- Peripheral IO libraries including; UART, I2C, I2S, SPI, QSPI, PDM microphones, and USB. These libraries support bare-metal and RTOS application development. +- Peripheral IO libraries including; UART, |I2C|, |I2S|, SPI, QSPI, PDM microphones, and USB. These libraries support bare-metal and RTOS application development. - Libraries core to DSP applications, including vectorized math and voice processing DSP. These libraries support bare-metal and RTOS application development. - Libraries for speech recognition applications. These libraries support bare-metal and RTOS application development. - Libraries that enable multi-core FreeRTOS development on xcore including a wide array of RTOS drivers and middleware. @@ -59,7 +59,7 @@ These include: - Boot from QSPI Flash - Default firmware image for power-on operation - Option to boot from a local host processor via SPI -- Device Firmware Update (DFU) via USB or I2C +- Device Firmware Update (DFU) via USB or |I2C| **Power Consumption** From ac8740af23e791d9a2cac7f30c23548f66df0984 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 30 Jul 2024 11:36:17 +0100 Subject: [PATCH 161/288] Review comments --- doc/programming_guide/ffd/deploying/configuration.rst | 4 ++-- doc/programming_guide/ffd/overview.rst | 2 +- examples/ffd/README.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 4db91f3f..51e07b60 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -32,7 +32,7 @@ If options are changed, the application firmware must be rebuilt. - Enables/disables the UART intent message - 1 * - appconfINTENT_UART_CMD_INFO - - Enables/disables the UART intent debug inform + - Enables/disables the UART intent debug information - 1 * - appconfINTENT_I2C_OUTPUT_ENABLED - Enables/disables the |I2C| intent message @@ -68,7 +68,7 @@ If options are changed, the application firmware must be rebuilt. :align: left * - appconfUSE_I2S_INPUT - - Replace |I2S| audio source instead of the mic array audio source. + - Replace |I2S| audio source instead of the microphone array audio source. - 1 * - appconfI2S_MODE - Select |I2S| mode, supported values are appconfI2S_MODE_MASTER and appconfI2S_MODE_SLAVE diff --git a/doc/programming_guide/ffd/overview.rst b/doc/programming_guide/ffd/overview.rst index 6d23c9f7..868a6c0c 100644 --- a/doc/programming_guide/ffd/overview.rst +++ b/doc/programming_guide/ffd/overview.rst @@ -5,7 +5,7 @@ Overview ******** -This is the far-field voice local command (FFD) example design. Three examples are provided: all examples include speech recognition and a local dictionary. One example uses the Sensory TrulyHandsfree™ (THF) libraries, and the other ones use the Cyberon DSPotter™ libraries. The two examples with the the Cyberon DSPotter™ libraries differ in the audio source fed into the intent engine. One example uses the audio source from the microphone array, and the other uses the audio source from the |I2S| interface. +This is the far-field voice local command (FFD) example design. Three examples are provided: all examples include speech recognition and a local dictionary. One example uses the Sensory TrulyHandsfree™ (THF) libraries, and the other ones use the Cyberon DSPotter™ libraries. The two examples with the Cyberon DSPotter™ libraries differ in the audio source fed into the intent engine. One example uses the audio source from the microphone array, and the other uses the audio source from the |I2S| interface. The examples using the microphone array as the audio source include an audio pipeline with the following stages: diff --git a/examples/ffd/README.rst b/examples/ffd/README.rst index f3600992..01956d24 100644 --- a/examples/ffd/README.rst +++ b/examples/ffd/README.rst @@ -2,7 +2,7 @@ Far-field Voice Local Command ***************************** -This is the XCORE-VOICE far-field voice local control firmware. Three examples are provided: all examples include speech recognition and a local dictionary. One example uses the Sensory TrulyHandsfree™ (THF) libraries, and the other ones use the Cyberon DSPotter™ libraries. The two examples with the the Cyberon DSPotter™ libraries differ in the audio source fed into the intent engine. One example uses the audio source from the microphone array, and the other uses the audio source from the I2S interface. +This is the XCORE-VOICE far-field voice local control firmware. Three examples are provided: all examples include speech recognition and a local dictionary. One example uses the Sensory TrulyHandsfree™ (THF) libraries, and the other ones use the Cyberon DSPotter™ libraries. The two examples with the Cyberon DSPotter™ libraries differ in the audio source fed into the intent engine. One example uses the audio source from the microphone array, and the other uses the audio source from the I2S interface. This software is an evaluation version only. It includes a mechanism that limits the maximum number of recognitions. You can reset the counter to 0 by restarting or rebooting the application. From 94158e7d9fef586363fc456bb6b610e17bb440d3 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 30 Jul 2024 11:53:09 +0100 Subject: [PATCH 162/288] Rename define --- doc/programming_guide/ffd/deploying/configuration.rst | 4 ++-- examples/ffd/ffd_i2s_input_cyberon.cmake | 2 +- examples/ffd/src/app_conf.h | 4 ++-- examples/ffva/src/app_conf.h | 4 ++-- modules/asr/Cyberon/DSpotter_asr.c | 2 +- modules/asr/intent_engine/intent_engine_io.c | 2 +- modules/asr/sensory/sensory_asr.c | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 51e07b60..6e68f3bc 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -31,9 +31,9 @@ If options are changed, the application firmware must be rebuilt. * - appconfINTENT_UART_OUTPUT_ENABLED - Enables/disables the UART intent message - 1 - * - appconfINTENT_UART_CMD_INFO + * - appconfINTENT_UART_DEBUG_INFO_ENABLED - Enables/disables the UART intent debug information - - 1 + - 0 * - appconfINTENT_I2C_OUTPUT_ENABLED - Enables/disables the |I2C| intent message - 1 diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 8ef83611..9c990b2e 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -81,7 +81,7 @@ set(APP_COMPILE_DEFINITIONS appconfI2S_AUDIO_SAMPLE_RATE=48000 appconfI2S_ENABLED=1 appconfRECOVER_MCLK_I2S_APP_PLL=1 - appconfINTENT_UART_CMD_INFO=1 + appconfINTENT_UART_DEBUG_INFO_ENABLED=1 appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR=1 appconfAUDIO_PIPELINE_SKIP_NS=1 appconfAUDIO_PIPELINE_SKIP_AGC=1 diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 6133e0b8..29eea01b 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -83,8 +83,8 @@ #define appconfINTENT_UART_OUTPUT_ENABLED 1 #endif -#ifndef appconfINTENT_UART_CMD_INFO -#define appconfINTENT_UART_CMD_INFO 0 +#ifndef appconfINTENT_UART_DEBUG_INFO_ENABLED +#define appconfINTENT_UART_DEBUG_INFO_ENABLED 0 #endif #ifndef appconfUART_BAUD_RATE diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index afdf9c00..6b5872ea 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -99,8 +99,8 @@ #define appconfINTENT_UART_OUTPUT_ENABLED 1 #endif -#ifndef appconfINTENT_UART_CMD_INFO -#define appconfINTENT_UART_CMD_INFO 0 +#ifndef appconfINTENT_UART_DEBUG_INFO_ENABLED +#define appconfINTENT_UART_DEBUG_INFO_ENABLED 0 #endif #ifndef appconfUART_BAUD_RATE diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index 44c23912..79da9ee7 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -176,7 +176,7 @@ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result) result->start_index = -1; result->end_index = -1; result->duration = -1; - #if appconfINTENT_UART_CMD_INFO + #if appconfINTENT_UART_DEBUG_INFO_ENABLED static char res_info[128]; snprintf(res_info, sizeof(res_info)-1, "ID:%d,Sc:%d,SGD:%d,En:%d\r\n", nCmdID, nCmdScore, nCmdSG, nCmdEnergy); // Enable the printout below to see the information sent over UART diff --git a/modules/asr/intent_engine/intent_engine_io.c b/modules/asr/intent_engine/intent_engine_io.c index bbe91be2..2c73d916 100644 --- a/modules/asr/intent_engine/intent_engine_io.c +++ b/modules/asr/intent_engine/intent_engine_io.c @@ -96,7 +96,7 @@ void intent_engine_process_asr_result(int word_id) } } rtos_printf("RECOGNIZED: 0x%x, %s\n", (int) word_id, (char*)text); -#if appconfINTENT_UART_CMD_INFO +#if appconfINTENT_UART_DEBUG_INFO_ENABLED static char res_info[128]; snprintf(res_info, sizeof(res_info)-1, "Cmd:%s\r\n", text); // Enable the printout below to see the information sent over UART diff --git a/modules/asr/sensory/sensory_asr.c b/modules/asr/sensory/sensory_asr.c index b07588e2..a05c51d1 100644 --- a/modules/asr/sensory/sensory_asr.c +++ b/modules/asr/sensory/sensory_asr.c @@ -200,7 +200,7 @@ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result) result->start_index = sensory_asr->start_index; result->end_index = sensory_asr->end_index; result->duration = sensory_asr->duration; -#if appconfINTENT_UART_CMD_INFO +#if appconfINTENT_UART_DEBUG_INFO_ENABLED static char res_info[128]; snprintf(res_info, sizeof(res_info)-1, "score:%d,start:%d,end:%d,dur:%d\r\n", result->score, result->start_index, result->end_index, result->duration); // Enable the printout below to see the information sent over UART From 8f6cddc3ba9a4add58ff038ff756ffb3ff0500c7 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 30 Jul 2024 12:06:11 +0100 Subject: [PATCH 163/288] More changes --- doc/programming_guide/asrc/resource_usage.rst | 4 +-- .../ffd/deploying/configuration.rst | 31 +++++++++---------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/doc/programming_guide/asrc/resource_usage.rst b/doc/programming_guide/asrc/resource_usage.rst index d628828d..360c2c16 100644 --- a/doc/programming_guide/asrc/resource_usage.rst +++ b/doc/programming_guide/asrc/resource_usage.rst @@ -68,8 +68,8 @@ Intertile contexts The application uses 3 intertile contexts for cross tile communication. - * A dedicated intertile context for sending ASRC output data from the I2S tile to the USB tile. - * A dedicated intertile context for sending ASRC output data from the USB tile to the I2S tile. + * A dedicated intertile context for sending ASRC output data from the |I2S| tile to the USB tile. + * A dedicated intertile context for sending ASRC output data from the USB tile to the |I2S| tile. * The intertile context for all other cross tile communication. diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 6e68f3bc..e46e9f19 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -40,6 +40,18 @@ If options are changed, the application firmware must be rebuilt. * - appconfUART_BAUD_RATE - Sets the baud rate for the UART tx intent interface - 9600 + * - appconfUSE_I2S_INPUT + - Replace |I2S| audio source instead of the microphone array audio source. + - 0 + * - appconfI2S_MODE + - Select |I2S| mode, supported values are appconfI2S_MODE_MASTER and appconfI2S_MODE_SLAVE + - master + * - appconfI2S_AUDIO_SAMPLE_RATE + - Select the sample rate of the |I2S| interface, supported values are 16000 and 48000 + - 16000 + * - appconfRECOVER_MCLK_I2S_APP_PLL + - Enables/disables the recovery of the MCLK from the Software PLL application; this removes the need to use an external MCLK. + - 0 * - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR - Sets the |I2C| slave address to transmit the intent to - 0x01 @@ -62,20 +74,5 @@ If options are changed, the application firmware must be rebuilt. - Enables/disables the AGC - 0 -.. list-table:: Additional Compile Options for FFD with |I2S| audio source - :widths: 90 85 20 - :header-rows: 1 - :align: left - - * - appconfUSE_I2S_INPUT - - Replace |I2S| audio source instead of the microphone array audio source. - - 1 - * - appconfI2S_MODE - - Select |I2S| mode, supported values are appconfI2S_MODE_MASTER and appconfI2S_MODE_SLAVE - - appconfI2S_MODE_SLAVE - * - appconfI2S_AUDIO_SAMPLE_RATE - - Select the sample rate of the |I2S| interface, supported values are 16000 and 48000 - - 48000 - * - appconfRECOVER_MCLK_I2S_APP_PLL - - Enables/disables the recovery of the MCLK from the Software PLL application; this removes the need to use an external MCLK. - - 1 +The ``example_ffd_i2s_input_cyberon`` has different default values from the ones in the table above. +The list of updated values can be found in the ``APP_COMPILE_DEFINITIONS`` list in ``examples\ffd\ffd_i2s_input_cyberon.cmake``. From 37d3632fdc468d8d0b18d69e3f26d987d00c4cab Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 30 Jul 2024 12:40:28 +0100 Subject: [PATCH 164/288] Use note --- doc/programming_guide/ffd/deploying/configuration.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index e46e9f19..0d130cf3 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -74,5 +74,7 @@ If options are changed, the application firmware must be rebuilt. - Enables/disables the AGC - 0 -The ``example_ffd_i2s_input_cyberon`` has different default values from the ones in the table above. -The list of updated values can be found in the ``APP_COMPILE_DEFINITIONS`` list in ``examples\ffd\ffd_i2s_input_cyberon.cmake``. +.. note:: + + The ``example_ffd_i2s_input_cyberon`` has different default values from the ones in the table above. + The list of updated values can be found in the ``APP_COMPILE_DEFINITIONS`` list in ``examples\ffd\ffd_i2s_input_cyberon.cmake``. From 91ce681e8e31ab3b545d9aea28e5bb40e87f36c4 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 31 Jul 2024 15:29:47 +0100 Subject: [PATCH 165/288] Example with I2C control compiles --- .../XK_VOICE_L71/platform/platform_conf.h | 18 +- .../XK_VOICE_L71/platform/platform_init.c | 21 +++ .../XK_VOICE_L71/platform/platform_start.c | 11 +- examples/ffd/ffd_i2s_input_cyberon.cmake | 3 + examples/ffd/src/app_conf.h | 1 + examples/ffd/src/control/cmd_map.h | 39 ++++ examples/ffd/src/control/intent_servicer.c | 125 +++++++++++++ examples/ffd/src/control/intent_servicer.h | 53 ++++++ examples/ffd/src/control/servicer.c | 169 ++++++++++++++++++ examples/ffd/src/control/servicer.h | 124 +++++++++++++ examples/ffd/src/main.c | 15 +- examples/ffd/src/tusb_config.h | 136 ++++++++++++++ .../platform/platform_start.c | 1 - .../XK_VOICE_L71/platform/platform_conf.h | 4 +- modules/asr/Cyberon/DSpotter_asr.c | 8 +- modules/asr/asr.h | 8 + 16 files changed, 721 insertions(+), 15 deletions(-) create mode 100644 examples/ffd/src/control/cmd_map.h create mode 100644 examples/ffd/src/control/intent_servicer.c create mode 100644 examples/ffd/src/control/intent_servicer.h create mode 100644 examples/ffd/src/control/servicer.c create mode 100644 examples/ffd/src/control/servicer.h create mode 100644 examples/ffd/src/tusb_config.h diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h index bd44a308..525ab376 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -89,6 +89,14 @@ /* Other required defines */ /*****************************************/ +#ifndef APP_CONTROL_TRANSPORT_COUNT +#define APP_CONTROL_TRANSPORT_COUNT appconfI2C_SLAVE_ENABLED +#endif // APP_CONTROL_TRANSPORT_COUNT + +#ifndef appconf_CONTROL_I2C_DEVICE_ADDR +#define appconf_CONTROL_I2C_DEVICE_ADDR 0x42 +#endif /* appconf_CONTROL_I2C_DEVICE_ADDR*/ + #ifndef appconf_CONTROL_I2C_DEVICE_ADDR #define appconf_CONTROL_I2C_DEVICE_ADDR 0x42 #endif /* appconf_CONTROL_I2C_DEVICE_ADDR*/ @@ -97,7 +105,15 @@ /* I/O Task Priorities */ /*****************************************/ #ifndef appconfQSPI_FLASH_TASK_PRIORITY -#define appconfQSPI_FLASH_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) +#define appconfQSPI_FLASH_TASK_PRIORITY (configMAX_PRIORITIES-1) #endif /* appconfQSPI_FLASH_TASK_PRIORITY */ +#ifndef appconfI2C_TASK_PRIORITY +#define appconfI2C_TASK_PRIORITY (configMAX_PRIORITIES/2) +#endif /* appconfI2C_TASK_PRIORITY */ + +#ifndef appconfDEVICE_CONTROL_I2C_PRIORITY +#define appconfDEVICE_CONTROL_I2C_PRIORITY (configMAX_PRIORITIES-1) +#endif // appconfDEVICE_CONTROL_I2C_PRIORITY + #endif /* PLATFORM_CONF_H_ */ diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 7ab6d2ea..8b4fa525 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -9,6 +9,10 @@ #include "platform/app_pll_ctrl.h" #include "platform/driver_instances.h" #include "platform/platform_init.h" +#include "device_control.h" +#include "servicer.h" + +extern device_control_t *device_control_i2c_ctx; static void mclk_init(chanend_t other_tile_c) { @@ -255,6 +259,22 @@ static void uart_init(void) #endif } +void control_init() { +#if appconfI2C_SLAVE+ENABLED == 1 && ON_TILE(I2C_TILE_NO) + control_ret_t ret = CONTROL_SUCCESS; + ret = device_control_init(device_control_i2c_ctx, + DEVICE_CONTROL_HOST_MODE, + (NUM_TILE_0_SERVICERS + NUM_TILE_1_SERVICERS), + NULL, 0); + xassert(ret == CONTROL_SUCCESS); + + ret = device_control_start(device_control_i2c_ctx, + -1, + -1); + xassert(ret == CONTROL_SUCCESS); +#endif +} + void platform_init(chanend_t other_tile_c) { rtos_intertile_init(intertile_ctx, other_tile_c); @@ -267,4 +287,5 @@ void platform_init(chanend_t other_tile_c) mics_init(); i2s_init(); uart_init(); + control_init(); } diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index 7f2a4496..3855b5d4 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -15,6 +15,11 @@ #include "platform/driver_instances.h" #include "dac3101.h" +#if appconfI2C_SLAVE_ENABLED == 1 +#include "servicer.h" +#include "device_control_i2c.h" +#endif + extern void i2s_rate_conversion_enable(void); static void gpio_start(void) @@ -39,7 +44,7 @@ static void flash_start(void) static void i2c_master_start(void) { -#if appconfI2C_MASTER_ENABLED +#if appconfI2C_ENABLED == 1 && appconfI2C_MODE == appconfI2C_MODE_MASTER rtos_i2c_master_rpc_config(i2c_master_ctx, appconfI2C_MASTER_RPC_PORT, appconfI2C_MASTER_RPC_PRIORITY); #if ON_TILE(I2C_TILE_NO) @@ -50,13 +55,15 @@ static void i2c_master_start(void) static void i2c_slave_start(void) { -#if 0 //appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_SLAVE_ENABLED == 1 && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_start(i2c_slave_ctx, device_control_i2c_ctx, (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, (rtos_i2c_slave_tx_done_cb_t) NULL, + NULL, + NULL, appconfI2C_INTERRUPT_CORE, appconfI2C_TASK_PRIORITY); #endif diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 55bb079b..eef54a63 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -11,6 +11,7 @@ file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src + ${CMAKE_CURRENT_LIST_DIR}/src/control ${CMAKE_CURRENT_LIST_DIR}/src/gpio_ctrl ${CMAKE_CURRENT_LIST_DIR}/src/intent_engine ${CMAKE_CURRENT_LIST_DIR}/src/power @@ -104,6 +105,8 @@ set(APP_COMMON_LINK_LIBRARIES sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 + rtos::sw_services::device_control + rtos::freertos_usb lib_src lib_sw_pll ) diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index a3f2079b..82af6bd3 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -10,6 +10,7 @@ #define appconfINTENT_MODEL_RUNNER_SAMPLES_PORT 3 #define appconfI2C_MASTER_RPC_PORT 4 #define appconfI2S_RPC_PORT 5 +#define appconfDEVICE_CONTROL_I2C_PORT 6 #define appconfINTENT_ENGINE_READY_SYNC_PORT 16 #define appconfI2S_OUTPUT_SLAVE_PORT 8 diff --git a/examples/ffd/src/control/cmd_map.h b/examples/ffd/src/control/cmd_map.h new file mode 100644 index 00000000..71467e20 --- /dev/null +++ b/examples/ffd/src/control/cmd_map.h @@ -0,0 +1,39 @@ +// Copyright 2022-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include +#include "device_control_shared.h" + +// Types of commands in the Command ID enum space - +// - Dedicated: Commands dedicated only for the resource +// - For SHF this is further spilt into dedicated SHF commands and dedicated custom commands +// - Shared: Commands shared between a resource and its servicer. These are present in the servicer's command map only though respources process them as well. (Used for special commands protocol) +// - External: space reserved for customers to add commands. +#define DEDICATED_COMMANDS_START_OFFSET (0) +#define SHARED_COMMANDS_START_OFFSET (90) +#define EXTERNAL_COMMANDS_START_OFFSET (110) +// Servicers will have commands in the above 4 categories. +// Resources will have commands in the dedicated, reserved and broadcast categories. + + +typedef enum +{ + CMD_READ_ONLY, + CMD_READ_WRITE, + CMD_WRITE_ONLY +}cmd_rw_type_t; + +typedef struct +{ + uint8_t cmd_id; + uint8_t num_vals; + uint8_t bytes_per_val; + uint8_t cmd_rw_type; +}control_cmd_info_t; + +typedef struct { + int32_t num_commands; + control_cmd_info_t *commands; +}command_map_t; diff --git a/examples/ffd/src/control/intent_servicer.c b/examples/ffd/src/control/intent_servicer.c new file mode 100644 index 00000000..6b2e022e --- /dev/null +++ b/examples/ffd/src/control/intent_servicer.c @@ -0,0 +1,125 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#define DEBUG_UNIT INTENT_SERVICER +#ifndef DEBUG_PRINT_ENABLE_INTENT_SERVICER +#define DEBUG_PRINT_ENABLE_INTENT_SERVICER 0 +#endif +#include "debug_print.h" + +#include +#include +#include +#include + +#include "platform/platform_conf.h" +#include "servicer.h" +#include "intent_servicer.h" + +#include "device_control_i2c.h" + +enum e_intent_controller_servicer_resid_cmds +{ +#ifndef INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION + INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION = 0, +#endif + NUM_INTENT_CONTROLLER_SERVICER_RESID_CMDS +}; +static control_cmd_info_t intent_controller_servicer_resid_cmd_map[] = +{ + { INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION, 1, sizeof(uint8_t), CMD_READ_ONLY }, +}; + +void intent_servicer_init(servicer_t *servicer) +{ + // Servicer resource info + static control_resource_info_t intent_res_info[NUM_RESOURCES_INTENT_SERVICER]; + + memset(servicer, 0, sizeof(servicer_t)); + servicer->id = INTENT_CONTROLLER_SERVICER_RESID; + servicer->start_io = 0; + servicer->num_resources = NUM_RESOURCES_INTENT_SERVICER; + + servicer->res_info = &intent_res_info[0]; + // Servicer resource + servicer->res_info[0].resource = INTENT_CONTROLLER_SERVICER_RESID; + servicer->res_info[0].command_map.num_commands = NUM_INTENT_CONTROLLER_SERVICER_RESID_CMDS; + servicer->res_info[0].command_map.commands = intent_controller_servicer_resid_cmd_map; +} + +void intent_servicer(void *args) { + device_control_servicer_t servicer_ctx; + + servicer_t *servicer = (servicer_t*)args; + xassert(servicer != NULL); + + control_resid_t *resources = (control_resid_t*)pvPortMalloc(servicer->num_resources * sizeof(control_resid_t)); + for(int i=0; inum_resources; i++) + { + resources[i] = servicer->res_info[i].resource; + } + + control_ret_t dc_ret; + debug_printf("Calling device_control_servicer_register(), servicer ID %d, on tile %d, core %d.\n", servicer->id, THIS_XCORE_TILE, rtos_core_id_get()); + + dc_ret = device_control_servicer_register(&servicer_ctx, + device_control_ctxs, + 1, + resources, servicer->num_resources); + debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); + + vPortFree(resources); + + for(;;){ + device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); + } +} + +control_ret_t intent_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) +{ + control_ret_t ret = CONTROL_SUCCESS; + uint8_t cmd_id = CONTROL_CMD_CLEAR_READ(cmd); + + memset(payload, 0, payload_len); + + debug_printf("intent_servicer_read_cmd, cmd_id: %d.\n", cmd_id); + + switch (cmd_id) + { + case INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION: + { + debug_printf("INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION\n"); + uint8_t last_detection = 0;//intent_get_last_detection(); + payload[0] = last_detection; + break; + } + + default: + { + debug_printf("INTENT_CONTROLLER_SERVICER UNHANDLED COMMAND!!!\n"); + ret = CONTROL_BAD_COMMAND; + break; + } + + } + return ret; +} + +control_ret_t intent_servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) +{ + control_ret_t ret = CONTROL_SUCCESS; + + uint8_t cmd_id = CONTROL_CMD_CLEAR_READ(cmd); + debug_printf("intent_servicer_write_cmd cmd_id %d.\n", cmd_id); + + switch (cmd_id) + { + + // Add the handling of the write commands here + default: + debug_printf("INTENT_CONTROLLER_SERVICER UNHANDLED COMMAND!!!\n"); + ret = CONTROL_BAD_COMMAND; + break; + } + + return ret; +} diff --git a/examples/ffd/src/control/intent_servicer.h b/examples/ffd/src/control/intent_servicer.h new file mode 100644 index 00000000..cb1cba99 --- /dev/null +++ b/examples/ffd/src/control/intent_servicer.h @@ -0,0 +1,53 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#pragma once + +#include "servicer.h" + +#define INTENT_CONTROLLER_SERVICER_RESID (1) +#define NUM_RESOURCES_INTENT_SERVICER (1) // DFU servicer + +/** + * @brief DFU servicer task. + * + * This task handles DFU commands from the device control interface and relays + * them to the internal DFU INT state machine. + * + * \param args Pointer to the Servicer's state data structure + */ +void intent_servicer(void *args); + +// Servicer initialization functions +/** + * @brief DFU servicer initialisation function. + * \param servicer Pointer to the Servicer's state data structure + */ +void intent_servicer_init(servicer_t *servicer); + +/** + * @brief DFU servicer read command handler + * + * Handles read commands dedicated to the DFU servicer resource + * + * @param res_info Resource info of the current command + * @param cmd Command ID of this command + * @param payload Pointer to the payload that contains the write data + * @param payload_len Length in bytes of the write command payload + * @return control_ret_t CONTROL_SUCCESS if command handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +control_ret_t intent_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); + +/** + * @brief DFU servicer write command handler + * + * Handles write commands dedicated to the DFU servicer resource + * + * @param res_info Resource info of the current command + * @param cmd Command ID of this command + * @param payload Pointer to the payload that contains the write data + * @param payload_len Length in bytes of the write command payload + * @return control_ret_t CONTROL_SUCCESS if command handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +control_ret_t intent_servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); \ No newline at end of file diff --git a/examples/ffd/src/control/servicer.c b/examples/ffd/src/control/servicer.c new file mode 100644 index 00000000..9d9a39ab --- /dev/null +++ b/examples/ffd/src/control/servicer.c @@ -0,0 +1,169 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#define DEBUG_UNIT SERVICER_TASK +#ifndef DEBUG_PRINT_ENABLE_SERVICER_TASK + #define DEBUG_PRINT_ENABLE_SERVICER_TASK 0 +#endif +#include "debug_print.h" +#include +#include +#include "platform/platform_conf.h" +#include "device_control_i2c.h" +#include "servicer.h" +#include "intent_servicer.h" + +#if appconfI2C_SLAVE_ENABLED == 1 && ON_TILE(I2C_CTRL_TILE_NO) + +static device_control_t device_control_i2c_ctx_s; +device_control_t *device_control_i2c_ctx = (device_control_t *) &device_control_i2c_ctx_s; +device_control_t *device_control_ctxs[APP_CONTROL_TRANSPORT_COUNT] = { + (device_control_t *) &device_control_i2c_ctx_s, +}; +#endif + +//-----------------Servicer read write callback functions-----------------------// +DEVICE_CONTROL_CALLBACK_ATTR +control_ret_t read_cmd(control_resid_t resid, control_cmd_t cmd, uint8_t *payload, size_t payload_len, void *app_data) +{ + control_ret_t ret = CONTROL_SUCCESS; + servicer_t *servicer = (servicer_t*)app_data; + + // For read commands, payload[0] is reserved from status. So payload_len is one more than the payload_len stored in the resource command map + payload_len -= 1; + uint8_t *payload_ptr = &payload[1]; //Excluding the status byte, which is updated later. + + debug_printf("Servicer ID %d on tile %d received READ command %02x for resid %02x\n\t",servicer->id, THIS_XCORE_TILE, cmd, resid); + debug_printf("The command is requesting %d bytes\n\t", payload_len); + + + control_resource_info_t *current_res_info = get_res_info(resid, servicer); + xassert(current_res_info != NULL); // This should never happen + control_cmd_info_t *current_cmd_info; + ret = validate_cmd(¤t_cmd_info, current_res_info, cmd, payload_ptr, payload_len); + if(ret != CONTROL_SUCCESS) + { + payload[0] = ret; // Update status in byte 0 + return ret; + } + // Check if command is for the servicer itself + if(current_res_info->resource == servicer->res_info[0].resource) + { + ret = servicer_read_cmd(current_res_info, cmd, payload_ptr, payload_len); + payload[0] = ret; + return ret; + } + return CONTROL_ERROR; +} + +DEVICE_CONTROL_CALLBACK_ATTR +control_ret_t write_cmd(control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len, void *app_data) +{ + control_ret_t ret = CONTROL_SUCCESS; + servicer_t *servicer = (servicer_t*)app_data; + //debug_printf("Device control WRITE. Servicer ID %d\n\t", servicer->id); + + debug_printf("Servicer ID %d on tile %d received WRITE command %02x for resid %02x\n\t", servicer->id, THIS_XCORE_TILE, cmd, resid); + debug_printf("The command has %d bytes\n\t", payload_len); + + control_resource_info_t *current_res_info = get_res_info(resid, servicer); + xassert(current_res_info != NULL); + control_cmd_info_t *current_cmd_info; + ret = validate_cmd(¤t_cmd_info, current_res_info, cmd, payload, payload_len); + if(ret != CONTROL_SUCCESS) + { + return ret; + } + // Check if command is for the servicer itself + if(current_res_info->resource == servicer->res_info[0].resource) + { + ret = servicer_write_cmd(current_res_info, cmd, payload, payload_len); + return ret; + } + return CONTROL_ERROR; +} + +// Initialise packet payload pointers to point to valid memory. + + +//-----------------Servicer helper functions-----------------------// +// Return a pointer to the control_cmd_info_t structure for a given command ID. Return NULL if command not found in the +// command map for the resource. +control_cmd_info_t* get_cmd_info(uint8_t cmd_id, const control_resource_info_t *res_info) +{ + for(int i=0; icommand_map.num_commands; i++) + { + if(res_info->command_map.commands[i].cmd_id == cmd_id) + { + return &res_info->command_map.commands[i]; + } + } + return NULL; +} + +// Return a pointer to the servicer's control_resource_info_t structure for a given resource ID. +// Return NULL if the resource ID is not found in the list of resources serviced by the servicer. +control_resource_info_t* get_res_info(control_resid_t resource, const servicer_t *servicer) +{ + for(int res=0; resnum_resources; res++) + { + // Get the cmd_info for the current command + if(servicer->res_info[res].resource == resource) + { + return &servicer->res_info[res]; + } + } + return NULL; +} + +// Validate the command from the host against that commands information in the stored command_map +control_ret_t validate_cmd(control_cmd_info_t **cmd_info, + control_resource_info_t *res_info, + control_cmd_t cmd, + const uint8_t *payload, + size_t payload_len) +{ + control_ret_t ret = CONTROL_SUCCESS; + *cmd_info = get_cmd_info(CONTROL_CMD_CLEAR_READ(cmd), res_info); + if(*cmd_info == NULL) + { + return SERVICER_WRONG_COMMAND_ID; + } + + // Validate non special command length + // Don't do payload check for special commands since for the last filter chunk, host might request less than the payload length specified in the cmd_map + + if(payload_len != (*cmd_info)->bytes_per_val * (*cmd_info)->num_vals) + { + return SERVICER_WRONG_COMMAND_LEN; + } + // Payload validation happens either on the host or in the specific command handlers. No generic payload validation is done in the servicer. + + return ret; +} + +// Process write commands directed to the servicer resource +control_ret_t servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) +{ + // Handle write commands directed to a servicer resource + switch(res_info->resource) + { + case INTENT_CONTROLLER_SERVICER_RESID: + return intent_servicer_write_cmd(res_info, cmd, payload, payload_len); + break; + } + return CONTROL_SUCCESS; +} + +// Process read commands directed to the servicer resource +control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) +{ + control_ret_t ret = CONTROL_SUCCESS; + // Handle read commands directed to a servicer resource + switch(res_info->resource) + { + case INTENT_CONTROLLER_SERVICER_RESID: + ret = intent_servicer_read_cmd(res_info, cmd, payload, payload_len); + break; + } + return ret; +} diff --git a/examples/ffd/src/control/servicer.h b/examples/ffd/src/control/servicer.h new file mode 100644 index 00000000..8e979b27 --- /dev/null +++ b/examples/ffd/src/control/servicer.h @@ -0,0 +1,124 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#pragma once +#include "device_control.h" +#include "cmd_map.h" + +#define NUM_TILE_0_SERVICERS (1) // only intent servicer is used +#define NUM_TILE_1_SERVICERS (0) // no control servicer + +extern device_control_t *device_control_i2c_ctx; +extern device_control_t *device_control_ctxs[1]; + +/** + * Clears the read bit on a command code + * + * \param[in,out] c The command code to clear the read bit on. + */ +#define CONTROL_CMD_CLEAR_READ(c) ((c) & ~0x80) + +// Structure encapsulating all the information about a resource +typedef struct +{ + control_resid_t resource; + command_map_t command_map; +}control_resource_info_t; + +typedef struct { + uint32_t start_io; // set to 1 on one servicer per tile to make it responsible for starting the IO tasks on that tile. + int32_t id; // Unique ID for the servicer. Used for debugging. + // Num resources + int32_t num_resources; + // Resource ID and command map for every resource + control_resource_info_t *res_info; +}servicer_t; + +// Servicer device_control callback functions +/** + * @brief Device control callback function to handle a read command. + * + * @param resid Resource ID of the command + * @param cmd Command ID of the command + * @param payload Pointer to the payload buffer that needs to be updated with the read command response. + * @param payload_len Length of the payload buffer + * @param app_data Application specific data. + * @return CONTROL_SUCCESS is command is handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +extern DEVICE_CONTROL_CALLBACK_ATTR +control_ret_t read_cmd(control_resid_t resid, control_cmd_t cmd, uint8_t *payload, size_t payload_len, void *app_data); + +/** + * @brief Device control callback function to handle a write command + * + * @param resid Resource ID of the command + * @param cmd Command ID of the command + * @param payload Pointer to the payload buffer containing the payload the host wants to write to the device. + * @param payload_len Length of the payload buffer + * @param app_data Application specific data + * @return CONTROL_SUCCESS is command is handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +extern DEVICE_CONTROL_CALLBACK_ATTR +control_ret_t write_cmd(control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len, void *app_data); + +// Servicer helper functions +/** + * @brief Check if a command exists in the command map for a given resource and return the pointer to the cmd info object for the given command + * + * @param cmd_id Command ID for which the control_cmd_info_t object is required. + * @param res_info Pointer to the resource info which contains all the command info objects for a given resource. + * @return Pointer to a control_cmd_info_t object when one is found for the given Command ID, NULL otherwise. + */ +control_cmd_info_t* get_cmd_info(uint8_t cmd_id, const control_resource_info_t *res_info); + +/** + * @brief Check if a resource ID is one of the resources supported by a servicer and return a pointer to the resource info object. + * + * @param resource Resource ID that needs to be checked. + * @param servicer Pointer to the servicer state structure that holds all the resource info objects for the servicer. + * @return Pointer to the resource_info_t object when one is found for a given resource ID, NULL otherwise. + */ +control_resource_info_t* get_res_info(control_resid_t resource, const servicer_t *servicer); + +/** + * @brief Validate the command received in the servicer and return a pointer to the command info if the command is valid. + * + * All checks related to the command such as command ID being valid, correctness of the payload length, + * validation of the actual payload for write commands are done in this function. + * + * @param cmd_info Pointer in which the address of the command info is returned if this is a valid command/ + * @param res_info Resource info of the resource the command is meant for. + * @param cmd Command ID of the command that needs validating + * @param payload Payload buffer of the command that needs validating. + * @param payload_len Payload length of the payload. + * @return CONTROL_SUCCESS if the command is found to be valid for the given resource. control_ret_t error otherwise + * indicating the error code of the specific validation check that failed. + */ +control_ret_t validate_cmd(control_cmd_info_t **cmd_info, + control_resource_info_t *res_info, + control_cmd_t cmd, + const uint8_t *payload, + size_t payload_len); + +/** + * @brief Function for handling write commands directed to the servicer resource itself. + * + * @param resid Command Resource ID + * @param cmd Command Command ID + * @param payload Command write payload buffer + * @param payload_len Length of the payload buffer + * @return CONTROL_SUCCESS if write command processed successfully. contro_ret_t error status otherwise. + */ +control_ret_t servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); + +/** + * @brief Function for handling read commands directed to the servicer resource itself. + * + * @param resid Command Resource ID + * @param cmd Command Command ID + * @param payload Payload buffer to populate with the read response + * @param payload_len Length of the payload buffer + * @return CONTROL_SUCCESS if read command processed successfully. contro_ret_t error status otherwise. + */ +control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index 780782b4..8addca03 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -30,6 +30,9 @@ #include "gpio_ctrl/gpi_ctrl.h" #include "gpio_ctrl/leds.h" #include "intent_handler/intent_handler.h" +#if appconfI2C_SLAVE_ENABLED == 1 +#include "intent_servicer.h" +#endif #if appconfRECOVER_MCLK_I2S_APP_PLL /* Config headers for sw_pll */ @@ -91,7 +94,7 @@ void audio_pipeline_input(void *input_app_data, size_t frame_count) { (void) input_app_data; - + #if !appconfUSE_I2S_INPUT static int flushed; while (!flushed) { @@ -125,8 +128,8 @@ void audio_pipeline_input(void *input_app_data, (int32_t *) tmp, frame_count, portMAX_DELAY); - - + + for (int i=0; i +#include "app_conf.h" +#include "audio_pipeline.h" +#include "rtos_printf.h" + +#define appconfUSB_INTERRUPT_CORE 2 /* Must be kept off I/O cores. Best kept off core 0 with the tick ISR. */ +#define appconfUSB_SOF_INTERRUPT_CORE 3 /* Must be kept off I/O cores. Best kept off cores with other ISRs. */ +#define appconfXUD_IO_CORE 1 /* Must be kept off core 0 with the RTOS tick ISR */ +#define appconfUSB_AUDIO_SAMPLE_RATE 48000 + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#define CFG_TUSB_OS OPT_OS_CUSTOM + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(8))) + +#ifndef CFG_TUSB_DEBUG_PRINTF +#ifdef rtos_printf +#define CFG_TUSB_DEBUG_PRINTF rtos_printf +#endif +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#define CFG_TUD_EP_MAX 12 +#define CFG_TUD_TASK_QUEUE_SZ 8 +#define CFG_TUD_ENDPOINT0_SIZE 64 + +#define CFG_TUD_XCORE_INTERRUPT_CORE appconfUSB_INTERRUPT_CORE +#define CFG_TUD_XCORE_SOF_INTERRUPT_CORE appconfUSB_SOF_INTERRUPT_CORE +#define CFG_TUD_XCORE_IO_CORE_MASK (1 << appconfXUD_IO_CORE) + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 1 +#define CFG_TUD_VENDOR 0 +#define CFG_TUD_DFU 1 + +//-------------------------------------------------------------------- +// DFU DRIVER CONFIGURATION +//-------------------------------------------------------------------- +// DFU buffer size, it has to be set to the buffer size used in TUD_DFU_DESCRIPTOR +#define CFG_TUD_DFU_XFER_BUFSIZE 4096 + +//-------------------------------------------------------------------- +// AUDIO CLASS DRIVER CONFIGURATION +//-------------------------------------------------------------------- +extern const uint16_t tud_audio_desc_lengths[CFG_TUD_AUDIO]; + +#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN tud_audio_desc_lengths[0] +#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 +#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 + +/* TODO make these configurable in app_conf? */ +#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 +#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX 2 + +#if appconfUSB_AUDIO_MODE == appconfUSB_AUDIO_RELEASE +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 2 +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 2 +#else +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 6 +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 4 +#endif + +#if (appconfMIC_SRC_DEFAULT == appconfMIC_SRC_USB) +// In appconfMIC_SRC_USB, we wait forever for input mic and AEC reference channels +// will not overflow output +#define USB_AUDIO_RECV_DELAY portMAX_DELAY +#else +// In or any other mode, we do not wait for input AEC reference channels. +// The reference will be all zeros if no AEC reference is received. +// This is the typical mode. +#define USB_AUDIO_RECV_DELAY 0 +#endif + +// EP and buffer sizes +#define AUDIO_FRAMES_PER_USB_FRAME (appconfUSB_AUDIO_SAMPLE_RATE / 1000) +#if appconfUSB_AUDIO_SAMPLE_RATE == 48000 +#define USB_TASK_STACK_SIZE 2000 +#endif + +// To support USB Adaptive/Asynchronous, maximum packet size must be large enough to accommodate an extra set of samples per frame. +// Adding 1 to AUDIO_SAMPLES_PER_USB_FRAME allows this. +#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ ((AUDIO_FRAMES_PER_USB_FRAME + 1) * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX) +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX (CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ) // Maximum EP IN size for all AS alternate settings used +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ + +#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1 +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ ((AUDIO_FRAMES_PER_USB_FRAME + 1) * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX) +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX (CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ + 2) // Maximum EP OUT size for all AS alternate settings used. Plus 2 for CRC +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ*3 + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c index 846b0055..8b548ad2 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c @@ -17,7 +17,6 @@ #include "usb_support.h" #if appconfI2C_DFU_ENABLED -#include "app_control/app_control.h" #include "device_control_i2c.h" #endif diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index 6615a310..dc989760 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -145,7 +145,7 @@ /* I/O Task Priorities */ /*****************************************/ #ifndef appconfQSPI_FLASH_TASK_PRIORITY -#define appconfQSPI_FLASH_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) +#define appconfQSPI_FLASH_TASK_PRIORITY (configMAX_PRIORITIES-1) #endif /* appconfQSPI_FLASH_TASK_PRIORITY */ #ifndef appconfI2C_TASK_PRIORITY @@ -153,7 +153,7 @@ #endif /* appconfI2C_TASK_PRIORITY */ #ifndef appconfSPI_TASK_PRIORITY -#define appconfSPI_TASK_PRIORITY (configMAX_PRIORITIES/2) +#define appconfSPI_TASK_PRIORITY ( configMAX_PRIORITIES/2) #endif /* appconfSPI_TASK_PRIORITY */ #ifndef appconfDEVICE_CONTROL_I2C_PRIORITY diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index 79da9ee7..0768342f 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -169,13 +169,9 @@ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result) { DBG_TRACE("\r\nGet %s, ID=%d, Score=%d, SG_Diff=%d, Energy=%d\r\n", szCommand, nCmdID, nCmdScore, nCmdSG, nCmdEnergy); result->id = nCmdID; - result->score = nCmdScore; - result->gscore = nCmdSG; - // The following result fields are not implemented - result->start_index = -1; - result->end_index = -1; - result->duration = -1; + result->sg_diff = nCmdSG; + result->energy = nCmdEnergy; #if appconfINTENT_UART_DEBUG_INFO_ENABLED static char res_info[128]; snprintf(res_info, sizeof(res_info)-1, "ID:%d,Sc:%d,SGD:%d,En:%d\r\n", nCmdID, nCmdScore, nCmdSG, nCmdEnergy); diff --git a/modules/asr/asr.h b/modules/asr/asr.h index 92ca22aa..1dd5d218 100644 --- a/modules/asr/asr.h +++ b/modules/asr/asr.h @@ -64,11 +64,19 @@ typedef struct asr_attributes_struct typedef struct asr_result_struct { uint16_t id; ///< Keyword or command ID +#if ASR_CYBERON==1 + uint32_t score; ///< The confidence score of the detection + uint32_t sg_diff; ///< The voice similarity of the detection + uint32_t energy; ///< The energy of the detection +#elif ASR_SENSORY==1 uint16_t score; ///< The confidence score of the detection uint16_t gscore; ///< The garbage score int32_t start_index; ///< The audio sample index that corresponds to the start of the utterance int32_t end_index; ///< The audio sample index that corresponds to the end of the utterance int32_t duration; ///< THe length of the utterance in samples +#else + #error "Model has to be either Sensory or Cyberon" +#endif void* reserved; ///< Reserved for future use } asr_result_t; From 269a4301a73b36d16ee5b3ab147d508f1d586312 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 1 Aug 2024 09:13:38 +0100 Subject: [PATCH 166/288] Commands are receivedintent_servicer_read_cmdfff --- .../ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c | 2 +- examples/ffd/src/control/intent_servicer.c | 2 -- examples/ffd/src/control/intent_servicer.h | 4 ++-- examples/ffd/src/main.c | 7 +++++++ modules/asr/intent_handler/intent_handler.c | 1 - 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 8b4fa525..4b987021 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -260,7 +260,7 @@ static void uart_init(void) } void control_init() { -#if appconfI2C_SLAVE+ENABLED == 1 && ON_TILE(I2C_TILE_NO) +#if appconfI2C_SLAVE_ENABLED == 1 && ON_TILE(I2C_TILE_NO) control_ret_t ret = CONTROL_SUCCESS; ret = device_control_init(device_control_i2c_ctx, DEVICE_CONTROL_HOST_MODE, diff --git a/examples/ffd/src/control/intent_servicer.c b/examples/ffd/src/control/intent_servicer.c index 6b2e022e..30276f7a 100644 --- a/examples/ffd/src/control/intent_servicer.c +++ b/examples/ffd/src/control/intent_servicer.c @@ -51,7 +51,6 @@ void intent_servicer(void *args) { servicer_t *servicer = (servicer_t*)args; xassert(servicer != NULL); - control_resid_t *resources = (control_resid_t*)pvPortMalloc(servicer->num_resources * sizeof(control_resid_t)); for(int i=0; inum_resources; i++) { @@ -82,7 +81,6 @@ control_ret_t intent_servicer_read_cmd(control_resource_info_t *res_info, contro memset(payload, 0, payload_len); debug_printf("intent_servicer_read_cmd, cmd_id: %d.\n", cmd_id); - switch (cmd_id) { case INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION: diff --git a/examples/ffd/src/control/intent_servicer.h b/examples/ffd/src/control/intent_servicer.h index cb1cba99..53914879 100644 --- a/examples/ffd/src/control/intent_servicer.h +++ b/examples/ffd/src/control/intent_servicer.h @@ -4,8 +4,8 @@ #include "servicer.h" -#define INTENT_CONTROLLER_SERVICER_RESID (1) -#define NUM_RESOURCES_INTENT_SERVICER (1) // DFU servicer +#define INTENT_CONTROLLER_SERVICER_RESID (240) +#define NUM_RESOURCES_INTENT_SERVICER (1) // Intent servicer /** * @brief DFU servicer task. diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index 8addca03..f88637bb 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -277,6 +277,13 @@ void startup_task(void *arg) // Initialise control related things servicer_t servicer_intent; intent_servicer_init(&servicer_intent); + + xTaskCreate((TaskFunction_t)intent_servicer, + "intent servicer", + RTOS_THREAD_STACK_SIZE(intent_servicer), + &servicer_intent, + appconfDEVICE_CONTROL_I2C_PRIORITY, + NULL); #endif #if ON_TILE(0) diff --git a/modules/asr/intent_handler/intent_handler.c b/modules/asr/intent_handler/intent_handler.c index cfa4cee6..7068ee70 100644 --- a/modules/asr/intent_handler/intent_handler.c +++ b/modules/asr/intent_handler/intent_handler.c @@ -98,7 +98,6 @@ int32_t intent_handler_create(uint32_t priority, void *args) args, priority, NULL); - return 0; } From 8fb0821a6ebf244a05bf96b5a9eafe3b41093fc4 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 1 Aug 2024 13:47:49 +0100 Subject: [PATCH 167/288] Command working with test ID's and external variables --- .../XK_VOICE_L71/platform/platform_init.c | 2 +- .../XK_VOICE_L71/platform/platform_start.c | 4 ++-- examples/ffd/src/control/intent_servicer.c | 20 ++++++++++++------- examples/ffd/src/control/servicer.c | 2 +- examples/ffd/src/main.c | 4 ++-- modules/asr/Cyberon/DSpotter_asr.c | 2 +- modules/asr/intent_engine/intent_engine.c | 5 ++++- 7 files changed, 24 insertions(+), 15 deletions(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 4b987021..0c9e0cd6 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -260,7 +260,7 @@ static void uart_init(void) } void control_init() { -#if appconfI2C_SLAVE_ENABLED == 1 && ON_TILE(I2C_TILE_NO) +#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_TILE_NO) control_ret_t ret = CONTROL_SUCCESS; ret = device_control_init(device_control_i2c_ctx, DEVICE_CONTROL_HOST_MODE, diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index 3855b5d4..db1da5d4 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -44,7 +44,7 @@ static void flash_start(void) static void i2c_master_start(void) { -#if appconfI2C_ENABLED == 1 && appconfI2C_MODE == appconfI2C_MODE_MASTER +#if appconfI2C_ENABLED && appconfI2C_MODE == appconfI2C_MODE_MASTER rtos_i2c_master_rpc_config(i2c_master_ctx, appconfI2C_MASTER_RPC_PORT, appconfI2C_MASTER_RPC_PRIORITY); #if ON_TILE(I2C_TILE_NO) @@ -55,7 +55,7 @@ static void i2c_master_start(void) static void i2c_slave_start(void) { -#if appconfI2C_SLAVE_ENABLED == 1 && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_start(i2c_slave_ctx, device_control_i2c_ctx, (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, diff --git a/examples/ffd/src/control/intent_servicer.c b/examples/ffd/src/control/intent_servicer.c index 30276f7a..371d591e 100644 --- a/examples/ffd/src/control/intent_servicer.c +++ b/examples/ffd/src/control/intent_servicer.c @@ -14,19 +14,19 @@ #include "platform/platform_conf.h" #include "servicer.h" #include "intent_servicer.h" - +#include "asr.h" #include "device_control_i2c.h" enum e_intent_controller_servicer_resid_cmds { #ifndef INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION - INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION = 0, + INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION = 88, #endif - NUM_INTENT_CONTROLLER_SERVICER_RESID_CMDS + NUM_INTENT_CONTROLLER_SERVICER_RESID_CMDS = 1 }; static control_cmd_info_t intent_controller_servicer_resid_cmd_map[] = { - { INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION, 1, sizeof(uint8_t), CMD_READ_ONLY }, + { INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION, 3, sizeof(uint8_t), CMD_READ_ONLY }, }; void intent_servicer_init(servicer_t *servicer) @@ -72,7 +72,8 @@ void intent_servicer(void *args) { device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); } } - +extern uint32_t detection_number; +extern asr_result_t last_asr_result; control_ret_t intent_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) { control_ret_t ret = CONTROL_SUCCESS; @@ -81,13 +82,18 @@ control_ret_t intent_servicer_read_cmd(control_resource_info_t *res_info, contro memset(payload, 0, payload_len); debug_printf("intent_servicer_read_cmd, cmd_id: %d.\n", cmd_id); + //asr_result_t asr_result; + + //asr_error_t asr_error = asr_get_result(NULL, &asr_result); + printf("detection_number %d, last_asr_result.id %d\n", detection_number, last_asr_result.id); + switch (cmd_id) { case INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION: { debug_printf("INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION\n"); - uint8_t last_detection = 0;//intent_get_last_detection(); - payload[0] = last_detection; + payload[0] = (uint8_t) last_asr_result.id; + payload[1] = (uint8_t) detection_number; break; } diff --git a/examples/ffd/src/control/servicer.c b/examples/ffd/src/control/servicer.c index 9d9a39ab..15f572b0 100644 --- a/examples/ffd/src/control/servicer.c +++ b/examples/ffd/src/control/servicer.c @@ -12,7 +12,7 @@ #include "servicer.h" #include "intent_servicer.h" -#if appconfI2C_SLAVE_ENABLED == 1 && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) static device_control_t device_control_i2c_ctx_s; device_control_t *device_control_i2c_ctx = (device_control_t *) &device_control_i2c_ctx_s; diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index f88637bb..7fc1d773 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -30,7 +30,7 @@ #include "gpio_ctrl/gpi_ctrl.h" #include "gpio_ctrl/leds.h" #include "intent_handler/intent_handler.h" -#if appconfI2C_SLAVE_ENABLED == 1 +#if appconfI2C_SLAVE_ENABLED #include "intent_servicer.h" #endif @@ -273,7 +273,7 @@ void startup_task(void *arg) #endif #endif -#if appconfI2C_SLAVE_ENABLED == 1 && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) // Initialise control related things servicer_t servicer_intent; intent_servicer_init(&servicer_intent); diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index 0768342f..3a7749eb 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -176,7 +176,7 @@ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result) static char res_info[128]; snprintf(res_info, sizeof(res_info)-1, "ID:%d,Sc:%d,SGD:%d,En:%d\r\n", nCmdID, nCmdScore, nCmdSG, nCmdEnergy); // Enable the printout below to see the information sent over UART - // rtos_printf(res_info); + rtos_printf(res_info); rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)&res_info, strlen(res_info)); #endif return ASR_OK; diff --git a/modules/asr/intent_engine/intent_engine.c b/modules/asr/intent_engine/intent_engine.c index 46a951ea..2d258a5c 100644 --- a/modules/asr/intent_engine/intent_engine.c +++ b/modules/asr/intent_engine/intent_engine.c @@ -112,7 +112,8 @@ static void timeout_event_handler(TimerHandle_t pxTimer) intent_state = STATE_EXPECTING_WAKEWORD; } } - +asr_result_t last_asr_result = {0}; +uint32_t detection_number = 0; #pragma stackfunction 1000 void intent_engine_task(void *args) { @@ -169,6 +170,8 @@ void intent_engine_task(void *args) if (asr_error != ASR_OK) continue; asr_error = asr_get_result(asr_ctx, &asr_result); + memcpy(&last_asr_result, &asr_result, sizeof(asr_result_t)); + detection_number++; if (asr_error != ASR_OK) continue; word_id = asr_result.id; From 989027fce1a99461fbfcf5d17f8798169eabce68 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 2 Aug 2024 09:10:06 +0100 Subject: [PATCH 168/288] Remove device control code --- .../XK_VOICE_L71/platform/platform_conf.h | 4 - .../XK_VOICE_L71/platform/platform_init.c | 21 --- .../XK_VOICE_L71/platform/platform_start.c | 55 +++++- examples/ffd/ffd_i2s_input_cyberon.cmake | 1 - examples/ffd/src/app_conf.h | 3 +- examples/ffd/src/control/cmd_map.h | 39 ---- examples/ffd/src/control/intent_servicer.c | 129 ------------- examples/ffd/src/control/intent_servicer.h | 53 ------ examples/ffd/src/control/servicer.c | 169 ------------------ examples/ffd/src/control/servicer.h | 124 ------------- examples/ffd/src/main.c | 16 -- examples/ffva/src/dfu_int/dfu_servicer.c | 4 +- modules/asr/intent_engine/intent_engine.c | 4 +- 13 files changed, 52 insertions(+), 570 deletions(-) delete mode 100644 examples/ffd/src/control/cmd_map.h delete mode 100644 examples/ffd/src/control/intent_servicer.c delete mode 100644 examples/ffd/src/control/intent_servicer.h delete mode 100644 examples/ffd/src/control/servicer.c delete mode 100644 examples/ffd/src/control/servicer.h diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h index 525ab376..515f1f2d 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -112,8 +112,4 @@ #define appconfI2C_TASK_PRIORITY (configMAX_PRIORITIES/2) #endif /* appconfI2C_TASK_PRIORITY */ -#ifndef appconfDEVICE_CONTROL_I2C_PRIORITY -#define appconfDEVICE_CONTROL_I2C_PRIORITY (configMAX_PRIORITIES-1) -#endif // appconfDEVICE_CONTROL_I2C_PRIORITY - #endif /* PLATFORM_CONF_H_ */ diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 0c9e0cd6..7ab6d2ea 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -9,10 +9,6 @@ #include "platform/app_pll_ctrl.h" #include "platform/driver_instances.h" #include "platform/platform_init.h" -#include "device_control.h" -#include "servicer.h" - -extern device_control_t *device_control_i2c_ctx; static void mclk_init(chanend_t other_tile_c) { @@ -259,22 +255,6 @@ static void uart_init(void) #endif } -void control_init() { -#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_TILE_NO) - control_ret_t ret = CONTROL_SUCCESS; - ret = device_control_init(device_control_i2c_ctx, - DEVICE_CONTROL_HOST_MODE, - (NUM_TILE_0_SERVICERS + NUM_TILE_1_SERVICERS), - NULL, 0); - xassert(ret == CONTROL_SUCCESS); - - ret = device_control_start(device_control_i2c_ctx, - -1, - -1); - xassert(ret == CONTROL_SUCCESS); -#endif -} - void platform_init(chanend_t other_tile_c) { rtos_intertile_init(intertile_ctx, other_tile_c); @@ -287,5 +267,4 @@ void platform_init(chanend_t other_tile_c) mics_init(); i2s_init(); uart_init(); - control_init(); } diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index db1da5d4..4ebde1a4 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -14,11 +14,9 @@ #include "platform_conf.h" #include "platform/driver_instances.h" #include "dac3101.h" +#include "asr.h" -#if appconfI2C_SLAVE_ENABLED == 1 -#include "servicer.h" -#include "device_control_i2c.h" -#endif +extern asr_result_t last_asr_result; extern void i2s_rate_conversion_enable(void); @@ -53,14 +51,55 @@ static void i2c_master_start(void) #endif } +#define WAKEWORD_REG_ADDRESS_START 0x40 +#define WAKEWORD_REG_ADDRESS_END 0x49 +#define WRITE_REQUEST_MIN_LEN 1 + +RTOS_I2C_SLAVE_CALLBACK_ATTR +size_t read_device_reg(rtos_i2c_slave_t *ctx, + asr_result_t *last_asr_result, + uint8_t **data) +{ + uint8_t * data_p = *data; + uint8_t reg_addr = data_p[0]; + uint8_t reg_value = 0; + if (reg_addr >= WAKEWORD_REG_ADDRESS_START && reg_addr <= WAKEWORD_REG_ADDRESS_END) { + if (last_asr_result->id == reg_addr - WAKEWORD_REG_ADDRESS_START + 1) + { + uint8_t score = (uint8_t) last_asr_result->score&0xF; + printf("Found wakeword information: ID %d, score %d\n", last_asr_result->id, score); + reg_value = score; + } else { + reg_value = 0; + } + } else { + reg_value = reg_addr - 1; + } + data_p[0] = reg_value; + printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); + return 1; +} + +RTOS_I2C_SLAVE_CALLBACK_ATTR +void write_device_reg(rtos_i2c_slave_t *ctx, + void *app_data, + uint8_t *data, + size_t len) +{ + // If the length is lower than WRITE_REQUEST_MIN_LEN, it is a read request + if (len > WRITE_REQUEST_MIN_LEN) { + printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); + } +} + static void i2c_slave_start(void) { #if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_start(i2c_slave_ctx, - device_control_i2c_ctx, - (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, - (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, - (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, + &last_asr_result, + (rtos_i2c_slave_start_cb_t) NULL, + (rtos_i2c_slave_rx_cb_t) write_device_reg, + (rtos_i2c_slave_tx_start_cb_t) read_device_reg, (rtos_i2c_slave_tx_done_cb_t) NULL, NULL, NULL, diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index eef54a63..600a3bbc 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -105,7 +105,6 @@ set(APP_COMMON_LINK_LIBRARIES sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 - rtos::sw_services::device_control rtos::freertos_usb lib_src lib_sw_pll diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 82af6bd3..c0662230 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -10,7 +10,6 @@ #define appconfINTENT_MODEL_RUNNER_SAMPLES_PORT 3 #define appconfI2C_MASTER_RPC_PORT 4 #define appconfI2S_RPC_PORT 5 -#define appconfDEVICE_CONTROL_I2C_PORT 6 #define appconfINTENT_ENGINE_READY_SYNC_PORT 16 #define appconfI2S_OUTPUT_SLAVE_PORT 8 @@ -116,7 +115,7 @@ #define appconfI2C_SLAVE_ENABLED 0 #endif -#if appconfINTENT_I2C_OUTPUT_ENABLED==1 && appconfI2C_MASTER_ENABLED==0 +#if appconfINTENT_I2C_OUTPUT_ENABLED && ! appconfI2C_MASTER_ENABLED #error "I2C master must be enabled for intent I2C output" #endif diff --git a/examples/ffd/src/control/cmd_map.h b/examples/ffd/src/control/cmd_map.h deleted file mode 100644 index 71467e20..00000000 --- a/examples/ffd/src/control/cmd_map.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022-2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#pragma once - -#include -#include "device_control_shared.h" - -// Types of commands in the Command ID enum space - -// - Dedicated: Commands dedicated only for the resource -// - For SHF this is further spilt into dedicated SHF commands and dedicated custom commands -// - Shared: Commands shared between a resource and its servicer. These are present in the servicer's command map only though respources process them as well. (Used for special commands protocol) -// - External: space reserved for customers to add commands. -#define DEDICATED_COMMANDS_START_OFFSET (0) -#define SHARED_COMMANDS_START_OFFSET (90) -#define EXTERNAL_COMMANDS_START_OFFSET (110) -// Servicers will have commands in the above 4 categories. -// Resources will have commands in the dedicated, reserved and broadcast categories. - - -typedef enum -{ - CMD_READ_ONLY, - CMD_READ_WRITE, - CMD_WRITE_ONLY -}cmd_rw_type_t; - -typedef struct -{ - uint8_t cmd_id; - uint8_t num_vals; - uint8_t bytes_per_val; - uint8_t cmd_rw_type; -}control_cmd_info_t; - -typedef struct { - int32_t num_commands; - control_cmd_info_t *commands; -}command_map_t; diff --git a/examples/ffd/src/control/intent_servicer.c b/examples/ffd/src/control/intent_servicer.c deleted file mode 100644 index 371d591e..00000000 --- a/examples/ffd/src/control/intent_servicer.c +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#define DEBUG_UNIT INTENT_SERVICER -#ifndef DEBUG_PRINT_ENABLE_INTENT_SERVICER -#define DEBUG_PRINT_ENABLE_INTENT_SERVICER 0 -#endif -#include "debug_print.h" - -#include -#include -#include -#include - -#include "platform/platform_conf.h" -#include "servicer.h" -#include "intent_servicer.h" -#include "asr.h" -#include "device_control_i2c.h" - -enum e_intent_controller_servicer_resid_cmds -{ -#ifndef INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION - INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION = 88, -#endif - NUM_INTENT_CONTROLLER_SERVICER_RESID_CMDS = 1 -}; -static control_cmd_info_t intent_controller_servicer_resid_cmd_map[] = -{ - { INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION, 3, sizeof(uint8_t), CMD_READ_ONLY }, -}; - -void intent_servicer_init(servicer_t *servicer) -{ - // Servicer resource info - static control_resource_info_t intent_res_info[NUM_RESOURCES_INTENT_SERVICER]; - - memset(servicer, 0, sizeof(servicer_t)); - servicer->id = INTENT_CONTROLLER_SERVICER_RESID; - servicer->start_io = 0; - servicer->num_resources = NUM_RESOURCES_INTENT_SERVICER; - - servicer->res_info = &intent_res_info[0]; - // Servicer resource - servicer->res_info[0].resource = INTENT_CONTROLLER_SERVICER_RESID; - servicer->res_info[0].command_map.num_commands = NUM_INTENT_CONTROLLER_SERVICER_RESID_CMDS; - servicer->res_info[0].command_map.commands = intent_controller_servicer_resid_cmd_map; -} - -void intent_servicer(void *args) { - device_control_servicer_t servicer_ctx; - - servicer_t *servicer = (servicer_t*)args; - xassert(servicer != NULL); - control_resid_t *resources = (control_resid_t*)pvPortMalloc(servicer->num_resources * sizeof(control_resid_t)); - for(int i=0; inum_resources; i++) - { - resources[i] = servicer->res_info[i].resource; - } - - control_ret_t dc_ret; - debug_printf("Calling device_control_servicer_register(), servicer ID %d, on tile %d, core %d.\n", servicer->id, THIS_XCORE_TILE, rtos_core_id_get()); - - dc_ret = device_control_servicer_register(&servicer_ctx, - device_control_ctxs, - 1, - resources, servicer->num_resources); - debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); - - vPortFree(resources); - - for(;;){ - device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); - } -} -extern uint32_t detection_number; -extern asr_result_t last_asr_result; -control_ret_t intent_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) -{ - control_ret_t ret = CONTROL_SUCCESS; - uint8_t cmd_id = CONTROL_CMD_CLEAR_READ(cmd); - - memset(payload, 0, payload_len); - - debug_printf("intent_servicer_read_cmd, cmd_id: %d.\n", cmd_id); - //asr_result_t asr_result; - - //asr_error_t asr_error = asr_get_result(NULL, &asr_result); - printf("detection_number %d, last_asr_result.id %d\n", detection_number, last_asr_result.id); - - switch (cmd_id) - { - case INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION: - { - debug_printf("INTENT_CONTROLLER_SERVICER_RESID_LAST_DETECTION\n"); - payload[0] = (uint8_t) last_asr_result.id; - payload[1] = (uint8_t) detection_number; - break; - } - - default: - { - debug_printf("INTENT_CONTROLLER_SERVICER UNHANDLED COMMAND!!!\n"); - ret = CONTROL_BAD_COMMAND; - break; - } - - } - return ret; -} - -control_ret_t intent_servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) -{ - control_ret_t ret = CONTROL_SUCCESS; - - uint8_t cmd_id = CONTROL_CMD_CLEAR_READ(cmd); - debug_printf("intent_servicer_write_cmd cmd_id %d.\n", cmd_id); - - switch (cmd_id) - { - - // Add the handling of the write commands here - default: - debug_printf("INTENT_CONTROLLER_SERVICER UNHANDLED COMMAND!!!\n"); - ret = CONTROL_BAD_COMMAND; - break; - } - - return ret; -} diff --git a/examples/ffd/src/control/intent_servicer.h b/examples/ffd/src/control/intent_servicer.h deleted file mode 100644 index 53914879..00000000 --- a/examples/ffd/src/control/intent_servicer.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#pragma once - -#include "servicer.h" - -#define INTENT_CONTROLLER_SERVICER_RESID (240) -#define NUM_RESOURCES_INTENT_SERVICER (1) // Intent servicer - -/** - * @brief DFU servicer task. - * - * This task handles DFU commands from the device control interface and relays - * them to the internal DFU INT state machine. - * - * \param args Pointer to the Servicer's state data structure - */ -void intent_servicer(void *args); - -// Servicer initialization functions -/** - * @brief DFU servicer initialisation function. - * \param servicer Pointer to the Servicer's state data structure - */ -void intent_servicer_init(servicer_t *servicer); - -/** - * @brief DFU servicer read command handler - * - * Handles read commands dedicated to the DFU servicer resource - * - * @param res_info Resource info of the current command - * @param cmd Command ID of this command - * @param payload Pointer to the payload that contains the write data - * @param payload_len Length in bytes of the write command payload - * @return control_ret_t CONTROL_SUCCESS if command handled successfully, - * otherwise control_ret_t error status indicating the error. - */ -control_ret_t intent_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); - -/** - * @brief DFU servicer write command handler - * - * Handles write commands dedicated to the DFU servicer resource - * - * @param res_info Resource info of the current command - * @param cmd Command ID of this command - * @param payload Pointer to the payload that contains the write data - * @param payload_len Length in bytes of the write command payload - * @return control_ret_t CONTROL_SUCCESS if command handled successfully, - * otherwise control_ret_t error status indicating the error. - */ -control_ret_t intent_servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); \ No newline at end of file diff --git a/examples/ffd/src/control/servicer.c b/examples/ffd/src/control/servicer.c deleted file mode 100644 index 15f572b0..00000000 --- a/examples/ffd/src/control/servicer.c +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#define DEBUG_UNIT SERVICER_TASK -#ifndef DEBUG_PRINT_ENABLE_SERVICER_TASK - #define DEBUG_PRINT_ENABLE_SERVICER_TASK 0 -#endif -#include "debug_print.h" -#include -#include -#include "platform/platform_conf.h" -#include "device_control_i2c.h" -#include "servicer.h" -#include "intent_servicer.h" - -#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) - -static device_control_t device_control_i2c_ctx_s; -device_control_t *device_control_i2c_ctx = (device_control_t *) &device_control_i2c_ctx_s; -device_control_t *device_control_ctxs[APP_CONTROL_TRANSPORT_COUNT] = { - (device_control_t *) &device_control_i2c_ctx_s, -}; -#endif - -//-----------------Servicer read write callback functions-----------------------// -DEVICE_CONTROL_CALLBACK_ATTR -control_ret_t read_cmd(control_resid_t resid, control_cmd_t cmd, uint8_t *payload, size_t payload_len, void *app_data) -{ - control_ret_t ret = CONTROL_SUCCESS; - servicer_t *servicer = (servicer_t*)app_data; - - // For read commands, payload[0] is reserved from status. So payload_len is one more than the payload_len stored in the resource command map - payload_len -= 1; - uint8_t *payload_ptr = &payload[1]; //Excluding the status byte, which is updated later. - - debug_printf("Servicer ID %d on tile %d received READ command %02x for resid %02x\n\t",servicer->id, THIS_XCORE_TILE, cmd, resid); - debug_printf("The command is requesting %d bytes\n\t", payload_len); - - - control_resource_info_t *current_res_info = get_res_info(resid, servicer); - xassert(current_res_info != NULL); // This should never happen - control_cmd_info_t *current_cmd_info; - ret = validate_cmd(¤t_cmd_info, current_res_info, cmd, payload_ptr, payload_len); - if(ret != CONTROL_SUCCESS) - { - payload[0] = ret; // Update status in byte 0 - return ret; - } - // Check if command is for the servicer itself - if(current_res_info->resource == servicer->res_info[0].resource) - { - ret = servicer_read_cmd(current_res_info, cmd, payload_ptr, payload_len); - payload[0] = ret; - return ret; - } - return CONTROL_ERROR; -} - -DEVICE_CONTROL_CALLBACK_ATTR -control_ret_t write_cmd(control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len, void *app_data) -{ - control_ret_t ret = CONTROL_SUCCESS; - servicer_t *servicer = (servicer_t*)app_data; - //debug_printf("Device control WRITE. Servicer ID %d\n\t", servicer->id); - - debug_printf("Servicer ID %d on tile %d received WRITE command %02x for resid %02x\n\t", servicer->id, THIS_XCORE_TILE, cmd, resid); - debug_printf("The command has %d bytes\n\t", payload_len); - - control_resource_info_t *current_res_info = get_res_info(resid, servicer); - xassert(current_res_info != NULL); - control_cmd_info_t *current_cmd_info; - ret = validate_cmd(¤t_cmd_info, current_res_info, cmd, payload, payload_len); - if(ret != CONTROL_SUCCESS) - { - return ret; - } - // Check if command is for the servicer itself - if(current_res_info->resource == servicer->res_info[0].resource) - { - ret = servicer_write_cmd(current_res_info, cmd, payload, payload_len); - return ret; - } - return CONTROL_ERROR; -} - -// Initialise packet payload pointers to point to valid memory. - - -//-----------------Servicer helper functions-----------------------// -// Return a pointer to the control_cmd_info_t structure for a given command ID. Return NULL if command not found in the -// command map for the resource. -control_cmd_info_t* get_cmd_info(uint8_t cmd_id, const control_resource_info_t *res_info) -{ - for(int i=0; icommand_map.num_commands; i++) - { - if(res_info->command_map.commands[i].cmd_id == cmd_id) - { - return &res_info->command_map.commands[i]; - } - } - return NULL; -} - -// Return a pointer to the servicer's control_resource_info_t structure for a given resource ID. -// Return NULL if the resource ID is not found in the list of resources serviced by the servicer. -control_resource_info_t* get_res_info(control_resid_t resource, const servicer_t *servicer) -{ - for(int res=0; resnum_resources; res++) - { - // Get the cmd_info for the current command - if(servicer->res_info[res].resource == resource) - { - return &servicer->res_info[res]; - } - } - return NULL; -} - -// Validate the command from the host against that commands information in the stored command_map -control_ret_t validate_cmd(control_cmd_info_t **cmd_info, - control_resource_info_t *res_info, - control_cmd_t cmd, - const uint8_t *payload, - size_t payload_len) -{ - control_ret_t ret = CONTROL_SUCCESS; - *cmd_info = get_cmd_info(CONTROL_CMD_CLEAR_READ(cmd), res_info); - if(*cmd_info == NULL) - { - return SERVICER_WRONG_COMMAND_ID; - } - - // Validate non special command length - // Don't do payload check for special commands since for the last filter chunk, host might request less than the payload length specified in the cmd_map - - if(payload_len != (*cmd_info)->bytes_per_val * (*cmd_info)->num_vals) - { - return SERVICER_WRONG_COMMAND_LEN; - } - // Payload validation happens either on the host or in the specific command handlers. No generic payload validation is done in the servicer. - - return ret; -} - -// Process write commands directed to the servicer resource -control_ret_t servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) -{ - // Handle write commands directed to a servicer resource - switch(res_info->resource) - { - case INTENT_CONTROLLER_SERVICER_RESID: - return intent_servicer_write_cmd(res_info, cmd, payload, payload_len); - break; - } - return CONTROL_SUCCESS; -} - -// Process read commands directed to the servicer resource -control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) -{ - control_ret_t ret = CONTROL_SUCCESS; - // Handle read commands directed to a servicer resource - switch(res_info->resource) - { - case INTENT_CONTROLLER_SERVICER_RESID: - ret = intent_servicer_read_cmd(res_info, cmd, payload, payload_len); - break; - } - return ret; -} diff --git a/examples/ffd/src/control/servicer.h b/examples/ffd/src/control/servicer.h deleted file mode 100644 index 8e979b27..00000000 --- a/examples/ffd/src/control/servicer.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#pragma once -#include "device_control.h" -#include "cmd_map.h" - -#define NUM_TILE_0_SERVICERS (1) // only intent servicer is used -#define NUM_TILE_1_SERVICERS (0) // no control servicer - -extern device_control_t *device_control_i2c_ctx; -extern device_control_t *device_control_ctxs[1]; - -/** - * Clears the read bit on a command code - * - * \param[in,out] c The command code to clear the read bit on. - */ -#define CONTROL_CMD_CLEAR_READ(c) ((c) & ~0x80) - -// Structure encapsulating all the information about a resource -typedef struct -{ - control_resid_t resource; - command_map_t command_map; -}control_resource_info_t; - -typedef struct { - uint32_t start_io; // set to 1 on one servicer per tile to make it responsible for starting the IO tasks on that tile. - int32_t id; // Unique ID for the servicer. Used for debugging. - // Num resources - int32_t num_resources; - // Resource ID and command map for every resource - control_resource_info_t *res_info; -}servicer_t; - -// Servicer device_control callback functions -/** - * @brief Device control callback function to handle a read command. - * - * @param resid Resource ID of the command - * @param cmd Command ID of the command - * @param payload Pointer to the payload buffer that needs to be updated with the read command response. - * @param payload_len Length of the payload buffer - * @param app_data Application specific data. - * @return CONTROL_SUCCESS is command is handled successfully, - * otherwise control_ret_t error status indicating the error. - */ -extern DEVICE_CONTROL_CALLBACK_ATTR -control_ret_t read_cmd(control_resid_t resid, control_cmd_t cmd, uint8_t *payload, size_t payload_len, void *app_data); - -/** - * @brief Device control callback function to handle a write command - * - * @param resid Resource ID of the command - * @param cmd Command ID of the command - * @param payload Pointer to the payload buffer containing the payload the host wants to write to the device. - * @param payload_len Length of the payload buffer - * @param app_data Application specific data - * @return CONTROL_SUCCESS is command is handled successfully, - * otherwise control_ret_t error status indicating the error. - */ -extern DEVICE_CONTROL_CALLBACK_ATTR -control_ret_t write_cmd(control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len, void *app_data); - -// Servicer helper functions -/** - * @brief Check if a command exists in the command map for a given resource and return the pointer to the cmd info object for the given command - * - * @param cmd_id Command ID for which the control_cmd_info_t object is required. - * @param res_info Pointer to the resource info which contains all the command info objects for a given resource. - * @return Pointer to a control_cmd_info_t object when one is found for the given Command ID, NULL otherwise. - */ -control_cmd_info_t* get_cmd_info(uint8_t cmd_id, const control_resource_info_t *res_info); - -/** - * @brief Check if a resource ID is one of the resources supported by a servicer and return a pointer to the resource info object. - * - * @param resource Resource ID that needs to be checked. - * @param servicer Pointer to the servicer state structure that holds all the resource info objects for the servicer. - * @return Pointer to the resource_info_t object when one is found for a given resource ID, NULL otherwise. - */ -control_resource_info_t* get_res_info(control_resid_t resource, const servicer_t *servicer); - -/** - * @brief Validate the command received in the servicer and return a pointer to the command info if the command is valid. - * - * All checks related to the command such as command ID being valid, correctness of the payload length, - * validation of the actual payload for write commands are done in this function. - * - * @param cmd_info Pointer in which the address of the command info is returned if this is a valid command/ - * @param res_info Resource info of the resource the command is meant for. - * @param cmd Command ID of the command that needs validating - * @param payload Payload buffer of the command that needs validating. - * @param payload_len Payload length of the payload. - * @return CONTROL_SUCCESS if the command is found to be valid for the given resource. control_ret_t error otherwise - * indicating the error code of the specific validation check that failed. - */ -control_ret_t validate_cmd(control_cmd_info_t **cmd_info, - control_resource_info_t *res_info, - control_cmd_t cmd, - const uint8_t *payload, - size_t payload_len); - -/** - * @brief Function for handling write commands directed to the servicer resource itself. - * - * @param resid Command Resource ID - * @param cmd Command Command ID - * @param payload Command write payload buffer - * @param payload_len Length of the payload buffer - * @return CONTROL_SUCCESS if write command processed successfully. contro_ret_t error status otherwise. - */ -control_ret_t servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); - -/** - * @brief Function for handling read commands directed to the servicer resource itself. - * - * @param resid Command Resource ID - * @param cmd Command Command ID - * @param payload Payload buffer to populate with the read response - * @param payload_len Length of the payload buffer - * @return CONTROL_SUCCESS if read command processed successfully. contro_ret_t error status otherwise. - */ -control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index 7fc1d773..eb14930b 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -30,9 +30,6 @@ #include "gpio_ctrl/gpi_ctrl.h" #include "gpio_ctrl/leds.h" #include "intent_handler/intent_handler.h" -#if appconfI2C_SLAVE_ENABLED -#include "intent_servicer.h" -#endif #if appconfRECOVER_MCLK_I2S_APP_PLL /* Config headers for sw_pll */ @@ -273,19 +270,6 @@ void startup_task(void *arg) #endif #endif -#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) - // Initialise control related things - servicer_t servicer_intent; - intent_servicer_init(&servicer_intent); - - xTaskCreate((TaskFunction_t)intent_servicer, - "intent servicer", - RTOS_THREAD_STACK_SIZE(intent_servicer), - &servicer_intent, - appconfDEVICE_CONTROL_I2C_PRIORITY, - NULL); -#endif - #if ON_TILE(0) led_task_create(appconfLED_TASK_PRIORITY, NULL); #endif diff --git a/examples/ffva/src/dfu_int/dfu_servicer.c b/examples/ffva/src/dfu_int/dfu_servicer.c index dfc5cc18..0606ad1c 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.c +++ b/examples/ffva/src/dfu_int/dfu_servicer.c @@ -114,7 +114,7 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c payload[2] = time_mid; payload[3] = time_high; payload[4] = retval.next_state; - + break; } @@ -130,7 +130,7 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK\n"); uint16_t transferblock = dfu_int_get_transfer_block(); - + uint8_t tb_high, tb_low; tb_low = (uint8_t)(transferblock & 0xFF); tb_high = (uint8_t)((transferblock >> 8) & 0xFF); diff --git a/modules/asr/intent_engine/intent_engine.c b/modules/asr/intent_engine/intent_engine.c index 2d258a5c..e8d231c6 100644 --- a/modules/asr/intent_engine/intent_engine.c +++ b/modules/asr/intent_engine/intent_engine.c @@ -113,7 +113,7 @@ static void timeout_event_handler(TimerHandle_t pxTimer) } } asr_result_t last_asr_result = {0}; -uint32_t detection_number = 0; + #pragma stackfunction 1000 void intent_engine_task(void *args) { @@ -171,7 +171,7 @@ void intent_engine_task(void *args) asr_error = asr_get_result(asr_ctx, &asr_result); memcpy(&last_asr_result, &asr_result, sizeof(asr_result_t)); - detection_number++; + if (asr_error != ASR_OK) continue; word_id = asr_result.id; From 4f004e29629307d45efd00103f52265ef505206a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 2 Aug 2024 09:13:09 +0100 Subject: [PATCH 169/288] Remove USB code --- .../XK_VOICE_L71/platform/platform_conf.h | 8 -- examples/ffd/ffd_i2s_input_cyberon.cmake | 1 - examples/ffd/src/tusb_config.h | 136 ------------------ 3 files changed, 145 deletions(-) delete mode 100644 examples/ffd/src/tusb_config.h diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h index 515f1f2d..306a3d32 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -89,14 +89,6 @@ /* Other required defines */ /*****************************************/ -#ifndef APP_CONTROL_TRANSPORT_COUNT -#define APP_CONTROL_TRANSPORT_COUNT appconfI2C_SLAVE_ENABLED -#endif // APP_CONTROL_TRANSPORT_COUNT - -#ifndef appconf_CONTROL_I2C_DEVICE_ADDR -#define appconf_CONTROL_I2C_DEVICE_ADDR 0x42 -#endif /* appconf_CONTROL_I2C_DEVICE_ADDR*/ - #ifndef appconf_CONTROL_I2C_DEVICE_ADDR #define appconf_CONTROL_I2C_DEVICE_ADDR 0x42 #endif /* appconf_CONTROL_I2C_DEVICE_ADDR*/ diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 600a3bbc..3f460f13 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -105,7 +105,6 @@ set(APP_COMMON_LINK_LIBRARIES sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 - rtos::freertos_usb lib_src lib_sw_pll ) diff --git a/examples/ffd/src/tusb_config.h b/examples/ffd/src/tusb_config.h deleted file mode 100644 index ac13616f..00000000 --- a/examples/ffd/src/tusb_config.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * Copyright (c) 2021 XMOS LIMITED - * - * 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. - * - */ - -#ifndef _TUSB_CONFIG_H_ -#define _TUSB_CONFIG_H_ - -#include -#include "app_conf.h" -#include "audio_pipeline.h" -#include "rtos_printf.h" - -#define appconfUSB_INTERRUPT_CORE 2 /* Must be kept off I/O cores. Best kept off core 0 with the tick ISR. */ -#define appconfUSB_SOF_INTERRUPT_CORE 3 /* Must be kept off I/O cores. Best kept off cores with other ISRs. */ -#define appconfXUD_IO_CORE 1 /* Must be kept off core 0 with the RTOS tick ISR */ -#define appconfUSB_AUDIO_SAMPLE_RATE 48000 - -//-------------------------------------------------------------------- -// COMMON CONFIGURATION -//-------------------------------------------------------------------- - -#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) -#define CFG_TUSB_OS OPT_OS_CUSTOM - -#ifndef CFG_TUSB_DEBUG -#define CFG_TUSB_DEBUG 0 -#endif - -#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(8))) - -#ifndef CFG_TUSB_DEBUG_PRINTF -#ifdef rtos_printf -#define CFG_TUSB_DEBUG_PRINTF rtos_printf -#endif -#endif - -//-------------------------------------------------------------------- -// DEVICE CONFIGURATION -//-------------------------------------------------------------------- - -#define CFG_TUD_EP_MAX 12 -#define CFG_TUD_TASK_QUEUE_SZ 8 -#define CFG_TUD_ENDPOINT0_SIZE 64 - -#define CFG_TUD_XCORE_INTERRUPT_CORE appconfUSB_INTERRUPT_CORE -#define CFG_TUD_XCORE_SOF_INTERRUPT_CORE appconfUSB_SOF_INTERRUPT_CORE -#define CFG_TUD_XCORE_IO_CORE_MASK (1 << appconfXUD_IO_CORE) - -//------------- CLASS -------------// -#define CFG_TUD_CDC 0 -#define CFG_TUD_MSC 0 -#define CFG_TUD_HID 0 -#define CFG_TUD_MIDI 0 -#define CFG_TUD_AUDIO 1 -#define CFG_TUD_VENDOR 0 -#define CFG_TUD_DFU 1 - -//-------------------------------------------------------------------- -// DFU DRIVER CONFIGURATION -//-------------------------------------------------------------------- -// DFU buffer size, it has to be set to the buffer size used in TUD_DFU_DESCRIPTOR -#define CFG_TUD_DFU_XFER_BUFSIZE 4096 - -//-------------------------------------------------------------------- -// AUDIO CLASS DRIVER CONFIGURATION -//-------------------------------------------------------------------- -extern const uint16_t tud_audio_desc_lengths[CFG_TUD_AUDIO]; - -#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN tud_audio_desc_lengths[0] -#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 -#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 - -/* TODO make these configurable in app_conf? */ -#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 -#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX 2 - -#if appconfUSB_AUDIO_MODE == appconfUSB_AUDIO_RELEASE -#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 2 -#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 2 -#else -#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 6 -#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 4 -#endif - -#if (appconfMIC_SRC_DEFAULT == appconfMIC_SRC_USB) -// In appconfMIC_SRC_USB, we wait forever for input mic and AEC reference channels -// will not overflow output -#define USB_AUDIO_RECV_DELAY portMAX_DELAY -#else -// In or any other mode, we do not wait for input AEC reference channels. -// The reference will be all zeros if no AEC reference is received. -// This is the typical mode. -#define USB_AUDIO_RECV_DELAY 0 -#endif - -// EP and buffer sizes -#define AUDIO_FRAMES_PER_USB_FRAME (appconfUSB_AUDIO_SAMPLE_RATE / 1000) -#if appconfUSB_AUDIO_SAMPLE_RATE == 48000 -#define USB_TASK_STACK_SIZE 2000 -#endif - -// To support USB Adaptive/Asynchronous, maximum packet size must be large enough to accommodate an extra set of samples per frame. -// Adding 1 to AUDIO_SAMPLES_PER_USB_FRAME allows this. -#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 -#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ ((AUDIO_FRAMES_PER_USB_FRAME + 1) * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX) -#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX (CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ) // Maximum EP IN size for all AS alternate settings used -#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ - -#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1 -#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ ((AUDIO_FRAMES_PER_USB_FRAME + 1) * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX) -#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX (CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ + 2) // Maximum EP OUT size for all AS alternate settings used. Plus 2 for CRC -#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ*3 - -#endif /* _TUSB_CONFIG_H_ */ From 547d4d2cbfb1b202a1a3caadd115f8ecc600940d Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 2 Aug 2024 09:20:46 +0100 Subject: [PATCH 170/288] Move code to new files --- .../XK_VOICE_L71/platform/platform_start.c | 43 +---------------- examples/ffd/src/i2c_reg_handling.c | 46 +++++++++++++++++++ examples/ffd/src/i2c_reg_handling.h | 20 ++++++++ 3 files changed, 67 insertions(+), 42 deletions(-) create mode 100644 examples/ffd/src/i2c_reg_handling.c create mode 100644 examples/ffd/src/i2c_reg_handling.h diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index 4ebde1a4..6be13160 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -14,7 +14,7 @@ #include "platform_conf.h" #include "platform/driver_instances.h" #include "dac3101.h" -#include "asr.h" +#include "i2c_reg_handling.h" extern asr_result_t last_asr_result; @@ -51,47 +51,6 @@ static void i2c_master_start(void) #endif } -#define WAKEWORD_REG_ADDRESS_START 0x40 -#define WAKEWORD_REG_ADDRESS_END 0x49 -#define WRITE_REQUEST_MIN_LEN 1 - -RTOS_I2C_SLAVE_CALLBACK_ATTR -size_t read_device_reg(rtos_i2c_slave_t *ctx, - asr_result_t *last_asr_result, - uint8_t **data) -{ - uint8_t * data_p = *data; - uint8_t reg_addr = data_p[0]; - uint8_t reg_value = 0; - if (reg_addr >= WAKEWORD_REG_ADDRESS_START && reg_addr <= WAKEWORD_REG_ADDRESS_END) { - if (last_asr_result->id == reg_addr - WAKEWORD_REG_ADDRESS_START + 1) - { - uint8_t score = (uint8_t) last_asr_result->score&0xF; - printf("Found wakeword information: ID %d, score %d\n", last_asr_result->id, score); - reg_value = score; - } else { - reg_value = 0; - } - } else { - reg_value = reg_addr - 1; - } - data_p[0] = reg_value; - printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); - return 1; -} - -RTOS_I2C_SLAVE_CALLBACK_ATTR -void write_device_reg(rtos_i2c_slave_t *ctx, - void *app_data, - uint8_t *data, - size_t len) -{ - // If the length is lower than WRITE_REQUEST_MIN_LEN, it is a read request - if (len > WRITE_REQUEST_MIN_LEN) { - printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); - } -} - static void i2c_slave_start(void) { #if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c new file mode 100644 index 00000000..eeffb922 --- /dev/null +++ b/examples/ffd/src/i2c_reg_handling.c @@ -0,0 +1,46 @@ + +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "i2c_reg_handling.h" + +#define WAKEWORD_REG_ADDRESS_START 0x40 +#define WAKEWORD_REG_ADDRESS_END 0x49 +#define WRITE_REQUEST_MIN_LEN 1 + +RTOS_I2C_SLAVE_CALLBACK_ATTR +size_t read_device_reg(rtos_i2c_slave_t *ctx, + asr_result_t *last_asr_result, + uint8_t **data) +{ + uint8_t * data_p = *data; + uint8_t reg_addr = data_p[0]; + uint8_t reg_value = 0; + if (reg_addr >= WAKEWORD_REG_ADDRESS_START && reg_addr <= WAKEWORD_REG_ADDRESS_END) { + if (last_asr_result->id == reg_addr - WAKEWORD_REG_ADDRESS_START + 1) + { + uint8_t score = (uint8_t) last_asr_result->score&0xF; + printf("Found wakeword information: ID %d, score %d\n", last_asr_result->id, score); + reg_value = score; + } else { + reg_value = 0; + } + } else { + reg_value = reg_addr - 1; + } + data_p[0] = reg_value; + printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); + return 1; +} + +RTOS_I2C_SLAVE_CALLBACK_ATTR +void write_device_reg(rtos_i2c_slave_t *ctx, + void *app_data, + uint8_t *data, + size_t len) +{ + // If the length is lower than WRITE_REQUEST_MIN_LEN, it is a read request + if (len > WRITE_REQUEST_MIN_LEN) { + printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); + } +} \ No newline at end of file diff --git a/examples/ffd/src/i2c_reg_handling.h b/examples/ffd/src/i2c_reg_handling.h new file mode 100644 index 00000000..a8e1f303 --- /dev/null +++ b/examples/ffd/src/i2c_reg_handling.h @@ -0,0 +1,20 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "rtos_i2c_slave.h" +#include "asr.h" + +#define WAKEWORD_REG_ADDRESS_START 0x40 +#define WAKEWORD_REG_ADDRESS_END 0x49 +#define WRITE_REQUEST_MIN_LEN 1 + +RTOS_I2C_SLAVE_CALLBACK_ATTR +size_t read_device_reg(rtos_i2c_slave_t *ctx, + asr_result_t *last_asr_result, + uint8_t **data); + +RTOS_I2C_SLAVE_CALLBACK_ATTR +void write_device_reg(rtos_i2c_slave_t *ctx, + void *app_data, + uint8_t *data, + size_t len); \ No newline at end of file From d0546a6bab36a07db86b7fc34499618a4552d71f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 2 Aug 2024 09:30:10 +0100 Subject: [PATCH 171/288] Add comments --- examples/ffd/src/i2c_reg_handling.c | 13 ++++++++++++ examples/ffd/src/i2c_reg_handling.h | 33 +++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index eeffb922..50f75f6f 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -4,8 +4,19 @@ #include "i2c_reg_handling.h" +/** + * @brief Start address for wakeword register. + */ #define WAKEWORD_REG_ADDRESS_START 0x40 + +/** + * @brief End address for wakeword register. + */ #define WAKEWORD_REG_ADDRESS_END 0x49 + +/** + * @brief Minimum length for a write request. + */ #define WRITE_REQUEST_MIN_LEN 1 RTOS_I2C_SLAVE_CALLBACK_ATTR @@ -16,6 +27,8 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, uint8_t * data_p = *data; uint8_t reg_addr = data_p[0]; uint8_t reg_value = 0; + // If the register address is in the wakeword range, return the score of the last wakeword detection + // Otherwise, return the register address - 1 if (reg_addr >= WAKEWORD_REG_ADDRESS_START && reg_addr <= WAKEWORD_REG_ADDRESS_END) { if (last_asr_result->id == reg_addr - WAKEWORD_REG_ADDRESS_START + 1) { diff --git a/examples/ffd/src/i2c_reg_handling.h b/examples/ffd/src/i2c_reg_handling.h index a8e1f303..40cc739d 100644 --- a/examples/ffd/src/i2c_reg_handling.h +++ b/examples/ffd/src/i2c_reg_handling.h @@ -4,17 +4,32 @@ #include "rtos_i2c_slave.h" #include "asr.h" -#define WAKEWORD_REG_ADDRESS_START 0x40 -#define WAKEWORD_REG_ADDRESS_END 0x49 -#define WRITE_REQUEST_MIN_LEN 1 - +/** + * @brief Callback for reading data from a device register over I2C. + * Only one byte of data is read from the register. + * + * @param ctx Pointer to the I2C slave context. + * @param last_asr_result Pointer to the last Automatic Speech Recognition (ASR) result. + * @param data Pointer to a pointer to the the data received from the master device. + * + * @return The size of the data read. + */ RTOS_I2C_SLAVE_CALLBACK_ATTR size_t read_device_reg(rtos_i2c_slave_t *ctx, - asr_result_t *last_asr_result, - uint8_t **data); + asr_result_t *last_asr_result, + uint8_t **data); +/** + * @brief Callback for writing data to a device register over I2C. + * Only one byte of data is written to the register. + * + * @param ctx Pointer to the I2C slave context. + * @param app_data Pointer to application-specific data. Not used. + * @param data Pointer pointer to the the data received from the master device. + * @param len The length of the data to be written. + */ RTOS_I2C_SLAVE_CALLBACK_ATTR void write_device_reg(rtos_i2c_slave_t *ctx, - void *app_data, - uint8_t *data, - size_t len); \ No newline at end of file + void *app_data, + uint8_t *data, + size_t len); From 8ed9894ff9acc339b7cbea0dec832ff7429778e6 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 2 Aug 2024 10:05:23 +0100 Subject: [PATCH 172/288] Fix typo --- examples/ffd/src/i2c_reg_handling.c | 116 ++++++++++++++-------------- examples/ffd/src/i2c_reg_handling.h | 70 ++++++++--------- 2 files changed, 93 insertions(+), 93 deletions(-) diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index 50f75f6f..4be77a75 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -1,59 +1,59 @@ - -// Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#include "i2c_reg_handling.h" - -/** - * @brief Start address for wakeword register. - */ -#define WAKEWORD_REG_ADDRESS_START 0x40 - -/** - * @brief End address for wakeword register. - */ -#define WAKEWORD_REG_ADDRESS_END 0x49 - -/** - * @brief Minimum length for a write request. - */ -#define WRITE_REQUEST_MIN_LEN 1 - -RTOS_I2C_SLAVE_CALLBACK_ATTR -size_t read_device_reg(rtos_i2c_slave_t *ctx, - asr_result_t *last_asr_result, - uint8_t **data) -{ - uint8_t * data_p = *data; - uint8_t reg_addr = data_p[0]; - uint8_t reg_value = 0; - // If the register address is in the wakeword range, return the score of the last wakeword detection - // Otherwise, return the register address - 1 - if (reg_addr >= WAKEWORD_REG_ADDRESS_START && reg_addr <= WAKEWORD_REG_ADDRESS_END) { - if (last_asr_result->id == reg_addr - WAKEWORD_REG_ADDRESS_START + 1) - { - uint8_t score = (uint8_t) last_asr_result->score&0xF; - printf("Found wakeword information: ID %d, score %d\n", last_asr_result->id, score); - reg_value = score; - } else { - reg_value = 0; - } - } else { - reg_value = reg_addr - 1; - } - data_p[0] = reg_value; - printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); - return 1; -} - -RTOS_I2C_SLAVE_CALLBACK_ATTR -void write_device_reg(rtos_i2c_slave_t *ctx, - void *app_data, - uint8_t *data, - size_t len) -{ - // If the length is lower than WRITE_REQUEST_MIN_LEN, it is a read request - if (len > WRITE_REQUEST_MIN_LEN) { - printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); - } + +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "i2c_reg_handling.h" + +/** + * @brief Start address for wakeword register. + */ +#define WAKEWORD_REG_ADDRESS_START 0x40 + +/** + * @brief End address for wakeword register. + */ +#define WAKEWORD_REG_ADDRESS_END 0x49 + +/** + * @brief Minimum length for a write request. + */ +#define WRITE_REQUEST_MIN_LEN 1 + +RTOS_I2C_SLAVE_CALLBACK_ATTR +size_t read_device_reg(rtos_i2c_slave_t *ctx, + asr_result_t *last_asr_result, + uint8_t **data) +{ + uint8_t * data_p = *data; + uint8_t reg_addr = data_p[0]; + uint8_t reg_value = 0; + // If the register address is in the wakeword range, return the score of the last wakeword detection + // Otherwise, return the register address - 1 + if (reg_addr >= WAKEWORD_REG_ADDRESS_START && reg_addr <= WAKEWORD_REG_ADDRESS_END) { + if (last_asr_result->id == reg_addr - WAKEWORD_REG_ADDRESS_START + 1) + { + uint8_t score = (uint8_t) last_asr_result->score&0xFF; + printf("Found wakeword information: ID %d, score %d\n", last_asr_result->id, score); + reg_value = score; + } else { + reg_value = 0; + } + } else { + reg_value = reg_addr - 1; + } + data_p[0] = reg_value; + printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); + return 1; +} + +RTOS_I2C_SLAVE_CALLBACK_ATTR +void write_device_reg(rtos_i2c_slave_t *ctx, + void *app_data, + uint8_t *data, + size_t len) +{ + // If the length is lower than WRITE_REQUEST_MIN_LEN, it is a read request + if (len > WRITE_REQUEST_MIN_LEN) { + printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); + } } \ No newline at end of file diff --git a/examples/ffd/src/i2c_reg_handling.h b/examples/ffd/src/i2c_reg_handling.h index 40cc739d..a29f501d 100644 --- a/examples/ffd/src/i2c_reg_handling.h +++ b/examples/ffd/src/i2c_reg_handling.h @@ -1,35 +1,35 @@ -// Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#include "rtos_i2c_slave.h" -#include "asr.h" - -/** - * @brief Callback for reading data from a device register over I2C. - * Only one byte of data is read from the register. - * - * @param ctx Pointer to the I2C slave context. - * @param last_asr_result Pointer to the last Automatic Speech Recognition (ASR) result. - * @param data Pointer to a pointer to the the data received from the master device. - * - * @return The size of the data read. - */ -RTOS_I2C_SLAVE_CALLBACK_ATTR -size_t read_device_reg(rtos_i2c_slave_t *ctx, - asr_result_t *last_asr_result, - uint8_t **data); - -/** - * @brief Callback for writing data to a device register over I2C. - * Only one byte of data is written to the register. - * - * @param ctx Pointer to the I2C slave context. - * @param app_data Pointer to application-specific data. Not used. - * @param data Pointer pointer to the the data received from the master device. - * @param len The length of the data to be written. - */ -RTOS_I2C_SLAVE_CALLBACK_ATTR -void write_device_reg(rtos_i2c_slave_t *ctx, - void *app_data, - uint8_t *data, - size_t len); +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "rtos_i2c_slave.h" +#include "asr.h" + +/** + * @brief Callback for reading data from a device register over I2C. + * Only one byte of data is read from the register. + * + * @param ctx Pointer to the I2C slave context. + * @param last_asr_result Pointer to the last Automatic Speech Recognition (ASR) result. + * @param data Pointer to a pointer to the the data received from the master device. + * + * @return The size of the data read. + */ +RTOS_I2C_SLAVE_CALLBACK_ATTR +size_t read_device_reg(rtos_i2c_slave_t *ctx, + asr_result_t *last_asr_result, + uint8_t **data); + +/** + * @brief Callback for writing data to a device register over I2C. + * Only one byte of data is written to the register. + * + * @param ctx Pointer to the I2C slave context. + * @param app_data Pointer to application-specific data. Not used. + * @param data Pointer pointer to the the data received from the master device. + * @param len The length of the data to be written. + */ +RTOS_I2C_SLAVE_CALLBACK_ATTR +void write_device_reg(rtos_i2c_slave_t *ctx, + void *app_data, + uint8_t *data, + size_t len); From b290aeeaa0c092dca8a9d66234eb30a96cbdf69e Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 2 Aug 2024 10:12:03 +0100 Subject: [PATCH 173/288] Remove masking --- examples/ffd/src/i2c_reg_handling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index 4be77a75..803b5d35 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -32,7 +32,7 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, if (reg_addr >= WAKEWORD_REG_ADDRESS_START && reg_addr <= WAKEWORD_REG_ADDRESS_END) { if (last_asr_result->id == reg_addr - WAKEWORD_REG_ADDRESS_START + 1) { - uint8_t score = (uint8_t) last_asr_result->score&0xFF; + uint8_t score = (uint8_t) last_asr_result->score; printf("Found wakeword information: ID %d, score %d\n", last_asr_result->id, score); reg_value = score; } else { From 677043541b88876ee4289849da6baddd2181e83e Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 7 Aug 2024 16:24:20 +0100 Subject: [PATCH 174/288] Fix defines and make code more generic --- examples/ffd/src/app_conf.h | 11 +++++++++- examples/ffd/src/i2c_reg_handling.c | 31 +++++------------------------ examples/ffd/src/i2c_reg_handling.h | 9 +++++---- 3 files changed, 20 insertions(+), 31 deletions(-) diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index c0662230..024e4b0f 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -79,6 +79,11 @@ #define appconfINTENT_I2C_OUTPUT_DEVICE_ADDR 0x01 #endif +/* @brief Address for wakeword register to be read over I2C slave*/ +#ifndef appconfWAKEWORD_REG_ADDRESS +#define appconfWAKEWORD_REG_ADDRESS 0x01 +#endif + #ifndef appconfINTENT_UART_OUTPUT_ENABLED #define appconfINTENT_UART_OUTPUT_ENABLED 1 #endif @@ -115,10 +120,14 @@ #define appconfI2C_SLAVE_ENABLED 0 #endif -#if appconfINTENT_I2C_OUTPUT_ENABLED && ! appconfI2C_MASTER_ENABLED +#if appconfINTENT_I2C_OUTPUT_ENABLED && !appconfI2C_MASTER_ENABLED #error "I2C master must be enabled for intent I2C output" #endif +#if appconfI2C_SLAVE_ENABLED && appconfINTENT_I2C_OUTPUT_ENABLED +#error "I2C slave cannot be enabled when intent I2C output over I2C master is enabled" +#endif + #ifndef appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR #define appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR 0 #endif diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index 803b5d35..ef92530a 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -4,20 +4,10 @@ #include "i2c_reg_handling.h" -/** - * @brief Start address for wakeword register. - */ -#define WAKEWORD_REG_ADDRESS_START 0x40 - -/** - * @brief End address for wakeword register. - */ -#define WAKEWORD_REG_ADDRESS_END 0x49 - /** * @brief Minimum length for a write request. */ -#define WRITE_REQUEST_MIN_LEN 1 +#define WRITE_REQUEST_MIN_LEN 1 RTOS_I2C_SLAVE_CALLBACK_ATTR size_t read_device_reg(rtos_i2c_slave_t *ctx, @@ -26,20 +16,9 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, { uint8_t * data_p = *data; uint8_t reg_addr = data_p[0]; - uint8_t reg_value = 0; - // If the register address is in the wakeword range, return the score of the last wakeword detection - // Otherwise, return the register address - 1 - if (reg_addr >= WAKEWORD_REG_ADDRESS_START && reg_addr <= WAKEWORD_REG_ADDRESS_END) { - if (last_asr_result->id == reg_addr - WAKEWORD_REG_ADDRESS_START + 1) - { - uint8_t score = (uint8_t) last_asr_result->score; - printf("Found wakeword information: ID %d, score %d\n", last_asr_result->id, score); - reg_value = score; - } else { - reg_value = 0; - } - } else { - reg_value = reg_addr - 1; + uint8_t reg_value = -1; + if (reg_addr == appconfWAKEWORD_REG_ADDRESS) { + reg_value = last_asr_result->id; } data_p[0] = reg_value; printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); @@ -56,4 +35,4 @@ void write_device_reg(rtos_i2c_slave_t *ctx, if (len > WRITE_REQUEST_MIN_LEN) { printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); } -} \ No newline at end of file +} diff --git a/examples/ffd/src/i2c_reg_handling.h b/examples/ffd/src/i2c_reg_handling.h index a29f501d..01ad7e0e 100644 --- a/examples/ffd/src/i2c_reg_handling.h +++ b/examples/ffd/src/i2c_reg_handling.h @@ -2,11 +2,12 @@ // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "rtos_i2c_slave.h" +#include "app_conf.h" #include "asr.h" /** - * @brief Callback for reading data from a device register over I2C. - * Only one byte of data is read from the register. + * Callback for reading data from a device register over I2C. + * Only one byte of data is read from the register. * * @param ctx Pointer to the I2C slave context. * @param last_asr_result Pointer to the last Automatic Speech Recognition (ASR) result. @@ -20,8 +21,8 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, uint8_t **data); /** - * @brief Callback for writing data to a device register over I2C. - * Only one byte of data is written to the register. + * Callback for writing data to a device register over I2C. + * Only one byte of data is written to the register. * * @param ctx Pointer to the I2C slave context. * @param app_data Pointer to application-specific data. Not used. From a1f111d117d02ee548604728582a102ce3744ace Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 7 Aug 2024 16:56:01 +0100 Subject: [PATCH 175/288] Update documentation --- .../ffd/deploying/configuration.rst | 51 +++++++++++++++++-- .../XK_VOICE_L71/platform/platform_conf.h | 8 --- .../XK_VOICE_L71/platform/platform_init.c | 2 +- examples/ffd/src/app_conf.h | 4 ++ .../platform/platform_conf.h | 6 +-- .../platform/platform_init.c | 2 +- .../XK_VOICE_L71/platform/platform_conf.h | 6 +-- .../XK_VOICE_L71/platform/platform_init.c | 2 +- 8 files changed, 60 insertions(+), 21 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 0d130cf3..1d5dd208 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -4,7 +4,7 @@ Configuring the Firmware ======================== -The default application performs as described in the :ref:`sln_voice_ffd_overview`. There are numerous compile time options that can be added to change the example design without requiring code changes. To change the options explained in the table below, add the desired configuration variables to the APP_COMPILE_DEFINITIONS cmake variable located `here `_. +The default application performs as described in the :ref:`sln_voice_ffd_overview`. There are numerous compile time options that can be added to change the example design without requiring code changes. To change the options explained in the table below, add the desired configuration variables to the APP_COMPILE_DEFINITIONS cmake variable in the ``.cmake`` file located in the ``examples/ffd/`` folder. If options are changed, the application firmware must be rebuilt. @@ -37,6 +37,21 @@ If options are changed, the application firmware must be rebuilt. * - appconfINTENT_I2C_OUTPUT_ENABLED - Enables/disables the |I2C| intent message - 1 + * - appconfI2C_MASTER_ENABLED + - Enabled the |I2C| master mode to configure the DAC and send the intent message + - 1 + * - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR + - Sets the |I2C| address to transmit the intent to via the |I2C| master interface + - 0x01 + * - appconfI2C_SALVE_ENABLED + - Enabled the |I2C| slave mode to read the device register with the intent message + - 0 + * - appconfI2C_SLAVE_DEVICE_ADDR + - Sets the |I2C| address to read the intent message from via the |I2C| slave interface + - 0x42 + * - appconfINTENT_I2C_REG_ADDRESS + - Sets the |I2C| register to store the intent message, this value can be read via the |I2C| slave interface + - 0x01 * - appconfUART_BAUD_RATE - Sets the baud rate for the UART tx intent interface - 9600 @@ -52,9 +67,6 @@ If options are changed, the application firmware must be rebuilt. * - appconfRECOVER_MCLK_I2S_APP_PLL - Enables/disables the recovery of the MCLK from the Software PLL application; this removes the need to use an external MCLK. - 0 - * - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR - - Sets the |I2C| slave address to transmit the intent to - - 0x01 * - appconfINTENT_TRANSPORT_DELAY_MS - Sets the delay between host wake up requested and |I2C| and UART keyword code transmission - 50 @@ -78,3 +90,34 @@ If options are changed, the application firmware must be rebuilt. The ``example_ffd_i2s_input_cyberon`` has different default values from the ones in the table above. The list of updated values can be found in the ``APP_COMPILE_DEFINITIONS`` list in ``examples\ffd\ffd_i2s_input_cyberon.cmake``. + +Configuring the |I2C| interfaces +-------------------------------- + +The |I2C| interfaces are used to communicate with the DAC and the host. The |I2C| interface can be configured as a master or slave. +The |I2C| master is used to send the intent message to the host, and the |I2C| slave is used to read the intent message from the host. +The |I2C| master and slave can be enabled or disabled by setting the ``appconfI2C_MASTER_ENABLED`` and ``appconfI2C_SLAVE_ENABLED`` configuration variables. +To send the intent ID via |I2C| master interface when a command is detected, the following variables must be set: + + - ``appconfINTENT_I2C_OUTPUT_ENABLED`` must be set to 1. + - ``appconfI2C_MASTER_ENABLED`` must be set to 1. + - ``appconfINTENT_I2C_OUTPUT_DEVICE_ADDR`` must be set to desired address used by the |I2C| slave device. + - ``appconfI2C_SLAVE_ENABLED`` must be set to 0. + +The retrieve the intent message from the host via the |I2C| slave interface, the following variables must be set: + + - ``appconfI2C_SLAVE_ENABLED`` must be set to 1. + - ``appconfI2C_SLAVE_DEVICE_ADDR`` must be set to the desired address used by the |I2C| master device. + - ``appconfINTENT_I2C_REG_ADDRESS`` must be set to the desired register read by of the |I2C| master device. + - ``appconfINTENT_I2C_OUTPUT_ENABLED`` must be set to 0. + +The handling of the |I2C| slave registers is done in the ``examples\ffd\src\i2c_reg_handling.c`` file. The vatiable ``appconfINTENT_I2C_REG_ADDRESS`` is used in the callback function ``read_device_reg()``. + +Configuring the |I2S| interfaces +-------------------------------- + +The |I2S| interface can be configured as a master or slave. The |I2S| interface is used to receive the audio data over the |I2S| interface. +The |I2S| interface can be enabled or disabled by setting the ``appconfI2S_MODE`` configuration variable either to ``appconfI2S_MODE_MASTER`` or ``appconfI2S_MODE_SLAVE``. +The sample rate of the |I2S| interface can be set by changing the ``appconfI2S_AUDIO_SAMPLE_RATE`` configuration variable. +The MCLK can be recovered from the Software PLL application by setting the ``appconfRECOVER_MCLK_I2S_APP_PLL`` configuration variable to 1, this will remove the need to use an external MCLK. + diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h index 306a3d32..40d68c24 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -85,14 +85,6 @@ #define appconfPIPELINE_AUDIO_SAMPLE_RATE 16000 #endif /* appconfPIPELINE_AUDIO_SAMPLE_RATE */ -/*****************************************/ -/* Other required defines */ -/*****************************************/ - -#ifndef appconf_CONTROL_I2C_DEVICE_ADDR -#define appconf_CONTROL_I2C_DEVICE_ADDR 0x42 -#endif /* appconf_CONTROL_I2C_DEVICE_ADDR*/ - /*****************************************/ /* I/O Task Priorities */ /*****************************************/ diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 7ab6d2ea..03cdf389 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -77,7 +77,7 @@ static void i2c_init(void) (1 << appconfI2C_IO_CORE), PORT_I2C_SCL, PORT_I2C_SDA, - appconf_CONTROL_I2C_DEVICE_ADDR); + appconfI2C_SLAVE_DEVICE_ADDR); #endif #if appconfI2C_MASTER_ENABLED diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 024e4b0f..084324d1 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -120,6 +120,10 @@ #define appconfI2C_SLAVE_ENABLED 0 #endif +#ifndef appconfI2C_SLAVE_DEVICE_ADDR +#define appconfI2C_SLAVE_DEVICE_ADDR 0x42 +#endif + #if appconfINTENT_I2C_OUTPUT_ENABLED && !appconfI2C_MASTER_ENABLED #error "I2C master must be enabled for intent I2C output" #endif diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h index bc5f6587..bfece44f 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h @@ -113,9 +113,9 @@ #define appconfEXTERNAL_MCLK 1 #endif /* appconfEXTERNAL_MCLK */ -#ifndef appconf_CONTROL_I2C_DEVICE_ADDR -#define appconf_CONTROL_I2C_DEVICE_ADDR 0x42 -#endif /* appconf_CONTROL_I2C_DEVICE_ADDR*/ +#ifndef appconfI2C_SLAVE_DEVICE_ADDR +#define appconfI2C_SLAVE_DEVICE_ADDR 0x42 +#endif /* appconfI2C_SLAVE_DEVICE_ADDR*/ #ifndef appconfSPI_OUTPUT_ENABLED #define appconfSPI_OUTPUT_ENABLED 0 diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c index 70116ab8..6d11e5fb 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c @@ -94,7 +94,7 @@ static void i2c_init(void) (1 << appconfI2C_IO_CORE), PORT_I2C_SLAVE_SCL, PORT_I2C_SLAVE_SDA, - appconf_CONTROL_I2C_DEVICE_ADDR); + appconfI2C_SLAVE_DEVICE_ADDR); #endif #else static rtos_driver_rpc_t i2c_rpc_config; diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index dc989760..0405c7fb 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -124,9 +124,9 @@ #define appconfEXTERNAL_MCLK 1 #endif /* appconfEXTERNAL_MCLK */ -#ifndef appconf_CONTROL_I2C_DEVICE_ADDR -#define appconf_CONTROL_I2C_DEVICE_ADDR 0x42 -#endif /* appconf_CONTROL_I2C_DEVICE_ADDR*/ +#ifndef appconfI2C_SLAVE_DEVICE_ADDR +#define appconfI2C_SLAVE_DEVICE_ADDR 0x42 +#endif /* appconfI2C_SLAVE_DEVICE_ADDR*/ #ifndef appconfSPI_OUTPUT_ENABLED #define appconfSPI_OUTPUT_ENABLED 0 diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index 04620821..f47d20f0 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -115,7 +115,7 @@ static void i2c_init(void) (1 << appconfI2C_IO_CORE), PORT_I2C_SCL, PORT_I2C_SDA, - appconf_CONTROL_I2C_DEVICE_ADDR); + appconfI2C_SLAVE_DEVICE_ADDR); #endif #if ON_TILE(I2C_TILE_NO) From 071a3e4feafd98194079d32baf225e73fcc776b9 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 7 Aug 2024 16:57:46 +0100 Subject: [PATCH 176/288] Update variable --- examples/ffd/src/i2c_reg_handling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index ef92530a..29f88c19 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -17,7 +17,7 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, uint8_t * data_p = *data; uint8_t reg_addr = data_p[0]; uint8_t reg_value = -1; - if (reg_addr == appconfWAKEWORD_REG_ADDRESS) { + if (reg_addr == appconfINTENT_I2C_REG_ADDRESS) { reg_value = last_asr_result->id; } data_p[0] = reg_value; From ea9b92470710358d7e07aac3d1e4f2f02e0ce494 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 8 Aug 2024 09:37:29 +0100 Subject: [PATCH 177/288] Update test --- test/asr/asr.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index f3567622..0aaeb156 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -82,6 +82,7 @@ set(APP_COMPILE_DEFINITIONS XUD_CORE_CLOCK=600 XSCOPE_HOST_IO_ENABLED=1 XSCOPE_HOST_IO_TILE=0 + ASR_SENSORY=1 QSPI_FLASH_CALIBRATION_ADDRESS=${CALIBRATION_PATTERN_START_ADDRESS} QSPI_FLASH_MODEL_START_ADDRESS=${MODEL_START_ADDRESS} appconfASR_LIBRARY_ID=${TEST_ASR_LIBRARY_ID} From 2eec481d4393e99cf6f3d08cc74fa8f3e1f989db Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 8 Aug 2024 10:13:50 +0100 Subject: [PATCH 178/288] Fix ASR builds --- examples/ffd/src/app_conf.h | 4 ++-- examples/ffd/src/i2c_reg_handling.c | 4 ++++ modules/asr/Cyberon/DSpotter_asr.c | 8 +++++--- modules/asr/asr.h | 13 +++++-------- test/asr/asr.cmake | 11 ++++------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 084324d1..f04400ac 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -80,8 +80,8 @@ #endif /* @brief Address for wakeword register to be read over I2C slave*/ -#ifndef appconfWAKEWORD_REG_ADDRESS -#define appconfWAKEWORD_REG_ADDRESS 0x01 +#ifndef appconfINTENT_I2C_REG_ADDRESS +#define appconfINTENT_I2C_REG_ADDRESS 0x01 #endif #ifndef appconfINTENT_UART_OUTPUT_ENABLED diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index 29f88c19..8f042da6 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -14,6 +14,7 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, asr_result_t *last_asr_result, uint8_t **data) { +#if appconfI2C_SLAVE_ENABLED==1 uint8_t * data_p = *data; uint8_t reg_addr = data_p[0]; uint8_t reg_value = -1; @@ -22,6 +23,7 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, } data_p[0] = reg_value; printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); +#endif return 1; } @@ -31,8 +33,10 @@ void write_device_reg(rtos_i2c_slave_t *ctx, uint8_t *data, size_t len) { +#if appconfI2C_SLAVE_ENABLED==1 // If the length is lower than WRITE_REQUEST_MIN_LEN, it is a read request if (len > WRITE_REQUEST_MIN_LEN) { printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); } +#endif } diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index 3a7749eb..fab7f147 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -169,14 +169,16 @@ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result) { DBG_TRACE("\r\nGet %s, ID=%d, Score=%d, SG_Diff=%d, Energy=%d\r\n", szCommand, nCmdID, nCmdScore, nCmdSG, nCmdEnergy); result->id = nCmdID; - result->score = nCmdScore; result->sg_diff = nCmdSG; - result->energy = nCmdEnergy; + // The following result fields are not implemented + result->start_index = -1; + result->end_index = -1; + result->duration = -1; #if appconfINTENT_UART_DEBUG_INFO_ENABLED static char res_info[128]; snprintf(res_info, sizeof(res_info)-1, "ID:%d,Sc:%d,SGD:%d,En:%d\r\n", nCmdID, nCmdScore, nCmdSG, nCmdEnergy); // Enable the printout below to see the information sent over UART - rtos_printf(res_info); + // rtos_printf(res_info); rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)&res_info, strlen(res_info)); #endif return ASR_OK; diff --git a/modules/asr/asr.h b/modules/asr/asr.h index 1dd5d218..4b6e496f 100644 --- a/modules/asr/asr.h +++ b/modules/asr/asr.h @@ -64,19 +64,16 @@ typedef struct asr_attributes_struct typedef struct asr_result_struct { uint16_t id; ///< Keyword or command ID -#if ASR_CYBERON==1 - uint32_t score; ///< The confidence score of the detection - uint32_t sg_diff; ///< The voice similarity of the detection - uint32_t energy; ///< The energy of the detection -#elif ASR_SENSORY==1 + + // The following fields are optional and may not be supported by all ASR ports uint16_t score; ///< The confidence score of the detection uint16_t gscore; ///< The garbage score int32_t start_index; ///< The audio sample index that corresponds to the start of the utterance int32_t end_index; ///< The audio sample index that corresponds to the end of the utterance int32_t duration; ///< THe length of the utterance in samples -#else - #error "Model has to be either Sensory or Cyberon" -#endif + uint32_t sg_diff; ///< The voice similarity of the detection + uint32_t energy; ///< The energy of the detection + void* reserved; ///< Reserved for future use } asr_result_t; diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index 0aaeb156..e222b122 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -48,6 +48,8 @@ if(${TEST_ASR} STREQUAL "SENSORY") set(MODEL_FILE ${FFD_SRC_ROOT}/model/english_usa/command-pc62w-6.4.0-op10-prod-net.bin.nibble_swapped) set(TEST_ASR_LIBRARY_ID 0) set(TEST_ASR_NAME test_asr_sensory) + set(ASR_FLAG ASR_SENSORY=1) + elseif(${TEST_ASR} STREQUAL "CYBERON") message(STATUS "Building Cyberon ASR test") set(ASR_LIBRARY sln_voice::app::asr::Cyberon) @@ -58,6 +60,7 @@ elseif(${TEST_ASR} STREQUAL "CYBERON") set(MODEL_FILE ${FFD_SRC_ROOT}/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap) set(TEST_ASR_LIBRARY_ID 1) set(TEST_ASR_NAME test_asr_cyberon) + set(ASR_FLAG ASR_CYBERON=1) else() message(FATAL_ERROR "Unable to build ${TEST_ASR} test") endif() @@ -82,19 +85,13 @@ set(APP_COMPILE_DEFINITIONS XUD_CORE_CLOCK=600 XSCOPE_HOST_IO_ENABLED=1 XSCOPE_HOST_IO_TILE=0 - ASR_SENSORY=1 + ${ASR_FLAG} QSPI_FLASH_CALIBRATION_ADDRESS=${CALIBRATION_PATTERN_START_ADDRESS} QSPI_FLASH_MODEL_START_ADDRESS=${MODEL_START_ADDRESS} appconfASR_LIBRARY_ID=${TEST_ASR_LIBRARY_ID} appconfASR_BRICK_SIZE_SAMPLES=${ASR_BRICK_SIZE_SAMPLES} ) -if(${TEST_ASR} STREQUAL "SENSORY") - set(APP_COMPILE_DEFINITIONS - ${APP_COMPILE_DEFINITIONS} - ) -endif() - set(APP_LINK_OPTIONS -report ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope From 4639dd61be4c4b813e10c7e6d7ca5096c99a7d21 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 8 Aug 2024 13:32:15 +0100 Subject: [PATCH 179/288] Update fwk_rtos --- CHANGELOG.rst | 1 + modules/rtos | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fc8aa04b..9fe40f4b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,7 @@ XCORE-VOICE change log 2.3.0 ----- + * CHANGED: Updated submodule fwk_rtos to version 3.2.0 from 3.0.5. * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). * CHANGED: Moved files in folders device_memory, gpio_ctrl, intent_engine and diff --git a/modules/rtos b/modules/rtos index 7be57246..44692113 160000 --- a/modules/rtos +++ b/modules/rtos @@ -1 +1 @@ -Subproject commit 7be57246f7541202b29f7cbab350a8b0e9a084f1 +Subproject commit 44692113625a04d85377ce4c4c909635a1c68932 From 3c9a00176f11d7434079aa68f181d895655e5a3b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 8 Aug 2024 13:58:29 +0100 Subject: [PATCH 180/288] Tidy up --- .../ffd/deploying/configuration.rst | 47 +++++++++++-------- .../XK_VOICE_L71/platform/platform_init.c | 2 +- .../XK_VOICE_L71/platform/platform_start.c | 4 +- examples/ffd/ffd_i2s_input_cyberon.cmake | 1 - examples/ffd/src/app_conf.h | 4 -- examples/ffd/src/main.c | 6 +-- 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 1d5dd208..b7353258 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -94,30 +94,39 @@ If options are changed, the application firmware must be rebuilt. Configuring the |I2C| interfaces -------------------------------- -The |I2C| interfaces are used to communicate with the DAC and the host. The |I2C| interface can be configured as a master or slave. -The |I2C| master is used to send the intent message to the host, and the |I2C| slave is used to read the intent message from the host. +The |I2C| interfaces are used to configure the DAC and to communicate with the host. The |I2C| interface can be configured as a master and a slave. +The DAC must be configured at bootup via the |I2C| master interface. +The |I2C| master is used to send intent messages to the host, and the |I2C| slave is used to read intent messages from the host. + +.. note:: + Since the |I2C| interface cannot operate as both a master and a slave simultaneously, the FFD example design uses the |I2C| master interface to configure the DAC at bootup. + However, if the |I2C| slave interface is used to read intent messages, the |I2C| master interface will be disabled after the DAC configuration is complete. + The |I2C| master and slave can be enabled or disabled by setting the ``appconfI2C_MASTER_ENABLED`` and ``appconfI2C_SLAVE_ENABLED`` configuration variables. -To send the intent ID via |I2C| master interface when a command is detected, the following variables must be set: - - ``appconfINTENT_I2C_OUTPUT_ENABLED`` must be set to 1. - - ``appconfI2C_MASTER_ENABLED`` must be set to 1. - - ``appconfINTENT_I2C_OUTPUT_DEVICE_ADDR`` must be set to desired address used by the |I2C| slave device. - - ``appconfI2C_SLAVE_ENABLED`` must be set to 0. +To send the intent ID via the |I2C| master interface when a command is detected, set the following variables:: -The retrieve the intent message from the host via the |I2C| slave interface, the following variables must be set: + - ``appconfINTENT_I2C_OUTPUT_ENABLED`` to 1. + - ``appconfI2C_MASTER_ENABLED`` to 1. + - ``appconfINTENT_I2C_OUTPUT_DEVICE_ADDR`` to the desired address used by the |I2C| slave device. + - ``appconfI2C_SLAVE_ENABLED`` to 0. - - ``appconfI2C_SLAVE_ENABLED`` must be set to 1. - - ``appconfI2C_SLAVE_DEVICE_ADDR`` must be set to the desired address used by the |I2C| master device. - - ``appconfINTENT_I2C_REG_ADDRESS`` must be set to the desired register read by of the |I2C| master device. - - ``appconfINTENT_I2C_OUTPUT_ENABLED`` must be set to 0. +The retrieve the intent message from the host via the |I2C| slave interface, set the following variables: -The handling of the |I2C| slave registers is done in the ``examples\ffd\src\i2c_reg_handling.c`` file. The vatiable ``appconfINTENT_I2C_REG_ADDRESS`` is used in the callback function ``read_device_reg()``. + - ``appconfI2C_SLAVE_ENABLED`` to 1. + - ``appconfI2C_SLAVE_DEVICE_ADDR`` to the desired address used by the |I2C| master device. + - ``appconfINTENT_I2C_REG_ADDRESS`` to the desired register read by of the |I2C| master device. + - ``appconfINTENT_I2C_OUTPUT_ENABLED`` to 0, this will disable the |I2C| master interface. -Configuring the |I2S| interfaces --------------------------------- +The handling of the |I2C| slave registers is done in the ``examples\ffd\src\i2c_reg_handling.c`` file. The variable ``appconfINTENT_I2C_REG_ADDRESS`` is used in the callback function ``read_device_reg()``. + +Configuring the |I2S| interface +------------------------------- -The |I2S| interface can be configured as a master or slave. The |I2S| interface is used to receive the audio data over the |I2S| interface. -The |I2S| interface can be enabled or disabled by setting the ``appconfI2S_MODE`` configuration variable either to ``appconfI2S_MODE_MASTER`` or ``appconfI2S_MODE_SLAVE``. -The sample rate of the |I2S| interface can be set by changing the ``appconfI2S_AUDIO_SAMPLE_RATE`` configuration variable. -The MCLK can be recovered from the Software PLL application by setting the ``appconfRECOVER_MCLK_I2S_APP_PLL`` configuration variable to 1, this will remove the need to use an external MCLK. +The |I2S| interface is used to receive the audio data from the host. The |I2S| interface can be configured as either a master or a slave. +To configure the |I2S| interface, set the following variables: + - ``appconfUSE_I2S_INPUT`` to 1. + - ``appconfI2S_MODE`` to the desired mode, either ``appconfI2S_MODE_MASTER`` or ``appconfI2S_MODE_SLAVE``. + - ``appconfI2S_AUDIO_SAMPLE_RATE`` to the desired sample rate, either 16000 or 48000. + - ``appconfRECOVER_MCLK_I2S_APP_PLL`` to 1 if an external MCLK is not available, otherwise set it to 0. diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 03cdf389..de560177 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -176,7 +176,7 @@ static void mics_init(void) static void i2s_init(void) { -#if appconfI2S_ENABLED +#if appconfUSE_I2S_INPUT #if appconfI2S_MODE == appconfI2S_MODE_MASTER static rtos_driver_rpc_t i2s_rpc_config; #endif diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index 6be13160..77690d1a 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -69,7 +69,7 @@ static void i2c_slave_start(void) static void audio_codec_start(void) { -#if appconfI2S_ENABLED && appconfI2C_MASTER_ENABLED +#if appconfUSE_I2S_INPUT && appconfI2C_MASTER_ENABLED int ret = 0; #if ON_TILE(I2C_TILE_NO) if (dac3101_init(appconfI2S_AUDIO_SAMPLE_RATE) != 0) { @@ -95,7 +95,7 @@ static void mics_start(void) static void i2s_start(void) { -#if appconfI2S_ENABLED +#if appconfUSE_I2S_INPUT #if appconfI2S_MODE == appconfI2S_MODE_MASTER rtos_i2s_rpc_config(i2s_ctx, appconfI2S_RPC_PORT, appconfI2S_RPC_PRIORITY); #endif diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 3f460f13..7d07d868 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -80,7 +80,6 @@ set(APP_COMPILE_DEFINITIONS appconfAUDIO_PLAYBACK_ENABLED=1 appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 - appconfI2S_ENABLED=1 appconfI2C_SLAVE_ENABLED=1 appconfI2C_MASTER_ENABLED=0 appconfINTENT_I2C_OUTPUT_ENABLED=0 diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index f04400ac..12dee278 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -96,10 +96,6 @@ #define appconfUART_BAUD_RATE 9600 #endif -#ifndef appconfI2S_ENABLED -#define appconfI2S_ENABLED 1 -#endif - #ifndef appconfI2S_MODE_MASTER #define appconfI2S_MODE_MASTER 0 #endif diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index eb14930b..0c0f253f 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -15,7 +15,7 @@ /* Library headers */ #include "rtos_printf.h" -#if appconfI2S_ENABLED +#if appconfUSE_I2S_INPUT #include "src.h" #endif @@ -40,7 +40,7 @@ #define MEM_ANALYSIS_ENABLED 0 #endif -#if appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) +#if appconfUSE_I2S_INPUT && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) void i2s_slave_intertile() { int32_t tmp[appconfAUDIO_PIPELINE_FRAME_ADVANCE][appconfAUDIO_PIPELINE_CHANNELS]; @@ -252,7 +252,7 @@ void startup_task(void *arg) platform_start(); -#if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) +#if ON_TILE(1) && appconfUSE_I2S_INPUT && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) xTaskCreate((TaskFunction_t) i2s_slave_intertile, "i2s_slave_intertile", From 29e620c86fa47c8094280b769b3e84578413535c Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 8 Aug 2024 15:05:59 +0100 Subject: [PATCH 181/288] Fix format --- doc/programming_guide/ffd/deploying/configuration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index b7353258..4f222131 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -104,7 +104,7 @@ The |I2C| master is used to send intent messages to the host, and the |I2C| slav The |I2C| master and slave can be enabled or disabled by setting the ``appconfI2C_MASTER_ENABLED`` and ``appconfI2C_SLAVE_ENABLED`` configuration variables. -To send the intent ID via the |I2C| master interface when a command is detected, set the following variables:: +To send the intent ID via the |I2C| master interface when a command is detected, set the following variables: - ``appconfINTENT_I2C_OUTPUT_ENABLED`` to 1. - ``appconfI2C_MASTER_ENABLED`` to 1. @@ -115,7 +115,7 @@ The retrieve the intent message from the host via the |I2C| slave interface, set - ``appconfI2C_SLAVE_ENABLED`` to 1. - ``appconfI2C_SLAVE_DEVICE_ADDR`` to the desired address used by the |I2C| master device. - - ``appconfINTENT_I2C_REG_ADDRESS`` to the desired register read by of the |I2C| master device. + - ``appconfINTENT_I2C_REG_ADDRESS`` to the desired register read by the |I2C| master device. - ``appconfINTENT_I2C_OUTPUT_ENABLED`` to 0, this will disable the |I2C| master interface. The handling of the |I2C| slave registers is done in the ``examples\ffd\src\i2c_reg_handling.c`` file. The variable ``appconfINTENT_I2C_REG_ADDRESS`` is used in the callback function ``read_device_reg()``. From 434f8f0de2b64348bd4933e5adaa806b7f117a00 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 8 Aug 2024 15:47:07 +0100 Subject: [PATCH 182/288] Update list --- CHANGELOG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9fe40f4b..4d5fd5b5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,7 +4,6 @@ XCORE-VOICE change log 2.3.0 ----- - * CHANGED: Updated submodule fwk_rtos to version 3.2.0 from 3.0.5. * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). * CHANGED: Moved files in folders device_memory, gpio_ctrl, intent_engine and @@ -13,10 +12,11 @@ XCORE-VOICE change log * ADDED: Support for DFU over I2C for FFVA INT example. * ADDED: FFD example with I2S audio input to Cyberon speech recognition engine and model. - * CHANGED: Updated submodule fwk_rtos to version 3.1.0 from 3.0.5. - * ADDED: lib_sw_pll submodule v1.1.0. * REMOVED: flash settings in .xn files, as they are not required by XMOS Tools 15.2.x. + * ADDED: Support for reading registers over I2C slave in FFD examples. + * CHANGED: Updated submodule fwk_rtos to version 3.2.0 from 3.0.5. + * ADDED: lib_sw_pll submodule v1.1.0. 2.2.0 ----- From 2bd7debb4b16dada5b25450d598492f4683dac7d Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 9 Aug 2024 08:13:30 +0100 Subject: [PATCH 183/288] Fix comments --- examples/ffd/src/i2c_reg_handling.c | 6 +++--- examples/ffd/src/i2c_reg_handling.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index 8f042da6..44417ee2 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -17,12 +17,12 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, #if appconfI2C_SLAVE_ENABLED==1 uint8_t * data_p = *data; uint8_t reg_addr = data_p[0]; - uint8_t reg_value = -1; + uint8_t reg_value = 0xFF; if (reg_addr == appconfINTENT_I2C_REG_ADDRESS) { reg_value = last_asr_result->id; } data_p[0] = reg_value; - printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); + rtos_printf("Read from register 0x%02X value 0x%02X\n", reg_addr, reg_value); #endif return 1; } @@ -36,7 +36,7 @@ void write_device_reg(rtos_i2c_slave_t *ctx, #if appconfI2C_SLAVE_ENABLED==1 // If the length is lower than WRITE_REQUEST_MIN_LEN, it is a read request if (len > WRITE_REQUEST_MIN_LEN) { - printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); + rtos_printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); } #endif } diff --git a/examples/ffd/src/i2c_reg_handling.h b/examples/ffd/src/i2c_reg_handling.h index 01ad7e0e..449b13de 100644 --- a/examples/ffd/src/i2c_reg_handling.h +++ b/examples/ffd/src/i2c_reg_handling.h @@ -6,12 +6,12 @@ #include "asr.h" /** - * Callback for reading data from a device register over I2C. + * Callback for reading data from a device register over I2C slave. * Only one byte of data is read from the register. * * @param ctx Pointer to the I2C slave context. * @param last_asr_result Pointer to the last Automatic Speech Recognition (ASR) result. - * @param data Pointer to a pointer to the the data received from the master device. + * @param data Pointer to a pointer to the the data to be sent to the master device. * * @return The size of the data read. */ @@ -21,12 +21,12 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, uint8_t **data); /** - * Callback for writing data to a device register over I2C. + * Callback for writing data to a device register over I2C slave. * Only one byte of data is written to the register. * * @param ctx Pointer to the I2C slave context. * @param app_data Pointer to application-specific data. Not used. - * @param data Pointer pointer to the the data received from the master device. + * @param data Pointer to the the data received from the master device. * @param len The length of the data to be written. */ RTOS_I2C_SLAVE_CALLBACK_ATTR From c8e1a261fc109ac1f2527ae3cfc9f63e076a5f1a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 9 Aug 2024 08:18:16 +0100 Subject: [PATCH 184/288] Fix text --- doc/programming_guide/ffd/deploying/configuration.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 4f222131..eedd2972 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -38,13 +38,13 @@ If options are changed, the application firmware must be rebuilt. - Enables/disables the |I2C| intent message - 1 * - appconfI2C_MASTER_ENABLED - - Enabled the |I2C| master mode to configure the DAC and send the intent message + - Enables/disables the |I2C| master mode to configure the DAC and send the intent message - 1 * - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR - Sets the |I2C| address to transmit the intent to via the |I2C| master interface - 0x01 - * - appconfI2C_SALVE_ENABLED - - Enabled the |I2C| slave mode to read the device register with the intent message + * - appconfI2C_SLAVE_ENABLED + - Enables/disables the |I2C| slave mode to read the device register with the intent message - 0 * - appconfI2C_SLAVE_DEVICE_ADDR - Sets the |I2C| address to read the intent message from via the |I2C| slave interface From 795ac29743ceb59813f06646c5eefeda3d4ed274 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 9 Aug 2024 14:52:17 +0100 Subject: [PATCH 185/288] Address some comments --- .../ffd/deploying/configuration.rst | 14 +++++++------- examples/ffd/src/i2c_reg_handling.c | 1 - .../XK_VOICE_L71/platform/platform_conf.h | 2 +- modules/asr/Cyberon/DSpotter_asr.c | 1 + 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index eedd2972..9be50891 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -41,16 +41,16 @@ If options are changed, the application firmware must be rebuilt. - Enables/disables the |I2C| master mode to configure the DAC and send the intent message - 1 * - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR - - Sets the |I2C| address to transmit the intent to via the |I2C| master interface + - Sets the address of the |I2C| device receiving the intent via the |I2C| master interface - 0x01 * - appconfI2C_SLAVE_ENABLED - Enables/disables the |I2C| slave mode to read the device register with the intent message - 0 * - appconfI2C_SLAVE_DEVICE_ADDR - - Sets the |I2C| address to read the intent message from via the |I2C| slave interface + - Sets the address of the |I2C| device receiving the intent via the |I2C| slave interface - 0x42 * - appconfINTENT_I2C_REG_ADDRESS - - Sets the |I2C| register to store the intent message, this value can be read via the |I2C| slave interface + - Sets the address of the |I2C| register to store the intent message, this value can be read via the |I2C| slave interface - 0x01 * - appconfUART_BAUD_RATE - Sets the baud rate for the UART tx intent interface @@ -94,13 +94,13 @@ If options are changed, the application firmware must be rebuilt. Configuring the |I2C| interfaces -------------------------------- -The |I2C| interfaces are used to configure the DAC and to communicate with the host. The |I2C| interface can be configured as a master and a slave. +The |I2C| interfaces are used to configure the DAC and to communicate with the host. The |I2C| interface can be configured as a master or a slave. The DAC must be configured at bootup via the |I2C| master interface. -The |I2C| master is used to send intent messages to the host, and the |I2C| slave is used to read intent messages from the host. +The |I2C| master is used when the FFD example asynchronously sends intent messages to the host. The |I2C| slave is used when the host wants to read intent messages from the FFD example through polling. .. note:: - Since the |I2C| interface cannot operate as both a master and a slave simultaneously, the FFD example design uses the |I2C| master interface to configure the DAC at bootup. - However, if the |I2C| slave interface is used to read intent messages, the |I2C| master interface will be disabled after the DAC configuration is complete. + The |I2C| interface cannot operate as both master and slave simultaneously. The FFD example design uses the |I2C| master interface to configure the DAC at device initialisation. + However, if the host reads intent messages from the FFD example using the |I2C| slave interface, the |I2C| master interface will be disabled after the DAC configuration is complete. The |I2C| master and slave can be enabled or disabled by setting the ``appconfI2C_MASTER_ENABLED`` and ``appconfI2C_SLAVE_ENABLED`` configuration variables. diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index 44417ee2..95ed45d3 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -1,4 +1,3 @@ - // Copyright 2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index 0405c7fb..81d68279 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -153,7 +153,7 @@ #endif /* appconfI2C_TASK_PRIORITY */ #ifndef appconfSPI_TASK_PRIORITY -#define appconfSPI_TASK_PRIORITY ( configMAX_PRIORITIES/2) +#define appconfSPI_TASK_PRIORITY (configMAX_PRIORITIES/2) #endif /* appconfSPI_TASK_PRIORITY */ #ifndef appconfDEVICE_CONTROL_I2C_PRIORITY diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index fab7f147..89c0f52a 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -170,6 +170,7 @@ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result) DBG_TRACE("\r\nGet %s, ID=%d, Score=%d, SG_Diff=%d, Energy=%d\r\n", szCommand, nCmdID, nCmdScore, nCmdSG, nCmdEnergy); result->id = nCmdID; result->sg_diff = nCmdSG; + result->energy = nCmdEnergy; // The following result fields are not implemented result->start_index = -1; result->end_index = -1; From 00c99d2707d8b0b1ee73311d2b8338f8a482225e Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 9 Aug 2024 15:12:01 +0100 Subject: [PATCH 186/288] Update doc/programming_guide/ffd/deploying/configuration.rst Co-authored-by: Michael Banther --- doc/programming_guide/ffd/deploying/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 9be50891..89fdb1e4 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -111,7 +111,7 @@ To send the intent ID via the |I2C| master interface when a command is detected, - ``appconfINTENT_I2C_OUTPUT_DEVICE_ADDR`` to the desired address used by the |I2C| slave device. - ``appconfI2C_SLAVE_ENABLED`` to 0. -The retrieve the intent message from the host via the |I2C| slave interface, set the following variables: +To configure the FFD example so that the host can poll for the intent via the |I2C| slave interface, set the following variables: - ``appconfI2C_SLAVE_ENABLED`` to 1. - ``appconfI2C_SLAVE_DEVICE_ADDR`` to the desired address used by the |I2C| master device. From 85f7642cfa15dbb068e87f4d89c9e7de115fae9e Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 9 Aug 2024 16:06:54 +0100 Subject: [PATCH 187/288] Improve defines for I2C and I2S --- .../ffd/deploying/configuration.rst | 37 ++++++++++--------- .../low_power_ffd/deploying/configuration.rst | 6 +-- .../XK_VOICE_L71/platform/platform_init.c | 6 +-- .../XK_VOICE_L71/platform/platform_start.c | 8 ++-- examples/ffd/ffd_i2s_input_cyberon.cmake | 5 +-- examples/ffd/src/app_conf.h | 34 +++++++++++------ examples/ffd/src/i2c_reg_handling.c | 4 +- examples/ffd/src/main.c | 8 ++-- examples/ffva/src/app_conf.h | 8 ++-- examples/low_power_ffd/src/app_conf.h | 8 ++-- .../src/intent_handler/intent_handler.c | 4 +- modules/asr/intent_handler/intent_handler.c | 4 +- test/ffd_gpio/src/app_conf.h | 4 +- 13 files changed, 74 insertions(+), 62 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 89fdb1e4..9bb05105 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -34,17 +34,17 @@ If options are changed, the application firmware must be rebuilt. * - appconfINTENT_UART_DEBUG_INFO_ENABLED - Enables/disables the UART intent debug information - 0 - * - appconfINTENT_I2C_OUTPUT_ENABLED - - Enables/disables the |I2C| intent message + * - appconfI2C_MASTER_DAC_ENABLED + - Enables/disables configuring the DAC over |I2C| master - 1 - * - appconfI2C_MASTER_ENABLED - - Enables/disables the |I2C| master mode to configure the DAC and send the intent message + * - appconfINTENT_I2C_MASTER_OUTPUT_ENABLED + - Enables/disables sending the intent message over |I2C| master - 1 - * - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR + * - appconfINTENT_I2C_MASTER_DEVICE_ADDR - Sets the address of the |I2C| device receiving the intent via the |I2C| master interface - 0x01 - * - appconfI2C_SLAVE_ENABLED - - Enables/disables the |I2C| slave mode to read the device register with the intent message + * - appconfINTENT_I2C_SLAVE_POLLED_ENABLED + - Enables/disables allowing polling the intent message via |I2C| slave - 0 * - appconfI2C_SLAVE_DEVICE_ADDR - Sets the address of the |I2C| device receiving the intent via the |I2C| slave interface @@ -102,31 +102,32 @@ The |I2C| master is used when the FFD example asynchronously sends intent messag The |I2C| interface cannot operate as both master and slave simultaneously. The FFD example design uses the |I2C| master interface to configure the DAC at device initialisation. However, if the host reads intent messages from the FFD example using the |I2C| slave interface, the |I2C| master interface will be disabled after the DAC configuration is complete. -The |I2C| master and slave can be enabled or disabled by setting the ``appconfI2C_MASTER_ENABLED`` and ``appconfI2C_SLAVE_ENABLED`` configuration variables. - To send the intent ID via the |I2C| master interface when a command is detected, set the following variables: - - ``appconfINTENT_I2C_OUTPUT_ENABLED`` to 1. - - ``appconfI2C_MASTER_ENABLED`` to 1. - - ``appconfINTENT_I2C_OUTPUT_DEVICE_ADDR`` to the desired address used by the |I2C| slave device. - - ``appconfI2C_SLAVE_ENABLED`` to 0. + - ``appconfINTENT_I2C_MASTER_OUTPUT_ENABLED`` to 1. + - ``appconfINTENT_I2C_MASTER_DEVICE_ADDR`` to the desired address used by the |I2C| slave device. + - ``appconfINTENT_I2C_SLAVE_POLLED_ENABLED`` to 0, this will disable the |I2C| slave interface. To configure the FFD example so that the host can poll for the intent via the |I2C| slave interface, set the following variables: - - ``appconfI2C_SLAVE_ENABLED`` to 1. + - ``appconfINTENT_I2C_SLAVE_POLLED_ENABLED`` to 1. - ``appconfI2C_SLAVE_DEVICE_ADDR`` to the desired address used by the |I2C| master device. - ``appconfINTENT_I2C_REG_ADDRESS`` to the desired register read by the |I2C| master device. - - ``appconfINTENT_I2C_OUTPUT_ENABLED`` to 0, this will disable the |I2C| master interface. + - ``appconfINTENT_I2C_MASTER_OUTPUT_ENABLED`` to 0, this will disable the |I2C| master interface. The handling of the |I2C| slave registers is done in the ``examples\ffd\src\i2c_reg_handling.c`` file. The variable ``appconfINTENT_I2C_REG_ADDRESS`` is used in the callback function ``read_device_reg()``. Configuring the |I2S| interface ------------------------------- -The |I2S| interface is used to receive the audio data from the host. The |I2S| interface can be configured as either a master or a slave. -To configure the |I2S| interface, set the following variables: +The |I2S| interface is used to send the intent audio to the DAC and to receive the audio data from the host. The |I2S| interface can be configured as either a master or a slave. +To configure the |I2S| interface to send the audio to the DAC, set the following variables: - - ``appconfUSE_I2S_INPUT`` to 1. + - ``appconfI2S_ENABLED`` to 1. - ``appconfI2S_MODE`` to the desired mode, either ``appconfI2S_MODE_MASTER`` or ``appconfI2S_MODE_SLAVE``. - ``appconfI2S_AUDIO_SAMPLE_RATE`` to the desired sample rate, either 16000 or 48000. - ``appconfRECOVER_MCLK_I2S_APP_PLL`` to 1 if an external MCLK is not available, otherwise set it to 0. + +To configure the |I2S| interface to receive audio data from the host, set the variables above and set the following variable: + + - ``appconfUSE_I2S_INPUT`` to 1. diff --git a/doc/programming_guide/low_power_ffd/deploying/configuration.rst b/doc/programming_guide/low_power_ffd/deploying/configuration.rst index 4faabefa..d9e93008 100644 --- a/doc/programming_guide/low_power_ffd/deploying/configuration.rst +++ b/doc/programming_guide/low_power_ffd/deploying/configuration.rst @@ -26,13 +26,13 @@ If options are changed, the application firmware must be rebuilt. * - appconfINTENT_UART_OUTPUT_ENABLED - Enables/disables the UART intent message - 1 - * - appconfINTENT_I2C_OUTPUT_ENABLED - - Enables/disables the |I2C| intent message + * - appconfINTENT_I2C_MASTER_OUTPUT_ENABLED + - Enables/disables sending the intent message over |I2C| master - 1 * - appconfUART_BAUD_RATE - Sets the baud rate for the UART tx intent interface - 9600 - * - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR + * - appconfINTENT_I2C_MASTER_DEVICE_ADDR - Sets the |I2C| slave address to transmit the intent to - 0x01 * - appconfINTENT_TRANSPORT_DELAY_MS diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index de560177..ec7117e9 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -72,7 +72,7 @@ static void gpio_init(void) static void i2c_init(void) { -#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfINTENT_I2C_SLAVE_POLLED_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_init(i2c_slave_ctx, (1 << appconfI2C_IO_CORE), PORT_I2C_SCL, @@ -80,7 +80,7 @@ static void i2c_init(void) appconfI2C_SLAVE_DEVICE_ADDR); #endif -#if appconfI2C_MASTER_ENABLED +#if appconfI2C_MASTER_DAC_ENABLED || appconfINTENT_I2C_OUTPUT_ENABLED static rtos_driver_rpc_t i2c_rpc_config; #if ON_TILE(I2C_TILE_NO) @@ -176,7 +176,7 @@ static void mics_init(void) static void i2s_init(void) { -#if appconfUSE_I2S_INPUT +#if appconfI2S_ENABLED #if appconfI2S_MODE == appconfI2S_MODE_MASTER static rtos_driver_rpc_t i2s_rpc_config; #endif diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index 77690d1a..c66af1d9 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -42,7 +42,7 @@ static void flash_start(void) static void i2c_master_start(void) { -#if appconfI2C_ENABLED && appconfI2C_MODE == appconfI2C_MODE_MASTER +#if appconfI2C_MASTER_DAC_ENABLED || appconfINTENT_I2C_MASTER_OUTPUT_ENABLED rtos_i2c_master_rpc_config(i2c_master_ctx, appconfI2C_MASTER_RPC_PORT, appconfI2C_MASTER_RPC_PRIORITY); #if ON_TILE(I2C_TILE_NO) @@ -53,7 +53,7 @@ static void i2c_master_start(void) static void i2c_slave_start(void) { -#if appconfI2C_SLAVE_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfINTENT_I2C_SLAVE_POLLED_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_start(i2c_slave_ctx, &last_asr_result, (rtos_i2c_slave_start_cb_t) NULL, @@ -69,7 +69,7 @@ static void i2c_slave_start(void) static void audio_codec_start(void) { -#if appconfUSE_I2S_INPUT && appconfI2C_MASTER_ENABLED +#if #if appconfI2S_ENABLED && appconfI2C_MASTER_DAC_ENABLED int ret = 0; #if ON_TILE(I2C_TILE_NO) if (dac3101_init(appconfI2S_AUDIO_SAMPLE_RATE) != 0) { @@ -95,7 +95,7 @@ static void mics_start(void) static void i2s_start(void) { -#if appconfUSE_I2S_INPUT +#if appconfI2S_ENABLED #if appconfI2S_MODE == appconfI2S_MODE_MASTER rtos_i2s_rpc_config(i2s_ctx, appconfI2S_RPC_PORT, appconfI2S_RPC_PRIORITY); #endif diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 7d07d868..35c3c4c9 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -80,9 +80,8 @@ set(APP_COMPILE_DEFINITIONS appconfAUDIO_PLAYBACK_ENABLED=1 appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 - appconfI2C_SLAVE_ENABLED=1 - appconfI2C_MASTER_ENABLED=0 - appconfINTENT_I2C_OUTPUT_ENABLED=0 + appconfINTENT_I2C_SLAVE_POLLED_ENABLED=1 + appconfINTENT_I2C_MASTER_OUTPUT_ENABLED=0 appconfRECOVER_MCLK_I2S_APP_PLL=1 appconfINTENT_UART_DEBUG_INFO_ENABLED=1 appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR=1 diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 12dee278..3900674d 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -71,12 +71,12 @@ #define appconfINTENT_TRANSPORT_DELAY_MS 50 #endif -#ifndef appconfINTENT_I2C_OUTPUT_ENABLED -#define appconfINTENT_I2C_OUTPUT_ENABLED 1 +#ifndef appconfINTENT_I2C_MASTER_OUTPUT_ENABLED +#define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 1 #endif -#ifndef appconfINTENT_I2C_OUTPUT_DEVICE_ADDR -#define appconfINTENT_I2C_OUTPUT_DEVICE_ADDR 0x01 +#ifndef appconfINTENT_I2C_MASTER_DEVICE_ADDR +#define appconfINTENT_I2C_MASTER_DEVICE_ADDR 0x01 #endif /* @brief Address for wakeword register to be read over I2C slave*/ @@ -104,28 +104,40 @@ #define appconfI2S_MODE_SLAVE 1 #endif +#ifndef appconfI2S_ENABLED +#define appconfI2S_ENABLED 1 +#endif + #ifndef appconfI2S_MODE #define appconfI2S_MODE appconfI2S_MODE_MASTER #endif -#ifndef appconfI2C_MASTER_ENABLED -#define appconfI2C_MASTER_ENABLED 1 +#ifndef appconfUSE_I2S_INPUT +#define appconfUSE_I2S_INPUT 0 +#endif + +#if appconfUSE_I2S_INPUT && !appconfI2S_ENABLED +#error "I2S must be enabled if receiving the audio over I2S" +#endif + +#ifndef appconfINTENT_I2C_MASTER_OUTPUT_ENABLED +#define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 1 #endif -#ifndef appconfI2C_SLAVE_ENABLED -#define appconfI2C_SLAVE_ENABLED 0 +#ifndef appconfINTENT_I2C_MASTER_OUTPUT_ENABLED +#define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 0 #endif #ifndef appconfI2C_SLAVE_DEVICE_ADDR #define appconfI2C_SLAVE_DEVICE_ADDR 0x42 #endif -#if appconfINTENT_I2C_OUTPUT_ENABLED && !appconfI2C_MASTER_ENABLED +#if appconfINTENT_I2C_MASTER_OUTPUT_ENABLED && !appconfI2C_MASTER_ENABLED #error "I2C master must be enabled for intent I2C output" #endif -#if appconfI2C_SLAVE_ENABLED && appconfINTENT_I2C_OUTPUT_ENABLED -#error "I2C slave cannot be enabled when intent I2C output over I2C master is enabled" +#if appconfINTENT_I2C_MASTER_OUTPUT_ENABLED && appconfINTENT_I2C_MASTER_OUTPUT_ENABLED +#error "The intent message cannot be sent over I2C master and polled via I2C slave simultaneously" #endif #ifndef appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR diff --git a/examples/ffd/src/i2c_reg_handling.c b/examples/ffd/src/i2c_reg_handling.c index 95ed45d3..9029d633 100644 --- a/examples/ffd/src/i2c_reg_handling.c +++ b/examples/ffd/src/i2c_reg_handling.c @@ -13,7 +13,7 @@ size_t read_device_reg(rtos_i2c_slave_t *ctx, asr_result_t *last_asr_result, uint8_t **data) { -#if appconfI2C_SLAVE_ENABLED==1 +#if appconfINTENT_I2C_SLAVE_POLLED_ENABLED==1 uint8_t * data_p = *data; uint8_t reg_addr = data_p[0]; uint8_t reg_value = 0xFF; @@ -32,7 +32,7 @@ void write_device_reg(rtos_i2c_slave_t *ctx, uint8_t *data, size_t len) { -#if appconfI2C_SLAVE_ENABLED==1 +#if appconfINTENT_I2C_SLAVE_POLLED_ENABLED==1 // If the length is lower than WRITE_REQUEST_MIN_LEN, it is a read request if (len > WRITE_REQUEST_MIN_LEN) { rtos_printf("Write to register 0x%02X value 0x%02X (len %d)\n", data[0], data[1], len); diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index 0c0f253f..a1cae78f 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -15,7 +15,7 @@ /* Library headers */ #include "rtos_printf.h" -#if appconfUSE_I2S_INPUT +#if appconfI2S_ENABLED #include "src.h" #endif @@ -40,7 +40,7 @@ #define MEM_ANALYSIS_ENABLED 0 #endif -#if appconfUSE_I2S_INPUT && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) +#if appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) void i2s_slave_intertile() { int32_t tmp[appconfAUDIO_PIPELINE_FRAME_ADVANCE][appconfAUDIO_PIPELINE_CHANNELS]; @@ -146,7 +146,7 @@ int audio_pipeline_output(void *output_app_data, return AUDIO_PIPELINE_FREE_FRAME; } -#if appconfUSE_I2S_INPUT +#if appconfI2S_ENABLED RTOS_I2S_APP_SEND_FILTER_CALLBACK_ATTR size_t i2s_send_upsample_cb(rtos_i2s_t *ctx, void *app_data, int32_t *i2s_frame, size_t i2s_frame_size, int32_t *send_buf, size_t samples_available) { @@ -252,7 +252,7 @@ void startup_task(void *arg) platform_start(); -#if ON_TILE(1) && appconfUSE_I2S_INPUT && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) +#if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) xTaskCreate((TaskFunction_t) i2s_slave_intertile, "i2s_slave_intertile", diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 6b5872ea..3d099331 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -87,12 +87,12 @@ #define appconfINTENT_TRANSPORT_DELAY_MS 50 #endif -#ifndef appconfINTENT_I2C_OUTPUT_ENABLED -#define appconfINTENT_I2C_OUTPUT_ENABLED 1 +#ifndef appconfINTENT_I2C_MASTER_OUTPUT_ENABLED +#define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 1 #endif -#ifndef appconfINTENT_I2C_OUTPUT_DEVICE_ADDR -#define appconfINTENT_I2C_OUTPUT_DEVICE_ADDR 0x01 +#ifndef appconfINTENT_I2C_MASTER_DEVICE_ADDR +#define appconfINTENT_I2C_MASTER_DEVICE_ADDR 0x01 #endif #ifndef appconfINTENT_UART_OUTPUT_ENABLED diff --git a/examples/low_power_ffd/src/app_conf.h b/examples/low_power_ffd/src/app_conf.h index 64061cfd..3d28cfbe 100644 --- a/examples/low_power_ffd/src/app_conf.h +++ b/examples/low_power_ffd/src/app_conf.h @@ -67,12 +67,12 @@ #define appconfINTENT_TRANSPORT_DELAY_MS 50 #endif -#ifndef appconfINTENT_I2C_OUTPUT_ENABLED -#define appconfINTENT_I2C_OUTPUT_ENABLED 1 +#ifndef appconfINTENT_I2C_MASTER_OUTPUT_ENABLED +#define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 1 #endif -#ifndef appconfINTENT_I2C_OUTPUT_DEVICE_ADDR -#define appconfINTENT_I2C_OUTPUT_DEVICE_ADDR 0x01 +#ifndef appconfINTENT_I2C_MASTER_DEVICE_ADDR +#define appconfINTENT_I2C_MASTER_DEVICE_ADDR 0x01 #endif #ifndef appconfINTENT_UART_OUTPUT_ENABLED diff --git a/examples/low_power_ffd/src/intent_handler/intent_handler.c b/examples/low_power_ffd/src/intent_handler/intent_handler.c index 84fca4bf..3c9dbd1b 100644 --- a/examples/low_power_ffd/src/intent_handler/intent_handler.c +++ b/examples/low_power_ffd/src/intent_handler/intent_handler.c @@ -50,14 +50,14 @@ static void proc_keyword_res(void *args) { vTaskDelay(pdMS_TO_TICKS(appconfINTENT_TRANSPORT_DELAY_MS)); rtos_gpio_port_out(gpio_ctx_t0, p_out_wakeup, WAKEUP_LOW); } -#if appconfINTENT_I2C_OUTPUT_ENABLED +#if appconfINTENT_I2C_MASTER_OUTPUT_ENABLED i2c_res_t ret; uint32_t buf = id; size_t sent = 0; ret = rtos_i2c_master_write( i2c_master_ctx, - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR, + appconfINTENT_I2C_MASTER_DEVICE_ADDR, (uint8_t*)&buf, sizeof(uint32_t), &sent, diff --git a/modules/asr/intent_handler/intent_handler.c b/modules/asr/intent_handler/intent_handler.c index 7068ee70..0d26de9d 100644 --- a/modules/asr/intent_handler/intent_handler.c +++ b/modules/asr/intent_handler/intent_handler.c @@ -56,14 +56,14 @@ static void proc_keyword_res(void *args) { vTaskDelay(pdMS_TO_TICKS(appconfINTENT_TRANSPORT_DELAY_MS)); rtos_gpio_port_out(gpio_ctx_t0, p_out_wakeup, WAKEUP_LOW); } -#if appconfINTENT_I2C_OUTPUT_ENABLED +#if appconfINTENT_I2C_MASTER_OUTPUT_ENABLED i2c_res_t ret; uint32_t buf = id; size_t sent = 0; ret = rtos_i2c_master_write( i2c_master_ctx, - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR, + appconfINTENT_I2C_MASTER_DEVICE_ADDR, (uint8_t*)&buf, sizeof(uint32_t), &sent, diff --git a/test/ffd_gpio/src/app_conf.h b/test/ffd_gpio/src/app_conf.h index f5747b96..33c096e7 100644 --- a/test/ffd_gpio/src/app_conf.h +++ b/test/ffd_gpio/src/app_conf.h @@ -15,10 +15,10 @@ #define I2C_SLAVE_CORE_MASK (1 << 3) #define I2C_SLAVE_ADDR 0x7A -#define appconfINTENT_I2C_OUTPUT_DEVICE_ADDR I2C_SLAVE_ADDR +#define appconfINTENT_I2C_MASTER_DEVICE_ADDR I2C_SLAVE_ADDR #define appconfAUDIO_PLAYBACK_ENABLED 0 -#define appconfINTENT_I2C_OUTPUT_ENABLED 0 +#define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 0 #define appconfINTENT_UART_OUTPUT_ENABLED 0 #define ASR_TILE_NO 0 From 2e9197c2cb0cbb2398e24168800ec46a9eb073d2 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 9 Aug 2024 16:18:04 +0100 Subject: [PATCH 188/288] Changes for I2S --- doc/programming_guide/ffd/deploying/configuration.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 9bb05105..953a4b22 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -113,21 +113,19 @@ To configure the FFD example so that the host can poll for the intent via the |I - ``appconfINTENT_I2C_SLAVE_POLLED_ENABLED`` to 1. - ``appconfI2C_SLAVE_DEVICE_ADDR`` to the desired address used by the |I2C| master device. - ``appconfINTENT_I2C_REG_ADDRESS`` to the desired register read by the |I2C| master device. - - ``appconfINTENT_I2C_MASTER_OUTPUT_ENABLED`` to 0, this will disable the |I2C| master interface. + - ``appconfINTENT_I2C_MASTER_OUTPUT_ENABLED`` to 0, this will disable the |I2C| master interface after initialization. The handling of the |I2C| slave registers is done in the ``examples\ffd\src\i2c_reg_handling.c`` file. The variable ``appconfINTENT_I2C_REG_ADDRESS`` is used in the callback function ``read_device_reg()``. Configuring the |I2S| interface ------------------------------- -The |I2S| interface is used to send the intent audio to the DAC and to receive the audio data from the host. The |I2S| interface can be configured as either a master or a slave. +The |I2S| interface is used to send the intent audio to the DAC, and to receive the audio samples from the host. The |I2S| interface can be configured as either a master or a slave. To configure the |I2S| interface to send the audio to the DAC, set the following variables: - ``appconfI2S_ENABLED`` to 1. - ``appconfI2S_MODE`` to the desired mode, either ``appconfI2S_MODE_MASTER`` or ``appconfI2S_MODE_SLAVE``. - ``appconfI2S_AUDIO_SAMPLE_RATE`` to the desired sample rate, either 16000 or 48000. - ``appconfRECOVER_MCLK_I2S_APP_PLL`` to 1 if an external MCLK is not available, otherwise set it to 0. - -To configure the |I2S| interface to receive audio data from the host, set the variables above and set the following variable: - - - ``appconfUSE_I2S_INPUT`` to 1. + - ``appconfAUDIO_PLAYBACK_ENABLED`` to 1, if the intent audio is to be played back. + - ``appconfUSE_I2S_INPUT`` to 1, if the |I2S| audio source is to be used instead of the microphone array audio source. From 7f1b7e468f85ea7e238a6ed5fca93d9812c1786b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 9 Aug 2024 17:14:55 +0100 Subject: [PATCH 189/288] Fix builds --- .../ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c | 2 +- examples/ffd/ffd_cyberon.cmake | 2 ++ examples/ffd/ffd_sensory.cmake | 2 ++ examples/ffd/src/app_conf.h | 6 +----- examples/ffd/src/main.c | 7 ++++--- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index c66af1d9..a4ea603e 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -69,7 +69,7 @@ static void i2c_slave_start(void) static void audio_codec_start(void) { -#if #if appconfI2S_ENABLED && appconfI2C_MASTER_DAC_ENABLED +#if appconfI2S_ENABLED && appconfI2C_MASTER_DAC_ENABLED int ret = 0; #if ON_TILE(I2C_TILE_NO) if (dac3101_init(appconfI2S_AUDIO_SAMPLE_RATE) != 0) { diff --git a/examples/ffd/ffd_cyberon.cmake b/examples/ffd/ffd_cyberon.cmake index 70d69f3c..355e9657 100644 --- a/examples/ffd/ffd_cyberon.cmake +++ b/examples/ffd/ffd_cyberon.cmake @@ -91,6 +91,8 @@ set(APP_COMMON_LINK_LIBRARIES sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 + lib_src + lib_sw_pll ) #********************** diff --git a/examples/ffd/ffd_sensory.cmake b/examples/ffd/ffd_sensory.cmake index 1e7cb56b..d2ad4ac6 100644 --- a/examples/ffd/ffd_sensory.cmake +++ b/examples/ffd/ffd_sensory.cmake @@ -103,6 +103,8 @@ set(APP_COMMON_LINK_LIBRARIES sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 + lib_src + lib_sw_pll ) #********************** diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index 3900674d..d46eda81 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -132,11 +132,7 @@ #define appconfI2C_SLAVE_DEVICE_ADDR 0x42 #endif -#if appconfINTENT_I2C_MASTER_OUTPUT_ENABLED && !appconfI2C_MASTER_ENABLED -#error "I2C master must be enabled for intent I2C output" -#endif - -#if appconfINTENT_I2C_MASTER_OUTPUT_ENABLED && appconfINTENT_I2C_MASTER_OUTPUT_ENABLED +#if appconfINTENT_I2C_MASTER_OUTPUT_ENABLED && appconfINTENT_I2C_SLAVE_POLLED_ENABLED #error "The intent message cannot be sent over I2C master and polled via I2C slave simultaneously" #endif diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index a1cae78f..f24d7fe3 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -15,9 +15,6 @@ /* Library headers */ #include "rtos_printf.h" -#if appconfI2S_ENABLED -#include "src.h" -#endif /* App headers */ #include "app_conf.h" @@ -31,6 +28,10 @@ #include "gpio_ctrl/leds.h" #include "intent_handler/intent_handler.h" +#if appconfI2S_ENABLED +#include "src.h" +#endif + #if appconfRECOVER_MCLK_I2S_APP_PLL /* Config headers for sw_pll */ #include "sw_pll.h" From a0c54b773a8a0c07b915c5f358e808de28eae699 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 9 Aug 2024 17:15:58 +0100 Subject: [PATCH 190/288] Fix text --- doc/programming_guide/ffd/deploying/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 953a4b22..a4405cef 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -121,7 +121,7 @@ Configuring the |I2S| interface ------------------------------- The |I2S| interface is used to send the intent audio to the DAC, and to receive the audio samples from the host. The |I2S| interface can be configured as either a master or a slave. -To configure the |I2S| interface to send the audio to the DAC, set the following variables: +To configure the |I2S| interface, set the following variables: - ``appconfI2S_ENABLED`` to 1. - ``appconfI2S_MODE`` to the desired mode, either ``appconfI2S_MODE_MASTER`` or ``appconfI2S_MODE_SLAVE``. From 45c1eeed1929c7daf9df55afe8d7feb1081cb196 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 12 Aug 2024 14:38:38 +0100 Subject: [PATCH 191/288] More changes, FFD cyberon builds work --- .../ffd/deploying/configuration.rst | 2 +- .../bsp_config/XK_VOICE_L71/platform/platform_init.c | 12 ++++++------ .../XK_VOICE_L71/platform/platform_start.c | 2 +- examples/ffd/src/app_conf.h | 4 ++-- examples/ffd/src/main.c | 2 +- .../XCORE-AI-EXPLORER/platform/platform_init.c | 2 +- .../bsp_config/XK_VOICE_L71/platform/platform_init.c | 6 +++--- examples/ffva/src/main.c | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index a4405cef..5b6e6a47 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -120,7 +120,7 @@ The handling of the |I2C| slave registers is done in the ``examples\ffd\src\i2c_ Configuring the |I2S| interface ------------------------------- -The |I2S| interface is used to send the intent audio to the DAC, and to receive the audio samples from the host. The |I2S| interface can be configured as either a master or a slave. +The |I2S| interface is used to send the intent audio to the DAC, and/or to receive the audio samples from the host. The |I2S| interface can be configured as either a master or a slave. To configure the |I2S| interface, set the following variables: - ``appconfI2S_ENABLED`` to 1. diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index ec7117e9..9b09714d 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -12,7 +12,7 @@ static void mclk_init(chanend_t other_tile_c) { -#if ON_TILE(1) +#if ON_TILE(I2S_TILE_NO) app_pll_init(); #endif } @@ -54,7 +54,7 @@ static void gpio_init(void) intertile_ctx); #endif -#if ON_TILE(1) +#if ON_TILE(I2S_TILE_NO) rtos_gpio_init(gpio_ctx_t1); rtos_gpio_rpc_client_init( @@ -72,7 +72,7 @@ static void gpio_init(void) static void i2c_init(void) { -#if appconfINTENT_I2C_SLAVE_POLLED_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfINTENT_I2C_SLAVE_POLLED_ENABLED && ON_TILE(I2C_TILE_NO) rtos_i2c_slave_init(i2c_slave_ctx, (1 << appconfI2C_IO_CORE), PORT_I2C_SCL, @@ -80,7 +80,7 @@ static void i2c_init(void) appconfI2C_SLAVE_DEVICE_ADDR); #endif -#if appconfI2C_MASTER_DAC_ENABLED || appconfINTENT_I2C_OUTPUT_ENABLED +#if appconfI2C_MASTER_DAC_ENABLED || appconfINTENT_I2C_MASTER_OUTPUT_ENABLED static rtos_driver_rpc_t i2c_rpc_config; #if ON_TILE(I2C_TILE_NO) @@ -106,7 +106,7 @@ static void i2c_init(void) #endif } -#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL +#if ON_TILE(I2S_TILE_NO) && appconfRECOVER_MCLK_I2S_APP_PLL static int *p_lock_status = NULL; /// @brief Save the pointer to the pll lock_status variable static void set_pll_lock_status_ptr(int* p) @@ -117,7 +117,7 @@ static void set_pll_lock_status_ptr(int* p) static void platform_sw_pll_init(void) { -#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL +#if ON_TILE(I2S_TILE_NO) && appconfRECOVER_MCLK_I2S_APP_PLL port_t p_bclk = PORT_I2S_BCLK; port_t p_mclk = PORT_MCLK; diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c index a4ea603e..d30445aa 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -53,7 +53,7 @@ static void i2c_master_start(void) static void i2c_slave_start(void) { -#if appconfINTENT_I2C_SLAVE_POLLED_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfINTENT_I2C_SLAVE_POLLED_ENABLED && ON_TILE(I2C_TILE_NO) rtos_i2c_slave_start(i2c_slave_ctx, &last_asr_result, (rtos_i2c_slave_start_cb_t) NULL, diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index d46eda81..1a0e9e58 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -124,8 +124,8 @@ #define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 1 #endif -#ifndef appconfINTENT_I2C_MASTER_OUTPUT_ENABLED -#define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 0 +#ifndef appconfI2C_MASTER_DAC_ENABLED +#define appconfI2C_MASTER_DAC_ENABLED 1 #endif #ifndef appconfI2C_SLAVE_DEVICE_ADDR diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index f24d7fe3..43144628 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -253,7 +253,7 @@ void startup_task(void *arg) platform_start(); -#if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) +#if ON_TILE(I2S_TILE_NO) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) xTaskCreate((TaskFunction_t) i2s_slave_intertile, "i2s_slave_intertile", diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c index 6d11e5fb..84724f17 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c @@ -14,7 +14,7 @@ static void mclk_init(chanend_t other_tile_c) { -#if ON_TILE(1) && !appconfEXTERNAL_MCLK +#if ON_TILE(I2S_TILE_NO) && !appconfEXTERNAL_MCLK app_pll_init(); #endif #if appconfUSB_ENABLED && ON_TILE(USB_TILE_NO) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index f47d20f0..0ab43438 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -18,7 +18,7 @@ extern device_control_t *device_control_i2c_ctx; static void mclk_init(chanend_t other_tile_c) { -#if !appconfEXTERNAL_MCLK && ON_TILE(1) +#if !appconfEXTERNAL_MCLK && ON_TILE(I2S_TILE_NO) app_pll_init(); #endif #if appconfUSB_ENABLED && ON_TILE(USB_TILE_NO) @@ -154,7 +154,7 @@ static void spi_init(void) #endif } -#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL +#if ON_TILE(I2S_TILE_NO) && appconfRECOVER_MCLK_I2S_APP_PLL static int *p_lock_status = NULL; /// @brief Save the pointer to the pll lock_status variable static void set_pll_lock_status_ptr(int* p) @@ -165,7 +165,7 @@ static void set_pll_lock_status_ptr(int* p) static void platform_sw_pll_init(void) { -#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL +#if ON_TILE(I2S_TILE_NO) && appconfRECOVER_MCLK_I2S_APP_PLL port_t p_bclk = PORT_I2S_BCLK; port_t p_mclk = PORT_MCLK; diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 1caceb76..faa80a72 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -68,7 +68,7 @@ void i2s_slave_intertile(void *args) { portMAX_DELAY); -#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL +#if ON_TILE(I2S_TILE_NO) && appconfRECOVER_MCLK_I2S_APP_PLL sw_pll_ctx_t* i2s_callback_args = (sw_pll_ctx_t*) args; port_clear_buffer(i2s_callback_args->p_bclk_count); port_in(i2s_callback_args->p_bclk_count); // Block until BCLK transition to synchronise. Will consume up to 1/64 of a LRCLK cycle @@ -353,7 +353,7 @@ void startup_task(void *arg) rtos_printf("Startup task running from tile %d on core %d\n", THIS_XCORE_TILE, portGET_CORE_ID()); platform_start(); -#if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) +#if ON_TILE(I2S_TILE_NO) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) // Use sw_pll_ctx only if the MCLK recovery is enabled #if appconfRECOVER_MCLK_I2S_APP_PLL From 78be7a2a1cfb3aca6a98aed08d56c344f46e498a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 12 Aug 2024 14:47:44 +0100 Subject: [PATCH 192/288] Disable DAC for I2S input FFD example --- examples/ffd/ffd_i2s_input_cyberon.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/ffd/ffd_i2s_input_cyberon.cmake b/examples/ffd/ffd_i2s_input_cyberon.cmake index 35c3c4c9..c5ebfa08 100644 --- a/examples/ffd/ffd_i2s_input_cyberon.cmake +++ b/examples/ffd/ffd_i2s_input_cyberon.cmake @@ -82,6 +82,7 @@ set(APP_COMPILE_DEFINITIONS appconfI2S_AUDIO_SAMPLE_RATE=48000 appconfINTENT_I2C_SLAVE_POLLED_ENABLED=1 appconfINTENT_I2C_MASTER_OUTPUT_ENABLED=0 + appconfI2C_MASTER_DAC_ENABLED=0 appconfRECOVER_MCLK_I2S_APP_PLL=1 appconfINTENT_UART_DEBUG_INFO_ENABLED=1 appconfAUDIO_PIPELINE_SKIP_IC_AND_VNR=1 From e30e4603aca7a98dcbc5ec7757ef893a8840ee83 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 14 Aug 2024 08:17:00 +0100 Subject: [PATCH 193/288] Add more info --- doc/programming_guide/ffd/deploying/configuration.rst | 4 ++-- doc/programming_guide/ffd/overview.rst | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 5b6e6a47..6b898d59 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -44,7 +44,7 @@ If options are changed, the application firmware must be rebuilt. - Sets the address of the |I2C| device receiving the intent via the |I2C| master interface - 0x01 * - appconfINTENT_I2C_SLAVE_POLLED_ENABLED - - Enables/disables allowing polling the intent message via |I2C| slave + - Enables/disables allowing another device to poll the intent message via |I2C| slave - 0 * - appconfI2C_SLAVE_DEVICE_ADDR - Sets the address of the |I2C| device receiving the intent via the |I2C| slave interface @@ -120,7 +120,7 @@ The handling of the |I2C| slave registers is done in the ``examples\ffd\src\i2c_ Configuring the |I2S| interface ------------------------------- -The |I2S| interface is used to send the intent audio to the DAC, and/or to receive the audio samples from the host. The |I2S| interface can be configured as either a master or a slave. +The |I2S| interface is used to play the audio command response to the DAC, and/or to receive the audio samples from the host. The |I2S| interface can be configured as either a master or a slave. To configure the |I2S| interface, set the following variables: - ``appconfI2S_ENABLED`` to 1. diff --git a/doc/programming_guide/ffd/overview.rst b/doc/programming_guide/ffd/overview.rst index 868a6c0c..f5adcc46 100644 --- a/doc/programming_guide/ffd/overview.rst +++ b/doc/programming_guide/ffd/overview.rst @@ -13,6 +13,13 @@ The examples using the microphone array as the audio source include an audio pip #. Noise Suppressor (NS) #. Adaptive Gain Control (AGC) +The FFD examples provide several options to inform the host of a possible intent detected by the intent engine. The device can notify the host by: + + - sending the intent ID over UART interface upon the detection + - sending the intent ID over |I2C| master interface upon the detection + - allowing the host polling the last detected intent ID over |I2C| slave interface + - listening to an audio message over the |I2S| interface + When a wakeword phrase is detected followed by a command phrase, the application will output an audio response and a discrete message over |I2C| and UART. Sensory's THF and Cyberon's DSpotter™ libraries ship with an expiring development license. The Sensory one will suspend recognition after 11.4 hours or 107 recognition events, and the Cyberon one will suspend recognition after 100 recognition events. After the maximum number of recognitions is reached, a device reset is required to resume normal operation. To perform a reset, either power cycle the device or press the SW2 button. From 2e4c50e53433b45b7e70b0990856266d7f306f5a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 15 Aug 2024 14:47:31 +0100 Subject: [PATCH 194/288] Add missing articles in doc --- doc/programming_guide/ffd/overview.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/programming_guide/ffd/overview.rst b/doc/programming_guide/ffd/overview.rst index f5adcc46..7e5cf1dc 100644 --- a/doc/programming_guide/ffd/overview.rst +++ b/doc/programming_guide/ffd/overview.rst @@ -15,10 +15,10 @@ The examples using the microphone array as the audio source include an audio pip The FFD examples provide several options to inform the host of a possible intent detected by the intent engine. The device can notify the host by: - - sending the intent ID over UART interface upon the detection - - sending the intent ID over |I2C| master interface upon the detection - - allowing the host polling the last detected intent ID over |I2C| slave interface - - listening to an audio message over the |I2S| interface + - sending the intent ID over a UART interface upon detecting the intent + - sending the intent ID over an |I2C| master interface upon detecting the intent + - allowing the host to poll the last detected intent ID over the |I2C| slave interface + - listening to an audio message over an |I2S| interface When a wakeword phrase is detected followed by a command phrase, the application will output an audio response and a discrete message over |I2C| and UART. From 31d89fc7658380adc08353b903fea5fdac614774 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 19 Aug 2024 12:09:20 +0100 Subject: [PATCH 195/288] Remove unused define --- test/ffd_gpio/src/app_conf.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/ffd_gpio/src/app_conf.h b/test/ffd_gpio/src/app_conf.h index 33c096e7..94a75cbb 100644 --- a/test/ffd_gpio/src/app_conf.h +++ b/test/ffd_gpio/src/app_conf.h @@ -15,8 +15,6 @@ #define I2C_SLAVE_CORE_MASK (1 << 3) #define I2C_SLAVE_ADDR 0x7A -#define appconfINTENT_I2C_MASTER_DEVICE_ADDR I2C_SLAVE_ADDR - #define appconfAUDIO_PLAYBACK_ENABLED 0 #define appconfINTENT_I2C_MASTER_OUTPUT_ENABLED 0 #define appconfINTENT_UART_OUTPUT_ENABLED 0 From fab5a0c61e3f7a4f8047a801a7705e1c2ae8cf1e Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 27 Sep 2024 16:53:10 +0100 Subject: [PATCH 196/288] Use \rightarrow character and use substituion for tools version --- doc/programming_guide/asrc/overview.rst | 10 +++---- .../asrc/software_architecture.rst | 28 +++++++++---------- doc/programming_guide/prerequisites.rst | 2 +- doc/shared/introduction.rst | 2 +- doc/substitutions.inc | 2 +- examples/asrc_demo/README.rst | 4 +-- examples/ffva/src/usb/usb_descriptors.c | 2 +- modules/rtos | 2 +- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index f1924842..3a0a366b 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -6,7 +6,7 @@ This is the |SOFTWARE_URL| Asynchronous Sampling Rate Converter (ASRC) example d The example system implements a stereo |I2S| Slave and a stereo Adaptive UAC2.0 interface and exchanges data between the two interfaces. Since the two interfaces are operating in different clock domains, there is an ASRC block between them that converts from the input to the output sampling rate. -There are two ASRC blocks, one each in the |I2S| → ASRC → USB and USB → ASRC → |I2S| path, as illustrated in the :ref:`fig-asrc-top-level-label`. +There are two ASRC blocks, one each in the |I2S| \rightarrow ASRC \rightarrow USB and USB \rightarrow ASRC \rightarrow |I2S| path, as illustrated in the :ref:`fig-asrc-top-level-label`. The diagram also shows the rate calculation path, which monitors and computes the instantaneous ratio between the ASRC input and output sampling rate. The rate ratio is used by the ASRC task to dynamically adapt filter coefficients using spline interpolation in its filtering stage. @@ -25,7 +25,7 @@ The |I2S| Slave interface is a stereo 32 bit interface supporting sampling rates The USB interface is a stereo, 32 bit, 48 kHz, High-Speed, USB Audio Class 2, Adaptive interface. The ASRC algorithm implemented in the `lib_src `_ library is used for the ASRC processing. -The ASRC processing is block based and works on a block size of 244 samples per channel in the |I2S| → ASRC → USB path and 96 samples per channel in the USB → ASRC → |I2S| path. +The ASRC processing is block based and works on a block size of 244 samples per channel in the |I2S| \rightarrow ASRC \rightarrow USB path and 96 samples per channel in the USB \rightarrow ASRC \rightarrow |I2S| path. Supported Hardware ================== @@ -39,7 +39,7 @@ The table :ref:`table-pin-connections-label` lists the pins on the XK-VOICE-L71 .. _table-pin-connections-label: .. list-table:: XK-VOICE-L71 RPI host interface header (J4) connections - :widths: 30 50 + :widths: 40 60 :header-rows: 1 :align: left @@ -72,14 +72,14 @@ Download the main repo and submodules using: Building the app ================ -First install and source the XTC version: 15.2.1 tools. The output should be +First install and source the XTC version: |TOOLS_VERSION| tools. The output should be something like this: :: $ xcc --version xcc: Build 19-198606c, Oct-25-2022 - XTC version: 15.2.1 + XTC version: |TOOLS_VERSION| Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. diff --git a/doc/programming_guide/asrc/software_architecture.rst b/doc/programming_guide/asrc/software_architecture.rst index 2336c6fb..51a98916 100644 --- a/doc/programming_guide/asrc/software_architecture.rst +++ b/doc/programming_guide/asrc/software_architecture.rst @@ -27,7 +27,7 @@ The :ref:`fig-ASRC-task-diagram-label` shows the RTOS tasks and other components The tasks can roughly be categorised as belonging to the USB driver, |I2S| driver or the application code categories. -The actual ASRC processing happens in four tasks across the two tiles; the **usb_audio_out_asrc task**, **i2s_audio_recv_asrc** task, and two instances of **asrc_one_channel_task**, one on each tile. +The actual ASRC processing happens in four tasks across the two tiles; the **usb_audio_out_asrc task**, **i2s_audio_recv_asrc task**, and two instances of **asrc_one_channel task**, one on each tile. This is described in more detail in the :ref:`application-components-label` section below. Most of the tasks are involved in the ASRC processing data path, while a few are involved in monitoring the input and output data rates @@ -53,9 +53,9 @@ It interfaces with the USB app level thread (**usb_task**) via shared memory and **usb_task** implements the app level USB driver functionality. The app level USB driver is based on `TinyUSB `_ which hooks into the application by means of callback functions. The **usb_isr** task is triggered by the interrupt and parses the data transferred from XUD and places it on a queue that the **usb_task** blocks on for further processing. -For example, on completion of an EP1 OUT transfer, the transfer completion gets notified on the **usb_xud_thread → usb_isr → usb_task** path, +For example, on completion of an EP1 OUT transfer, the transfer completion gets notified on the **usb_xud_thread \rightarrow usb_isr \rightarrow usb_task** path, and the **usb_task** calls the ``tud_audio_rx_done_post_read_cb()`` function to have the application process the data received from the host. -On completion of an EP1 IN transfer, the transfer completion again follows the **usb_xud_thread → usb_isr → usb_task** path, and **usb_task** calls the ``tud_audio_tx_done_pre_load_cb()`` +On completion of an EP1 IN transfer, the transfer completion again follows the **usb_xud_thread \rightarrow usb_isr \rightarrow usb_task** path, and **usb_task** calls the ``tud_audio_tx_done_pre_load_cb()`` callback function to have the application load the EP1 IN data for the next transfer. **samples_to_host_stream_buf** and **samples_from_host_stream_buf** are circular buffers shared between the application and the USB driver and allow for decoupling one from the other. @@ -111,34 +111,34 @@ It has other rate-monitoring related responsibilities that are described in the **i2s_to_usb_intertile** task receives the ASRC output data generated by **i2s_audio_recv_asrc** over the inter-tile context onto the USB tile and writes it to the USB ``samples_to_host_stream_buf``. It has other rate-monitoring related responsibilities that are described in the :ref:`rate-server-label` section. -The :ref:`asrc_i2s_to_usb_data_path-label` diagram shows the application tasks involved in the |I2S| → ASRC → USB path processing and their interaction with each other. +The :ref:`asrc_i2s_to_usb_data_path-label` diagram shows the application tasks involved in the |I2S| \rightarrow ASRC \rightarrow USB path processing and their interaction with each other. .. _asrc_i2s_to_usb_data_path-label: .. figure:: diagrams/asrc_i2s_to_usb_data_path.png :align: center :width: 100% - :alt: ASRC |I2S| → ASRC → USB data path + :alt: ASRC |I2S| \rightarrow ASRC \rightarrow USB data path - |I2S| → ASRC → USB data path + |I2S| \rightarrow ASRC \rightarrow USB data path -The :ref:`asrc_usb_to_i2s_data_path-label` diagram shows the application tasks involved in the USB → ASRC → |I2S| path processing and their interaction with each other. +The :ref:`asrc_usb_to_i2s_data_path-label` diagram shows the application tasks involved in the USB \rightarrow ASRC \rightarrow |I2S| path processing and their interaction with each other. .. _asrc_usb_to_i2s_data_path-label: .. figure:: diagrams/asrc_usb_to_i2s_data_path.png :align: center :width: 100% - :alt: USB → ASRC → |I2S| data path + :alt: USB \rightarrow ASRC \rightarrow |I2S| data path - USB → ASRC → |I2S| data path + USB \rightarrow ASRC \rightarrow |I2S| data path .. _rate-server-label: **rate_server** --------------- -The ASRC ``process_frame`` API requires the caller to calculate and send the instantaneous ratio between the ASRC input and output rate. The **rate_server** is responsible for calculating these rate ratios for both USB → ASRC → |I2S| and |I2S| → ASRC → USB directions. +The ASRC ``process_frame`` API requires the caller to calculate and send the instantaneous ratio between the ASRC input and output rate. The **rate_server** is responsible for calculating these rate ratios for both USB \rightarrow ASRC \rightarrow |I2S| and |I2S| \rightarrow ASRC \rightarrow USB directions. Additionally, the application also monitors the average buffer fill levels of the buffers holding ASRC output to prevent any overflows or underflows of the respective buffer. A gradual drift in the buffer fill level indicates that the rate ratio is being under or over calculated by the **rate_server**. This could happen either due to jitter in the actual rates or precision limitations when calculating the rates. @@ -179,8 +179,8 @@ either calculating it or getting it through shared memory from other USB tasks o The |I2S| related information (1 and 4 above) is calculated in the **rate_server** itself with information available for calculating these available through shared memory from other tasks on this tile. -After calculating the rates, the **rate_server** sends the rate ratio for the USB → ASRC → |I2S| side to the **usb_to_i2s_intertile** task over the inter-tile context and it is made available to the -**usb_audio_out_asrc** task through shared memory. The |I2S| → ASRC → USB side rate ratio is also made available to the **i2s_audio_recv_asrc** task through shared memory since it runs on the same tile as the rate server. +After calculating the rates, the **rate_server** sends the rate ratio for the USB \rightarrow ASRC \rightarrow |I2S| side to the **usb_to_i2s_intertile** task over the inter-tile context and it is made available to the +**usb_audio_out_asrc** task through shared memory. The |I2S| \rightarrow ASRC \rightarrow USB side rate ratio is also made available to the **i2s_audio_recv_asrc** task through shared memory since it runs on the same tile as the rate server. The :ref:`fig-rate-server-label` diagram shows the code flow during the rate ratio calculation process, focussing on the **usb_to_intertile** task that triggers the **rate_server** and the **rate_server** task where the rate ratios are calculated. @@ -211,7 +211,7 @@ Handling USB speaker interface close -> open events =================================================== When the USB host stops streaming to the device and then starts again, this event is detected through calls to the ``tud_audio_set_itf_close_EP_cb`` and ``tud_audio_set_itf_cb`` functions. -The ASRC output buffer in the USB → ASRC → |I2S| path (|I2S| ``send_buffer``) is reset. +The ASRC output buffer in the USB \rightarrow ASRC \rightarrow |I2S| path (|I2S| ``send_buffer``) is reset. Zeroes are then sent over |I2S| until the buffer fills to a stable level, when we resume streaming out of this buffer to send samples over |I2S|. The average buffer calculation state for the |I2S| ``send_buffer`` is also reset and a new stable average is calculated against which the average buffer levels are corrected. @@ -219,6 +219,6 @@ Handling USB mic interface close -> open events =============================================== If the USB host stops streaming from the device and then starts again, this event is detected through calls to the ``tud_audio_set_itf_close_EP_cb`` and ``tud_audio_set_itf_cb`` functions. -The ASRC output buffer in the |I2S| → ASRC → USB is reset (USB ``samples_to_host_stream_buf``). +The ASRC output buffer in the |I2S| \rightarrow ASRC \rightarrow USB is reset (USB ``samples_to_host_stream_buf``). Zeroes are streamed to the host until the buffer fills to a stable level, when we resume streaming out of this buffer to send samples over USB. The average buffer calculation state for the USB ``samples_to_host_stream_buf`` is also reset and a new stable average is calculated against which the average buffer levels are corrected. diff --git a/doc/programming_guide/prerequisites.rst b/doc/programming_guide/prerequisites.rst index 1c09257e..51f557eb 100644 --- a/doc/programming_guide/prerequisites.rst +++ b/doc/programming_guide/prerequisites.rst @@ -4,7 +4,7 @@ Prerequisites ############# -It is recommended that you download and install the latest release of the `XTC Tools `__. XTC Tools 15.2.1 or newer are required for building, running, flashing and debugging the example applications. +It is recommended that you download and install the latest release of the `XTC Tools `__. XTC Tools |TOOLS_VERSION| or newer are required for building, running, flashing and debugging the example applications. `CMake 3.21 `_ or newer and `Git `_ are also required for building the example applications. diff --git a/doc/shared/introduction.rst b/doc/shared/introduction.rst index a99be59e..23e55240 100644 --- a/doc/shared/introduction.rst +++ b/doc/shared/introduction.rst @@ -86,7 +86,7 @@ Obtaining the Software Development Tools ***************** -It is recommended that you download and install the latest release of the `XTC Tools `__. XTC Tools 15.2.1 or newer are required. If you already have the XTC Toolchain installed, you can check the version with the following command: +It is recommended that you download and install the latest release of the `XTC Tools `__. XTC Tools |TOOLS_VERSION| or newer are required. If you already have the XTC Toolchain installed, you can check the version with the following command: .. code-block:: console diff --git a/doc/substitutions.inc b/doc/substitutions.inc index 8aa28a31..2156c185 100644 --- a/doc/substitutions.inc +++ b/doc/substitutions.inc @@ -1,6 +1,6 @@ .. |HARDWARE_URL| replace:: `XK-VOICE-L71 `__ .. |SOFTWARE_URL| replace:: `XCORE-VOICE `__ - +.. |TOOLS_VERSION| replace:: `15.2.1` .. |newpage| raw:: latex \clearpage diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index d2e3f7c8..b6cf86bc 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -6,7 +6,7 @@ This is the XCORE-VOICE Asynchronous Sampling Rate Converter (ASRC) example desi The example system implements a stereo I2S slave and a stereo adaptive UAC2.0 interface and exchanges data between the two interfaces. Since the two interfaces are operating in different clock domains, there is an ASRC block between them that converts from the input to the output sampling rate. -There are two ASRC blocks, one in the I2S → ASRC → USB path and the other in the USB → ASRC → I2S path. +There are two ASRC blocks, one in the I2S \rightarrow ASRC \rightarrow USB path and the other in the USB \rightarrow ASRC \rightarrow I2S path. The application also monitors and computes the instantaneous ratio between the ASRC input and output sampling rate. The rate ratio is used by the ASRC task to dynamically adapt filter coefficients using spline interpolation in its filtering stage. @@ -15,7 +15,7 @@ The I2S slave interface is a stereo 32 bit interface supporting sampling rates b The USB interface is a stereo, 32 bit, 48 kHz, High-Speed, USB Audio Class 2, Adaptive interface. -The ASRC algorithm in the `lib_src `_ library is used for the ASRC processing. The ASRC processing is block based and works on a block size of 244 samples per channel in the I2S → ASRC → USB path and 96 samples per channel in the USB → ASRC → I2S path. +The ASRC algorithm in the `lib_src `_ library is used for the ASRC processing. The ASRC processing is block based and works on a block size of 244 samples per channel in the I2S \rightarrow ASRC \rightarrow USB path and 96 samples per channel in the USB \rightarrow ASRC \rightarrow I2S path. Supported Hardware ================== diff --git a/examples/ffva/src/usb/usb_descriptors.c b/examples/ffva/src/usb/usb_descriptors.c index 4eead130..6456ec9e 100644 --- a/examples/ffva/src/usb/usb_descriptors.c +++ b/examples/ffva/src/usb/usb_descriptors.c @@ -28,7 +28,7 @@ #include "tusb.h" #define XMOS_VID 0x20B1 -#define XCORE_VOICE_PID 0x4001 +#define XCORE_VOICE_PID 0x4010 #define XCORE_VOICE_PRODUCT_STR "XCORE-VOICE" //--------------------------------------------------------------------+ diff --git a/modules/rtos b/modules/rtos index 44692113..196eda34 160000 --- a/modules/rtos +++ b/modules/rtos @@ -1 +1 @@ -Subproject commit 44692113625a04d85377ce4c4c909635a1c68932 +Subproject commit 196eda34743d947bd8147fd4265108d18e4001af From 9235acfa3ad43aa83630731c9b657d6edda29486 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 30 Sep 2024 11:49:05 +0100 Subject: [PATCH 197/288] Replace rightarrow with >> --- doc/programming_guide/asrc/overview.rst | 8 +++--- .../asrc/software_architecture.rst | 26 +++++++++---------- examples/asrc_demo/README.rst | 4 +-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 3a0a366b..007db9b5 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -6,7 +6,7 @@ This is the |SOFTWARE_URL| Asynchronous Sampling Rate Converter (ASRC) example d The example system implements a stereo |I2S| Slave and a stereo Adaptive UAC2.0 interface and exchanges data between the two interfaces. Since the two interfaces are operating in different clock domains, there is an ASRC block between them that converts from the input to the output sampling rate. -There are two ASRC blocks, one each in the |I2S| \rightarrow ASRC \rightarrow USB and USB \rightarrow ASRC \rightarrow |I2S| path, as illustrated in the :ref:`fig-asrc-top-level-label`. +There are two ASRC blocks, one each in the |I2S| >> ASRC >> USB and USB >> ASRC >> |I2S| path, as illustrated in the :ref:`fig-asrc-top-level-label`. The diagram also shows the rate calculation path, which monitors and computes the instantaneous ratio between the ASRC input and output sampling rate. The rate ratio is used by the ASRC task to dynamically adapt filter coefficients using spline interpolation in its filtering stage. @@ -25,7 +25,7 @@ The |I2S| Slave interface is a stereo 32 bit interface supporting sampling rates The USB interface is a stereo, 32 bit, 48 kHz, High-Speed, USB Audio Class 2, Adaptive interface. The ASRC algorithm implemented in the `lib_src `_ library is used for the ASRC processing. -The ASRC processing is block based and works on a block size of 244 samples per channel in the |I2S| \rightarrow ASRC \rightarrow USB path and 96 samples per channel in the USB \rightarrow ASRC \rightarrow |I2S| path. +The ASRC processing is block based and works on a block size of 244 samples per channel in the |I2S| >> ASRC >> USB path and 96 samples per channel in the USB >> ASRC >> |I2S| path. Supported Hardware ================== @@ -72,14 +72,14 @@ Download the main repo and submodules using: Building the app ================ -First install and source the XTC version: |TOOLS_VERSION| tools. The output should be +First install and source the XTC version: |TOOLS_VERSION| tools. For example with version 15.2.1, the output should be something like this: :: $ xcc --version xcc: Build 19-198606c, Oct-25-2022 - XTC version: |TOOLS_VERSION| + XTC version: 15.2.1 Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. diff --git a/doc/programming_guide/asrc/software_architecture.rst b/doc/programming_guide/asrc/software_architecture.rst index 51a98916..0e9148c8 100644 --- a/doc/programming_guide/asrc/software_architecture.rst +++ b/doc/programming_guide/asrc/software_architecture.rst @@ -53,9 +53,9 @@ It interfaces with the USB app level thread (**usb_task**) via shared memory and **usb_task** implements the app level USB driver functionality. The app level USB driver is based on `TinyUSB `_ which hooks into the application by means of callback functions. The **usb_isr** task is triggered by the interrupt and parses the data transferred from XUD and places it on a queue that the **usb_task** blocks on for further processing. -For example, on completion of an EP1 OUT transfer, the transfer completion gets notified on the **usb_xud_thread \rightarrow usb_isr \rightarrow usb_task** path, +For example, on completion of an EP1 OUT transfer, the transfer completion gets notified on the **usb_xud_thread >> usb_isr >> usb_task** path, and the **usb_task** calls the ``tud_audio_rx_done_post_read_cb()`` function to have the application process the data received from the host. -On completion of an EP1 IN transfer, the transfer completion again follows the **usb_xud_thread \rightarrow usb_isr \rightarrow usb_task** path, and **usb_task** calls the ``tud_audio_tx_done_pre_load_cb()`` +On completion of an EP1 IN transfer, the transfer completion again follows the **usb_xud_thread >> usb_isr >> usb_task** path, and **usb_task** calls the ``tud_audio_tx_done_pre_load_cb()`` callback function to have the application load the EP1 IN data for the next transfer. **samples_to_host_stream_buf** and **samples_from_host_stream_buf** are circular buffers shared between the application and the USB driver and allow for decoupling one from the other. @@ -111,34 +111,34 @@ It has other rate-monitoring related responsibilities that are described in the **i2s_to_usb_intertile** task receives the ASRC output data generated by **i2s_audio_recv_asrc** over the inter-tile context onto the USB tile and writes it to the USB ``samples_to_host_stream_buf``. It has other rate-monitoring related responsibilities that are described in the :ref:`rate-server-label` section. -The :ref:`asrc_i2s_to_usb_data_path-label` diagram shows the application tasks involved in the |I2S| \rightarrow ASRC \rightarrow USB path processing and their interaction with each other. +The :ref:`asrc_i2s_to_usb_data_path-label` diagram shows the application tasks involved in the |I2S| >> ASRC >> USB path processing and their interaction with each other. .. _asrc_i2s_to_usb_data_path-label: .. figure:: diagrams/asrc_i2s_to_usb_data_path.png :align: center :width: 100% - :alt: ASRC |I2S| \rightarrow ASRC \rightarrow USB data path + :alt: ASRC |I2S| >> ASRC >> USB data path - |I2S| \rightarrow ASRC \rightarrow USB data path + |I2S| >> ASRC >> USB data path -The :ref:`asrc_usb_to_i2s_data_path-label` diagram shows the application tasks involved in the USB \rightarrow ASRC \rightarrow |I2S| path processing and their interaction with each other. +The :ref:`asrc_usb_to_i2s_data_path-label` diagram shows the application tasks involved in the USB >> ASRC >> |I2S| path processing and their interaction with each other. .. _asrc_usb_to_i2s_data_path-label: .. figure:: diagrams/asrc_usb_to_i2s_data_path.png :align: center :width: 100% - :alt: USB \rightarrow ASRC \rightarrow |I2S| data path + :alt: USB >> ASRC >> |I2S| data path - USB \rightarrow ASRC \rightarrow |I2S| data path + USB >> ASRC >> |I2S| data path .. _rate-server-label: **rate_server** --------------- -The ASRC ``process_frame`` API requires the caller to calculate and send the instantaneous ratio between the ASRC input and output rate. The **rate_server** is responsible for calculating these rate ratios for both USB \rightarrow ASRC \rightarrow |I2S| and |I2S| \rightarrow ASRC \rightarrow USB directions. +The ASRC ``process_frame`` API requires the caller to calculate and send the instantaneous ratio between the ASRC input and output rate. The **rate_server** is responsible for calculating these rate ratios for both USB >> ASRC >> |I2S| and |I2S| >> ASRC >> USB directions. Additionally, the application also monitors the average buffer fill levels of the buffers holding ASRC output to prevent any overflows or underflows of the respective buffer. A gradual drift in the buffer fill level indicates that the rate ratio is being under or over calculated by the **rate_server**. This could happen either due to jitter in the actual rates or precision limitations when calculating the rates. @@ -179,8 +179,8 @@ either calculating it or getting it through shared memory from other USB tasks o The |I2S| related information (1 and 4 above) is calculated in the **rate_server** itself with information available for calculating these available through shared memory from other tasks on this tile. -After calculating the rates, the **rate_server** sends the rate ratio for the USB \rightarrow ASRC \rightarrow |I2S| side to the **usb_to_i2s_intertile** task over the inter-tile context and it is made available to the -**usb_audio_out_asrc** task through shared memory. The |I2S| \rightarrow ASRC \rightarrow USB side rate ratio is also made available to the **i2s_audio_recv_asrc** task through shared memory since it runs on the same tile as the rate server. +After calculating the rates, the **rate_server** sends the rate ratio for the USB >> ASRC >> |I2S| side to the **usb_to_i2s_intertile** task over the inter-tile context and it is made available to the +**usb_audio_out_asrc** task through shared memory. The |I2S| >> ASRC >> USB side rate ratio is also made available to the **i2s_audio_recv_asrc** task through shared memory since it runs on the same tile as the rate server. The :ref:`fig-rate-server-label` diagram shows the code flow during the rate ratio calculation process, focussing on the **usb_to_intertile** task that triggers the **rate_server** and the **rate_server** task where the rate ratios are calculated. @@ -211,7 +211,7 @@ Handling USB speaker interface close -> open events =================================================== When the USB host stops streaming to the device and then starts again, this event is detected through calls to the ``tud_audio_set_itf_close_EP_cb`` and ``tud_audio_set_itf_cb`` functions. -The ASRC output buffer in the USB \rightarrow ASRC \rightarrow |I2S| path (|I2S| ``send_buffer``) is reset. +The ASRC output buffer in the USB >> ASRC >> |I2S| path (|I2S| ``send_buffer``) is reset. Zeroes are then sent over |I2S| until the buffer fills to a stable level, when we resume streaming out of this buffer to send samples over |I2S|. The average buffer calculation state for the |I2S| ``send_buffer`` is also reset and a new stable average is calculated against which the average buffer levels are corrected. @@ -219,6 +219,6 @@ Handling USB mic interface close -> open events =============================================== If the USB host stops streaming from the device and then starts again, this event is detected through calls to the ``tud_audio_set_itf_close_EP_cb`` and ``tud_audio_set_itf_cb`` functions. -The ASRC output buffer in the |I2S| \rightarrow ASRC \rightarrow USB is reset (USB ``samples_to_host_stream_buf``). +The ASRC output buffer in the |I2S| >> ASRC >> USB is reset (USB ``samples_to_host_stream_buf``). Zeroes are streamed to the host until the buffer fills to a stable level, when we resume streaming out of this buffer to send samples over USB. The average buffer calculation state for the USB ``samples_to_host_stream_buf`` is also reset and a new stable average is calculated against which the average buffer levels are corrected. diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index b6cf86bc..45392188 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -6,7 +6,7 @@ This is the XCORE-VOICE Asynchronous Sampling Rate Converter (ASRC) example desi The example system implements a stereo I2S slave and a stereo adaptive UAC2.0 interface and exchanges data between the two interfaces. Since the two interfaces are operating in different clock domains, there is an ASRC block between them that converts from the input to the output sampling rate. -There are two ASRC blocks, one in the I2S \rightarrow ASRC \rightarrow USB path and the other in the USB \rightarrow ASRC \rightarrow I2S path. +There are two ASRC blocks, one in the I2S >> ASRC >> USB path and the other in the USB >> ASRC >> I2S path. The application also monitors and computes the instantaneous ratio between the ASRC input and output sampling rate. The rate ratio is used by the ASRC task to dynamically adapt filter coefficients using spline interpolation in its filtering stage. @@ -15,7 +15,7 @@ The I2S slave interface is a stereo 32 bit interface supporting sampling rates b The USB interface is a stereo, 32 bit, 48 kHz, High-Speed, USB Audio Class 2, Adaptive interface. -The ASRC algorithm in the `lib_src `_ library is used for the ASRC processing. The ASRC processing is block based and works on a block size of 244 samples per channel in the I2S \rightarrow ASRC \rightarrow USB path and 96 samples per channel in the USB \rightarrow ASRC \rightarrow I2S path. +The ASRC algorithm in the `lib_src `_ library is used for the ASRC processing. The ASRC processing is block based and works on a block size of 244 samples per channel in the I2S >> ASRC >> USB path and 96 samples per channel in the USB >> ASRC >> I2S path. Supported Hardware ================== From c30d4e54d32c32c48b0bfeb6c55cb2c82cd4e625 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 30 Sep 2024 11:52:37 +0100 Subject: [PATCH 198/288] Undo update of fwk_rtos --- modules/rtos | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rtos b/modules/rtos index 196eda34..44692113 160000 --- a/modules/rtos +++ b/modules/rtos @@ -1 +1 @@ -Subproject commit 196eda34743d947bd8147fd4265108d18e4001af +Subproject commit 44692113625a04d85377ce4c4c909635a1c68932 From 04cb63601826477320078989d6bbe8f310970567 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 30 Sep 2024 11:56:10 +0100 Subject: [PATCH 199/288] Replace >> with -> --- doc/programming_guide/asrc/overview.rst | 4 +-- .../asrc/software_architecture.rst | 26 +++++++++---------- examples/asrc_demo/README.rst | 4 +-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 007db9b5..305308cb 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -6,7 +6,7 @@ This is the |SOFTWARE_URL| Asynchronous Sampling Rate Converter (ASRC) example d The example system implements a stereo |I2S| Slave and a stereo Adaptive UAC2.0 interface and exchanges data between the two interfaces. Since the two interfaces are operating in different clock domains, there is an ASRC block between them that converts from the input to the output sampling rate. -There are two ASRC blocks, one each in the |I2S| >> ASRC >> USB and USB >> ASRC >> |I2S| path, as illustrated in the :ref:`fig-asrc-top-level-label`. +There are two ASRC blocks, one each in the |I2S| -> ASRC -> USB and USB -> ASRC -> |I2S| path, as illustrated in the :ref:`fig-asrc-top-level-label`. The diagram also shows the rate calculation path, which monitors and computes the instantaneous ratio between the ASRC input and output sampling rate. The rate ratio is used by the ASRC task to dynamically adapt filter coefficients using spline interpolation in its filtering stage. @@ -25,7 +25,7 @@ The |I2S| Slave interface is a stereo 32 bit interface supporting sampling rates The USB interface is a stereo, 32 bit, 48 kHz, High-Speed, USB Audio Class 2, Adaptive interface. The ASRC algorithm implemented in the `lib_src `_ library is used for the ASRC processing. -The ASRC processing is block based and works on a block size of 244 samples per channel in the |I2S| >> ASRC >> USB path and 96 samples per channel in the USB >> ASRC >> |I2S| path. +The ASRC processing is block based and works on a block size of 244 samples per channel in the |I2S| -> ASRC -> USB path and 96 samples per channel in the USB -> ASRC -> |I2S| path. Supported Hardware ================== diff --git a/doc/programming_guide/asrc/software_architecture.rst b/doc/programming_guide/asrc/software_architecture.rst index 0e9148c8..f3ab04fe 100644 --- a/doc/programming_guide/asrc/software_architecture.rst +++ b/doc/programming_guide/asrc/software_architecture.rst @@ -53,9 +53,9 @@ It interfaces with the USB app level thread (**usb_task**) via shared memory and **usb_task** implements the app level USB driver functionality. The app level USB driver is based on `TinyUSB `_ which hooks into the application by means of callback functions. The **usb_isr** task is triggered by the interrupt and parses the data transferred from XUD and places it on a queue that the **usb_task** blocks on for further processing. -For example, on completion of an EP1 OUT transfer, the transfer completion gets notified on the **usb_xud_thread >> usb_isr >> usb_task** path, +For example, on completion of an EP1 OUT transfer, the transfer completion gets notified on the **usb_xud_thread -> usb_isr -> usb_task** path, and the **usb_task** calls the ``tud_audio_rx_done_post_read_cb()`` function to have the application process the data received from the host. -On completion of an EP1 IN transfer, the transfer completion again follows the **usb_xud_thread >> usb_isr >> usb_task** path, and **usb_task** calls the ``tud_audio_tx_done_pre_load_cb()`` +On completion of an EP1 IN transfer, the transfer completion again follows the **usb_xud_thread -> usb_isr -> usb_task** path, and **usb_task** calls the ``tud_audio_tx_done_pre_load_cb()`` callback function to have the application load the EP1 IN data for the next transfer. **samples_to_host_stream_buf** and **samples_from_host_stream_buf** are circular buffers shared between the application and the USB driver and allow for decoupling one from the other. @@ -111,34 +111,34 @@ It has other rate-monitoring related responsibilities that are described in the **i2s_to_usb_intertile** task receives the ASRC output data generated by **i2s_audio_recv_asrc** over the inter-tile context onto the USB tile and writes it to the USB ``samples_to_host_stream_buf``. It has other rate-monitoring related responsibilities that are described in the :ref:`rate-server-label` section. -The :ref:`asrc_i2s_to_usb_data_path-label` diagram shows the application tasks involved in the |I2S| >> ASRC >> USB path processing and their interaction with each other. +The :ref:`asrc_i2s_to_usb_data_path-label` diagram shows the application tasks involved in the |I2S| -> ASRC -> USB path processing and their interaction with each other. .. _asrc_i2s_to_usb_data_path-label: .. figure:: diagrams/asrc_i2s_to_usb_data_path.png :align: center :width: 100% - :alt: ASRC |I2S| >> ASRC >> USB data path + :alt: ASRC |I2S| -> ASRC -> USB data path - |I2S| >> ASRC >> USB data path + |I2S| -> ASRC -> USB data path -The :ref:`asrc_usb_to_i2s_data_path-label` diagram shows the application tasks involved in the USB >> ASRC >> |I2S| path processing and their interaction with each other. +The :ref:`asrc_usb_to_i2s_data_path-label` diagram shows the application tasks involved in the USB -> ASRC -> |I2S| path processing and their interaction with each other. .. _asrc_usb_to_i2s_data_path-label: .. figure:: diagrams/asrc_usb_to_i2s_data_path.png :align: center :width: 100% - :alt: USB >> ASRC >> |I2S| data path + :alt: USB -> ASRC -> |I2S| data path - USB >> ASRC >> |I2S| data path + USB -> ASRC -> |I2S| data path .. _rate-server-label: **rate_server** --------------- -The ASRC ``process_frame`` API requires the caller to calculate and send the instantaneous ratio between the ASRC input and output rate. The **rate_server** is responsible for calculating these rate ratios for both USB >> ASRC >> |I2S| and |I2S| >> ASRC >> USB directions. +The ASRC ``process_frame`` API requires the caller to calculate and send the instantaneous ratio between the ASRC input and output rate. The **rate_server** is responsible for calculating these rate ratios for both USB -> ASRC -> |I2S| and |I2S| -> ASRC -> USB directions. Additionally, the application also monitors the average buffer fill levels of the buffers holding ASRC output to prevent any overflows or underflows of the respective buffer. A gradual drift in the buffer fill level indicates that the rate ratio is being under or over calculated by the **rate_server**. This could happen either due to jitter in the actual rates or precision limitations when calculating the rates. @@ -179,8 +179,8 @@ either calculating it or getting it through shared memory from other USB tasks o The |I2S| related information (1 and 4 above) is calculated in the **rate_server** itself with information available for calculating these available through shared memory from other tasks on this tile. -After calculating the rates, the **rate_server** sends the rate ratio for the USB >> ASRC >> |I2S| side to the **usb_to_i2s_intertile** task over the inter-tile context and it is made available to the -**usb_audio_out_asrc** task through shared memory. The |I2S| >> ASRC >> USB side rate ratio is also made available to the **i2s_audio_recv_asrc** task through shared memory since it runs on the same tile as the rate server. +After calculating the rates, the **rate_server** sends the rate ratio for the USB -> ASRC -> |I2S| side to the **usb_to_i2s_intertile** task over the inter-tile context and it is made available to the +**usb_audio_out_asrc** task through shared memory. The |I2S| -> ASRC -> USB side rate ratio is also made available to the **i2s_audio_recv_asrc** task through shared memory since it runs on the same tile as the rate server. The :ref:`fig-rate-server-label` diagram shows the code flow during the rate ratio calculation process, focussing on the **usb_to_intertile** task that triggers the **rate_server** and the **rate_server** task where the rate ratios are calculated. @@ -211,7 +211,7 @@ Handling USB speaker interface close -> open events =================================================== When the USB host stops streaming to the device and then starts again, this event is detected through calls to the ``tud_audio_set_itf_close_EP_cb`` and ``tud_audio_set_itf_cb`` functions. -The ASRC output buffer in the USB >> ASRC >> |I2S| path (|I2S| ``send_buffer``) is reset. +The ASRC output buffer in the USB -> ASRC -> |I2S| path (|I2S| ``send_buffer``) is reset. Zeroes are then sent over |I2S| until the buffer fills to a stable level, when we resume streaming out of this buffer to send samples over |I2S|. The average buffer calculation state for the |I2S| ``send_buffer`` is also reset and a new stable average is calculated against which the average buffer levels are corrected. @@ -219,6 +219,6 @@ Handling USB mic interface close -> open events =============================================== If the USB host stops streaming from the device and then starts again, this event is detected through calls to the ``tud_audio_set_itf_close_EP_cb`` and ``tud_audio_set_itf_cb`` functions. -The ASRC output buffer in the |I2S| >> ASRC >> USB is reset (USB ``samples_to_host_stream_buf``). +The ASRC output buffer in the |I2S| -> ASRC -> USB is reset (USB ``samples_to_host_stream_buf``). Zeroes are streamed to the host until the buffer fills to a stable level, when we resume streaming out of this buffer to send samples over USB. The average buffer calculation state for the USB ``samples_to_host_stream_buf`` is also reset and a new stable average is calculated against which the average buffer levels are corrected. diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index 45392188..85aad9c5 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -6,7 +6,7 @@ This is the XCORE-VOICE Asynchronous Sampling Rate Converter (ASRC) example desi The example system implements a stereo I2S slave and a stereo adaptive UAC2.0 interface and exchanges data between the two interfaces. Since the two interfaces are operating in different clock domains, there is an ASRC block between them that converts from the input to the output sampling rate. -There are two ASRC blocks, one in the I2S >> ASRC >> USB path and the other in the USB >> ASRC >> I2S path. +There are two ASRC blocks, one in the I2S -> ASRC -> USB path and the other in the USB -> ASRC -> I2S path. The application also monitors and computes the instantaneous ratio between the ASRC input and output sampling rate. The rate ratio is used by the ASRC task to dynamically adapt filter coefficients using spline interpolation in its filtering stage. @@ -15,7 +15,7 @@ The I2S slave interface is a stereo 32 bit interface supporting sampling rates b The USB interface is a stereo, 32 bit, 48 kHz, High-Speed, USB Audio Class 2, Adaptive interface. -The ASRC algorithm in the `lib_src `_ library is used for the ASRC processing. The ASRC processing is block based and works on a block size of 244 samples per channel in the I2S >> ASRC >> USB path and 96 samples per channel in the USB >> ASRC >> I2S path. +The ASRC algorithm in the `lib_src `_ library is used for the ASRC processing. The ASRC processing is block based and works on a block size of 244 samples per channel in the I2S -> ASRC -> USB path and 96 samples per channel in the USB -> ASRC -> I2S path. Supported Hardware ================== From deda1c768029ab877270c6816e01c7b0bceeabcc Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 30 Sep 2024 13:37:47 +0100 Subject: [PATCH 200/288] Revert PID value --- examples/ffva/src/usb/usb_descriptors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ffva/src/usb/usb_descriptors.c b/examples/ffva/src/usb/usb_descriptors.c index 6456ec9e..4eead130 100644 --- a/examples/ffva/src/usb/usb_descriptors.c +++ b/examples/ffva/src/usb/usb_descriptors.c @@ -28,7 +28,7 @@ #include "tusb.h" #define XMOS_VID 0x20B1 -#define XCORE_VOICE_PID 0x4010 +#define XCORE_VOICE_PID 0x4001 #define XCORE_VOICE_PRODUCT_STR "XCORE-VOICE" //--------------------------------------------------------------------+ From 8fc08ac1ef2a14ac5a0c4c8ce7d252ea9ea8e15d Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 30 Sep 2024 15:44:43 +0100 Subject: [PATCH 201/288] Add warnings about latency --- doc/programming_guide/asrc/overview.rst | 22 ++++++++++++++++++++-- examples/asrc_demo/README.rst | 12 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 305308cb..33559baf 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -2,6 +2,26 @@ Overview ******** +.. warning:: + + This example is based on the RTOS framework and drivers. This choice allowed simplifying the example design, but it lead to some latency in the system. + The main sources of latency are: + + - the size of the buffer to which the ASRC output samples are written + - RTOS task scheduling overhead between the tasks + - bInterval of USB in the RTOS drivers is set to 4, i.e. one frame every 1ms + - Block based implementation of the USB and I2S RTOS drivers + + The expected latencies for USB at 48 kHz are as follows: + + - USB -> ASRC -> I2S: from 8 ms at |I2S| at 192 kHz to 22 ms at 44.1 kHz + - I2S -> ASRC -> USB: from 13 ms at |I2S| at 192 kHz to 19 ms at 44.1 kHz + + For a proposed implementation with lower latency, please refer to the bare-metal examples below: + + - TODO- Update link! `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__. + - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ + This is the |SOFTWARE_URL| Asynchronous Sampling Rate Converter (ASRC) example design. The example system implements a stereo |I2S| Slave and a stereo Adaptive UAC2.0 interface and exchanges data between the two interfaces. @@ -56,8 +76,6 @@ The table :ref:`table-pin-connections-label` lists the pins on the XK-VOICE-L71 * - One of the GND pins (6, 14, 20, 30, 34, 9, 25 or 39) - GND on the |I2S| Master board - - Obtaining the app files ======================= diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index 85aad9c5..3f6f87ca 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -2,6 +2,18 @@ ASRC Demo Application ********************* +.. warning:: + + This example is based on the RTOS framework and drivers, and it can lead to latency of up to 20 ms in the system. + More information can be found in the Overview section of the ASRC example in the Programming Guide. + + For a proposed implementation with lower latency, please refer to the bare-metal examples below: + + - TODO- Update link! `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__. + - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ + + + This is the XCORE-VOICE Asynchronous Sampling Rate Converter (ASRC) example design. The example system implements a stereo I2S slave and a stereo adaptive UAC2.0 interface and exchanges data between the two interfaces. From 8cbaf9a155311ab0dc9d5c72031614aa21fcb41a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 1 Oct 2024 08:26:10 +0100 Subject: [PATCH 202/288] Add more details --- doc/programming_guide/asrc/overview.rst | 9 +++++---- examples/asrc_demo/README.rst | 6 ++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 33559baf..e06fdf26 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -7,10 +7,11 @@ Overview This example is based on the RTOS framework and drivers. This choice allowed simplifying the example design, but it lead to some latency in the system. The main sources of latency are: - - the size of the buffer to which the ASRC output samples are written - - RTOS task scheduling overhead between the tasks - - bInterval of USB in the RTOS drivers is set to 4, i.e. one frame every 1ms - - Block based implementation of the USB and I2S RTOS drivers + - Large block size used for ASRC processing: this is necessary to minimise latency associated with the intertile context and thread switching overhead. + - Large size of the buffer to which the ASRC output samples are written: a stable level (half full) must be reached before starting streaming out over USB. + - RTOS task scheduling overhead between the tasks. + - bInterval of USB in the RTOS drivers is set to 4, i.e. one frame every 1ms. + - Block based implementation of the USB and I2S RTOS drivers. The expected latencies for USB at 48 kHz are as follows: diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index 3f6f87ca..c21b2533 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -9,10 +9,8 @@ ASRC Demo Application For a proposed implementation with lower latency, please refer to the bare-metal examples below: - - TODO- Update link! `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__. - - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ - - + - TODO- Update link! `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__. + - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ This is the XCORE-VOICE Asynchronous Sampling Rate Converter (ASRC) example design. From 212fee28b7ba5f905e86eb5ea647f8a46c89e974 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 1 Oct 2024 09:10:15 +0100 Subject: [PATCH 203/288] Fix links --- doc/programming_guide/asrc/overview.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index e06fdf26..c2aff8cd 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -20,8 +20,8 @@ Overview For a proposed implementation with lower latency, please refer to the bare-metal examples below: - - TODO- Update link! `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__. - - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ + - TODO- Update link! `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__. + - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ This is the |SOFTWARE_URL| Asynchronous Sampling Rate Converter (ASRC) example design. @@ -60,7 +60,7 @@ The table :ref:`table-pin-connections-label` lists the pins on the XK-VOICE-L71 .. _table-pin-connections-label: .. list-table:: XK-VOICE-L71 RPI host interface header (J4) connections - :widths: 40 60 + :widths: 50 50 :header-rows: 1 :align: left From 86b87358a4a8763ecd3426194ce91bcc9141c5fa Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 1 Oct 2024 11:16:24 +0100 Subject: [PATCH 204/288] Accept suggestions from Michael --- doc/programming_guide/asrc/overview.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index c2aff8cd..8a382739 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -4,19 +4,19 @@ Overview .. warning:: - This example is based on the RTOS framework and drivers. This choice allowed simplifying the example design, but it lead to some latency in the system. + This example is based on the RTOS framework and drivers. This choice simplifies the example design, but it leads to high latency in the system. The main sources of latency are: - Large block size used for ASRC processing: this is necessary to minimise latency associated with the intertile context and thread switching overhead. - - Large size of the buffer to which the ASRC output samples are written: a stable level (half full) must be reached before starting streaming out over USB. + - Large size of the buffer to which the ASRC output samples are written: a stable level (half full) must be reached before the start of streaming out over USB. - RTOS task scheduling overhead between the tasks. - - bInterval of USB in the RTOS drivers is set to 4, i.e. one frame every 1ms. + - bInterval of USB in the RTOS drivers is set to 4, i.e. one frame every 1 ms. - Block based implementation of the USB and I2S RTOS drivers. The expected latencies for USB at 48 kHz are as follows: - - USB -> ASRC -> I2S: from 8 ms at |I2S| at 192 kHz to 22 ms at 44.1 kHz - - I2S -> ASRC -> USB: from 13 ms at |I2S| at 192 kHz to 19 ms at 44.1 kHz + - USB -> ASRC -> |I2S|: from 8 ms at |I2S| at 192 kHz to 22 ms at 44.1 kHz + - |I2S| -> ASRC -> USB: from 13 ms at |I2S| at 192 kHz to 19 ms at 44.1 kHz For a proposed implementation with lower latency, please refer to the bare-metal examples below: From b2580e8b8ca150967fe31bfc96e2d2e55f7c46a3 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 1 Oct 2024 11:24:38 +0100 Subject: [PATCH 205/288] Use substituion for I2S --- doc/programming_guide/asrc/overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 8a382739..8539d7cd 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -11,7 +11,7 @@ Overview - Large size of the buffer to which the ASRC output samples are written: a stable level (half full) must be reached before the start of streaming out over USB. - RTOS task scheduling overhead between the tasks. - bInterval of USB in the RTOS drivers is set to 4, i.e. one frame every 1 ms. - - Block based implementation of the USB and I2S RTOS drivers. + - Block based implementation of the USB and |I2S| RTOS drivers. The expected latencies for USB at 48 kHz are as follows: From 763e4e1393f6c42f8ded393b3ce69ca4749e0bf8 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 2 Oct 2024 12:24:32 +0100 Subject: [PATCH 206/288] Remove todo warning --- doc/programming_guide/asrc/overview.rst | 2 +- examples/asrc_demo/README.rst | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 8539d7cd..e4073fc8 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -20,7 +20,7 @@ Overview For a proposed implementation with lower latency, please refer to the bare-metal examples below: - - TODO- Update link! `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__. + - `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__ - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ This is the |SOFTWARE_URL| Asynchronous Sampling Rate Converter (ASRC) example design. diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index c21b2533..7e31db3b 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -9,9 +9,10 @@ ASRC Demo Application For a proposed implementation with lower latency, please refer to the bare-metal examples below: - - TODO- Update link! `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__. + - `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__ - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ + This is the XCORE-VOICE Asynchronous Sampling Rate Converter (ASRC) example design. The example system implements a stereo I2S slave and a stereo adaptive UAC2.0 interface and exchanges data between the two interfaces. From be0cc54a7c4b794255c52a821ae6827db82a0fa9 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 2 Oct 2024 13:48:13 +0100 Subject: [PATCH 207/288] Update links to tools guide --- doc/programming_guide/asr/deploying/linux_macos.rst | 2 +- doc/programming_guide/asr/deploying/native_windows.rst | 2 +- examples/speech_recognition/README.rst | 6 +++--- modules/io | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/programming_guide/asr/deploying/linux_macos.rst b/doc/programming_guide/asr/deploying/linux_macos.rst index 4cad2100..dfd11d61 100644 --- a/doc/programming_guide/asr/deploying/linux_macos.rst +++ b/doc/programming_guide/asr/deploying/linux_macos.rst @@ -27,7 +27,7 @@ Run the following commands in the root folder to build the host application usin The host application, ``xscope_host_endpoint``, will be installed at ``/opt/xmos/bin``, and may be moved if desired. You may wish to add this directory to your ``PATH`` variable. -Before running the host application, you may need to add the location of ``xscope_endpoint.so`` to your ``LD_LIBRARY_PATH`` environment variable. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. +Before running the host application, you may need to add the location of ``xscope_endpoint.so`` to your ``LD_LIBRARY_PATH`` environment variable. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. Building the Firmware ===================== diff --git a/doc/programming_guide/asr/deploying/native_windows.rst b/doc/programming_guide/asr/deploying/native_windows.rst index 2f6085d3..ffe7fd0e 100644 --- a/doc/programming_guide/asr/deploying/native_windows.rst +++ b/doc/programming_guide/asr/deploying/native_windows.rst @@ -47,7 +47,7 @@ Then build the host application: The host application, ``xscope_host_endpoint.exe``, will install at ``\.xmos\bin``, and may be moved if desired. You may wish to add this directory to your ``PATH`` variable. -Before running the host application, you may need to add the location of ``xscope_endpoint.dll`` to your ``PATH``. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. +Before running the host application, you may need to add the location of ``xscope_endpoint.dll`` to your ``PATH``. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. Building the Firmware ===================== diff --git a/examples/speech_recognition/README.rst b/examples/speech_recognition/README.rst index 6d81061f..462e57e9 100644 --- a/examples/speech_recognition/README.rst +++ b/examples/speech_recognition/README.rst @@ -61,7 +61,7 @@ Linux or Mac The host application, `xscope_host_endpoint`, will be installed at `/opt/xmos/bin/`, and may be moved if desired. You may wish to add this directory to your `PATH` variable. -Before running the host application, you may need to add the location of `xscope_endpoint.so` to your `LD_LIBRARY_PATH` environment variable. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. +Before running the host application, you may need to add the location of `xscope_endpoint.so` to your `LD_LIBRARY_PATH` environment variable. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. Windows ------- @@ -99,7 +99,7 @@ Then build the host application: The host application, `xscope_host_endpoint.exe`, will install at `\.xmos\bin`, and may be moved if desired. You may wish to add this directory to your `PATH` variable. -Before running the host application, you may need to add the location of `xscope_endpoint.dll` to your `PATH`. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. +Before running the host application, you may need to add the location of `xscope_endpoint.dll` to your `PATH`. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. Building the Firmware ===================== @@ -138,7 +138,7 @@ Run the following command in the build folder to run the firmware: :: - xrun --xscope-realtime --xscope-port localhost:12345 example_asr.xe + xrun --xscope --xscope-port localhost:12345 example_asr.xe In a second console, run the following command in the ``examples/speech_recognition`` folder to run the host server: diff --git a/modules/io b/modules/io index dcf1a87d..424e33f7 160000 --- a/modules/io +++ b/modules/io @@ -1 +1 @@ -Subproject commit dcf1a87ddd006c328906094ec12493e604bc9b2f +Subproject commit 424e33f774d7cb505f63650fe944082cb7c04606 From 529a9c83ed7d04500f2a3f2d774212cc0e566cc1 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 2 Oct 2024 13:56:01 +0100 Subject: [PATCH 208/288] Replace xmos.ai with xmos.com --- doc/shared/introduction.rst | 4 ++-- doc/substitutions.inc | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/shared/introduction.rst b/doc/shared/introduction.rst index 23e55240..5f2008a8 100644 --- a/doc/shared/introduction.rst +++ b/doc/shared/introduction.rst @@ -72,11 +72,11 @@ Obtaining the Hardware The XK-VOICE-L71 DevKit and Hardware Manual can be obtained from the |HARDWARE_URL| product information page. -The XK-VOICE-L71 is based on the: `XU316-1024-QF60A `_ +The XK-VOICE-L71 is based on the: `XU316-1024-QF60A `_ The XCORE-AI-EXPLORER DevKit and Hardware Manual used in the :ref:`Microphone Aggregation ` example can be obtained from the |HARDWARE_URL| product information page. -Learn more about the `The XMOS XS3 Architecture `_ +Learn more about the `The XMOS XS3 Architecture `_ ###################### Obtaining the Software diff --git a/doc/substitutions.inc b/doc/substitutions.inc index 2156c185..d148245a 100644 --- a/doc/substitutions.inc +++ b/doc/substitutions.inc @@ -1,6 +1,6 @@ -.. |HARDWARE_URL| replace:: `XK-VOICE-L71 `__ -.. |SOFTWARE_URL| replace:: `XCORE-VOICE `__ -.. |TOOLS_VERSION| replace:: `15.2.1` +.. |HARDWARE_URL| replace:: `XK-VOICE-L71 `__ +.. |SOFTWARE_URL| replace:: `XCORE-VOICE `__ +.. |TOOLS_VERSION| replace:: 15.2.1 .. |newpage| raw:: latex \clearpage From 42f3d55a020a0782d8a1bf421d7845ea1d05fa2b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 2 Oct 2024 14:12:08 +0100 Subject: [PATCH 209/288] Update link to tools installation guide --- doc/programming_guide/asr/deploying/linux_macos.rst | 2 +- doc/programming_guide/asr/deploying/native_windows.rst | 2 +- examples/speech_recognition/README.rst | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/programming_guide/asr/deploying/linux_macos.rst b/doc/programming_guide/asr/deploying/linux_macos.rst index dfd11d61..afda6d63 100644 --- a/doc/programming_guide/asr/deploying/linux_macos.rst +++ b/doc/programming_guide/asr/deploying/linux_macos.rst @@ -27,7 +27,7 @@ Run the following commands in the root folder to build the host application usin The host application, ``xscope_host_endpoint``, will be installed at ``/opt/xmos/bin``, and may be moved if desired. You may wish to add this directory to your ``PATH`` variable. -Before running the host application, you may need to add the location of ``xscope_endpoint.so`` to your ``LD_LIBRARY_PATH`` environment variable. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. +Before running the host application, you may need to add the location of ``xscope_endpoint.so`` to your ``LD_LIBRARY_PATH`` environment variable. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. Building the Firmware ===================== diff --git a/doc/programming_guide/asr/deploying/native_windows.rst b/doc/programming_guide/asr/deploying/native_windows.rst index ffe7fd0e..7aca1049 100644 --- a/doc/programming_guide/asr/deploying/native_windows.rst +++ b/doc/programming_guide/asr/deploying/native_windows.rst @@ -47,7 +47,7 @@ Then build the host application: The host application, ``xscope_host_endpoint.exe``, will install at ``\.xmos\bin``, and may be moved if desired. You may wish to add this directory to your ``PATH`` variable. -Before running the host application, you may need to add the location of ``xscope_endpoint.dll`` to your ``PATH``. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. +Before running the host application, you may need to add the location of ``xscope_endpoint.dll`` to your ``PATH``. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. Building the Firmware ===================== diff --git a/examples/speech_recognition/README.rst b/examples/speech_recognition/README.rst index 462e57e9..d00ed4ac 100644 --- a/examples/speech_recognition/README.rst +++ b/examples/speech_recognition/README.rst @@ -61,7 +61,7 @@ Linux or Mac The host application, `xscope_host_endpoint`, will be installed at `/opt/xmos/bin/`, and may be moved if desired. You may wish to add this directory to your `PATH` variable. -Before running the host application, you may need to add the location of `xscope_endpoint.so` to your `LD_LIBRARY_PATH` environment variable. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. +Before running the host application, you may need to add the location of `xscope_endpoint.so` to your `LD_LIBRARY_PATH` environment variable. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. Windows ------- @@ -99,7 +99,7 @@ Then build the host application: The host application, `xscope_host_endpoint.exe`, will install at `\.xmos\bin`, and may be moved if desired. You may wish to add this directory to your `PATH` variable. -Before running the host application, you may need to add the location of `xscope_endpoint.dll` to your `PATH`. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. +Before running the host application, you may need to add the location of `xscope_endpoint.dll` to your `PATH`. This environment variable will be set if you run the host application in the XTC Tools command-line environment. For more information see `Configuring the command-line environment `__. Building the Firmware ===================== From a1fcff490400f5bd169c037f343ee36ac32656e8 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 2 Oct 2024 14:42:32 +0100 Subject: [PATCH 210/288] Undo changes --- doc/programming_guide/asrc/overview.rst | 5 +++-- examples/asrc_demo/README.rst | 4 ++-- modules/io | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index e4073fc8..765f23de 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -20,8 +20,9 @@ Overview For a proposed implementation with lower latency, please refer to the bare-metal examples below: - - `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__ - - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ + - `AN02002: Adding an additional I2S bus to USB Audio via SRC `__ + - `AN02003: SPDIF/ADAT/I2S Slave Receive to I2S Slave Bridge with ASRC `__ + This is the |SOFTWARE_URL| Asynchronous Sampling Rate Converter (ASRC) example design. diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index 7e31db3b..d1bfe395 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -9,8 +9,8 @@ ASRC Demo Application For a proposed implementation with lower latency, please refer to the bare-metal examples below: - - `AN02002: Adding an additional |I2S| bus to USB Audio (via SRC )`__ - - `AN02003: SPDIF/ADAT/|I2S| Slave Receive to |I2S| Slave Bridge with ASRC `__ + - `AN02002: Adding an additional I2S bus to USB Audio via SRC `__ + - `AN02003: SPDIF/ADAT/I2S Slave Receive to I2S Slave Bridge with ASRC `__ This is the XCORE-VOICE Asynchronous Sampling Rate Converter (ASRC) example design. diff --git a/modules/io b/modules/io index 424e33f7..dcf1a87d 160000 --- a/modules/io +++ b/modules/io @@ -1 +1 @@ -Subproject commit 424e33f774d7cb505f63650fe944082cb7c04606 +Subproject commit dcf1a87ddd006c328906094ec12493e604bc9b2f From 8e929853bbda987d242f9d77bf432daf208bc381 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 8 Oct 2024 08:00:48 +0100 Subject: [PATCH 211/288] Skip link check for AN02002 --- settings.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.yml b/settings.yml index 3f19710d..f103260f 100644 --- a/settings.yml +++ b/settings.yml @@ -10,6 +10,7 @@ documentation: - .*digikey.* # digikey don't allow auto link checking - .*github.com/xmos/.* # many links to private repos can't be checked - .*percepio.com/.* # Semi broken link in fwk_rtos that actually still takes you to a sensible place. This may be removed on the next release (>v2.2.0) + - https://www.xmos.com/file/AN02002 # TODO: Remove this link when AN02002 is published doxygen_projects: sln_voice: doxyfile_path: doc/Doxyfile.inc From 32d87898c7bbea197403bfc8737f963ef090974a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 8 Oct 2024 08:04:12 +0100 Subject: [PATCH 212/288] Add info in changelog --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4d5fd5b5..6abc3653 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,8 @@ XCORE-VOICE change log * REMOVED: flash settings in .xn files, as they are not required by XMOS Tools 15.2.x. * ADDED: Support for reading registers over I2C slave in FFD examples. + * ADDED: Note in ASRC demo documentation about large latency in ASRC + processing. References to alternative application notes have been provided. * CHANGED: Updated submodule fwk_rtos to version 3.2.0 from 3.0.5. * ADDED: lib_sw_pll submodule v1.1.0. From 402cf71b9dbd0d2992b51d7c79c021810117b1d2 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 8 Oct 2024 10:31:08 +0100 Subject: [PATCH 213/288] Remove double space --- doc/programming_guide/asrc/overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 765f23de..86ec8f44 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -8,7 +8,7 @@ Overview The main sources of latency are: - Large block size used for ASRC processing: this is necessary to minimise latency associated with the intertile context and thread switching overhead. - - Large size of the buffer to which the ASRC output samples are written: a stable level (half full) must be reached before the start of streaming out over USB. + - Large size of the buffer to which the ASRC output samples are written: a stable level (half full) must be reached before the start of streaming out over USB. - RTOS task scheduling overhead between the tasks. - bInterval of USB in the RTOS drivers is set to 4, i.e. one frame every 1 ms. - Block based implementation of the USB and |I2S| RTOS drivers. From 10df27b5e5572f92d205cef8fcfc97bd1d1305f8 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 22 Oct 2024 10:43:30 +0100 Subject: [PATCH 214/288] Use latest xmosdoc and xjsl versions --- Jenkinsfile | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 598ca498..4426cc0d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,4 +1,4 @@ -@Library('xmos_jenkins_shared_library@v0.28.0') _ +@Library('xmos_jenkins_shared_library@v0.34.0') _ getApproval() @@ -18,9 +18,15 @@ pipeline { defaultValue: '15.2.1', description: 'The XTC tools version' ) + string( + name: 'XMOSDOC_VERSION', + defaultValue: 'v6.1.2', + description: 'The xmosdoc version' + ) booleanParam(name: 'NIGHTLY_TEST_ONLY', defaultValue: false, description: 'Tests that only run during nightly builds.') + } environment { REPO = 'sln_voice' @@ -275,40 +281,28 @@ pipeline { } } } - stage('Build docs') { - agent { label 'docker' } + stage ('Build Documentation') { + agent { + label 'documentation' + } stages { - stage ('Build docs with docker') { + stage('Build Documentation') { steps { + runningOn(env.NODE_NAME) checkout scm sh 'git submodule update --init --recursive --depth 1 --jobs \$(nproc)' - sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" - sh """docker run -u "\$(id -u):\$(id -g)" \ - --rm \ - -v ${WORKSPACE}:/build \ - ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" - // Zip all the generated files - zip dir: "doc/_build/", zipFile: "xcore_voice_docs_original.zip" - // Rename latex folder as pdf - sh "mv doc/_build/latex doc/_build/pdf" - // Update links to latex folder in html files - sh "find doc/_build/html -type f -exec sed -i -e 's/latex\\/sln_voice_programming_guide_/pdf\\/sln_voice_programming_guide_/g' {} \\;" - sh "find doc/_build/html -type f -exec sed -i -e 's/latex\\/sln_voice_quick_start_guide_/pdf\\/sln_voice_quick_start_guide_/g' {} \\;" - // Remove linkcheck folder - sh "rm -rf doc/_build/linkcheck" - // Zip all the generated files - zip dir: "doc/_build/", zipFile: "xcore_voice_docs_release.zip" - // Archive doc files - archiveArtifacts artifacts: "xcore_voice_docs*.zip" - } - } - } + warnError("Docs") { + buildDocs() + } // warnError("Docs") + } // steps + } // stage('Build Documentation') + } // stages post { cleanup { xcoreCleanSandbox() } } - } + } // Build Documentation } } } From d03583505142174beab491809011284249324636 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 22 Oct 2024 10:58:43 +0100 Subject: [PATCH 215/288] Fix stage --- Jenkinsfile | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4426cc0d..ce78f260 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -281,28 +281,25 @@ pipeline { } } } - stage ('Build Documentation') { + stage('Build Documentation') { agent { label 'documentation' } - stages { - stage('Build Documentation') { - steps { - runningOn(env.NODE_NAME) - checkout scm - sh 'git submodule update --init --recursive --depth 1 --jobs \$(nproc)' - warnError("Docs") { - buildDocs() - } // warnError("Docs") - } // steps - } // stage('Build Documentation') - } // stages + steps { + runningOn(env.NODE_NAME) + checkout scm + sh 'git submodule update --init --recursive --depth 1 --jobs \$(nproc)' + warnError("Docs") { + buildDocs() + } // warnError("Docs") + } // steps post { cleanup { xcoreCleanSandbox() } } - } // Build Documentation + } // stage('Build Documentation') + } } } From 5d7a83b97ac543af758ab0e671e398d891df3243 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 22 Oct 2024 11:39:15 +0100 Subject: [PATCH 216/288] Remove incorrect variables --- Jenkinsfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ce78f260..3089bbdd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -34,7 +34,6 @@ pipeline { PYTHON_VERSION = "3.8.11" VENV_DIRNAME = ".venv" BUILD_DIRNAME = "dist" - XMOSDOC_VERSION = 'v4.0' VRD_TEST_RIG_TARGET = "XCORE-AI-EXPLORER" PIPELINE_TEST_VECTORS = "pipeline_test_vectors" ASR_TEST_VECTORS = "asr_test_vectors" @@ -286,7 +285,6 @@ pipeline { label 'documentation' } steps { - runningOn(env.NODE_NAME) checkout scm sh 'git submodule update --init --recursive --depth 1 --jobs \$(nproc)' warnError("Docs") { From 6400eb0ac7fd8ad2375eae0373b9ab8eed41191f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 22 Oct 2024 12:02:24 +0100 Subject: [PATCH 217/288] Update substitutions --- doc/programming_guide/ffd/speech_recognition_cyberon.rst | 2 -- doc/substitutions.inc | 3 --- 2 files changed, 5 deletions(-) diff --git a/doc/programming_guide/ffd/speech_recognition_cyberon.rst b/doc/programming_guide/ffd/speech_recognition_cyberon.rst index 191f1361..f1bf64da 100644 --- a/doc/programming_guide/ffd/speech_recognition_cyberon.rst +++ b/doc/programming_guide/ffd/speech_recognition_cyberon.rst @@ -1,5 +1,3 @@ -.. include:: - .. _sln_voice_ffd_speech_recognition_cyberon: ############################ diff --git a/doc/substitutions.inc b/doc/substitutions.inc index d148245a..51de9fd5 100644 --- a/doc/substitutions.inc +++ b/doc/substitutions.inc @@ -1,6 +1,3 @@ .. |HARDWARE_URL| replace:: `XK-VOICE-L71 `__ .. |SOFTWARE_URL| replace:: `XCORE-VOICE `__ .. |TOOLS_VERSION| replace:: 15.2.1 -.. |newpage| raw:: latex - - \clearpage From 9c18db9ee34f8061c169c9396aceeb4559178762 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 22 Oct 2024 12:24:09 +0100 Subject: [PATCH 218/288] Update some tables --- doc/programming_guide/ffd/deploying/configuration.rst | 1 + doc/programming_guide/low_power_ffd/deploying/configuration.rst | 2 +- doc/programming_guide/low_power_ffd/software_desc/overview.rst | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 6b898d59..38ab18b2 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -9,6 +9,7 @@ The default application performs as described in the :ref:`sln_voice_ffd_overvie If options are changed, the application firmware must be rebuilt. .. list-table:: FFD Compile Options + :class: longtable :widths: 90 85 20 :header-rows: 1 :align: left diff --git a/doc/programming_guide/low_power_ffd/deploying/configuration.rst b/doc/programming_guide/low_power_ffd/deploying/configuration.rst index d9e93008..181450a6 100644 --- a/doc/programming_guide/low_power_ffd/deploying/configuration.rst +++ b/doc/programming_guide/low_power_ffd/deploying/configuration.rst @@ -13,7 +13,7 @@ variables to the APP_COMPILE_DEFINITIONS CMake variable located in the example's If options are changed, the application firmware must be rebuilt. .. list-table:: Low Power FFD Compile Options - :widths: 90 85 20 + :widths: 92 85 20 :header-rows: 1 :align: left diff --git a/doc/programming_guide/low_power_ffd/software_desc/overview.rst b/doc/programming_guide/low_power_ffd/software_desc/overview.rst index 2148205a..668a40b0 100644 --- a/doc/programming_guide/low_power_ffd/software_desc/overview.rst +++ b/doc/programming_guide/low_power_ffd/software_desc/overview.rst @@ -44,7 +44,7 @@ added code and/or user added compile options. The description of the software is split up by folder: .. list-table:: Low Power FFD Software Description - :widths: 40 120 + :widths: 40 60 :header-rows: 1 :align: left From 5c49f7e6c2a09482b6b41fb55698389374aeb4c6 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 22 Oct 2024 14:32:35 +0100 Subject: [PATCH 219/288] Fix width of a table --- doc/programming_guide/ffd/software_desc/overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/programming_guide/ffd/software_desc/overview.rst b/doc/programming_guide/ffd/software_desc/overview.rst index 65ae4af4..fd291887 100644 --- a/doc/programming_guide/ffd/software_desc/overview.rst +++ b/doc/programming_guide/ffd/software_desc/overview.rst @@ -99,7 +99,7 @@ Therefore, the underlying distribution of these 10 ms bins should not be assumed The description of the software is split up by folder: .. list-table:: FFD Software Description - :widths: 40 120 + :widths: 40 60 :header-rows: 1 :align: left From 80801450215bb5cd2fb03bcb756e50f3ab24ab18 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 22 Oct 2024 15:21:13 +0100 Subject: [PATCH 220/288] Fix widths of some tables --- doc/programming_guide/ffd/deploying/configuration.rst | 2 +- doc/programming_guide/low_power_ffd/deploying/configuration.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/programming_guide/ffd/deploying/configuration.rst b/doc/programming_guide/ffd/deploying/configuration.rst index 38ab18b2..720b399c 100644 --- a/doc/programming_guide/ffd/deploying/configuration.rst +++ b/doc/programming_guide/ffd/deploying/configuration.rst @@ -10,7 +10,7 @@ If options are changed, the application firmware must be rebuilt. .. list-table:: FFD Compile Options :class: longtable - :widths: 90 85 20 + :widths: 48 42 10 :header-rows: 1 :align: left diff --git a/doc/programming_guide/low_power_ffd/deploying/configuration.rst b/doc/programming_guide/low_power_ffd/deploying/configuration.rst index 181450a6..1cb88de9 100644 --- a/doc/programming_guide/low_power_ffd/deploying/configuration.rst +++ b/doc/programming_guide/low_power_ffd/deploying/configuration.rst @@ -13,7 +13,7 @@ variables to the APP_COMPILE_DEFINITIONS CMake variable located in the example's If options are changed, the application firmware must be rebuilt. .. list-table:: Low Power FFD Compile Options - :widths: 92 85 20 + :widths: 48 42 10 :header-rows: 1 :align: left From a46245032443efa72523f738788c7b8113e5d04f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 23 Oct 2024 09:35:20 +0100 Subject: [PATCH 221/288] Remove building docs for submodules --- index.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/index.rst b/index.rst index 5e18505f..590aa5cb 100644 --- a/index.rst +++ b/index.rst @@ -9,12 +9,6 @@ XCORE |reg| -VOICE Solutions doc/quick_start_guide/index doc/programming_guide/index - modules/voice/doc/user_guide/audio_processing/index - modules/rtos/doc/programming_guide/index - modules/rtos/doc/build_system_guide/index - modules/io/doc/programming_guide/index - modules/io/modules/mic_array/doc/programming_guide/index.rst - modules/core/modules/xcore_math/lib_xcore_math/doc/programming_guide/index.rst .. only:: html From a3889e826da9e0ee8a9e492aaa8a063b03339852 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 23 Oct 2024 09:36:40 +0100 Subject: [PATCH 222/288] Exclude submodules --- doc/exclude_patterns.inc | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc index 81ccc0cd..928b7c4c 100644 --- a/doc/exclude_patterns.inc +++ b/doc/exclude_patterns.inc @@ -1,28 +1,11 @@ # The following patterns are to be excluded from the documentation build documents/shared -modules/core/modules/legacy_compat -modules/core/modules/otpinfo -modules/core/modules/random -modules/core/modules/trycatch -modules/rtos/modules/FreeRTOS -modules/inferencing -modules/io/modules/xud -modules/io/doc/substitutions.rst -modules/sample_rate_conversion -modules/voice/doc/substitutions.rst -modules/xscope_fileio -modules/xua +modules/** sln_voice_venv **/.venv test xmos_cmake_toolchain build_* -modules/*/build -modules/*/build_* -modules/*/test -modules/*/doc/shared/legal.rst -modules/*/index* -modules/*/modules/*/index* **/*.md **CHANGELOG* **LICENSE* From bd98a2db76f15fe1496cd4f4943546f06353021c Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 23 Oct 2024 11:38:57 +0100 Subject: [PATCH 223/288] Remove inexistent link --- doc/programming_guide/ffd/software_description.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/programming_guide/ffd/software_description.rst b/doc/programming_guide/ffd/software_description.rst index 3d18d42a..4000d418 100644 --- a/doc/programming_guide/ffd/software_description.rst +++ b/doc/programming_guide/ffd/software_description.rst @@ -10,7 +10,6 @@ Software Description software_desc/overview software_desc/bsp_config - software_desc/ext software_desc/filesystem_support software_desc/src software_desc/intent_engine From caa741a78a3ba2fbd255a232d66581dccb80d46c Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 23 Oct 2024 12:13:16 +0100 Subject: [PATCH 224/288] Revert "Remove building docs for submodules" This reverts commit a46245032443efa72523f738788c7b8113e5d04f. --- index.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/index.rst b/index.rst index 590aa5cb..5e18505f 100644 --- a/index.rst +++ b/index.rst @@ -9,6 +9,12 @@ XCORE |reg| -VOICE Solutions doc/quick_start_guide/index doc/programming_guide/index + modules/voice/doc/user_guide/audio_processing/index + modules/rtos/doc/programming_guide/index + modules/rtos/doc/build_system_guide/index + modules/io/doc/programming_guide/index + modules/io/modules/mic_array/doc/programming_guide/index.rst + modules/core/modules/xcore_math/lib_xcore_math/doc/programming_guide/index.rst .. only:: html From 6d02ad6f26d3aabb4d4bd261810a268f6d4cdb7f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 23 Oct 2024 12:13:33 +0100 Subject: [PATCH 225/288] Revert "Exclude submodules" This reverts commit a3889e826da9e0ee8a9e492aaa8a063b03339852. --- doc/exclude_patterns.inc | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc index 928b7c4c..81ccc0cd 100644 --- a/doc/exclude_patterns.inc +++ b/doc/exclude_patterns.inc @@ -1,11 +1,28 @@ # The following patterns are to be excluded from the documentation build documents/shared -modules/** +modules/core/modules/legacy_compat +modules/core/modules/otpinfo +modules/core/modules/random +modules/core/modules/trycatch +modules/rtos/modules/FreeRTOS +modules/inferencing +modules/io/modules/xud +modules/io/doc/substitutions.rst +modules/sample_rate_conversion +modules/voice/doc/substitutions.rst +modules/xscope_fileio +modules/xua sln_voice_venv **/.venv test xmos_cmake_toolchain build_* +modules/*/build +modules/*/build_* +modules/*/test +modules/*/doc/shared/legal.rst +modules/*/index* +modules/*/modules/*/index* **/*.md **CHANGELOG* **LICENSE* From 8673100c7324fb6541e52a2752130ddd776d9186 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 23 Oct 2024 12:19:44 +0100 Subject: [PATCH 226/288] Add label for docker --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 3089bbdd..341130aa 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -282,7 +282,7 @@ pipeline { } stage('Build Documentation') { agent { - label 'documentation' + label 'documentation&&docker' } steps { checkout scm From 2c54b41cb8669f39b7d1ffd4fa9221a2b5b9d871 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 23 Oct 2024 12:24:17 +0100 Subject: [PATCH 227/288] Remove building docs for fwk_voice and fwk_rtos --- doc/exclude_patterns.inc | 4 ++-- index.rst | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc index 81ccc0cd..9d973e2a 100644 --- a/doc/exclude_patterns.inc +++ b/doc/exclude_patterns.inc @@ -4,12 +4,12 @@ modules/core/modules/legacy_compat modules/core/modules/otpinfo modules/core/modules/random modules/core/modules/trycatch -modules/rtos/modules/FreeRTOS +modules/rtos/** modules/inferencing modules/io/modules/xud modules/io/doc/substitutions.rst modules/sample_rate_conversion -modules/voice/doc/substitutions.rst +modules/voice/** modules/xscope_fileio modules/xua sln_voice_venv diff --git a/index.rst b/index.rst index 5e18505f..de0dbf23 100644 --- a/index.rst +++ b/index.rst @@ -9,9 +9,6 @@ XCORE |reg| -VOICE Solutions doc/quick_start_guide/index doc/programming_guide/index - modules/voice/doc/user_guide/audio_processing/index - modules/rtos/doc/programming_guide/index - modules/rtos/doc/build_system_guide/index modules/io/doc/programming_guide/index modules/io/modules/mic_array/doc/programming_guide/index.rst modules/core/modules/xcore_math/lib_xcore_math/doc/programming_guide/index.rst From c64b2edb12515936abc8ebe0196fd3db07a2d64a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 23 Oct 2024 13:30:39 +0100 Subject: [PATCH 228/288] Revert "Remove building docs for fwk_voice and fwk_rtos" This reverts commit 2c54b41cb8669f39b7d1ffd4fa9221a2b5b9d871. --- doc/exclude_patterns.inc | 4 ++-- index.rst | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc index 9d973e2a..81ccc0cd 100644 --- a/doc/exclude_patterns.inc +++ b/doc/exclude_patterns.inc @@ -4,12 +4,12 @@ modules/core/modules/legacy_compat modules/core/modules/otpinfo modules/core/modules/random modules/core/modules/trycatch -modules/rtos/** +modules/rtos/modules/FreeRTOS modules/inferencing modules/io/modules/xud modules/io/doc/substitutions.rst modules/sample_rate_conversion -modules/voice/** +modules/voice/doc/substitutions.rst modules/xscope_fileio modules/xua sln_voice_venv diff --git a/index.rst b/index.rst index de0dbf23..5e18505f 100644 --- a/index.rst +++ b/index.rst @@ -9,6 +9,9 @@ XCORE |reg| -VOICE Solutions doc/quick_start_guide/index doc/programming_guide/index + modules/voice/doc/user_guide/audio_processing/index + modules/rtos/doc/programming_guide/index + modules/rtos/doc/build_system_guide/index modules/io/doc/programming_guide/index modules/io/modules/mic_array/doc/programming_guide/index.rst modules/core/modules/xcore_math/lib_xcore_math/doc/programming_guide/index.rst From b2f860f8aa23c70e48a61e8b6d0335a5d0fe37d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 11:04:25 +0000 Subject: [PATCH 229/288] removing tflite dep from ffva --- examples/ffva/ffva.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index 7e63f13b..f7637dd7 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -44,7 +44,6 @@ set(APP_LINK_OPTIONS ) set(APP_COMMON_LINK_LIBRARIES - inferencing_tflite_micro rtos::freertos_usb rtos::sw_services::device_control lib_src @@ -94,4 +93,4 @@ include(${CMAKE_CURRENT_LIST_DIR}/ffva_ua.cmake) # Include FFVA Debug and Extension targets #********************** include(${CMAKE_CURRENT_LIST_DIR}/ffva_int_dev.cmake) -include(${CMAKE_CURRENT_LIST_DIR}/ffva_ua_dev.cmake) \ No newline at end of file +include(${CMAKE_CURRENT_LIST_DIR}/ffva_ua_dev.cmake) From ea9e5c5e9b5d2a82bdb2762b77666ffef7cbf3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 13:27:28 +0000 Subject: [PATCH 230/288] Removing inferencing module --- .gitmodules | 7 -- modules/CMakeLists.txt | 7 +- modules/inferencing/CMakeLists.txt | 159 --------------------------- modules/inferencing/lib_nn | 1 - modules/inferencing/lib_tflite_micro | 1 - 5 files changed, 1 insertion(+), 174 deletions(-) delete mode 100644 modules/inferencing/CMakeLists.txt delete mode 160000 modules/inferencing/lib_nn delete mode 160000 modules/inferencing/lib_tflite_micro diff --git a/.gitmodules b/.gitmodules index 25473ae0..775a704d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,16 +19,9 @@ [submodule "modules/xscope_fileio"] path = modules/xscope_fileio/xscope_fileio url = git@github.com:xmos/xscope_fileio.git -[submodule "modules/inferencing/lib_nn"] - path = modules/inferencing/lib_nn - url = git@github.com:xmos/lib_nn.git [submodule "modules/lib_qspi_fast_read"] path = modules/lib_qspi_fast_read url = git@github.com:xmos/lib_qspi_fast_read.git -[submodule "modules/inferencing/lib_tflite_micro"] - path = modules/inferencing/lib_tflite_micro - url = git@github.com:xmos/lib_tflite_micro.git - ignore = dirty [submodule "modules/xua"] path = modules/xua url = git@github.com:xmos/lib_xua.git diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 4acffb6f..ffdab11c 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -11,16 +11,11 @@ add_subdirectory(rtos) if(${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) ## Need to guard so host targets will not be built add_subdirectory(voice) - add_subdirectory(inferencing) add_subdirectory(sw_pll/lib_sw_pll) - - ## The following alias is added to support in intermediate version of fwk_voick - ## This can be removed once fwk_voice is updated to use the new, core::lib_tflite_micro alias - add_library(sdk::inferencing::lib_tflite_micro ALIAS inferencing_tflite_micro) endif() ## Add additional modules add_subdirectory(asr) add_subdirectory(audio_pipelines) add_subdirectory(sample_rate_conversion) -add_subdirectory(xscope_fileio) \ No newline at end of file +add_subdirectory(xscope_fileio) diff --git a/modules/inferencing/CMakeLists.txt b/modules/inferencing/CMakeLists.txt deleted file mode 100644 index a6db7b1a..00000000 --- a/modules/inferencing/CMakeLists.txt +++ /dev/null @@ -1,159 +0,0 @@ -## ******************** -## Create lib_nn target -## ******************** - -add_library(inferencing_lib_nn STATIC) - - ## Source files -file(GLOB_RECURSE NN_SOURCES "lib_nn/lib_nn/src/c/*.c") - -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/AggregateFn.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/AggregateFn_DW.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/Filter2D.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/MemCpyFn.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/OutputTransformFn.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/filt2d/conv2d_utils.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/filt2d/util.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/filt2d/geom/Filter2dGeometry.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/filt2d/geom/ImageGeometry.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/filt2d/geom/WindowGeometry.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/cpp/filt2d/geom/WindowLocation.cpp") -list(APPEND NN_SOURCES "lib_nn/lib_nn/src/asm/asm_constants.c") - -if(${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) - - file(GLOB_RECURSE NN_SOURCES_ASM lib_nn/lib_nn/src/asm/*.S) - - ## cmake doesn't recognize .S files as assembly by default - set_source_files_properties(LIB_NN_SOURCES_ASM PROPERTIES LANGUAGE ASM) - - ## Assume all asm is XS3A for now - set(XCORE_XS3A_SOURCES ${NN_SOURCES_ASM}) - - target_compile_options(inferencing_lib_nn - PRIVATE - "-Os" - "-Wno-xcore-fptrgroup" - "-Wp,-w" - $<$:-std=c++11> - ) -else() - set(CMAKE_CXX_FLAGS "-std=c++11" CACHE STRING "C++ Compiler Base Flags" FORCE) - target_compile_definitions(inferencing_lib_nn - PRIVATE - NN_USE_REF - ) - - target_compile_options(inferencing_lib_nn - PRIVATE - $<$:-std=c++11> - ) -endif() - -target_sources(inferencing_lib_nn - PRIVATE - ${NN_SOURCES} - ${NN_SOURCES_ASM} -) -target_include_directories(inferencing_lib_nn - PUBLIC - lib_nn/lib_nn/api - lib_nn/lib_nn/.. -) - -## ********************************** -## Patch lib_tflite_micro -## ********************************** - -find_package(Git) -if(NOT Git_FOUND) - message(FATAL_ERROR "Git not found. Please install git and retry.") -endif() - -set(PATCHED_FLAG_FILE ${CMAKE_CURRENT_LIST_DIR}/lib_tflite_micro/patched.flag) - -add_custom_command( - OUTPUT ${PATCHED_FLAG_FILE} - COMMAND git submodule update -f lib_tflite_micro/submodules/tflite-micro - COMMAND git apply --directory lib_tflite_micro/submodules/tflite-micro/tensorflow patches/tflite-micro.patch - COMMAND ${CMAKE_COMMAND} -E touch ${PATCHED_FLAG_FILE} - COMMENT - "Resetting and applying patch to lib_tflite_micro" - WORKING_DIRECTORY - ${CMAKE_CURRENT_LIST_DIR}/lib_tflite_micro - VERBATIM -) - -add_custom_target(lib_tflite_micro_patch DEPENDS ${PATCHED_FLAG_FILE}) - -## ****************************** -## Create lib_tflite_micro target -## ****************************** - -SET(TOP_DIR ${CMAKE_CURRENT_LIST_DIR}/lib_tflite_micro) -include(${TOP_DIR}/cmakefiles/xtflm.cmake) - -add_library(inferencing_tflite_micro STATIC) - -if(${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) - ## Source files - file(GLOB_RECURSE TFLIB_SOURCES_ASM ${TOP_DIR}/lib_tflite_micro/src/*.S) - set_source_files_properties(TFLIB_SOURCES_ASM PROPERTIES LANGUAGE ASM) - - target_compile_options(inferencing_tflite_micro - PRIVATE - "-Os" - "-Wno-xcore-fptrgroup" - $<$:-std=c++11> - "-Wp,-w" - ) -else() - target_compile_definitions(inferencing_tflite_micro - PRIVATE - __xtflm_conf_h_exists__ - ) -endif() - -target_compile_definitions(inferencing_tflite_micro - PUBLIC - NO_INTERPRETER - TF_LITE_STATIC_MEMORY - TF_LITE_STRIP_ERROR_STRINGS - XTFLM_DISABLED -) - -target_sources(inferencing_tflite_micro - PRIVATE - ${TFLIB_SOURCES_ASM} - ${TFLITE_SOURCES} - ${TFLM_KERNEL_SOURCES} - ${XTFLIB_KERNEL_SOURCES} -) - -target_include_directories(inferencing_tflite_micro - PUBLIC - "${TFLIB_DIR}/../.." - "${TFLIB_DIR}/api" - "${TFLIB_DIR}/submodules/tflite-micro" - "${TFLIB_DIR}/submodules/flatbuffers/include" - PRIVATE - "src" - "${TFLIB_DIR}/.." - "${TFLIB_DIR}/submodules/tflite-micro" - "${TFLIB_DIR}/submodules/gemmlowp" - "${TFLIB_DIR}/submodules/ruy" - "${TFLIB_DIR}/src/tflite-xcore-kernels" -) - -target_link_libraries(inferencing_tflite_micro - PRIVATE - inferencing_lib_nn -) - -## suppress all linker warnings -target_link_options(inferencing_tflite_micro - INTERFACE - -Wl,-w -) - -add_dependencies(inferencing_tflite_micro lib_tflite_micro_patch) diff --git a/modules/inferencing/lib_nn b/modules/inferencing/lib_nn deleted file mode 160000 index f85b4804..00000000 --- a/modules/inferencing/lib_nn +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f85b4804ea5f52f5fa4ca4b709a787ac62a8c526 diff --git a/modules/inferencing/lib_tflite_micro b/modules/inferencing/lib_tflite_micro deleted file mode 160000 index c59ee18f..00000000 --- a/modules/inferencing/lib_tflite_micro +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c59ee18f013f159abaa10ef0faf70b18f80f0315 From bb8b7dac2ae6f0fa2b84fd48a8459dbd928a3931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 14:51:33 +0000 Subject: [PATCH 231/288] updating fwk_voice --- modules/voice | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/voice b/modules/voice index 8b80d3cf..e230d556 160000 --- a/modules/voice +++ b/modules/voice @@ -1 +1 @@ -Subproject commit 8b80d3cf684934f038b6ae3167a70cacfb6dd2c1 +Subproject commit e230d556f52d70bc3a09367c90bac35242351b59 From 6880583139adb1b344d01b88c894fb280540bb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 15:03:12 +0000 Subject: [PATCH 232/288] adding reqs.txt for ai tools --- Jenkinsfile | 23 +++++++++++++---------- requirements.txt | 4 ++++ 2 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 requirements.txt diff --git a/Jenkinsfile b/Jenkinsfile index 341130aa..d805d630 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -61,16 +61,19 @@ pipeline { uid = sh(returnStdout: true, script: 'id -u').trim() gid = sh(returnStdout: true, script: 'id -g').trim() } - // pull docker images - sh "docker pull ghcr.io/xmos/xcore_builder:latest" - sh "docker pull ghcr.io/xmos/xcore_voice_tester:develop" - // host apps - sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_host_apps.sh" - // test firmware and filesystems - sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_tests.sh" - // List built files for log - sh "ls -la dist_host/" - sh "ls -la dist/" + createVenv(reqFile: "requirements.txt") + withVenv { + // pull docker images + sh "docker pull ghcr.io/xmos/xcore_builder:latest" + sh "docker pull ghcr.io/xmos/xcore_voice_tester:develop" + // host apps + sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_host_apps.sh" + // test firmware and filesystems + sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_tests.sh" + // List built files for log + sh "ls -la dist_host/" + sh "ls -la dist/" + } } } stage('ASRC Unit tests') { diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..6968044f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +# python_version 3.10 +# pip_version 24.* + +xmos-ai-tools==1.3.1 From 985c8919e2a5b94221f07693ad60b4acef49b601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 15:33:14 +0000 Subject: [PATCH 233/288] bumping python version and adding xmos_ai_tools --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index d805d630..0c18d246 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -31,7 +31,7 @@ pipeline { environment { REPO = 'sln_voice' VIEW = getViewName(REPO) - PYTHON_VERSION = "3.8.11" + PYTHON_VERSION = "3.10" VENV_DIRNAME = ".venv" BUILD_DIRNAME = "dist" VRD_TEST_RIG_TARGET = "XCORE-AI-EXPLORER" From 2c76c679b6c23b0cc7bd25db9f1133c587149cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 15:51:35 +0000 Subject: [PATCH 234/288] Add Python environment setup in CI build script --- tools/ci/build_tests.sh | 3 +++ tools/ci/helper_functions.sh | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index 11751adb..deda5663 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -6,6 +6,9 @@ XCORE_VOICE_ROOT=`git rev-parse --show-toplevel` source ${XCORE_VOICE_ROOT}/tools/ci/helper_functions.sh export_ci_build_vars +# Call the Python setup function +setup_python_env "${XCORE_VOICE_ROOT}" + # setup distribution folder DIST_DIR=${XCORE_VOICE_ROOT}/dist DIST_HOST_DIR=${XCORE_VOICE_ROOT}/dist_host diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 9eefd171..921c6a59 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -60,3 +60,23 @@ function export_ci_build_vars { export CI_BUILD_TOOL_ARGS="-j" fi } + +# Function to set up Python environment and install dependencies +function setup_python_env() { + local root=$1 + local venv_dir="${root}/venv" + local req_file="${root}/requirements.txt" + if [ ! -d "${venv_dir}" ]; then + echo "Creating Python virtual environment in ${venv_dir}..." + python3 -m venv "${venv_dir}" + fi + echo "Activating virtual environment in ${venv_dir}..." + source "${venv_dir}/bin/activate" + if [ -f "${req_file}" ]; then + echo "Installing Python dependencies from ${req_file}..." + pip install --no-cache-dir -r "${req_file}" + else + echo "No requirements.txt found at ${req_file}. Skipping dependency installation." + fi + echo "Python environment setup complete." +} From 8d1e75f517237b09f0fbef014d439407ab2236f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 16:27:32 +0000 Subject: [PATCH 235/288] Enhance Python environment setup by checking for python3-venv installation and creating a virtual environment if it doesn't exist --- tools/ci/helper_functions.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 921c6a59..090a0a21 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -66,17 +66,32 @@ function setup_python_env() { local root=$1 local venv_dir="${root}/venv" local req_file="${root}/requirements.txt" + + # Check if python3-venv is installed and install it if necessary + if ! dpkg -s python3-venv > /dev/null 2>&1; then + echo "Installing python3-venv..." + apt-get update && apt-get install -y python3-venv + else + echo "python3-venv is already installed." + fi + + # Check if virtual environment exists, if not, create it if [ ! -d "${venv_dir}" ]; then echo "Creating Python virtual environment in ${venv_dir}..." python3 -m venv "${venv_dir}" fi + + # Activate the virtual environment echo "Activating virtual environment in ${venv_dir}..." source "${venv_dir}/bin/activate" + + # Install dependencies if requirements.txt is found if [ -f "${req_file}" ]; then echo "Installing Python dependencies from ${req_file}..." pip install --no-cache-dir -r "${req_file}" else echo "No requirements.txt found at ${req_file}. Skipping dependency installation." fi + echo "Python environment setup complete." } From fbe008a8076d556dde83d8962ac2396038713ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 16:37:12 +0000 Subject: [PATCH 236/288] Fix installation command for python3-venv by adding sudo --- tools/ci/helper_functions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 090a0a21..98461100 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -70,7 +70,7 @@ function setup_python_env() { # Check if python3-venv is installed and install it if necessary if ! dpkg -s python3-venv > /dev/null 2>&1; then echo "Installing python3-venv..." - apt-get update && apt-get install -y python3-venv + sudo apt-get update && sudo apt-get install -y python3-venv else echo "python3-venv is already installed." fi From 107facaa0ca658f5190b90fa7907c0fa02176ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 10:32:35 +0000 Subject: [PATCH 237/288] removing python-venv install --- tools/ci/helper_functions.sh | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 98461100..56a5cc9a 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -64,17 +64,9 @@ function export_ci_build_vars { # Function to set up Python environment and install dependencies function setup_python_env() { local root=$1 - local venv_dir="${root}/venv" + local venv_dir="${root}/.venv" local req_file="${root}/requirements.txt" - # Check if python3-venv is installed and install it if necessary - if ! dpkg -s python3-venv > /dev/null 2>&1; then - echo "Installing python3-venv..." - sudo apt-get update && sudo apt-get install -y python3-venv - else - echo "python3-venv is already installed." - fi - # Check if virtual environment exists, if not, create it if [ ! -d "${venv_dir}" ]; then echo "Creating Python virtual environment in ${venv_dir}..." From 30d5bb4d221cd95fb203daa663cf47ac3d813db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 10:55:32 +0000 Subject: [PATCH 238/288] adding --user to reqs install --- tools/ci/helper_functions.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 56a5cc9a..63c509c1 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -79,8 +79,8 @@ function setup_python_env() { # Install dependencies if requirements.txt is found if [ -f "${req_file}" ]; then - echo "Installing Python dependencies from ${req_file}..." - pip install --no-cache-dir -r "${req_file}" + echo "Installing Python dependencies from ${req_file}" + pip install --no-cache-dir -r --user "${req_file}" else echo "No requirements.txt found at ${req_file}. Skipping dependency installation." fi From a05c1f0bf04b3a3999ad306196c267441626e2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 11:16:09 +0000 Subject: [PATCH 239/288] adding --user to reqs install --- tools/ci/helper_functions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 63c509c1..b420e88e 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -80,7 +80,7 @@ function setup_python_env() { # Install dependencies if requirements.txt is found if [ -f "${req_file}" ]; then echo "Installing Python dependencies from ${req_file}" - pip install --no-cache-dir -r --user "${req_file}" + pip install --no-cache-dir --user -r "${req_file}" else echo "No requirements.txt found at ${req_file}. Skipping dependency installation." fi From 28262cbed6dfcae5c0bcfd3197330270fede5701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 11:23:04 +0000 Subject: [PATCH 240/288] adding --user to reqs install --- tools/ci/helper_functions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index b420e88e..e87ba7d8 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -80,7 +80,7 @@ function setup_python_env() { # Install dependencies if requirements.txt is found if [ -f "${req_file}" ]; then echo "Installing Python dependencies from ${req_file}" - pip install --no-cache-dir --user -r "${req_file}" + pip install --user -r "${req_file}" else echo "No requirements.txt found at ${req_file}. Skipping dependency installation." fi From f1d63fd762de47eef06d25b0e541614e57410fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 11:34:54 +0000 Subject: [PATCH 241/288] adding --user to reqs install --- test/requirements.txt | 1 + tools/ci/build_tests.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/requirements.txt b/test/requirements.txt index de8cd837..74c3aac1 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -7,3 +7,4 @@ pytest-xdist==1.34.0 scipy==1.10.1 soundfile==0.11.0 pyserial==3.5 +xmos-ai-tools==1.3.1 diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index deda5663..b68c24f3 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -7,7 +7,7 @@ source ${XCORE_VOICE_ROOT}/tools/ci/helper_functions.sh export_ci_build_vars # Call the Python setup function -setup_python_env "${XCORE_VOICE_ROOT}" +# setup_python_env "${XCORE_VOICE_ROOT}" # setup distribution folder DIST_DIR=${XCORE_VOICE_ROOT}/dist From f9fed104af39983fded2238f0e8a836060d0dfe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 11:39:38 +0000 Subject: [PATCH 242/288] adding --user to reqs install --- Jenkinsfile | 23 ++++++++++------------- tools/ci/helper_functions.sh | 12 ++---------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0c18d246..716af736 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -61,19 +61,16 @@ pipeline { uid = sh(returnStdout: true, script: 'id -u').trim() gid = sh(returnStdout: true, script: 'id -g').trim() } - createVenv(reqFile: "requirements.txt") - withVenv { - // pull docker images - sh "docker pull ghcr.io/xmos/xcore_builder:latest" - sh "docker pull ghcr.io/xmos/xcore_voice_tester:develop" - // host apps - sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_host_apps.sh" - // test firmware and filesystems - sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_tests.sh" - // List built files for log - sh "ls -la dist_host/" - sh "ls -la dist/" - } + // pull docker images + sh "docker pull ghcr.io/xmos/xcore_builder:latest" + sh "docker pull ghcr.io/xmos/xcore_voice_tester:develop" + // host apps + sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_host_apps.sh" + // test firmware and filesystems + sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_tests.sh" + // List built files for log + sh "ls -la dist_host/" + sh "ls -la dist/" } } stage('ASRC Unit tests') { diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index e87ba7d8..a8ac40cf 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -72,18 +72,10 @@ function setup_python_env() { echo "Creating Python virtual environment in ${venv_dir}..." python3 -m venv "${venv_dir}" fi - + # Activate the virtual environment echo "Activating virtual environment in ${venv_dir}..." source "${venv_dir}/bin/activate" - - # Install dependencies if requirements.txt is found - if [ -f "${req_file}" ]; then - echo "Installing Python dependencies from ${req_file}" - pip install --user -r "${req_file}" - else - echo "No requirements.txt found at ${req_file}. Skipping dependency installation." - fi - + pip install --user xmos_ai_tools echo "Python environment setup complete." } From 10a7004d03f026f58bfbe9a08682399ed96798a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 11:50:06 +0000 Subject: [PATCH 243/288] adding --user to reqs install --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 716af736..18c08c9a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -288,7 +288,7 @@ pipeline { checkout scm sh 'git submodule update --init --recursive --depth 1 --jobs \$(nproc)' warnError("Docs") { - buildDocs() + //TODO REACTIVATE THIS!!!!!!! buildDocs() } // warnError("Docs") } // steps post { From 53a88d11fb1b85d3ee34e749d2115361a97c45ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 11:54:49 +0000 Subject: [PATCH 244/288] adding --user to reqs install --- tools/ci/build_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index b68c24f3..deda5663 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -7,7 +7,7 @@ source ${XCORE_VOICE_ROOT}/tools/ci/helper_functions.sh export_ci_build_vars # Call the Python setup function -# setup_python_env "${XCORE_VOICE_ROOT}" +setup_python_env "${XCORE_VOICE_ROOT}" # setup distribution folder DIST_DIR=${XCORE_VOICE_ROOT}/dist From 2c9f83e41b13a8599a01824038c00c5be69586e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 13:07:09 +0000 Subject: [PATCH 245/288] adding --user to reqs install --- Jenkinsfile | 3 +++ tools/ci/build_tests.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 18c08c9a..775ffc69 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -61,6 +61,8 @@ pipeline { uid = sh(returnStdout: true, script: 'id -u').trim() gid = sh(returnStdout: true, script: 'id -g').trim() } + createVenv(reqFile: "requirements.txt") + withVenv { // pull docker images sh "docker pull ghcr.io/xmos/xcore_builder:latest" sh "docker pull ghcr.io/xmos/xcore_voice_tester:develop" @@ -71,6 +73,7 @@ pipeline { // List built files for log sh "ls -la dist_host/" sh "ls -la dist/" + } } } stage('ASRC Unit tests') { diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index deda5663..b68c24f3 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -7,7 +7,7 @@ source ${XCORE_VOICE_ROOT}/tools/ci/helper_functions.sh export_ci_build_vars # Call the Python setup function -setup_python_env "${XCORE_VOICE_ROOT}" +# setup_python_env "${XCORE_VOICE_ROOT}" # setup distribution folder DIST_DIR=${XCORE_VOICE_ROOT}/dist From ee7b6b1f2add0ae051159f28802a862d8f8329f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 14:06:51 +0000 Subject: [PATCH 246/288] adding --user to reqs install --- Jenkinsfile | 29 ++++++++++++----------------- tools/ci/build_tests.sh | 4 ++-- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 775ffc69..c039742e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -57,23 +57,18 @@ pipeline { } stage('Build tests') { steps { - script { - uid = sh(returnStdout: true, script: 'id -u').trim() - gid = sh(returnStdout: true, script: 'id -g').trim() - } createVenv(reqFile: "requirements.txt") - withVenv { - // pull docker images - sh "docker pull ghcr.io/xmos/xcore_builder:latest" - sh "docker pull ghcr.io/xmos/xcore_voice_tester:develop" - // host apps - sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_host_apps.sh" - // test firmware and filesystems - sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_tests.sh" - // List built files for log - sh "ls -la dist_host/" - sh "ls -la dist/" - } + withTools(params.TOOLS_VERSION) { + withVenv { + // host apps + sh "tools/ci/build_host_apps.sh" + // test firmware and filesystems + sh "tools/ci/build_tests.sh" + // List built files for log + sh "ls -la dist_host/" + sh "ls -la dist/" + } // venv + } // tools } } stage('ASRC Unit tests') { @@ -291,7 +286,7 @@ pipeline { checkout scm sh 'git submodule update --init --recursive --depth 1 --jobs \$(nproc)' warnError("Docs") { - //TODO REACTIVATE THIS!!!!!!! buildDocs() + buildDocs() } // warnError("Docs") } // steps post { diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index b68c24f3..93dfaee4 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -6,8 +6,8 @@ XCORE_VOICE_ROOT=`git rev-parse --show-toplevel` source ${XCORE_VOICE_ROOT}/tools/ci/helper_functions.sh export_ci_build_vars -# Call the Python setup function -# setup_python_env "${XCORE_VOICE_ROOT}" +# Call the Python setup function (only needed if venv is not active) +# setup_python_env "${XCORE_VOICE_ROOT}" # setup distribution folder DIST_DIR=${XCORE_VOICE_ROOT}/dist From 84379d1220564cba0acb37a079313cd5838eca51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 14:56:43 +0000 Subject: [PATCH 247/288] refactor: streamline Python environment setup in CI scripts --- tools/ci/build_examples.sh | 1 + tools/ci/helper_functions.sh | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/ci/build_examples.sh b/tools/ci/build_examples.sh index d94896de..601ccf47 100755 --- a/tools/ci/build_examples.sh +++ b/tools/ci/build_examples.sh @@ -35,6 +35,7 @@ XCORE_VOICE_ROOT=`git rev-parse --show-toplevel` source ${XCORE_VOICE_ROOT}/tools/ci/helper_functions.sh export_ci_build_vars +setup_python_env "${XCORE_VOICE_ROOT}" # setup distribution folder DIST_DIR=${XCORE_VOICE_ROOT}/dist diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index a8ac40cf..7f365543 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -71,11 +71,10 @@ function setup_python_env() { if [ ! -d "${venv_dir}" ]; then echo "Creating Python virtual environment in ${venv_dir}..." python3 -m venv "${venv_dir}" + # Activate the virtual environment + echo "Activating virtual environment in ${venv_dir}..." + source "${venv_dir}/bin/activate" + pip install --user xmos_ai_tools + echo "Python environment setup complete." fi - - # Activate the virtual environment - echo "Activating virtual environment in ${venv_dir}..." - source "${venv_dir}/bin/activate" - pip install --user xmos_ai_tools - echo "Python environment setup complete." } From 45acf0e0feeb1963cc4f7f61c2b83804a5e368d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 15:00:52 +0000 Subject: [PATCH 248/288] fix: use --no-cache-dir for pip install in Python environment setup --- tools/ci/helper_functions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 7f365543..54fd8b13 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -74,7 +74,7 @@ function setup_python_env() { # Activate the virtual environment echo "Activating virtual environment in ${venv_dir}..." source "${venv_dir}/bin/activate" - pip install --user xmos_ai_tools + pip install --no-cache-dir xmos_ai_tools echo "Python environment setup complete." fi } From 3d5384b7bac22f1db63df7768925a32cc2fc6ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 15:11:46 +0000 Subject: [PATCH 249/288] bumping modules/io --- modules/io | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/io b/modules/io index dcf1a87d..3263de1f 160000 --- a/modules/io +++ b/modules/io @@ -1 +1 @@ -Subproject commit dcf1a87ddd006c328906094ec12493e604bc9b2f +Subproject commit 3263de1f960f25c6851ae49eacbbfb364bcc4eb5 From b7d64b4cd07ba33195e523658cd5b58f4981d92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 15:33:02 +0000 Subject: [PATCH 250/288] fix: update xrun command to remove --xscope-realtime flag and bump tools version --- Jenkinsfile | 2 +- doc/programming_guide/asr/deploying/linux_macos.rst | 2 +- doc/programming_guide/asr/deploying/native_windows.rst | 2 +- examples/mic_aggregator/mic_aggregator.cmake | 10 +++++----- examples/speech_recognition/speech_recognition.cmake | 2 +- test/asr/asr.cmake | 2 +- test/asr/check_asr.sh | 4 ++-- test/pipeline/check_pipeline.sh | 2 +- test/pipeline/pipeline.cmake | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c039742e..ffded48e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,7 @@ pipeline { parameters { string( name: 'TOOLS_VERSION', - defaultValue: '15.2.1', + defaultValue: '15.3.0', description: 'The XTC tools version' ) string( diff --git a/doc/programming_guide/asr/deploying/linux_macos.rst b/doc/programming_guide/asr/deploying/linux_macos.rst index afda6d63..ff170a53 100644 --- a/doc/programming_guide/asr/deploying/linux_macos.rst +++ b/doc/programming_guide/asr/deploying/linux_macos.rst @@ -64,7 +64,7 @@ From the build folder run: .. code-block:: console - xrun --xscope-realtime --xscope-port localhost:12345 example_asr.xe + xrun --xscope --xscope-port localhost:12345 example_asr.xe In a second console, run the following command in the ``examples/speech_recognition`` folder to run the host server: diff --git a/doc/programming_guide/asr/deploying/native_windows.rst b/doc/programming_guide/asr/deploying/native_windows.rst index 7aca1049..df20a43d 100644 --- a/doc/programming_guide/asr/deploying/native_windows.rst +++ b/doc/programming_guide/asr/deploying/native_windows.rst @@ -86,7 +86,7 @@ From the build folder run: .. code-block:: console - xrun --xscope-realtime --xscope-port localhost:12345 example_asr.xe + xrun --xscope --xscope-port localhost:12345 example_asr.xe In a second console, run the following command in the ``examples/speech_recognition`` folder to run the host server: diff --git a/examples/mic_aggregator/mic_aggregator.cmake b/examples/mic_aggregator/mic_aggregator.cmake index 9e17835b..fdada95f 100644 --- a/examples/mic_aggregator/mic_aggregator.cmake +++ b/examples/mic_aggregator/mic_aggregator.cmake @@ -3,15 +3,15 @@ #********************** set(APP_SRC_PATH ${CMAKE_CURRENT_LIST_DIR}/src) -set(MIC_ARRAY_DEMO_PATH ${CMAKE_CURRENT_LIST_DIR}/../../modules/io/modules/mic_array/demos/) +set(MIC_ARRAY_DEMO_PATH ${CMAKE_CURRENT_LIST_DIR}/../../modules/io/modules/mic_array/examples/) set(PLATFORM_FILE ${APP_SRC_PATH}/XCORE-AI-EXPLORER.xn) #We make a copy of the par decimator files to avoid include clashes from the demo -set(DEMO_PAR_DECIMATOR_FILES ${MIC_ARRAY_DEMO_PATH}/demo_par_decimator/src/decimator_subtask.c - ${MIC_ARRAY_DEMO_PATH}/demo_par_decimator/src/decimator_subtask.h - ${MIC_ARRAY_DEMO_PATH}/demo_par_decimator/src/app_decimator.hpp - ${MIC_ARRAY_DEMO_PATH}/demo_par_decimator/src/app_mic_array.hpp +set(DEMO_PAR_DECIMATOR_FILES ${MIC_ARRAY_DEMO_PATH}/app_par_decimator/src/decimator_subtask.c + ${MIC_ARRAY_DEMO_PATH}/app_par_decimator/src/decimator_subtask.h + ${MIC_ARRAY_DEMO_PATH}/app_par_decimator/src/app_decimator.hpp + ${MIC_ARRAY_DEMO_PATH}/app_par_decimator/src/app_mic_array.hpp ) file(COPY ${DEMO_PAR_DECIMATOR_FILES} DESTINATION ${APP_SRC_PATH}/par_decimator) diff --git a/examples/speech_recognition/speech_recognition.cmake b/examples/speech_recognition/speech_recognition.cmake index 137ce818..5d331dca 100644 --- a/examples/speech_recognition/speech_recognition.cmake +++ b/examples/speech_recognition/speech_recognition.cmake @@ -99,7 +99,7 @@ add_custom_target(flash_app_example_asr # Create run and debug targets #********************** add_custom_target(run_example_asr - COMMAND xrun --xscope-realtime --xscope-port ${XSCOPE_PORT} example_asr.xe + COMMAND xrun --xscope --xscope-port ${XSCOPE_PORT} example_asr.xe DEPENDS example_asr COMMENT "Run application" diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index e222b122..77253c27 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -198,7 +198,7 @@ create_flash_app_target( # Create run target #********************** add_custom_target(run_${TEST_ASR_NAME} - COMMAND xrun --xscope-realtime --xscope-port localhost:12345 ${TEST_ASR_NAME}.xe + COMMAND xrun --xscope --xscope-port localhost:12345 ${TEST_ASR_NAME}.xe DEPENDS ${TEST_ASR_NAME} COMMENT "Run application" diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index f4168d43..805c3411 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -128,7 +128,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${TEMP_WAV} # call xrun (in background) - (xrun ${ADAPTER_ID} --xscope-realtime --xscope-port localhost:12345 ${PIPELINE_FIRMWARE}) & + (xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${PIPELINE_FIRMWARE}) & # wait for app to load sleep 15 @@ -157,7 +157,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do cp ${PROCESSED_WAV} ${TEMP_XSCOPE_FILEIO_INPUT_WAV} # call xrun (in background) - (xrun ${ADAPTER_ID} --xscope-realtime --xscope-port localhost:12345 ${ASR_FIRMWARE}) & + (xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${ASR_FIRMWARE}) & # wait for app to load sleep 15 diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index c1219127..28c4fafa 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -112,7 +112,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do sox ${INPUT_WAV} --no-dither -r 16000 -b 32 ${XSCOPE_FILEIO_INPUT_WAV} ${REMIX_PATTERN} # call xrun (in background) - xrun ${ADAPTER_ID} --xscope-realtime --xscope-port localhost:12345 ${FIRMWARE} & + xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${FIRMWARE} & # wait for app to load sleep 10 diff --git a/test/pipeline/pipeline.cmake b/test/pipeline/pipeline.cmake index 2d613057..47b85a92 100644 --- a/test/pipeline/pipeline.cmake +++ b/test/pipeline/pipeline.cmake @@ -129,7 +129,7 @@ merge_binaries(${TEST_PIPELINE_NAME} tile0_${TEST_PIPELINE_NAME} tile1_${TEST_PI # Create run and debug targets #********************** add_custom_target(run_${TEST_PIPELINE_NAME} - COMMAND xrun --xscope-realtime --xscope-port localhost:12345 ${TEST_PIPELINE_NAME}.xe + COMMAND xrun --xscope --xscope-port localhost:12345 ${TEST_PIPELINE_NAME}.xe DEPENDS ${TEST_PIPELINE_NAME} COMMENT "Run application" From e231c929539fa9ff3f86aef13d7a2cc23ae8f90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 25 Nov 2024 16:37:12 +0000 Subject: [PATCH 251/288] Fix installation command for python3-venv by adding sudo removing python-venv install adding --user to reqs install adding --user to reqs install adding --user to reqs install adding --user to reqs install adding --user to reqs install adding --user to reqs install adding --user to reqs install adding --user to reqs install adding --user to reqs install refactor: streamline Python environment setup in CI scripts fix: use --no-cache-dir for pip install in Python environment setup bumping modules/io --- Jenkinsfile | 27 +++++++++++---------------- modules/io | 2 +- test/requirements.txt | 1 + tools/ci/build_examples.sh | 1 + tools/ci/build_tests.sh | 4 ++-- tools/ci/helper_functions.sh | 29 ++++++----------------------- 6 files changed, 22 insertions(+), 42 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0c18d246..c039742e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -57,23 +57,18 @@ pipeline { } stage('Build tests') { steps { - script { - uid = sh(returnStdout: true, script: 'id -u').trim() - gid = sh(returnStdout: true, script: 'id -g').trim() - } createVenv(reqFile: "requirements.txt") - withVenv { - // pull docker images - sh "docker pull ghcr.io/xmos/xcore_builder:latest" - sh "docker pull ghcr.io/xmos/xcore_voice_tester:develop" - // host apps - sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_host_apps.sh" - // test firmware and filesystems - sh "docker run --rm -u $uid:$gid -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_builder:latest bash -l tools/ci/build_tests.sh" - // List built files for log - sh "ls -la dist_host/" - sh "ls -la dist/" - } + withTools(params.TOOLS_VERSION) { + withVenv { + // host apps + sh "tools/ci/build_host_apps.sh" + // test firmware and filesystems + sh "tools/ci/build_tests.sh" + // List built files for log + sh "ls -la dist_host/" + sh "ls -la dist/" + } // venv + } // tools } } stage('ASRC Unit tests') { diff --git a/modules/io b/modules/io index dcf1a87d..3263de1f 160000 --- a/modules/io +++ b/modules/io @@ -1 +1 @@ -Subproject commit dcf1a87ddd006c328906094ec12493e604bc9b2f +Subproject commit 3263de1f960f25c6851ae49eacbbfb364bcc4eb5 diff --git a/test/requirements.txt b/test/requirements.txt index de8cd837..74c3aac1 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -7,3 +7,4 @@ pytest-xdist==1.34.0 scipy==1.10.1 soundfile==0.11.0 pyserial==3.5 +xmos-ai-tools==1.3.1 diff --git a/tools/ci/build_examples.sh b/tools/ci/build_examples.sh index d94896de..601ccf47 100755 --- a/tools/ci/build_examples.sh +++ b/tools/ci/build_examples.sh @@ -35,6 +35,7 @@ XCORE_VOICE_ROOT=`git rev-parse --show-toplevel` source ${XCORE_VOICE_ROOT}/tools/ci/helper_functions.sh export_ci_build_vars +setup_python_env "${XCORE_VOICE_ROOT}" # setup distribution folder DIST_DIR=${XCORE_VOICE_ROOT}/dist diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index deda5663..93dfaee4 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -6,8 +6,8 @@ XCORE_VOICE_ROOT=`git rev-parse --show-toplevel` source ${XCORE_VOICE_ROOT}/tools/ci/helper_functions.sh export_ci_build_vars -# Call the Python setup function -setup_python_env "${XCORE_VOICE_ROOT}" +# Call the Python setup function (only needed if venv is not active) +# setup_python_env "${XCORE_VOICE_ROOT}" # setup distribution folder DIST_DIR=${XCORE_VOICE_ROOT}/dist diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 090a0a21..54fd8b13 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -64,34 +64,17 @@ function export_ci_build_vars { # Function to set up Python environment and install dependencies function setup_python_env() { local root=$1 - local venv_dir="${root}/venv" + local venv_dir="${root}/.venv" local req_file="${root}/requirements.txt" - # Check if python3-venv is installed and install it if necessary - if ! dpkg -s python3-venv > /dev/null 2>&1; then - echo "Installing python3-venv..." - apt-get update && apt-get install -y python3-venv - else - echo "python3-venv is already installed." - fi - # Check if virtual environment exists, if not, create it if [ ! -d "${venv_dir}" ]; then echo "Creating Python virtual environment in ${venv_dir}..." python3 -m venv "${venv_dir}" + # Activate the virtual environment + echo "Activating virtual environment in ${venv_dir}..." + source "${venv_dir}/bin/activate" + pip install --no-cache-dir xmos_ai_tools + echo "Python environment setup complete." fi - - # Activate the virtual environment - echo "Activating virtual environment in ${venv_dir}..." - source "${venv_dir}/bin/activate" - - # Install dependencies if requirements.txt is found - if [ -f "${req_file}" ]; then - echo "Installing Python dependencies from ${req_file}..." - pip install --no-cache-dir -r "${req_file}" - else - echo "No requirements.txt found at ${req_file}. Skipping dependency installation." - fi - - echo "Python environment setup complete." } From e98bfeb5674f4de8dc5e14edd6de4222cf6173aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 15:33:02 +0000 Subject: [PATCH 252/288] fix: update xrun command to remove --xscope-realtime flag and bump tools version --- Jenkinsfile | 2 +- doc/programming_guide/asr/deploying/linux_macos.rst | 2 +- doc/programming_guide/asr/deploying/native_windows.rst | 2 +- examples/mic_aggregator/mic_aggregator.cmake | 10 +++++----- examples/speech_recognition/speech_recognition.cmake | 2 +- test/asr/asr.cmake | 2 +- test/asr/check_asr.sh | 4 ++-- test/pipeline/check_pipeline.sh | 2 +- test/pipeline/pipeline.cmake | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c039742e..ffded48e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,7 @@ pipeline { parameters { string( name: 'TOOLS_VERSION', - defaultValue: '15.2.1', + defaultValue: '15.3.0', description: 'The XTC tools version' ) string( diff --git a/doc/programming_guide/asr/deploying/linux_macos.rst b/doc/programming_guide/asr/deploying/linux_macos.rst index afda6d63..ff170a53 100644 --- a/doc/programming_guide/asr/deploying/linux_macos.rst +++ b/doc/programming_guide/asr/deploying/linux_macos.rst @@ -64,7 +64,7 @@ From the build folder run: .. code-block:: console - xrun --xscope-realtime --xscope-port localhost:12345 example_asr.xe + xrun --xscope --xscope-port localhost:12345 example_asr.xe In a second console, run the following command in the ``examples/speech_recognition`` folder to run the host server: diff --git a/doc/programming_guide/asr/deploying/native_windows.rst b/doc/programming_guide/asr/deploying/native_windows.rst index 7aca1049..df20a43d 100644 --- a/doc/programming_guide/asr/deploying/native_windows.rst +++ b/doc/programming_guide/asr/deploying/native_windows.rst @@ -86,7 +86,7 @@ From the build folder run: .. code-block:: console - xrun --xscope-realtime --xscope-port localhost:12345 example_asr.xe + xrun --xscope --xscope-port localhost:12345 example_asr.xe In a second console, run the following command in the ``examples/speech_recognition`` folder to run the host server: diff --git a/examples/mic_aggregator/mic_aggregator.cmake b/examples/mic_aggregator/mic_aggregator.cmake index 9e17835b..fdada95f 100644 --- a/examples/mic_aggregator/mic_aggregator.cmake +++ b/examples/mic_aggregator/mic_aggregator.cmake @@ -3,15 +3,15 @@ #********************** set(APP_SRC_PATH ${CMAKE_CURRENT_LIST_DIR}/src) -set(MIC_ARRAY_DEMO_PATH ${CMAKE_CURRENT_LIST_DIR}/../../modules/io/modules/mic_array/demos/) +set(MIC_ARRAY_DEMO_PATH ${CMAKE_CURRENT_LIST_DIR}/../../modules/io/modules/mic_array/examples/) set(PLATFORM_FILE ${APP_SRC_PATH}/XCORE-AI-EXPLORER.xn) #We make a copy of the par decimator files to avoid include clashes from the demo -set(DEMO_PAR_DECIMATOR_FILES ${MIC_ARRAY_DEMO_PATH}/demo_par_decimator/src/decimator_subtask.c - ${MIC_ARRAY_DEMO_PATH}/demo_par_decimator/src/decimator_subtask.h - ${MIC_ARRAY_DEMO_PATH}/demo_par_decimator/src/app_decimator.hpp - ${MIC_ARRAY_DEMO_PATH}/demo_par_decimator/src/app_mic_array.hpp +set(DEMO_PAR_DECIMATOR_FILES ${MIC_ARRAY_DEMO_PATH}/app_par_decimator/src/decimator_subtask.c + ${MIC_ARRAY_DEMO_PATH}/app_par_decimator/src/decimator_subtask.h + ${MIC_ARRAY_DEMO_PATH}/app_par_decimator/src/app_decimator.hpp + ${MIC_ARRAY_DEMO_PATH}/app_par_decimator/src/app_mic_array.hpp ) file(COPY ${DEMO_PAR_DECIMATOR_FILES} DESTINATION ${APP_SRC_PATH}/par_decimator) diff --git a/examples/speech_recognition/speech_recognition.cmake b/examples/speech_recognition/speech_recognition.cmake index 137ce818..5d331dca 100644 --- a/examples/speech_recognition/speech_recognition.cmake +++ b/examples/speech_recognition/speech_recognition.cmake @@ -99,7 +99,7 @@ add_custom_target(flash_app_example_asr # Create run and debug targets #********************** add_custom_target(run_example_asr - COMMAND xrun --xscope-realtime --xscope-port ${XSCOPE_PORT} example_asr.xe + COMMAND xrun --xscope --xscope-port ${XSCOPE_PORT} example_asr.xe DEPENDS example_asr COMMENT "Run application" diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index e222b122..77253c27 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -198,7 +198,7 @@ create_flash_app_target( # Create run target #********************** add_custom_target(run_${TEST_ASR_NAME} - COMMAND xrun --xscope-realtime --xscope-port localhost:12345 ${TEST_ASR_NAME}.xe + COMMAND xrun --xscope --xscope-port localhost:12345 ${TEST_ASR_NAME}.xe DEPENDS ${TEST_ASR_NAME} COMMENT "Run application" diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index f4168d43..805c3411 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -128,7 +128,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${TEMP_WAV} # call xrun (in background) - (xrun ${ADAPTER_ID} --xscope-realtime --xscope-port localhost:12345 ${PIPELINE_FIRMWARE}) & + (xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${PIPELINE_FIRMWARE}) & # wait for app to load sleep 15 @@ -157,7 +157,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do cp ${PROCESSED_WAV} ${TEMP_XSCOPE_FILEIO_INPUT_WAV} # call xrun (in background) - (xrun ${ADAPTER_ID} --xscope-realtime --xscope-port localhost:12345 ${ASR_FIRMWARE}) & + (xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${ASR_FIRMWARE}) & # wait for app to load sleep 15 diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index c1219127..28c4fafa 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -112,7 +112,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do sox ${INPUT_WAV} --no-dither -r 16000 -b 32 ${XSCOPE_FILEIO_INPUT_WAV} ${REMIX_PATTERN} # call xrun (in background) - xrun ${ADAPTER_ID} --xscope-realtime --xscope-port localhost:12345 ${FIRMWARE} & + xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${FIRMWARE} & # wait for app to load sleep 10 diff --git a/test/pipeline/pipeline.cmake b/test/pipeline/pipeline.cmake index 2d613057..47b85a92 100644 --- a/test/pipeline/pipeline.cmake +++ b/test/pipeline/pipeline.cmake @@ -129,7 +129,7 @@ merge_binaries(${TEST_PIPELINE_NAME} tile0_${TEST_PIPELINE_NAME} tile1_${TEST_PI # Create run and debug targets #********************** add_custom_target(run_${TEST_PIPELINE_NAME} - COMMAND xrun --xscope-realtime --xscope-port localhost:12345 ${TEST_PIPELINE_NAME}.xe + COMMAND xrun --xscope --xscope-port localhost:12345 ${TEST_PIPELINE_NAME}.xe DEPENDS ${TEST_PIPELINE_NAME} COMMENT "Run application" From 3c9be2886345ee405af641f62efcb1bc3fe79adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 15:56:31 +0000 Subject: [PATCH 253/288] fix: replace xgdb reset command with xrun for board reset in firmware update scripts --- test/device_firmware_update/check_dfu.sh | 5 ++--- test/sample_rate_conversion/check_sample_rate_conversion.sh | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/device_firmware_update/check_dfu.sh b/test/device_firmware_update/check_dfu.sh index 8f623170..78cca5be 100755 --- a/test/device_firmware_update/check_dfu.sh +++ b/test/device_firmware_update/check_dfu.sh @@ -50,7 +50,7 @@ fi # reset board -xgdb -batch -ex "connect ${ADAPTER_ID} --reset-to-mode-pins" -ex detach +xrun --reset ${ADAPTER_ID} # flash the data partition # build_tests.sh creates example_ffva_ua_adec_altarch_data_partition.bin used here @@ -78,7 +78,7 @@ xflash ${ADAPTER_ID} --factory-version ${XTC_VERSION_MAJOR}.${XTC_VERSION_MINOR} dfu-util -e -d ${USB_VID}:${USB_PID} -a 1 -D ${OUTPUT_DIR}/${FIRMWARE_NAME}_upgrade.bin # reset board -xgdb -batch -ex "connect ${ADAPTER_ID} --reset-to-mode-pins" -ex detach +xrun --reset ${ADAPTER_ID} # wait for dust to gather sleep 5 @@ -88,4 +88,3 @@ dfu-util -e -d ${USB_VID}:${USB_PID} -a 1 -U ${OUTPUT_DIR}/readback_upgrade.bin # cleanup afterwards so we don't leave an image on the flash. Leaving an image may cause issues as we have multiple targets xflash ${ADAPTER_ID} --erase-all --target-file "${SLN_VOICE_ROOT}"/examples/ffd/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn - diff --git a/test/sample_rate_conversion/check_sample_rate_conversion.sh b/test/sample_rate_conversion/check_sample_rate_conversion.sh index 44c4867c..12cba8da 100755 --- a/test/sample_rate_conversion/check_sample_rate_conversion.sh +++ b/test/sample_rate_conversion/check_sample_rate_conversion.sh @@ -81,4 +81,4 @@ echo " OUTPUT_WAV="${OUTPUT_WAV} echo " LENGTH="${LENGTH} # reset board for the next test -xgdb -batch -ex "connect ${ADAPTER_ID} --reset-to-mode-pins" -ex detach +xrun --reset ${ADAPTER_ID} From f87444d8dd543d9aa903cc795f5d3bb739e0d38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 16:28:10 +0000 Subject: [PATCH 254/288] Update fwk_core submodule to tag 1.1.0 --- modules/core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core b/modules/core index c4582cb3..ebe8ee43 160000 --- a/modules/core +++ b/modules/core @@ -1 +1 @@ -Subproject commit c4582cb3ce4da34c8757a6f8f7df8935496038eb +Subproject commit ebe8ee43467132dee919eb754b5bb9d93558906f From e3a366f39a9074864c20c59998071f40bd3aa77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Tue, 26 Nov 2024 16:45:51 +0000 Subject: [PATCH 255/288] testing nproc and nightly On --- Jenkinsfile | 2 +- tools/ci/helper_functions.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ffded48e..79f71c57 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,7 +24,7 @@ pipeline { description: 'The xmosdoc version' ) booleanParam(name: 'NIGHTLY_TEST_ONLY', - defaultValue: false, + defaultValue: true, //TODO reset value to false description: 'Tests that only run during nightly builds.') } diff --git a/tools/ci/helper_functions.sh b/tools/ci/helper_functions.sh index 54fd8b13..0151f122 100755 --- a/tools/ci/helper_functions.sh +++ b/tools/ci/helper_functions.sh @@ -57,7 +57,7 @@ function export_ci_build_vars { export CI_BUILD_TOOL_ARGS="" else export CI_BUILD_TOOL="make" - export CI_BUILD_TOOL_ARGS="-j" + export CI_BUILD_TOOL_ARGS="-j$(nproc)" fi } From c80ca4e3c13ea164f8601a879b2f1872aebc2713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Wed, 27 Nov 2024 08:08:16 +0000 Subject: [PATCH 256/288] update NIGHTLY_TEST_ONLY parameter to false --- Jenkinsfile | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 79f71c57..ead27951 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,10 +24,9 @@ pipeline { description: 'The xmosdoc version' ) booleanParam(name: 'NIGHTLY_TEST_ONLY', - defaultValue: true, //TODO reset value to false + defaultValue: false, description: 'Tests that only run during nightly builds.') - - } + } // parameters environment { REPO = 'sln_voice' VIEW = getViewName(REPO) @@ -102,7 +101,7 @@ pipeline { } stage('Create virtual environment') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { // Create venv @@ -117,7 +116,7 @@ pipeline { } stage('Cleanup xtagctl') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { // Cleanup any xtagctl cruft from previous failed runs @@ -131,7 +130,7 @@ pipeline { } stage('Run Sample Rate Conversion test') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { withTools(params.TOOLS_VERSION) { @@ -148,7 +147,7 @@ pipeline { } stage('Run GPIO test') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { withTools(params.TOOLS_VERSION) { @@ -163,7 +162,7 @@ pipeline { } stage('Run FFD Low Power Audio Buffer test') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { withTools(params.TOOLS_VERSION) { @@ -178,7 +177,7 @@ pipeline { } stage('Run Device Firmware Update test') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { withTools(params.TOOLS_VERSION) { @@ -197,7 +196,7 @@ pipeline { } stage('Checkout Amazon WWE') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { sh 'git clone git@github.com:xmos/amazon_wwe.git' @@ -205,7 +204,7 @@ pipeline { } stage('Setup test vectors') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { sh "cp -r /projects/hydra_audio/xcore-voice_xvf3510_no_processing_xmos_test_suite_subset $PIPELINE_TEST_VECTORS" @@ -216,7 +215,7 @@ pipeline { } stage('Run FFVA Pipeline test') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { withTools(params.TOOLS_VERSION) { @@ -233,7 +232,7 @@ pipeline { } stage('Run FFD Pipeline test') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == false } } steps { withTools(params.TOOLS_VERSION) { From 5ea12a41e9f50b1afd5f1ad48aa48827326170a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Wed, 27 Nov 2024 08:24:05 +0000 Subject: [PATCH 257/288] adding FORCE_FULL_RUN --- Jenkinsfile | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ead27951..09148098 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,7 +25,12 @@ pipeline { ) booleanParam(name: 'NIGHTLY_TEST_ONLY', defaultValue: false, - description: 'Tests that only run during nightly builds.') + description: 'Tests that only run during nightly builds.' + ) + booleanParam(name: 'FORCE_FULL_RUN', + defaultValue: false, + description: 'Force to run all tests, including nigthly.' + ) } // parameters environment { REPO = 'sln_voice' @@ -101,7 +106,7 @@ pipeline { } stage('Create virtual environment') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { // Create venv @@ -116,7 +121,7 @@ pipeline { } stage('Cleanup xtagctl') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { // Cleanup any xtagctl cruft from previous failed runs @@ -130,7 +135,7 @@ pipeline { } stage('Run Sample Rate Conversion test') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { withTools(params.TOOLS_VERSION) { @@ -147,7 +152,7 @@ pipeline { } stage('Run GPIO test') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { withTools(params.TOOLS_VERSION) { @@ -162,7 +167,7 @@ pipeline { } stage('Run FFD Low Power Audio Buffer test') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { withTools(params.TOOLS_VERSION) { @@ -177,7 +182,7 @@ pipeline { } stage('Run Device Firmware Update test') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { withTools(params.TOOLS_VERSION) { @@ -196,7 +201,7 @@ pipeline { } stage('Checkout Amazon WWE') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { sh 'git clone git@github.com:xmos/amazon_wwe.git' @@ -204,7 +209,7 @@ pipeline { } stage('Setup test vectors') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { sh "cp -r /projects/hydra_audio/xcore-voice_xvf3510_no_processing_xmos_test_suite_subset $PIPELINE_TEST_VECTORS" @@ -215,7 +220,7 @@ pipeline { } stage('Run FFVA Pipeline test') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { withTools(params.TOOLS_VERSION) { @@ -232,7 +237,7 @@ pipeline { } stage('Run FFD Pipeline test') { when { - expression { params.NIGHTLY_TEST_ONLY == false } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { withTools(params.TOOLS_VERSION) { @@ -249,7 +254,7 @@ pipeline { } stage('Run ASR test') { when { - expression { params.NIGHTLY_TEST_ONLY == true } + expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { withTools(params.TOOLS_VERSION) { From 67772a99c021fa8abcfe7acdd58206aa3d4d2ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Wed, 27 Nov 2024 09:21:40 +0000 Subject: [PATCH 258/288] removing manual create venv --- Jenkinsfile | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 09148098..f91442df 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,14 +28,13 @@ pipeline { description: 'Tests that only run during nightly builds.' ) booleanParam(name: 'FORCE_FULL_RUN', - defaultValue: false, + defaultValue: true, description: 'Force to run all tests, including nigthly.' ) } // parameters environment { REPO = 'sln_voice' VIEW = getViewName(REPO) - PYTHON_VERSION = "3.10" VENV_DIRNAME = ".venv" BUILD_DIRNAME = "dist" VRD_TEST_RIG_TARGET = "XCORE-AI-EXPLORER" @@ -104,14 +103,11 @@ pipeline { } } } - stage('Create virtual environment') { + stage('Install test requirements') { when { expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } steps { - // Create venv - sh "pyenv install -s $PYTHON_VERSION" - sh "~/.pyenv/versions/$PYTHON_VERSION/bin/python -m venv $VENV_DIRNAME" // Install dependencies withVenv() { sh "pip install git+https://github0.xmos.com/xmos-int/xtagctl.git" From 1982f186441e16481ceaacbb34cc8a3c43162a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Wed, 27 Nov 2024 09:46:07 +0000 Subject: [PATCH 259/288] Update test dependencies to specify version ranges for numpy and pytest --- test/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index 74c3aac1..b9dc6977 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,8 +1,8 @@ -e git+https://github.com/xmos/audio_test_tools@develop#egg=audio_test_tools&subdirectory=python matplotlib==3.3.1 -numpy==1.19.5 +numpy>=1.19.5,<2 pylint==2.5.3 -pytest==6.0.0 +pytest>6,<=8 pytest-xdist==1.34.0 scipy==1.10.1 soundfile==0.11.0 From c934d9621903b84b899b87e703d1ab23d6cd8f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Wed, 27 Nov 2024 10:17:59 +0000 Subject: [PATCH 260/288] removing docker from tests --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f91442df..e1b6802b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -187,7 +187,7 @@ pipeline { uid = sh(returnStdout: true, script: 'id -u').trim() gid = sh(returnStdout: true, script: 'id -g').trim() withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "docker run --rm -u $uid:$gid --privileged -v /dev/bus/usb:/dev/bus/usb -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_voice_tester:develop bash -l test/device_firmware_update/check_dfu.sh " + adapterIDs[0] + sh "test/device_firmware_update/check_dfu.sh " + adapterIDs[0] } sh "pytest test/device_firmware_update/test_dfu.py --readback_image test/device_firmware_update/test_output/readback_upgrade.bin --upgrade_image test/device_firmware_update/test_output/test_ffva_dfu_upgrade.bin" } From d6984bd1ccf6b538d9d467a0199aa846ed294d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Wed, 27 Nov 2024 11:23:21 +0000 Subject: [PATCH 261/288] updating changelog and jenkins --- CHANGELOG.rst | 10 ++++++++-- Jenkinsfile | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6abc3653..29b93324 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,13 @@ XCORE-VOICE change log 2.3.0 ----- + * CHANGED: Updated submodule fwk_io to version v3.6.0 from v3.3.0. + * CHANGED: Updated submodule fwk_core to version v1.1.0 from v1.0.2. + * CHANGED: Updated submodule fwk_voice to version v0.8.0 from v0.7.0. + * CHANGED: Updated Xmosdoc to version v6.2.0. + * CHANGED: Updated XTC Tools to 15.3.0. + * REMOVED: Deleted inferencing submodule. + * ADDED: xmos-ai-tools v1.3.1 Python requirement. * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). * CHANGED: Moved files in folders device_memory, gpio_ctrl, intent_engine and @@ -13,7 +20,7 @@ XCORE-VOICE change log * ADDED: FFD example with I2S audio input to Cyberon speech recognition engine and model. * REMOVED: flash settings in .xn files, as they are not required by XMOS - Tools 15.2.x. + Tools 15.3.0. * ADDED: Support for reading registers over I2C slave in FFD examples. * ADDED: Note in ASRC demo documentation about large latency in ASRC processing. References to alternative application notes have been provided. @@ -102,4 +109,3 @@ XCORE-VOICE change log ------ * ADDED: FFD demo using OLED display - diff --git a/Jenkinsfile b/Jenkinsfile index e1b6802b..cbc73ec7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -20,7 +20,7 @@ pipeline { ) string( name: 'XMOSDOC_VERSION', - defaultValue: 'v6.1.2', + defaultValue: 'v6.2.0', description: 'The xmosdoc version' ) booleanParam(name: 'NIGHTLY_TEST_ONLY', From e526e45c9802c3ff47e5045bd07835fa53f8820e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 11:50:12 +0000 Subject: [PATCH 262/288] signle ffva file --- Jenkinsfile | 6 +++++- test/pipeline/ffva_quick.txt | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index cbc73ec7..f0c211d9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -49,7 +49,7 @@ pipeline { expression { !env.GH_LABEL_DOC_ONLY.toBoolean() } } agent { - label 'xcore.ai && vrd' + label 'sw-hw-xcai-vrd0' //TODO put back } stages { stage('Checkout') { @@ -215,6 +215,7 @@ pipeline { } } stage('Run FFVA Pipeline test') { + input {message "Should we continue?"} when { expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } @@ -223,6 +224,9 @@ pipeline { withVenv { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> + sh "xtagctl reset " + adapterIDs[0] + sh "pip install -e modules/xscope_fileio/xscope_fileio/" + sh "cp modules/xscope_fileio/xscope_fileio/host/xscope_host_endpoint dist_host/" sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffva_test_output/results.csv" diff --git a/test/pipeline/ffva_quick.txt b/test/pipeline/ffva_quick.txt index 7a6b866f..0de8bbb9 100644 --- a/test/pipeline/ffva_quick.txt +++ b/test/pipeline/ffva_quick.txt @@ -1,5 +1,2 @@ # filename, AEC, min instances, total instances InHouse_XVF3510v080_v1.2_20190423_Loc1_Clean_XMOS_DUT1_80dB_Take1 Y 24 25 -InHouse_XVF3510v080_v1.2_20190423_Loc1_Noise1_65dB_XMOS_DUT1_80dB_Take1 Y 22 25 -InHouse_XVF3510v080_v1.2_20190423_Loc1_Noise2_70dB__Take1 Y 21 25 -InHouse_XVF3510v080_v1.2_20190423_Loc2_Noise1_65dB__Take1 Y 24 25 From 34c37d8ab279e4a9418aeeec29b6a22ca24716b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 13:15:59 +0000 Subject: [PATCH 263/288] Remove input prompt from Jenkinsfile and add new test cases to ffva_quick.txt; reset xtags in check_pipeline.sh --- Jenkinsfile | 1 - test/pipeline/check_pipeline.sh | 5 +++++ test/pipeline/ffva_quick.txt | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f0c211d9..94c0f080 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -215,7 +215,6 @@ pipeline { } } stage('Run FFVA Pipeline test') { - input {message "Should we continue?"} when { expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} } diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index 28c4fafa..e14821df 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -149,6 +149,11 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm "${OUTPUT_DIR}/${AMAZON_WAV}" rm ${XSCOPE_FILEIO_INPUT_WAV} rm ${XSCOPE_FILEIO_OUTPUT_WAV} + + # Temp solution (reset all xtags between tests) + # This will be solved in: bugzilla id 18895 + xtagctl reset_all /.*/ + done # clean up diff --git a/test/pipeline/ffva_quick.txt b/test/pipeline/ffva_quick.txt index 0de8bbb9..7a6b866f 100644 --- a/test/pipeline/ffva_quick.txt +++ b/test/pipeline/ffva_quick.txt @@ -1,2 +1,5 @@ # filename, AEC, min instances, total instances InHouse_XVF3510v080_v1.2_20190423_Loc1_Clean_XMOS_DUT1_80dB_Take1 Y 24 25 +InHouse_XVF3510v080_v1.2_20190423_Loc1_Noise1_65dB_XMOS_DUT1_80dB_Take1 Y 22 25 +InHouse_XVF3510v080_v1.2_20190423_Loc1_Noise2_70dB__Take1 Y 21 25 +InHouse_XVF3510v080_v1.2_20190423_Loc2_Noise1_65dB__Take1 Y 24 25 From 144e728b6f90414d682507e84beaee0963273d66 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Thu, 28 Nov 2024 14:16:06 +0000 Subject: [PATCH 264/288] updating docs to include installing a python package --- doc/programming_guide/asr/deploying/linux_macos.rst | 3 ++- doc/programming_guide/asr/deploying/native_windows.rst | 3 ++- doc/programming_guide/asrc/overview.rst | 6 ++++-- doc/programming_guide/ffd/deploying/linux_macos.rst | 3 ++- doc/programming_guide/ffd/deploying/native_windows.rst | 3 ++- doc/programming_guide/ffva/deploying/linux_macos.rst | 9 ++++++--- doc/programming_guide/ffva/deploying/native_windows.rst | 9 ++++++--- .../low_power_ffd/deploying/linux_macos.rst | 3 ++- .../low_power_ffd/deploying/native_windows.rst | 3 ++- 9 files changed, 28 insertions(+), 14 deletions(-) diff --git a/doc/programming_guide/asr/deploying/linux_macos.rst b/doc/programming_guide/asr/deploying/linux_macos.rst index ff170a53..99d7d0bb 100644 --- a/doc/programming_guide/asr/deploying/linux_macos.rst +++ b/doc/programming_guide/asr/deploying/linux_macos.rst @@ -32,10 +32,11 @@ Before running the host application, you may need to add the location of ``xscop Building the Firmware ===================== -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: .. code-block:: console + pip install -r requirements.txt cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build make example_asr diff --git a/doc/programming_guide/asr/deploying/native_windows.rst b/doc/programming_guide/asr/deploying/native_windows.rst index df20a43d..f49303c8 100644 --- a/doc/programming_guide/asr/deploying/native_windows.rst +++ b/doc/programming_guide/asr/deploying/native_windows.rst @@ -52,10 +52,11 @@ Before running the host application, you may need to add the location of ``xscop Building the Firmware ===================== -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: .. code-block:: console + pip install -r requirements.txt cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build ninja example_asr diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 86ec8f44..083573ac 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -106,11 +106,12 @@ something like this: Linux or Mac ------------ -To build for the first time, run ``cmake`` to create the +To build for the first time, activate your python environment, run ``cmake`` to create the make files: :: + $ pip install -r requirements.txt $ mkdir build $ cd build $ cmake --toolchain ../xmos_cmake_toolchain/xs3a.cmake .. @@ -142,11 +143,12 @@ To install Ninja, follow these steps: set the path in the current command line session using something like ``set PATH=%PATH%;C:\Users\xmos\utils\ninja`` -To build for the first time, run ``cmake`` to create the +To build for the first time, activate your python environment, run ``cmake`` to create the make files: :: + $ pip install -r requirements.txt $ md build $ cd build $ cmake -G "Ninja" --toolchain ..\xmos_cmake_toolchain\xs3a.cmake .. diff --git a/doc/programming_guide/ffd/deploying/linux_macos.rst b/doc/programming_guide/ffd/deploying/linux_macos.rst index 22afbe7f..43bdee5c 100644 --- a/doc/programming_guide/ffd/deploying/linux_macos.rst +++ b/doc/programming_guide/ffd/deploying/linux_macos.rst @@ -36,10 +36,11 @@ The host applications will be installed at ``/opt/xmos/bin``, and may be moved i Building the Firmware ===================== -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: .. code-block:: console + pip install -r requirements.txt cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build make example_ffd_ diff --git a/doc/programming_guide/ffd/deploying/native_windows.rst b/doc/programming_guide/ffd/deploying/native_windows.rst index fe9f401d..ce5cdc38 100644 --- a/doc/programming_guide/ffd/deploying/native_windows.rst +++ b/doc/programming_guide/ffd/deploying/native_windows.rst @@ -57,10 +57,11 @@ The host applications will be installed at ``%USERPROFILE%\.xmos\bin``, and may Building the Firmware ===================== -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: .. code-block:: console + pip install -r requirements.txt cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build ninja example_ffd_ diff --git a/doc/programming_guide/ffva/deploying/linux_macos.rst b/doc/programming_guide/ffva/deploying/linux_macos.rst index 20ed6be8..126aee9a 100644 --- a/doc/programming_guide/ffva/deploying/linux_macos.rst +++ b/doc/programming_guide/ffva/deploying/linux_macos.rst @@ -27,26 +27,29 @@ The host applications will be installed at ``/opt/xmos/bin``, and may be moved i Building the Firmware ===================== -Run the following commands in the root folder to build the |I2S| firmware: +After having your python environment activated, run the following commands in the root folder to build the |I2S| firmware: .. code-block:: console + pip install -r requirements.txt cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build make example_ffva_int_fixed_delay -Run the following commands in the root folder to build the |I2S| firmware with the Cyberon ASR engine: +After having your python environment activated, run the following commands in the root folder to build the |I2S| firmware with the Cyberon ASR engine: .. code-block:: console + pip install -r requirements.txt cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build make example_ffva_int_cyberon_fixed_delay -Run the following commands in the root folder to build the USB firmware: +After having your python environment activated, run the following commands in the root folder to build the USB firmware: .. code-block:: console + pip install -r requirements.txt cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build make example_ffva_ua_adec_altarch diff --git a/doc/programming_guide/ffva/deploying/native_windows.rst b/doc/programming_guide/ffva/deploying/native_windows.rst index 58712997..6de1e4d1 100644 --- a/doc/programming_guide/ffva/deploying/native_windows.rst +++ b/doc/programming_guide/ffva/deploying/native_windows.rst @@ -47,26 +47,29 @@ The host applications will be installed at ``%USERPROFILE%\.xmos\bin``, and may Building the Firmware ===================== -Run the following commands in the root folder to build the |I2S| firmware: +After having your python environment activated, run the following commands in the root folder to build the |I2S| firmware: .. code-block:: console + pip install -r requirements.txt cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build ninja example_ffva_int_fixed_delay -Run the following commands in the root folder to build the |I2S| firmware with the Cyberon ASR engine: +After having your python environment activated, run the following commands in the root folder to build the |I2S| firmware with the Cyberon ASR engine: .. code-block:: console + pip install -r requirements.txt cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build ninja example_ffva_int_cyberon_fixed_delay -Run the following commands in the root folder to build the USB firmware: +After having your python environment activated, run the following commands in the root folder to build the USB firmware: .. code-block:: console + pip install -r requirements.txt cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build ninja example_ffva_ua_adec_altarch diff --git a/doc/programming_guide/low_power_ffd/deploying/linux_macos.rst b/doc/programming_guide/low_power_ffd/deploying/linux_macos.rst index fda2f158..230e402e 100644 --- a/doc/programming_guide/low_power_ffd/deploying/linux_macos.rst +++ b/doc/programming_guide/low_power_ffd/deploying/linux_macos.rst @@ -29,10 +29,11 @@ wish to add this directory to your ``PATH`` variable. Building the Firmware ===================== -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: .. code-block:: console + pip install -r requirements.txt cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build make example_low_power_ffd_sensory diff --git a/doc/programming_guide/low_power_ffd/deploying/native_windows.rst b/doc/programming_guide/low_power_ffd/deploying/native_windows.rst index 7fbb8dcd..5e022132 100644 --- a/doc/programming_guide/low_power_ffd/deploying/native_windows.rst +++ b/doc/programming_guide/low_power_ffd/deploying/native_windows.rst @@ -53,10 +53,11 @@ You may wish to add this directory to your ``PATH`` variable. Building the Firmware ===================== -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: .. code-block:: console + pip install -r requirements.txt cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake cd build ninja example_low_power_ffd_sensory From 12a4b9a40f2c11917b42fb6898f6af7ec45c8b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 14:46:11 +0000 Subject: [PATCH 265/288] Refactor Jenkinsfile to remove FORCE_FULL_RUN parameter and update test conditions; replace _Exit calls with xassert for error handling in C files --- Jenkinsfile | 31 +++++++------------ .../speech_recognition/src/process_file.c | 5 ++- test/asr/src/xscope_fileio_task.c | 11 +++---- test/ffd_gpio/src/main.c | 3 -- test/pipeline/check_pipeline.sh | 4 --- test/pipeline/src/xscope_fileio_task.c | 11 +++---- 6 files changed, 23 insertions(+), 42 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 94c0f080..eb585fb2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,10 +27,6 @@ pipeline { defaultValue: false, description: 'Tests that only run during nightly builds.' ) - booleanParam(name: 'FORCE_FULL_RUN', - defaultValue: true, - description: 'Force to run all tests, including nigthly.' - ) } // parameters environment { REPO = 'sln_voice' @@ -49,7 +45,7 @@ pipeline { expression { !env.GH_LABEL_DOC_ONLY.toBoolean() } } agent { - label 'sw-hw-xcai-vrd0' //TODO put back + label 'xcore.ai && vrd' } stages { stage('Checkout') { @@ -105,7 +101,7 @@ pipeline { } stage('Install test requirements') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { // Install dependencies @@ -117,7 +113,7 @@ pipeline { } stage('Cleanup xtagctl') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { // Cleanup any xtagctl cruft from previous failed runs @@ -131,7 +127,7 @@ pipeline { } stage('Run Sample Rate Conversion test') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { withTools(params.TOOLS_VERSION) { @@ -148,7 +144,7 @@ pipeline { } stage('Run GPIO test') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { withTools(params.TOOLS_VERSION) { @@ -163,7 +159,7 @@ pipeline { } stage('Run FFD Low Power Audio Buffer test') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { withTools(params.TOOLS_VERSION) { @@ -178,7 +174,7 @@ pipeline { } stage('Run Device Firmware Update test') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { withTools(params.TOOLS_VERSION) { @@ -197,7 +193,7 @@ pipeline { } stage('Checkout Amazon WWE') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { sh 'git clone git@github.com:xmos/amazon_wwe.git' @@ -205,7 +201,7 @@ pipeline { } stage('Setup test vectors') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { sh "cp -r /projects/hydra_audio/xcore-voice_xvf3510_no_processing_xmos_test_suite_subset $PIPELINE_TEST_VECTORS" @@ -216,16 +212,13 @@ pipeline { } stage('Run FFVA Pipeline test') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { withTools(params.TOOLS_VERSION) { withVenv { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "xtagctl reset " + adapterIDs[0] - sh "pip install -e modules/xscope_fileio/xscope_fileio/" - sh "cp modules/xscope_fileio/xscope_fileio/host/xscope_host_endpoint dist_host/" sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffva_test_output/results.csv" @@ -236,7 +229,7 @@ pipeline { } stage('Run FFD Pipeline test') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { withTools(params.TOOLS_VERSION) { @@ -253,7 +246,7 @@ pipeline { } stage('Run ASR test') { when { - expression { params.NIGHTLY_TEST_ONLY == true || params.FORCE_FULL_RUN == true} + expression { params.NIGHTLY_TEST_ONLY == true } } steps { withTools(params.TOOLS_VERSION) { diff --git a/examples/speech_recognition/src/process_file.c b/examples/speech_recognition/src/process_file.c index f939d23a..bff32980 100644 --- a/examples/speech_recognition/src/process_file.c +++ b/examples/speech_recognition/src/process_file.c @@ -6,6 +6,7 @@ #include #include +#include #include "app_conf.h" #include "asr.h" @@ -64,8 +65,7 @@ void process_file() { // Validate input wav file if(get_wav_header_details(&file, &header_struct, &header_size) != 0){ - printf("Error: error in get_wav_header_details()\n"); - _Exit(1); + xassert(0 && "Error: error in get_wav_header_details()\n"); } assert(header_struct.bit_depth == 16); assert(header_struct.num_channels == MAX_CHANNELS); @@ -116,5 +116,4 @@ void process_file() { asr_release(asr_port); xscope_close_all_files(); - _Exit(0); } diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index e25e411d..a87f0185 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -10,6 +10,7 @@ #include "task.h" #include "semphr.h" #include +#include #include "soc_xscope_host.h" #include "app_conf.h" @@ -114,8 +115,7 @@ void xscope_fileio_task(void *arg) { infile = xscope_open_file(appconfINPUT_FILENAME, "rb"); // Validate input wav file if(get_wav_header_details(&infile, &input_header_struct, &input_header_size) != 0){ - rtos_printf("Error: error in get_wav_header_details()\n"); - _Exit(1); + xassert(0 && "Error: error in get_wav_header_details()\n"); } xscope_fseek(&infile, input_header_size, SEEK_SET); vTaskDelay(pdMS_TO_TICKS(1000)); @@ -127,12 +127,12 @@ void xscope_fileio_task(void *arg) { if(input_header_struct.bit_depth != appconfSAMPLE_BIT_DEPTH) { rtos_printf("Error: unsupported wav bit depth (%d) for %s file. Only 32 supported\n", input_header_struct.bit_depth, appconfINPUT_FILENAME); - _Exit(1); + xassert(0); } // Ensure input wav file contains correct number of channels if(input_header_struct.num_channels != appconfINPUT_CHANNELS){ rtos_printf("Error: wav num channels(%d) does not match (%u)\n", input_header_struct.num_channels, appconfINPUT_CHANNELS); - _Exit(1); + xassert(0); } // Calculate number of frames in the wav file @@ -238,9 +238,6 @@ void xscope_fileio_task(void *arg) { xscope_close_all_files(); } rtos_osal_critical_exit(state); - - /* Close the app */ - _Exit(0); } void xscope_fileio_tasks_create(unsigned priority, void* app_data) { diff --git a/test/ffd_gpio/src/main.c b/test/ffd_gpio/src/main.c index 97c46721..35d2c066 100644 --- a/test/ffd_gpio/src/main.c +++ b/test/ffd_gpio/src/main.c @@ -65,7 +65,6 @@ void vWD(void *arg) vTaskDelay(10); test_printf("Host Timeout"); test_printf("FAIL"); - _Exit(0); } #if ON_TILE(1) @@ -152,8 +151,6 @@ void vApplicationDaemonTaskStartup(void *arg) sync(other_tile_c); test_printf("PASS GPIO"); - - _Exit(0); #endif chanend_free(other_tile_c); diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index e14821df..53435b43 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -150,10 +150,6 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${XSCOPE_FILEIO_INPUT_WAV} rm ${XSCOPE_FILEIO_OUTPUT_WAV} - # Temp solution (reset all xtags between tests) - # This will be solved in: bugzilla id 18895 - xtagctl reset_all /.*/ - done # clean up diff --git a/test/pipeline/src/xscope_fileio_task.c b/test/pipeline/src/xscope_fileio_task.c index c32829e5..78282bc7 100644 --- a/test/pipeline/src/xscope_fileio_task.c +++ b/test/pipeline/src/xscope_fileio_task.c @@ -6,6 +6,8 @@ #include #include +#include + #include "FreeRTOS.h" #include "task.h" #include "semphr.h" @@ -92,7 +94,7 @@ void xscope_fileio_task(void *arg) { // Validate input wav file if(get_wav_header_details(&audio_infile, &input_header_struct, &input_header_size) != 0){ rtos_printf("Error: error in get_wav_header_details()\n"); - _Exit(1); + xassert(0); } xscope_fseek(&audio_infile, input_header_size, SEEK_SET); } @@ -102,12 +104,12 @@ void xscope_fileio_task(void *arg) { if(input_header_struct.bit_depth != appconfSAMPLE_BIT_DEPTH) { rtos_printf("Error: unsupported wav bit depth (%d) for %s file. Only 32 supported\n", input_header_struct.bit_depth, appconfINPUT_FILENAME); - _Exit(1); + xassert(0); } // Ensure input wav file contains correct number of channels if(input_header_struct.num_channels != appconfAUDIO_PIPELINE_INPUT_CHANNELS){ rtos_printf("Error: wav num channels(%d) does not match (%u)\n", input_header_struct.num_channels, appconfAUDIO_PIPELINE_INPUT_CHANNELS); - _Exit(1); + xassert(0); } // Calculate number of frames in the wav file @@ -200,9 +202,6 @@ void xscope_fileio_task(void *arg) { xscope_close_all_files(); } rtos_osal_critical_exit(state); - - /* Close the app */ - _Exit(0); } void xscope_fileio_tasks_create(unsigned priority, void* app_data) { From dd30aba598c2bc48fdd8758ef6e2e1b536db3ef6 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Thu, 28 Nov 2024 15:21:21 +0000 Subject: [PATCH 266/288] trying to update xua, src, sw_pll. xscope_fileio --- modules/sample_rate_conversion/lib_src | 2 +- modules/sw_pll | 2 +- modules/xscope_fileio/xscope_fileio | 2 +- modules/xua | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/sample_rate_conversion/lib_src b/modules/sample_rate_conversion/lib_src index e3bb0651..3b25970d 160000 --- a/modules/sample_rate_conversion/lib_src +++ b/modules/sample_rate_conversion/lib_src @@ -1 +1 @@ -Subproject commit e3bb065134b52d0b16bb123a3d40c0761d30f2a5 +Subproject commit 3b25970d8842a71127473cc3c786e25973d271fd diff --git a/modules/sw_pll b/modules/sw_pll index 6c5db866..0a24b96f 160000 --- a/modules/sw_pll +++ b/modules/sw_pll @@ -1 +1 @@ -Subproject commit 6c5db866a13ff20d5d23845e542f39951bca5592 +Subproject commit 0a24b96f2a380d0ecb6c310de2c5e845dffbd894 diff --git a/modules/xscope_fileio/xscope_fileio b/modules/xscope_fileio/xscope_fileio index 52cff082..10c696a4 160000 --- a/modules/xscope_fileio/xscope_fileio +++ b/modules/xscope_fileio/xscope_fileio @@ -1 +1 @@ -Subproject commit 52cff0826b2773beec49044a0729bb000c011379 +Subproject commit 10c696a47c3168704a749dd9d79f192ae42965f9 diff --git a/modules/xua b/modules/xua index a485ffe4..3c245641 160000 --- a/modules/xua +++ b/modules/xua @@ -1 +1 @@ -Subproject commit a485ffe41abcaf0f02ed289a9c759751dd75b29a +Subproject commit 3c245641fdb6280f8021bb13f7f9af00b562f9ac From 159e288e301c759d9817b8eaef952e79ae084747 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Thu, 28 Nov 2024 15:40:19 +0000 Subject: [PATCH 267/288] sw_pll api rename --- examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c | 2 +- examples/ffd/src/main.c | 2 +- examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c | 2 +- examples/ffva/src/main.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 9b09714d..7d9dfdfa 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -143,7 +143,7 @@ static void platform_sw_pll_init(void) // Allow p_bclk_count to count bclks port_set_clock(p_bclk_count, ck_bclk); - sw_pll_init(&sw_pll, + sw_pll_lut_init(&sw_pll, SW_PLL_15Q16(0.0), SW_PLL_15Q16(1.0), PLL_CONTROL_LOOP_COUNT_INT, diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index 43144628..028bb7c0 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -81,7 +81,7 @@ void sw_pll_control(void *args) uint16_t mclk_pt = port_get_trigger_time(i2s_callback_args->p_mclk_count); // Immediately sample mclk_count uint16_t bclk_pt = port_get_trigger_time(i2s_callback_args->p_bclk_count); // Now grab bclk_count (which won't have changed) - sw_pll_do_control(i2s_callback_args->sw_pll, mclk_pt, bclk_pt); + sw_pll_lut_do_control(i2s_callback_args->sw_pll, mclk_pt, bclk_pt); } } #endif diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index 0ab43438..e6ba2efe 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -191,7 +191,7 @@ static void platform_sw_pll_init(void) // Allow p_bclk_count to count bclks port_set_clock(p_bclk_count, ck_bclk); - sw_pll_init(&sw_pll, + sw_pll_lut_init(&sw_pll, SW_PLL_15Q16(0.0), SW_PLL_15Q16(1.0), PLL_CONTROL_LOOP_COUNT_INT, diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index faa80a72..eb12d405 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -75,7 +75,7 @@ void i2s_slave_intertile(void *args) { uint16_t mclk_pt = port_get_trigger_time(i2s_callback_args->p_mclk_count); // Immediately sample mclk_count uint16_t bclk_pt = port_get_trigger_time(i2s_callback_args->p_bclk_count); // Now grab bclk_count (which won't have changed) - sw_pll_do_control(i2s_callback_args->sw_pll, mclk_pt, bclk_pt); + sw_pll_lut_do_control(i2s_callback_args->sw_pll, mclk_pt, bclk_pt); #endif } From c8a17952df12878be2302d13416230e39e94e518 Mon Sep 17 00:00:00 2001 From: xmos-jenkins Date: Thu, 28 Nov 2024 15:53:17 +0000 Subject: [PATCH 268/288] replacing exit and adding xgdb command --- examples/speech_recognition/src/process_file.c | 3 ++- test/asr/src/xscope_fileio_task.c | 1 + test/ffd_gpio/src/main.c | 2 ++ test/pipeline/check_pipeline.sh | 4 ++++ test/pipeline/src/xscope_fileio_task.c | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/speech_recognition/src/process_file.c b/examples/speech_recognition/src/process_file.c index bff32980..028374d4 100644 --- a/examples/speech_recognition/src/process_file.c +++ b/examples/speech_recognition/src/process_file.c @@ -65,7 +65,8 @@ void process_file() { // Validate input wav file if(get_wav_header_details(&file, &header_struct, &header_size) != 0){ - xassert(0 && "Error: error in get_wav_header_details()\n"); + printf("Error: error in get_wav_header_details()\n"); + xassert(0); } assert(header_struct.bit_depth == 16); assert(header_struct.num_channels == MAX_CHANNELS); diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index a87f0185..4e0b1477 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -238,6 +238,7 @@ void xscope_fileio_task(void *arg) { xscope_close_all_files(); } rtos_osal_critical_exit(state); + exit(0); } void xscope_fileio_tasks_create(unsigned priority, void* app_data) { diff --git a/test/ffd_gpio/src/main.c b/test/ffd_gpio/src/main.c index 35d2c066..0802f0f3 100644 --- a/test/ffd_gpio/src/main.c +++ b/test/ffd_gpio/src/main.c @@ -65,6 +65,7 @@ void vWD(void *arg) vTaskDelay(10); test_printf("Host Timeout"); test_printf("FAIL"); + exit(0); } #if ON_TILE(1) @@ -151,6 +152,7 @@ void vApplicationDaemonTaskStartup(void *arg) sync(other_tile_c); test_printf("PASS GPIO"); + exit(0); #endif chanend_free(other_tile_c); diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index 53435b43..ad106de5 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -150,6 +150,10 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${XSCOPE_FILEIO_INPUT_WAV} rm ${XSCOPE_FILEIO_OUTPUT_WAV} + # Temp solution (reset all xtags between tests) + # This will be solved in: bugzilla id 18895 + xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + done # clean up diff --git a/test/pipeline/src/xscope_fileio_task.c b/test/pipeline/src/xscope_fileio_task.c index 78282bc7..cd7990b7 100644 --- a/test/pipeline/src/xscope_fileio_task.c +++ b/test/pipeline/src/xscope_fileio_task.c @@ -202,6 +202,7 @@ void xscope_fileio_task(void *arg) { xscope_close_all_files(); } rtos_osal_critical_exit(state); + exit(0); } void xscope_fileio_tasks_create(unsigned priority, void* app_data) { From 932da28438fb49c19443bcd92dba1e015cd300cb Mon Sep 17 00:00:00 2001 From: xmos-jenkins Date: Thu, 28 Nov 2024 17:05:02 +0000 Subject: [PATCH 269/288] adding xdbg path --- test/asr/check_asr.sh | 5 +++++ test/pipeline/check_pipeline.sh | 2 ++ 2 files changed, 7 insertions(+) diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index 805c3411..21d15d46 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -191,6 +191,11 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${TEMP_XSCOPE_FILEIO_INPUT_WAV} rm ${TEMP_XSCOPE_FILEIO_OUTPUT_WAV} rm ${TEMP_XSCOPE_FILEIO_OUTPUT_LOG} + + # Temp solution (reset all xtags between tests) + # This will be solved in: bugzilla id 18895 + xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + done # print results diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index ad106de5..ae6d2623 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -153,6 +153,8 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # Temp solution (reset all xtags between tests) # This will be solved in: bugzilla id 18895 xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 1 done From f34f34d56c74a5437f401b62791e4a0eb94d85d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 18:38:38 +0000 Subject: [PATCH 270/288] adding xgb patch in asr test --- Jenkinsfile | 2 +- test/asr/check_asr.sh | 2 ++ test/asr/src/xscope_fileio_task.c | 2 +- test/pipeline/check_pipeline.sh | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index eb585fb2..e9128742 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,7 +24,7 @@ pipeline { description: 'The xmosdoc version' ) booleanParam(name: 'NIGHTLY_TEST_ONLY', - defaultValue: false, + defaultValue: true, //TODO replace with false description: 'Tests that only run during nightly builds.' ) } // parameters diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index 21d15d46..bb949af0 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -194,7 +194,9 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # Temp solution (reset all xtags between tests) # This will be solved in: bugzilla id 18895 + # Only needed for adapter id=0 according to jenkinsfile xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 1 done diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index 4e0b1477..e1ed75c3 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -238,7 +238,7 @@ void xscope_fileio_task(void *arg) { xscope_close_all_files(); } rtos_osal_critical_exit(state); - exit(0); + exit(0); // exit syscall can cause issues with XTC 15.3.0 if called from both tiles } void xscope_fileio_tasks_create(unsigned priority, void* app_data) { diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index ae6d2623..c417b06f 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -152,8 +152,8 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # Temp solution (reset all xtags between tests) # This will be solved in: bugzilla id 18895 + # Only needed for adapter id=0 according to jenkinsfile xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" sleep 1 done From adbdf59cdf00e3a6ab8dfe772693846baccef9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 20:06:51 +0000 Subject: [PATCH 271/288] cleanup both ids --- Jenkinsfile | 7 ++++++- test/asr/check_asr.sh | 3 ++- test/pipeline/check_pipeline.sh | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e9128742..c72bd33f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -219,7 +219,11 @@ pipeline { withVenv { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] + sh "xtagctl reset_all /.*/ " + sh "test/pipeline/check_pipeline.sh \ + $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe \ + $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt \ + test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffva_test_output/results.csv" } @@ -236,6 +240,7 @@ pipeline { withVenv { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> + sh "xtagctl reset_all /.*/ " sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffd.xe $PIPELINE_TEST_VECTORS test/pipeline/ffd_quick.txt test/pipeline/ffd_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffd_test_output/results.csv" diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index bb949af0..60a63436 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -194,9 +194,10 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # Temp solution (reset all xtags between tests) # This will be solved in: bugzilla id 18895 - # Only needed for adapter id=0 according to jenkinsfile xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" sleep 1 + xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 1 done diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index c417b06f..a8454dc3 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -155,6 +155,8 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # Only needed for adapter id=0 according to jenkinsfile xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" sleep 1 + xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 1 done From 1382ecb052e020bbe90af5dc5bc0107ee8010b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 20:07:18 +0000 Subject: [PATCH 272/288] cleaning id 1 --- test/pipeline/check_pipeline.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index a8454dc3..20e0d59a 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -152,7 +152,6 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # Temp solution (reset all xtags between tests) # This will be solved in: bugzilla id 18895 - # Only needed for adapter id=0 according to jenkinsfile xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" sleep 1 xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" From f78a65c71c26af998ff18c44e352a1ec4adbba61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 20:09:57 +0000 Subject: [PATCH 273/288] single line ffva --- Jenkinsfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c72bd33f..f22c5f70 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -220,10 +220,7 @@ pipeline { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "xtagctl reset_all /.*/ " - sh "test/pipeline/check_pipeline.sh \ - $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe \ - $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt \ - test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] + sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffva_test_output/results.csv" } From 34a9dcd0332a8ce2dcac38021ca6b0c751b068f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 20:41:13 +0000 Subject: [PATCH 274/288] alternating adapter id --- Jenkinsfile | 6 ++---- test/asr/check_asr.sh | 4 ++-- test/pipeline/check_pipeline.sh | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f22c5f70..7466ba98 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -219,7 +219,6 @@ pipeline { withVenv { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "xtagctl reset_all /.*/ " sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffva_test_output/results.csv" @@ -237,8 +236,7 @@ pipeline { withVenv { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "xtagctl reset_all /.*/ " - sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffd.xe $PIPELINE_TEST_VECTORS test/pipeline/ffd_quick.txt test/pipeline/ffd_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] + sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffd.xe $PIPELINE_TEST_VECTORS test/pipeline/ffd_quick.txt test/pipeline/ffd_test_output $WORKSPACE/amazon_wwe " + adapterIDs[1] } sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffd_test_output/results.csv" } @@ -256,7 +254,7 @@ pipeline { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "test/asr/check_asr.sh Sensory $ASR_TEST_VECTORS test/asr/ffd_quick_sensory.txt test/asr/sensory_output " + adapterIDs[0] - sh "test/asr/check_asr.sh Cyberon $ASR_TEST_VECTORS test/asr/ffd_quick_cyberon.txt test/asr/cyberon_output " + adapterIDs[0] + sh "test/asr/check_asr.sh Cyberon $ASR_TEST_VECTORS test/asr/ffd_quick_cyberon.txt test/asr/cyberon_output " + adapterIDs[1] } sh "pytest test/asr/test_asr.py --log test/asr/sensory_output/results.csv" sh "pytest test/asr/test_asr.py --log test/asr/cyberon_output/results.csv" diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index 60a63436..1c96dad7 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -195,9 +195,9 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # Temp solution (reset all xtags between tests) # This will be solved in: bugzilla id 18895 xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 1 + sleep 5 xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 1 + sleep 5 done diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index 20e0d59a..abd97428 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -153,9 +153,9 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # Temp solution (reset all xtags between tests) # This will be solved in: bugzilla id 18895 xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 1 + sleep 5 xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 1 + sleep 5 done From 3c86a00d8fffa8aaa6c45a98a61e18bf59921db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Thu, 28 Nov 2024 23:37:40 +0000 Subject: [PATCH 275/288] xgdb twice --- Jenkinsfile | 12 +++++++----- test/asr/check_asr.sh | 12 ++++++++++++ test/pipeline/check_pipeline.sh | 14 +++++++------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7466ba98..15346358 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,7 +24,7 @@ pipeline { description: 'The xmosdoc version' ) booleanParam(name: 'NIGHTLY_TEST_ONLY', - defaultValue: true, //TODO replace with false + defaultValue: false, description: 'Tests that only run during nightly builds.' ) } // parameters @@ -45,7 +45,7 @@ pipeline { expression { !env.GH_LABEL_DOC_ONLY.toBoolean() } } agent { - label 'xcore.ai && vrd' + label 'sw-hw-xcai-vrd1' } stages { stage('Checkout') { @@ -218,6 +218,7 @@ pipeline { withTools(params.TOOLS_VERSION) { withVenv { script { + sh "xtagctl reset NEAPPG2F && xtagctl reset UY899HH6 && sleep 20" withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } @@ -234,9 +235,10 @@ pipeline { steps { withTools(params.TOOLS_VERSION) { withVenv { + sh "xtagctl reset NEAPPG2F && xtagctl reset UY899HH6 && sleep 20" script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffd.xe $PIPELINE_TEST_VECTORS test/pipeline/ffd_quick.txt test/pipeline/ffd_test_output $WORKSPACE/amazon_wwe " + adapterIDs[1] + sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffd.xe $PIPELINE_TEST_VECTORS test/pipeline/ffd_quick.txt test/pipeline/ffd_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffd_test_output/results.csv" } @@ -252,13 +254,13 @@ pipeline { withTools(params.TOOLS_VERSION) { withVenv { script { + sh "xtagctl reset NEAPPG2F && xtagctl reset UY899HH6 && sleep 20" withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "test/asr/check_asr.sh Sensory $ASR_TEST_VECTORS test/asr/ffd_quick_sensory.txt test/asr/sensory_output " + adapterIDs[0] - sh "test/asr/check_asr.sh Cyberon $ASR_TEST_VECTORS test/asr/ffd_quick_cyberon.txt test/asr/cyberon_output " + adapterIDs[1] + sh "test/asr/check_asr.sh Cyberon $ASR_TEST_VECTORS test/asr/ffd_quick_cyberon.txt test/asr/cyberon_output " + adapterIDs[0] } sh "pytest test/asr/test_asr.py --log test/asr/sensory_output/results.csv" sh "pytest test/asr/test_asr.py --log test/asr/cyberon_output/results.csv" - } } } diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index 1c96dad7..769390bf 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -128,6 +128,12 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${TEMP_WAV} # call xrun (in background) + # Temp solution (reset all xtags between tests) + # This will be solved in: bugzilla id 18895 + xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 + xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 (xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${PIPELINE_FIRMWARE}) & # wait for app to load @@ -157,6 +163,12 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do cp ${PROCESSED_WAV} ${TEMP_XSCOPE_FILEIO_INPUT_WAV} # call xrun (in background) + # Temp solution (reset all xtags between tests) + # This will be solved in: bugzilla id 18895 + xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 + xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 (xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${ASR_FIRMWARE}) & # wait for app to load diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index abd97428..107b9bca 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -111,6 +111,13 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # remix and create input wav to the filename expected for xscope_fileio (input.wav) sox ${INPUT_WAV} --no-dither -r 16000 -b 32 ${XSCOPE_FILEIO_INPUT_WAV} ${REMIX_PATTERN} + # Temp solution (reset all xtags between tests) + # This will be solved in: bugzilla id 18895 + xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 + xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 + # call xrun (in background) xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${FIRMWARE} & @@ -150,13 +157,6 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${XSCOPE_FILEIO_INPUT_WAV} rm ${XSCOPE_FILEIO_OUTPUT_WAV} - # Temp solution (reset all xtags between tests) - # This will be solved in: bugzilla id 18895 - xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 - xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 - done # clean up From d7ca67a9c9a59a4b4cbf3358a6edccfd41d06b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Fri, 29 Nov 2024 09:16:37 +0000 Subject: [PATCH 276/288] refactor: streamline reset commands and improve readability in scripts --- Jenkinsfile | 6 ++-- .../speech_recognition/src/process_file.c | 1 + test/asr/check_asr.sh | 35 +++++++++---------- test/pipeline/check_pipeline.sh | 21 +++++++---- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 15346358..e9536c58 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -218,7 +218,7 @@ pipeline { withTools(params.TOOLS_VERSION) { withVenv { script { - sh "xtagctl reset NEAPPG2F && xtagctl reset UY899HH6 && sleep 20" + sh "xtagctl reset_all $VRD_TEST_RIG_TARGET" withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] } @@ -235,7 +235,7 @@ pipeline { steps { withTools(params.TOOLS_VERSION) { withVenv { - sh "xtagctl reset NEAPPG2F && xtagctl reset UY899HH6 && sleep 20" + sh "xtagctl reset_all $VRD_TEST_RIG_TARGET" script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffd.xe $PIPELINE_TEST_VECTORS test/pipeline/ffd_quick.txt test/pipeline/ffd_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] @@ -254,7 +254,7 @@ pipeline { withTools(params.TOOLS_VERSION) { withVenv { script { - sh "xtagctl reset NEAPPG2F && xtagctl reset UY899HH6 && sleep 20" + sh "xtagctl reset_all $VRD_TEST_RIG_TARGET" withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "test/asr/check_asr.sh Sensory $ASR_TEST_VECTORS test/asr/ffd_quick_sensory.txt test/asr/sensory_output " + adapterIDs[0] sh "test/asr/check_asr.sh Cyberon $ASR_TEST_VECTORS test/asr/ffd_quick_cyberon.txt test/asr/cyberon_output " + adapterIDs[0] diff --git a/examples/speech_recognition/src/process_file.c b/examples/speech_recognition/src/process_file.c index 028374d4..95f140f3 100644 --- a/examples/speech_recognition/src/process_file.c +++ b/examples/speech_recognition/src/process_file.c @@ -117,4 +117,5 @@ void process_file() { asr_release(asr_port); xscope_close_all_files(); + exit(0); // Note: exit syscall can cause issues with tools XTC 15.3.0 if called from both tiles } diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index 769390bf..d3720d97 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -90,6 +90,18 @@ rm -rf ${RESULTS} echo "Log file: ${RESULTS}" echo "Filename, Max_Allowable_WER, Computed_WER" >> ${RESULTS} +# Writes the XS3 TestMode register in the JTAG domain to reboot the device into JTAG-boot mode. +# Unsets the 'reboot' bit to release the chip from reset and waits for connection. +# Will be Fixed in XTC > 15.3.0 (Bugzilla ID 18895). +target_reset_reboot() { + local id=$1 + xgdb --batch \ + -ex "attach --id=${id}" \ + -ex "monitor sysreg write 0 8 8 0xA1006300" \ + -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 # Fixed delay +} + for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do read -ra FIELDS <<< ${INPUT_ARRAY[j]} FILE_NAME=${FIELDS[0]} @@ -128,12 +140,8 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${TEMP_WAV} # call xrun (in background) - # Temp solution (reset all xtags between tests) - # This will be solved in: bugzilla id 18895 - xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 - xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 + target_reset_reboot 0 + target_reset_reboot 1 (xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${PIPELINE_FIRMWARE}) & # wait for app to load @@ -163,12 +171,8 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do cp ${PROCESSED_WAV} ${TEMP_XSCOPE_FILEIO_INPUT_WAV} # call xrun (in background) - # Temp solution (reset all xtags between tests) - # This will be solved in: bugzilla id 18895 - xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 - xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 + target_reset_reboot 0 + target_reset_reboot 1 (xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${ASR_FIRMWARE}) & # wait for app to load @@ -204,13 +208,6 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do rm ${TEMP_XSCOPE_FILEIO_OUTPUT_WAV} rm ${TEMP_XSCOPE_FILEIO_OUTPUT_LOG} - # Temp solution (reset all xtags between tests) - # This will be solved in: bugzilla id 18895 - xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 - xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 - done # print results diff --git a/test/pipeline/check_pipeline.sh b/test/pipeline/check_pipeline.sh index 107b9bca..d6b86fde 100755 --- a/test/pipeline/check_pipeline.sh +++ b/test/pipeline/check_pipeline.sh @@ -77,6 +77,18 @@ echo "***********************************" echo "Log file: ${RESULTS}" echo "***********************************" +# Writes the XS3 TestMode register in the JTAG domain to reboot the device into JTAG-boot mode. +# Unsets the 'reboot' bit to release the chip from reset and waits for connection. +# Will be Fixed in XTC > 15.3.0 (Bugzilla ID 18895). +target_reset_reboot() { + local id=$1 + xgdb --batch \ + -ex "attach --id=${id}" \ + -ex "monitor sysreg write 0 8 8 0xA1006300" \ + -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 # Fixed delay +} + for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do read -ra FIELDS <<< ${INPUT_ARRAY[j]} FILE_NAME=${FIELDS[0]} @@ -110,15 +122,10 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # remix and create input wav to the filename expected for xscope_fileio (input.wav) sox ${INPUT_WAV} --no-dither -r 16000 -b 32 ${XSCOPE_FILEIO_INPUT_WAV} ${REMIX_PATTERN} - - # Temp solution (reset all xtags between tests) - # This will be solved in: bugzilla id 18895 - xgdb --batch -ex "attach --id=0" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 - xgdb --batch -ex "attach --id=1" -ex "monitor sysreg write 0 8 8 0xA1006300" -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 # call xrun (in background) + target_reset_reboot 0 + target_reset_reboot 1 xrun ${ADAPTER_ID} --xscope --xscope-port localhost:12345 ${FIRMWARE} & # wait for app to load From 7fc2bdac84b7b5fbb83fcb26902123e0a8035b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Fri, 29 Nov 2024 10:36:56 +0000 Subject: [PATCH 277/288] refactor: update buildDocs to archive only and add target_reset_reboot function --- Jenkinsfile | 2 +- test/asr/check_asr.sh | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e9536c58..ce0d5609 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -284,7 +284,7 @@ pipeline { checkout scm sh 'git submodule update --init --recursive --depth 1 --jobs \$(nproc)' warnError("Docs") { - buildDocs() + buildDocs(archiveZipOnly: true) } // warnError("Docs") } // steps post { diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index d3720d97..0903ae86 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -21,6 +21,18 @@ help() echo " h Print this Help." } +# Writes the XS3 TestMode register in the JTAG domain to reboot the device into JTAG-boot mode. +# Unsets the 'reboot' bit to release the chip from reset and waits for connection. +# Will be Fixed in XTC > 15.3.0 (Bugzilla ID 18895). +target_reset_reboot() { + local id=$1 + xgdb --batch \ + -ex "attach --id=${id}" \ + -ex "monitor sysreg write 0 8 8 0xA1006300" \ + -ex "monitor sysreg write 0 8 8 0x21006300" + sleep 5 # Fixed delay +} + # flag arguments while getopts h option do @@ -70,6 +82,8 @@ then fi # flash the data partition file +target_reset_reboot 0 +target_reset_reboot 1 xflash ${ADAPTER_ID} --quad-spi-clock 50MHz --factory ${ASR_FIRMWARE} --boot-partition-size 0x100000 --data ${DATA_PARTITION} # read input list @@ -90,18 +104,6 @@ rm -rf ${RESULTS} echo "Log file: ${RESULTS}" echo "Filename, Max_Allowable_WER, Computed_WER" >> ${RESULTS} -# Writes the XS3 TestMode register in the JTAG domain to reboot the device into JTAG-boot mode. -# Unsets the 'reboot' bit to release the chip from reset and waits for connection. -# Will be Fixed in XTC > 15.3.0 (Bugzilla ID 18895). -target_reset_reboot() { - local id=$1 - xgdb --batch \ - -ex "attach --id=${id}" \ - -ex "monitor sysreg write 0 8 8 0xA1006300" \ - -ex "monitor sysreg write 0 8 8 0x21006300" - sleep 5 # Fixed delay -} - for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do read -ra FIELDS <<< ${INPUT_ARRAY[j]} FILE_NAME=${FIELDS[0]} From aa8edcd9bc9e8cf1728154dcff2c2e3d0ff18455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Fri, 29 Nov 2024 10:40:21 +0000 Subject: [PATCH 278/288] refactor: remove unnecessary uid and gid retrieval from Jenkinsfile --- Jenkinsfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ce0d5609..945b4d5e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -180,8 +180,6 @@ pipeline { withTools(params.TOOLS_VERSION) { withVenv { script { - uid = sh(returnStdout: true, script: 'id -u').trim() - gid = sh(returnStdout: true, script: 'id -g').trim() withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "test/device_firmware_update/check_dfu.sh " + adapterIDs[0] } From 3e3e96265f459445c3faee4f07e9429aeb9956f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Fri, 29 Nov 2024 10:42:45 +0000 Subject: [PATCH 279/288] update jenkins label --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 945b4d5e..455915bf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -45,7 +45,7 @@ pipeline { expression { !env.GH_LABEL_DOC_ONLY.toBoolean() } } agent { - label 'sw-hw-xcai-vrd1' + label 'xcore.ai && vrd' } stages { stage('Checkout') { From fd2e1a81364a9c37bb791a3dd7267f4f0ffe2cef Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Fri, 29 Nov 2024 14:23:36 +0000 Subject: [PATCH 280/288] sw pll init with the correct no of params --- examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c | 1 + examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c index 7d9dfdfa..f2df6780 100644 --- a/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffd/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -146,6 +146,7 @@ static void platform_sw_pll_init(void) sw_pll_lut_init(&sw_pll, SW_PLL_15Q16(0.0), SW_PLL_15Q16(1.0), + SW_PLL_15Q16(0.0), PLL_CONTROL_LOOP_COUNT_INT, PLL_RATIO, (appconfBCLK_NOMINAL_HZ / appconfLRCLK_NOMINAL_HZ), diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index e6ba2efe..8fbb692e 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -194,6 +194,7 @@ static void platform_sw_pll_init(void) sw_pll_lut_init(&sw_pll, SW_PLL_15Q16(0.0), SW_PLL_15Q16(1.0), + SW_PLL_15Q16(0.0), PLL_CONTROL_LOOP_COUNT_INT, PLL_RATIO, (appconfBCLK_NOMINAL_HZ / appconfLRCLK_NOMINAL_HZ), From 26e7f451ab8589d24e15c76bde06afc6fe7df37d Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Fri, 29 Nov 2024 15:32:57 +0000 Subject: [PATCH 281/288] rolling back xua --- modules/xua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/xua b/modules/xua index 3c245641..a485ffe4 160000 --- a/modules/xua +++ b/modules/xua @@ -1 +1 @@ -Subproject commit 3c245641fdb6280f8021bb13f7f9af00b562f9ac +Subproject commit a485ffe41abcaf0f02ed289a9c759751dd75b29a From b8bcea9a6f1981be21630dae4ab0b83d16f8ba54 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Fri, 29 Nov 2024 15:50:34 +0000 Subject: [PATCH 282/288] minor cleanup --- CHANGELOG.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 29b93324..9d21fb71 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,7 +7,6 @@ XCORE-VOICE change log * CHANGED: Updated submodule fwk_io to version v3.6.0 from v3.3.0. * CHANGED: Updated submodule fwk_core to version v1.1.0 from v1.0.2. * CHANGED: Updated submodule fwk_voice to version v0.8.0 from v0.7.0. - * CHANGED: Updated Xmosdoc to version v6.2.0. * CHANGED: Updated XTC Tools to 15.3.0. * REMOVED: Deleted inferencing submodule. * ADDED: xmos-ai-tools v1.3.1 Python requirement. From 5af981c79bbc5cc249b484fa519d908c564df962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xalbertoisorna=E2=80=9D?= Date: Mon, 2 Dec 2024 16:53:20 +0000 Subject: [PATCH 283/288] update max allowable WER values --- test/asr/ffd_quick_cyberon.txt | 4 ++-- test/asr/ffd_quick_sensory.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/asr/ffd_quick_cyberon.txt b/test/asr/ffd_quick_cyberon.txt index 1d4a3f01..77e368c5 100644 --- a/test/asr/ffd_quick_cyberon.txt +++ b/test/asr/ffd_quick_cyberon.txt @@ -1,5 +1,5 @@ # filename, Max allowable WER ETSIRock_Speech54dB_Noise45dB 0.40 Pink_Speech54dB_Noise45dB 0.37 -Pub_Speech54dB_Noise45dB 0.35 -Silence_Speech59dB 0.21 +Pub_Speech54dB_Noise45dB 0.37 +Silence_Speech59dB 0.24 diff --git a/test/asr/ffd_quick_sensory.txt b/test/asr/ffd_quick_sensory.txt index bdd5c9ce..cf7a7656 100644 --- a/test/asr/ffd_quick_sensory.txt +++ b/test/asr/ffd_quick_sensory.txt @@ -1,5 +1,5 @@ # filename, Max allowable WER ETSIRock_Speech54dB_Noise45dB 0.25 -Pink_Speech54dB_Noise45dB 0.22 +Pink_Speech54dB_Noise45dB 0.23 Pub_Speech54dB_Noise45dB 0.30 -Silence_Speech59dB 0.04 +Silence_Speech59dB 0.06 From 6348afcdba61db23f2544c329273a5c2214bac71 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Tue, 3 Dec 2024 10:21:52 +0000 Subject: [PATCH 284/288] updating docs for examples builds --- doc/README.rst | 63 ------------------------- doc/programming_guide/asrc/overview.rst | 7 ++- doc/programming_guide/prerequisites.rst | 2 +- examples/asrc_demo/README.rst | 16 +++---- examples/ffd/README.rst | 11 +++-- examples/ffva/README.rst | 15 +++--- examples/low_power_ffd/README.rst | 11 +++-- examples/mic_aggregator/README.rst | 15 +++--- examples/speech_recognition/README.rst | 27 +++-------- 9 files changed, 45 insertions(+), 122 deletions(-) delete mode 100644 doc/README.rst diff --git a/doc/README.rst b/doc/README.rst deleted file mode 100644 index 1845dd26..00000000 --- a/doc/README.rst +++ /dev/null @@ -1,63 +0,0 @@ -#################### -Documentation Source -#################### - -This folder contains source files for the documentation and is intended for XMOS employees. Pre-built documentation is published on `the XMOS website`_. -The sources do not render well in GitHub or an RST viewer. In addition, some information is not visible at all and some links will not be functional. - -********************** -Building Documentation -********************** - -============= -Prerequisites -============= - -Use the `xmosdoc tool `_ either via docker or install it into a pip environment. - -======== -Building -======== - -To build the documentation, run the following command in the root of the repository: - -.. code-block:: console - - # via pip package - xmosdoc clean html latex - # via docker - $ docker run --rm -t -u "$(id -u):$(id -g)" -v $(pwd):/build ghcr.io/xmos/xmosdoc clean html latex - -HTML document output is saved in the ``doc/_build/html`` folder. Open ``index.html`` to preview the saved documentation. - -Please refer to the ``xmosdoc`` documentation for a complete guide on how to use the tool. - -********************** -Adding a New Component -********************** - -Follow the following steps to add a new component. - -- Add an entry for the new component's top-level document to the appropriate TOC in the documents tree. -- If the new component uses `Doxygen`, append the appropriate path(s) to the INPUT variable in `Doxyfile.inc`. -- If the new component includes `.rst` files that should **not** be part of the documentation build, append the appropriate pattern(s) to `exclude_patterns.inc`. - -*** -FAQ -*** - -Q: Is it possible to build just a subset of the documentation? - -A: Yes, however it is not recommended at this time. - -Q: Is it possible to used the ``livehtml`` feature of Sphinx? - -A: Yes, run xmosdoc with the ``--auto`` option. - -Q: Where can I learn more about the XMOS ``xmosdoc`` tools? - -A: See the https://github.com/xmos/xmosdoc repository. See the ``xmosdoc`` repository README for details on additional build options. - -Q: How do I suggest enhancements to the XMOS ``xmosdoc`` tool? - -A: Create a new issue here: https://github.com/xmos/xmosdoc/issues diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 083573ac..23b0aefa 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -128,10 +128,9 @@ Following initial ``cmake`` build, for subsequent builds, as long as new source Windows ------- -It is highly recommended to use ``Ninja`` as the make system under -``cmake``. Not only is it a lot faster than MSVC ``nmake``, it also -works around an issue where certain path names may cause an issue with -the XMOS compiler under Windows. +It is recommended to use `Ninja` or `xmake` as the make system under Windows. +`Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. +This firmware has been tested with `Ninja` version v1.11.1. To install Ninja, follow these steps: diff --git a/doc/programming_guide/prerequisites.rst b/doc/programming_guide/prerequisites.rst index 51f557eb..34b1c7b1 100644 --- a/doc/programming_guide/prerequisites.rst +++ b/doc/programming_guide/prerequisites.rst @@ -14,7 +14,7 @@ Windows A standard C/C++ compiler is required to build applications for the host PC. Windows users may use `Build Tools for Visual Studio `__ command-line interface. -It is highly recommended to use *Ninja* as the build system for native Windows firmware builds. +It is recommended to use *Ninja* as the build system for native Windows firmware builds. To install *Ninja* follow install instructions at https://ninja-build.org/ or on Windows install with ``winget`` by running the following commands in *PowerShell*: diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index d1bfe395..0562be13 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -83,11 +83,10 @@ something like this: Linux or Mac ------------ -To build for the first time, run ``cmake`` to create the -make files: +After having your python environment activated, run the following commands in the root folder to build the firmware: :: - + $ pip install -r requirements.txt $ mkdir build $ cd build $ cmake --toolchain ../xmos_cmake_toolchain/xs3a.cmake .. @@ -104,10 +103,9 @@ Following initial ``cmake`` build, for subsequent builds, as long as new source Windows ------- -It is highly recommended to use ``Ninja`` as the make system under -``cmake``. Not only is it a lot faster than MSVC ``nmake``, it also -works around an issue where certain path names may cause an issue with -the XMOS compiler under Windows. +It is recommended to use `Ninja` or `xmake` as the make system under Windows. +`Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. +This firmware has been tested with `Ninja` version v1.11.1. To install Ninja, follow these steps: @@ -120,11 +118,11 @@ To install Ninja, follow these steps: set the path in the current command line session using something like ``set PATH=%PATH%;C:\Users\xmos\utils\ninja`` -To build for the first time, run ``cmake`` to create the -make files: +After having your python environment activated, run the following commands in the root folder to build the firmware: :: + $ pip install -r requirements.txt $ md build $ cd build $ cmake -G "Ninja" --toolchain ..\xmos_cmake_toolchain\xs3a.cmake .. diff --git a/examples/ffd/README.rst b/examples/ffd/README.rst index 01956d24..2899985e 100644 --- a/examples/ffd/README.rst +++ b/examples/ffd/README.rst @@ -59,10 +59,9 @@ something like this: XTC version: 15.2.1 Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. -On Windows it is highly recommended to use ``Ninja`` as the make system under -``cmake``. Not only is it a lot faster than MSVC ``nmake``, it also -works around an issue where certain path names may cause an issue with -the XMOS compiler under Windows. +It is recommended to use `Ninja` or `xmake` as the make system under Windows. +`Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. +This firmware has been tested with `Ninja` version v1.11.1. To install Ninja, follow these steps: @@ -120,12 +119,13 @@ The host applications will be installed at ``%USERPROFILE%\.xmos\bin``, and may Building the Firmware ===================== -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: On Linux and Mac run: :: + pip install -r requirements.txt cmake -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build make example_ffd_ @@ -134,6 +134,7 @@ On Windows run: :: + pip install -r requirements.txt cmake -G Ninja -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build ninja example_ffd_ diff --git a/examples/ffva/README.rst b/examples/ffva/README.rst index efa01100..dd429055 100644 --- a/examples/ffva/README.rst +++ b/examples/ffva/README.rst @@ -20,10 +20,9 @@ something like this: XTC version: 15.2.1 Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. -On Windows it is highly recommended to use ``Ninja`` as the make system under -``cmake``. Not only is it a lot faster than MSVC ``nmake``, it also -works around an issue where certain path names may cause an issue with -the XMOS compiler under Windows. +It is recommended to use `Ninja` or `xmake` as the make system under Windows. +`Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. +This firmware has been tested with `Ninja` version v1.11.1. To install Ninja, follow these steps: @@ -78,12 +77,13 @@ The host applications will be installed at ``%USERPROFILE%\.xmos\bin``, and may Building the Firmware ===================== -Run the following commands in the root folder to build the firmware. +After having your python environment activated, run the following commands in the root folder to build the firmware: On Linux and Mac run: :: + pip install -r requirements.txt cmake -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build @@ -95,6 +95,7 @@ On Windows run: :: + pip install -r requirements.txt cmake -G Ninja -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build @@ -156,12 +157,13 @@ This application supports USB audio input and output debug configuration. To enable USB audio debug, configure cmake with: -Run the following commands in the root folder to build the firmware. +After having your python environment activated, run the following commands in the root folder to build the firmware: On Linux and Mac run:: :: + pip install -r requirements.txt cmake -B build --toolchain xmos_cmake_toolchain/xs3a.cmake -DDEBUG_FFVA_USB_MIC_INPUT=1 cd build @@ -171,6 +173,7 @@ On Windows run: :: + pip install -r requirements.txt cmake -G Ninja -B build --toolchain xmos_cmake_toolchain/xs3a.cmake -DDEBUG_FFVA_USB_MIC_INPUT=1 cd build diff --git a/examples/low_power_ffd/README.rst b/examples/low_power_ffd/README.rst index 319f8336..a116c586 100644 --- a/examples/low_power_ffd/README.rst +++ b/examples/low_power_ffd/README.rst @@ -57,10 +57,9 @@ something like this: XTC version: 15.2.1 Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. -On Windows it is highly recommended to use ``Ninja`` as the make system under -``cmake``. Not only is it a lot faster than MSVC ``nmake``, it also -works around an issue where certain path names may cause an issue with -the XMOS compiler under Windows. +It is recommended to use `Ninja` or `xmake` as the make system under Windows. +`Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. +This firmware has been tested with `Ninja` version v1.11.1. To install Ninja, follow these steps: @@ -110,12 +109,13 @@ The host applications will be installed at ``%USERPROFILE%\.xmos\bin``, and may Building the Firmware ********************* -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: On Linux and Mac run: :: + pip install -r requirements.txt cmake -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build make example_low_power_ffd_sensory @@ -124,6 +124,7 @@ On Windows run: :: + pip install -r requirements.txt cmake -G Ninja -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build ninja example_low_power_ffd_sensory diff --git a/examples/mic_aggregator/README.rst b/examples/mic_aggregator/README.rst index 52e81746..bb480390 100644 --- a/examples/mic_aggregator/README.rst +++ b/examples/mic_aggregator/README.rst @@ -43,11 +43,11 @@ Running the compiler binary ``xcc`` will produce an output like this: Linux or Mac ------------ -To build for the first time you will need to run ``cmake`` to create the -make files: +After having your python environment activated, run the following commands in the root folder to build the firmware: :: + $ pip install -r requirements.txt $ mkdir build $ cd build $ cmake --toolchain ../xmos_cmake_toolchain/xs3a.cmake .. @@ -68,10 +68,9 @@ again. Windows ------- -It is highly recommended to use ``Ninja`` as the make system under -``cmake``. Not only is it a lot faster than MSVC ``nmake``, it also -works around an issue where certain path names may cause an issue with -the XMOS compiler under windows. +It is recommended to use `Ninja` or `xmake` as the make system under Windows. +`Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. +This firmware has been tested with `Ninja` version v1.11.1. To install Ninja, follow these steps: @@ -84,11 +83,11 @@ To install Ninja, follow these steps: may set the path in the current command line session using something like ``set PATH=%PATH%;C:\Users\xmos\utils\ninja`` -To build for the first time you will need to run ``cmake`` to create the -make files: +After having your python environment activated, run the following commands in the root folder to build the firmware: :: + $ pip install -r requirements.txt $ md build $ cd build $ cmake -G "Ninja" --toolchain ..\xmos_cmake_toolchain\xs3a.cmake .. diff --git a/examples/speech_recognition/README.rst b/examples/speech_recognition/README.rst index d00ed4ac..ffaf2c62 100644 --- a/examples/speech_recognition/README.rst +++ b/examples/speech_recognition/README.rst @@ -20,10 +20,9 @@ something like this: XTC version: 15.2.1 Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. -On Windows it is highly recommended to use ``Ninja`` as the make system under -``cmake``. Not only is it a lot faster than MSVC ``nmake``, it also -works around an issue where certain path names may cause an issue with -the XMOS compiler under Windows. +It is recommended to use `Ninja` or `xmake` as the make system under Windows. +`Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. +This firmware has been tested with `Ninja` version v1.11.1. To install Ninja, follow these steps: @@ -66,22 +65,6 @@ Before running the host application, you may need to add the location of `xscope Windows ------- -It is highly recommended to use ``Ninja`` as the make system under -``cmake``. Not only is it a lot faster than MSVC ``nmake``, it also -works around an issue where certain path names may cause an issue with -the XMOS compiler under windows. - -To install Ninja, follow these steps: - -- Download ``ninja.exe`` from - https://github.com/ninja-build/ninja/releases. This firmware has been - tested with Ninja version v1.11.1. -- Ensure Ninja is on the command line path. You can add to the path - permanently by following these steps - https://www.computerhope.com/issues/ch000549.htm. Alternatively you - may set the path in the current command line session using something - like ``set PATH=%PATH%;C:\Users\xmos\utils\ninja`` - Before building the host application, you will need to add the path to the XTC Tools to your environment: :: @@ -104,12 +87,13 @@ Before running the host application, you may need to add the location of `xscope Building the Firmware ===================== -Run the following commands in the root folder to build the firmware: +After having your python environment activated, run the following commands in the root folder to build the firmware: On Linux and Mac run: :: + pip install -r requirements.txt cmake -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build make example_asr @@ -118,6 +102,7 @@ On Windows run: :: + pip install -r requirements.txt cmake -G "Ninja" -B build --toolchain xmos_cmake_toolchain/xs3a.cmake cd build ninja example_asr From 49f2aaea0356767c9c9270d156ae385e0d6e8d65 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Tue, 3 Dec 2024 11:07:53 +0000 Subject: [PATCH 285/288] changelog update --- CHANGELOG.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9d21fb71..091179c1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,12 +4,16 @@ XCORE-VOICE change log 2.3.0 ----- - * CHANGED: Updated submodule fwk_io to version v3.6.0 from v3.3.0. - * CHANGED: Updated submodule fwk_core to version v1.1.0 from v1.0.2. - * CHANGED: Updated submodule fwk_voice to version v0.8.0 from v0.7.0. * CHANGED: Updated XTC Tools to 15.3.0. - * REMOVED: Deleted inferencing submodule. + * CHANGED: Updated submodule fwk_io to version v3.6.0 from v3.3.0. + * CHANGED: Updated submodule fwk_core to version v1.1.0 from v1.0.2. + * CHANGED: Updated submodule fwk_voice to version v0.8.0 from v0.7.0. + * CHANGED: Updated submodule fwk_rtos to version 3.2.0 from 3.0.5. + * CHANGED: Updated submodule lib_src to version 2.7.0 from 2.4.0. + * CHANGED: Updated submodule xscope_fileio to version 1.2.0 from 1.1.2. + * ADDED: lib_sw_pll submodule v2.3.1. * ADDED: xmos-ai-tools v1.3.1 Python requirement. + * REMOVED: Deleted inferencing submodule. * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). * CHANGED: Moved files in folders device_memory, gpio_ctrl, intent_engine and @@ -23,8 +27,6 @@ XCORE-VOICE change log * ADDED: Support for reading registers over I2C slave in FFD examples. * ADDED: Note in ASRC demo documentation about large latency in ASRC processing. References to alternative application notes have been provided. - * CHANGED: Updated submodule fwk_rtos to version 3.2.0 from 3.0.5. - * ADDED: lib_sw_pll submodule v1.1.0. 2.2.0 ----- From 9bd313b5fb564f1bbd7083ee5e2a99a59c74ceb0 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Tue, 3 Dec 2024 13:50:35 +0000 Subject: [PATCH 286/288] Updating all tools vesrions in docs, putting a deprecation warning for the mic aggregator --- doc/substitutions.inc | 2 +- examples/asrc_demo/README.rst | 11 +---------- examples/ffd/README.rst | 11 +---------- examples/ffva/README.rst | 11 +---------- examples/low_power_ffd/README.rst | 11 +---------- examples/mic_aggregator/README.rst | 14 ++++---------- examples/speech_recognition/README.rst | 11 +---------- 7 files changed, 10 insertions(+), 61 deletions(-) diff --git a/doc/substitutions.inc b/doc/substitutions.inc index 51de9fd5..ecd74800 100644 --- a/doc/substitutions.inc +++ b/doc/substitutions.inc @@ -1,3 +1,3 @@ .. |HARDWARE_URL| replace:: `XK-VOICE-L71 `__ .. |SOFTWARE_URL| replace:: `XCORE-VOICE `__ -.. |TOOLS_VERSION| replace:: 15.2.1 +.. |TOOLS_VERSION| replace:: 15.3.0 diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index 0562be13..b15ca735 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -69,16 +69,7 @@ Download the main repo and submodules using: Building the app ================ -First install and source the XTC version: 15.2.1 tools. The output should be -something like this: - -:: - - $ xcc --version - xcc: Build 19-198606c, Oct-25-2022 - XTC version: 15.2.1 - Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. - +First make sure that your XTC tools environment is activated. Linux or Mac ------------ diff --git a/examples/ffd/README.rst b/examples/ffd/README.rst index 2899985e..9d5fe4c4 100644 --- a/examples/ffd/README.rst +++ b/examples/ffd/README.rst @@ -48,16 +48,7 @@ Supported Hardware and pre-requisites This example is supported on the XK_VOICE_L71 board. -On the host machine the XTC tools, version 15.2.1, must be installed and sourced. -The output should be -something like this: - -:: - - $ xcc --version - xcc: Build 19-198606c, Oct-25-2022 - XTC version: 15.2.1 - Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. +Make sure that your XTC tools environment is activated. It is recommended to use `Ninja` or `xmake` as the make system under Windows. `Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. diff --git a/examples/ffva/README.rst b/examples/ffva/README.rst index dd429055..7d661da8 100644 --- a/examples/ffva/README.rst +++ b/examples/ffva/README.rst @@ -9,16 +9,7 @@ Supported Hardware and pre-requisites This example is supported on the XK_VOICE_L71 board. -On the host machine the XTC tools, version 15.2.1, must be installed and sourced. -The output should be -something like this: - -:: - - $ xcc --version - xcc: Build 19-198606c, Oct-25-2022 - XTC version: 15.2.1 - Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. +Make sure that your XTC tools environment is activated. It is recommended to use `Ninja` or `xmake` as the make system under Windows. `Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. diff --git a/examples/low_power_ffd/README.rst b/examples/low_power_ffd/README.rst index a116c586..b73bf4c5 100644 --- a/examples/low_power_ffd/README.rst +++ b/examples/low_power_ffd/README.rst @@ -46,16 +46,7 @@ Supported Hardware and pre-requisites This example is supported on the XK_VOICE_L71 board. -On the host machine the XTC tools, version 15.2.1, must be installed and sourced. -The output should be -something like this: - -:: - - $ xcc --version - xcc: Build 19-198606c, Oct-25-2022 - XTC version: 15.2.1 - Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. +Make sure that your XTC tools environment is activated. It is recommended to use `Ninja` or `xmake` as the make system under Windows. `Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. diff --git a/examples/mic_aggregator/README.rst b/examples/mic_aggregator/README.rst index bb480390..ca905bb4 100644 --- a/examples/mic_aggregator/README.rst +++ b/examples/mic_aggregator/README.rst @@ -2,6 +2,9 @@ PDM Microphone Aggregator Example ################################# +.. warning:: + This example is deprecated and will be moved into a separate + Application Note and may be removed in the next major release. This example provides a bridge between 16 PDM microphones to either TDM16 slave or USB Audio and targets the xcore-ai explorer board. @@ -29,16 +32,7 @@ Download the main repo and submodules using: Building the app ================ -First install and source the XTC version: 15.2.1 tools. The easiest way to source -the tools is to open the provided shortcut to ``XTC Tools 15.2.1 Command Prompt``. -Running the compiler binary ``xcc`` will produce an output like this: - -:: - - xcc --version - xcc: Build 19-198606c, Oct-25-2022 - XTC version: 15.2.1 - Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. +First make sure that your XTC tools environment is activated. Linux or Mac ------------ diff --git a/examples/speech_recognition/README.rst b/examples/speech_recognition/README.rst index ffaf2c62..f2836b1f 100644 --- a/examples/speech_recognition/README.rst +++ b/examples/speech_recognition/README.rst @@ -9,16 +9,7 @@ Supported Hardware and pre-requisites This example is supported on the XK_VOICE_L71 board. However, the XCORE-AI-EXPLORER board can be supported with a couple minor modifications. -On the host machine the XTC tools, version 15.2.1, must be installed and sourced. -The output should be -something like this: - -:: - - $ xcc --version - xcc: Build 19-198606c, Oct-25-2022 - XTC version: 15.2.1 - Copyright (C) XMOS Limited 2008-2021. All Rights Reserved. +Make sure that your XTC tools environment is activated. It is recommended to use `Ninja` or `xmake` as the make system under Windows. `Ninja` has been observed to be faster than `xmake`, however `xmake` comes natively with XTC tools. From a900c4bf64e7d81a29913ecc9e60800567d57f34 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Mon, 16 Dec 2024 15:50:22 +0000 Subject: [PATCH 287/288] new cyberon engine --- CHANGELOG.rst | 1 + modules/asr/Cyberon/lib/libDSpotter.a | Bin 837882 -> 849160 bytes 2 files changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 091179c1..e2318472 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,7 @@ XCORE-VOICE change log 2.3.0 ----- + * CHANGED: Updated Cyberon Wake Word engine. * CHANGED: Updated XTC Tools to 15.3.0. * CHANGED: Updated submodule fwk_io to version v3.6.0 from v3.3.0. * CHANGED: Updated submodule fwk_core to version v1.1.0 from v1.0.2. diff --git a/modules/asr/Cyberon/lib/libDSpotter.a b/modules/asr/Cyberon/lib/libDSpotter.a index 215155e2b85531e1b12366ffce053c2bc5cf90ea..5677d8cbcbdb941958ea7fa9d352350fbf6d3bf9 100644 GIT binary patch delta 103450 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 JPX@;a{vQlu+6n*w delta 94375 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 From 03eb16f484bf52911ea159f6ea1b0b76613b0726 Mon Sep 17 00:00:00 2001 From: uvvpavel Date: Wed, 18 Dec 2024 15:28:18 +0000 Subject: [PATCH 288/288] release review comments --- doc/programming_guide/asrc/overview.rst | 1 - doc/programming_guide/ffd/software_desc/intent_engine.rst | 2 +- examples/asrc_demo/README.rst | 1 - examples/ffva/README.rst | 1 + settings.yml | 1 - 5 files changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/programming_guide/asrc/overview.rst b/doc/programming_guide/asrc/overview.rst index 23b0aefa..fa48b641 100644 --- a/doc/programming_guide/asrc/overview.rst +++ b/doc/programming_guide/asrc/overview.rst @@ -20,7 +20,6 @@ Overview For a proposed implementation with lower latency, please refer to the bare-metal examples below: - - `AN02002: Adding an additional I2S bus to USB Audio via SRC `__ - `AN02003: SPDIF/ADAT/I2S Slave Receive to I2S Slave Bridge with ASRC `__ diff --git a/doc/programming_guide/ffd/software_desc/intent_engine.rst b/doc/programming_guide/ffd/software_desc/intent_engine.rst index 0c29389e..49f81760 100644 --- a/doc/programming_guide/ffd/software_desc/intent_engine.rst +++ b/doc/programming_guide/ffd/software_desc/intent_engine.rst @@ -4,7 +4,7 @@ modules/asr/intent_engine ######################### -This folder contains the intent engine module for the and FFVA application. +This folder contains the intent engine module for the FFD and FFVA applications. .. list-table:: ASR Intent Engine :widths: 30 50 diff --git a/examples/asrc_demo/README.rst b/examples/asrc_demo/README.rst index b15ca735..4fe066fb 100644 --- a/examples/asrc_demo/README.rst +++ b/examples/asrc_demo/README.rst @@ -9,7 +9,6 @@ ASRC Demo Application For a proposed implementation with lower latency, please refer to the bare-metal examples below: - - `AN02002: Adding an additional I2S bus to USB Audio via SRC `__ - `AN02003: SPDIF/ADAT/I2S Slave Receive to I2S Slave Bridge with ASRC `__ diff --git a/examples/ffva/README.rst b/examples/ffva/README.rst index 7d661da8..a4f84b24 100644 --- a/examples/ffva/README.rst +++ b/examples/ffva/README.rst @@ -138,6 +138,7 @@ Run the following commands in the build folder: :: xgdb -ex "conn --xscope" -ex "r" example_ffva_ua_adec_altarch.xe + xgdb -ex "conn --xscope" -ex "r" example_ffva_int_fixed_delay.xe xgdb -ex "conn --xscope" -ex "r" example_ffva_int_cyberon_fixed_delay.xe diff --git a/settings.yml b/settings.yml index f103260f..3f19710d 100644 --- a/settings.yml +++ b/settings.yml @@ -10,7 +10,6 @@ documentation: - .*digikey.* # digikey don't allow auto link checking - .*github.com/xmos/.* # many links to private repos can't be checked - .*percepio.com/.* # Semi broken link in fwk_rtos that actually still takes you to a sensible place. This may be removed on the next release (>v2.2.0) - - https://www.xmos.com/file/AN02002 # TODO: Remove this link when AN02002 is published doxygen_projects: sln_voice: doxyfile_path: doc/Doxyfile.inc

    >(@{`{B^{qh+2`KBXc8@qGF<>qDMxy4^Mzsv@JU1 zhskOT>NvI;xd5Jgcghb0V_=yt!!y-sD0-KBAlsCeSkNhVXy(w&teky?QN>LQ*W{}O zZ%Xo<1@5V?RgM8J&9^sL68tPA1rN*fkg@n+Vj$TSWNNnR1{h;Z34r{*8X|WDJ zxG=M@D9@O^F#CRfRB@Z)ulf7)S`?kM*K%e!6P?E#yz^Jzaej;tCNvI?k#mrlcz?Vn z@rKG}7+t2mmhpgTrKLw`I^cre+YW^XBQ8ajj~o|KBjPgTb`FL5tQRfkO??bkxr@wG zsueK^?WrmfEpFp)d0RL}6m=~w=a^+b0Z$>tc^is50p?xJpILCQc)nwSi*uEC0F=ia z6PPB@f|KtW{HFB5M&KLpC_p)_V20{<8ipD&^bJj_H7j&@XiVtbFx=L`w!(J9*4ef^ z)Njc)pEGA0kLe4TTrv}K86}vgK~^Hzoj>8@JWpLmU2`2xitiTXl$^0EC0j}c7oRAx z+ZD$fH|1IETHqS$`Rwl{9N|AhUUsDNQhR~!#%ZD^?k8wq1!%f@x>PR4_?uR;v(aH$O3 zQvE$(UmSw@L?UF)lPz`4@qpKCY)FN<&IfJ_@O1AmZJBqZ2|KDCQJ2U~g<*kW{uh3~ zZ;)rOYm~dhWA~Wd1+K@g=dKcONB=hO7WZ1ua9>-#qIg}rCEgG($g#-3kndT8+(kE& zed(RFn;FTS;bw88bYBh6L6O6&dj>9h9M?e?r#k?g-EoYODg+*0J*=U&QjQVJ z1os5r@h|9zaL1lsxj@bvU_^>5_o2jT;i|AucS?-KjV3*@`f z4Y{LM1&7s6xWY%sbow6CjP)}ryOQHjz37>IF(9OkE?n z`IeHbuETD@dTTA!h99J=km=YVv^kzjpJc0%1M$1WHSz$tj4Dr$rB{YLy3Sg}`ZTtxkhHdK*ziL$v+D7yPE+8py2;6Bmft@&UQ8a9r?Z;;M^$LCnucoSEkrqQwCdPw)R~T^wW}{|o9I+02kFUk+5uJ!zSYH$}g48Z3 zO(7aBhsd?DbaFc6U}s8IghZJlmry0zJ25rbQ{99efOX{M(pIs8at!T{I>fHx6jZ^& z)y-0_S_5Xz^X0k0&SYo02H@{&BHO4?>WTVQm?M9u61Z1tsCZL(sFe^i@g#Y?wiwIP z0{nF8C$R$ijE$E+@#)eAgeQt2lcZClrD(aj+Cz3|v(PS(S*)Xw(oM-h{6=ihUQ0KT zD9~(&%kngo2;%V#*m5hHz>dQV+k7W_*vZ(Gs zpQ_KreB~;3TP5VjDkpD}TOfnv%StG*Qz{XY$$!@aDS{5l9;?J) z@-olRA4pZmJ)EEtu$F4PGzhs&^+jtcyFoo|h&B%Grp2H(Y1?{8w09nn5_zh&Z94oiMBiZk0DArD!99%>XA+iz&;$di+axLCXhmd{LY;KY? zOZ-9ZLSD;vjUCA+LP~IjGSQgDok9jkU(tSOt}3B1+C1#Jl&nUw~dOJ*xA z0zHt~hTn)fcqjLifGtF%Dhew_J7jknQ)7tn!E&MvR@b*-b>x^pxctHNFXeOBk&XB# zN{9Bwy2`ciRq}XeiJ{PZ*Wb~rZ_x67AJ=UK$4CJGH)0GPNSNxGS z1Nu&=(m$Am9;GYexObqqCZsFnwO0t9h1S);*cT8Q1z&0Cf)>FV#kq%M5=N& z5JOo>P2P*8@)MDorjGpZz)mU$d#jkRt>R3rj=l|c!WG9y>n^ZOrDozQO_KhU!|6X^ zhLEOk_JLwv^g?D-(GesLXQV2+=32Bf*RY+=@_4j5j7?CUS53H?dw5aA^dEd$&+Oq2=R}K+D1~_iW2Ly0W~6pCint_UJ z1O+KW2|+)0Pd%wjXA-sJ0T+xJd$jJ@u3$TS2)2*3N}O*Lp=WXYl*$#NYw@SK3sI}!-!wn?E zpNrlGHN{FwHtW|W1TIQr89#apH^@tUci2$!tx#2dM(YV%V5G1>XQ0aBqkN6LU08!< z2l~j#1v-=WEa}LbKzaW;t+(MOdEGlzL+DAe73(BEM1$0~KrG^=S7@3XCyzvaC1#_S z0}sj8hSmP>o(IHYlH}eZvCdg)BT!VDDyB*=iJ#~lP z`~8=lLy7g=LH37tw%5i^CiW4liNvA->MC1;6c#8?9#N{u4RiyvGu}0T zXR%diF?&M__nXl~dV^9Inz=q&u40PKr_K3dZbl&8}bkJfv*=!s=^`K4 z%$(%H6v4F+nQs1}Tnk*+E@HTHk1LeE1iEt}>LY$C=_7HmjPjKnuZ;`-K}$$4siQVo zt&Dq!yU2{d8ELDrrj{&>CC&t&VX=6aYF3kRze>rSC@(TBcnPs#qhyL<)m_Rhygho2{=aLwcqu8Y_$b{=EmU%N+>Q|aG5btxA9;f^l8b|d{s+KE4HFIuFLWEl zL24PbIa<+hM4av$Ch3fzL*ZJe!CMqvqI-|5B$4_xuaP3EiGL3va` zBxZ}!^)&z9KVI+Vih>He(leT)pQ7?&n=iUr_-3PHjSaZoffVO` zn2$Wi*QvLIj}Z^@i&8@8lX5jgomVnS`UDpCmp^gN$5j}`LVxrhwN!InEt9xrp zzp2RvCq3W&$iJ2u&aM*Isg<=~)c1xDY8}UJtPDAbX@uSmR28<^hEO>2ss4n!k+jAPOB{&rG7#KT^vt_mH65h%{Q&<@K6<(@p1 zdw`6P8WU}WgUBIW*knjRh_Br)LNi}(Wyv@l1(f^qU0=4&8SG7(YgffauEvZ ziCP_gm|PW$Co=F{xve@4{R2L!Q$b%Nvz&q$#lkcS|1Cg|yR#0n(&53Oz+lmk$F%Y9T%eX|A@^nkn_Lc1pgs z6(6nru5DB+tAFD6QB1vu3{w(NANp8X3aFm1>IrQox=F>c`N$}(Env#pXq>VEqd`I8 z1gIR&A=)5u$|jsd#)4`_AEY<_Sc{Ykm1M{)Y1&$qmkh{lWG2!Kura#;v1tMX-W}u* zwGU_wM*)U3N}j0BBMZot_yItrBIrtVF}hzV511*dD9YuLZ%7~YyhzH!wN(5zHVv45 zJpmQU$O@~_lLAiU9MbKu8389llTezB0m#I za<_5Rwp?i)gFtu{JcYl{S6jvp{CgH^;>;{(7xoHl>4yflA? zjBF+wp`T;SF!(_a=Ps@3CYaXhRakvVqugvUi*h@dZ2XS4P}!jUiake$in0CyzR8|0 zo`sHC`JJ*Zr}j-b^~s(#BJXJ4j67f72Ky(mF1ngmwuk&;9) zw=4pkKLu96rkD~8pgKcuhJ0aVY=jywtn#(*o@F|#D*%qH*SIaD#Cq9OL!V<9Vzr0KVM*qph8KX~?x^bx8j1;k48@gbQ3{q7 zMR9cSlmDH&x!qBGCI4E6{o}G{4WD26I6u3;!{GkmG4ZwV-sVtSd}uF2M`|f@UactE zeZb#yjCURO?G~n~7l}5;s$mJX(P5RWQI_xKIfkbY?I`Lty;%1NbUx~uD?&!>M@aXu zjkYGXjIhTc!%Y?S8|Y=A*!nB>LrVZPas+j11EuYOME?_hDZk9yrQ~Y%j_(~m^?5b* z>4F#WAM-Ptc%s#lWFb|Nap>NgBCJDnnie0p;5b^`p?E||jO#srT)m6^WDXc>hMA(% zqX^q|(*n*yS&6D-1@a&A7y5VZx^Y^_T-!QZ&+szgxqupZ7{Z&|8UN%?QB!aVYm7A_ zTyz2V0#vZNQO%Lv(o6p)NBfe&_Sg1nMY`N8=?P!GA5HI+w_85%%a~A-D+Y*>`ic4_ zRAuTFZKLj@AB6#+3~{x1x&4K+jh6xxLj^A0S`>aUDk6Gy)b@ zJ2PK5$l?mSW-AWc5{iTdt?kVf^(OW=+CwpPTdFI4kZlc|;N8YYdX#HKHzNvU#lsiO z&YGFoCI5(XgnuC4-+Qw7b|#kMeVzN7OBs+o(cMa(gZH4yat)1r_1oCZWKeF*zjS{s zT2bgL>FqC6_7e@cr3O<-YD8{iVq|1kC1Vx(uDT((OE@If#=f%Aroxb!w&IA^QQvL< zhP*Qm;r0+Mk+*6uYzlda8DhYV=S@${ulO8Y$h)%r)T9+m&tCU3panOubuOl0Y@>t6&N`l%-T(HJP^5&C2 zJAB>q3H`Y4%Yoc_uET*AN_#Y!o~74u9Fv9>iYPzUO*nhH|Msqdm7GY5(>sjEpr@?? znunh4K{mp{Q$W6|B%y6-hkh<(fR|X;TUT28m^58kx-Py~`=G`k2(ek00_wYwAp^|y z4ZrHH(o@Kvctf-{;13vOpwLz z^wa0F9(p3x40praVip?2UXT=%!A;VyGmbZ98w!Bk-Itt?lfVhF6BkK=9867RtlS}8 zL;VE(6hO-SWNHJ-W(Q(Lf5pb*680YW8p+sH>>!{rj;Y_Ihk_dD>mTVq&To_yrHgu$ zZ)!iEz2nEH)HUDBrajI)S-i!&MoL37$&R{dhI7nK+$*i-3j#@A#r+R|MShLR)Kcac zFegVC=I9K}N#ZDR0WA+qfdKjip2VYcRScjeZhWL`$E+fw$#P_6+>Gr&_1HJ;cl;=1 zj!cGaM#3Ij?UJIT+sX+{rhDsG8q-1Xybfq;ZKHNVSJ*Ro zNT7=6t$V+BB`-@$weDyGEj##&_kjJcf^WHDS+CO5(>rC)EBMD9DyPvGE!V=jhsK&F z5&MOy?r8f<`vG@ZK1;nzon$NO31Ia9bY z9IhN&On0Cz;%~KhWnpk?;Jc?9@U~|2Ta>Lh0~%f1u^ZA)&zxd!F7SHODy1$?`#pPj zNw%*QI!o`c5mD>IWIcnI2{g60EClwsgAKUlV!RgD!7$7`z(^YQaS`+~3|uAFFu%b^<) zaj0LZE`OEmN?A0Xti~-e>^B+=J-9M-KOzz-63h4(xT9P?*L3eEAs@-6GMKOAHSG*f zxNhb@$$XqPCDri#a(Zgc6=%HMp9xq^;oWTGEDpLzs^rlMYZdy6Dm!BX2K5MWid$f~ zWjttVXnd**(s6{Mosho>h5Vu50YJ?*VgEGE3RS}Bkk&>Yor_MClY>~WlhjWA4I55% zLjG` zb%b1DN^rhRA_@35e2+F(+V8ixn8H5UNJd2Z!yj$4+7^a+%PKae#AFH?Z2i-)k4%#U zZ++)O$63b-&nDrrItinw4NRu4n_;C+Vv}e$APtu(Q^mpZTcr+g?VB?z40X)sP3w*2 z^sVS6SSlz{?31ggEOG^(0X*BP>@oHb=j6U~OV|TUAG#9R3jJG4Qf_KGbU#i|Ft)RH zdNdhF#NsMAmb3-ek%sDg*%quLjOFu$@?xyw*RG*!wH#?G-^#-}wiUk3H31%!&AD9I z+kx}HVl{Qcj8`DGmskwl#y9iExSP5kdGZ3W(p01@K7xEs5o`=wj@`u6qt`*~9%$p# zBk(M>21BS2Zj3(8uvh0{t`d7e*W#7JYVFZjJV=bCb7%+6&=u(&bTwu*C~R(o=Z*Qm zYiWsys7RP;6MLTR$PT2-5vP%EnhLHVJ(Mgp4*eHatZyK_wKek2U>ct$P*NjM+peed zmwy#lcirOZ`IU3dW^!4z3(C8S0z=f{bcCsvFZ@EmKMZQLm89$9S zf*yK|ZpAKS?=cb7??h|77M800tBl7MZUo6W3`bab&~2vdH`m* z6Ol(vW2Q29=|gl6aOe06`rnQ5CMdWh0h8tyMv;lYR4-zEtVG`>=EL*#OnHO!K)$E` z4t%dS>Jz0bpw8|DuR#Va0ePV@%3Dzj>~;+;nv|zzm&;DdnwMAX*u(eJ66ph`iD3gn zcNi8?1?t4W3wvVW{^AR+L_g>u5$knl4A1rVbwAkxY7s6YNyr5xQu|YFt?ow};!Vh5 zbQRrAKrdGY?A8-JsD2l>3L)YqML|cCVeDn@gRU;%_T1z?q8GjyvtaAd@@Orj8xo5+ z&=&Z2Ql&}&+f$86A!=a@wQ0&F>8Yewhih*E>HY;Ac@8NG=%LP*Ps-KBy}?|;#^-yk zJHkuo!W+3n&ccFD&Yyw0>H?OwehIr~Ij4&uv*o&;U`c!X442veXOM$j+Cla(Q^R(RI1H zIuoa3pHZ^`0XhTg11>NIEE`Qm=b;$(JLbo2Bw&>3DvS>J1C{U=$bBVW8X&EaQ-GtP z0?)$_`5anqDqWDqNUcD%@|frhSbg7II{UAM5Ar_cH!N=N(gGMdhmA8$Hor9W)LkT4 z`Ky1MXSw@@yPJQQ_){H;4=3*e9{T{ZgC0YVCodB*SRJINN-N9d+pwk(h8Iv-tXmhV zo6d#;p8d7DUEU`-@4k+rzwTN*DREp3JKn)&L8%| zqGkmp1zqifJi`Q&MloYd^(=^~lKwQgN4e*3>00JYaUJ!}3PvDB#9oGDTe5DtD%@dc z>=N1z+l2Dq+%y}Qk1N#_?FM#$?8`IrBX?hoDZuiL13f)j%)$;UvK0MQU^~a=Q8%Y#^{Bn*hUdjiIf*Zo;dn`=ax? zyOV!4e?-_S&%@h*Ce}9^fheaZ(1NW_*CwVU8A;1ce{&jQwSB$kGE!{^~;@q9qMUj;t!QEV;#nD`ygzDw!t)W2i`k%VN#r+6ux~ae^ZKdr6x2Hzv3E-k#!_#mU*I~2a z?tMw#EoA_rgO)B!?c_>wU-_KeT3#>3NfV^|V*g-&A(g)!c;It-T6tnUe$OEP;^1<% z0DDClKwG~9Q;7G{qNND|vv0V+Kv*L;Lk?WYw&?&G^TY}a@ z+XB;i3$W~(BlkgZwKiTGKaD5jH$d52#53{AgbS~TZvd|NzgR77IIz-&*bW+0P#_~Iw)yZO+GBxNBUQ}2_K$JtK&<7+rJx)$13A}@WJ>; zd>fI9*Tqw?U+_JkDf$v!gw?}lz}l}9IgI>(asMj#+X=v4M8QdLn)Y0cR_7_{a$A{% z+*%iTnEXX<0?437N@t~mvQi!;Jrehe^~D>(LE; z22T)w0zd!0GD0~k-v=e$N#YQ3uXsr+quf{hz@e-PIH*y`IBmUJMXd+9tw?anOGJCZ zwMF6Tcg41&Bhj{?3h)D%rnlhSSJ9i$^Kk4ux*dIpRs&4|0X_?WxsRh;pe*;nhi?@E znNd(h4?(9O(ZCmIr|wo3E4jc3U9ZM#>kt8Xhnzyr1GC?+woomqs!WFMVZh>Fr>3gY zfQ6k4-jRPHXORuix)Z>`aJN=oeAdTUhJ+K~s5Na_Um>9#6nn)XP;cE{bjMo3$8}q@bu`HCl8`2%h zX9n-Z)xe+b4Rfa*FoTK!1%PhA!N@S6Ik~& zY5}!6+XlfMIhT$wi8Q@w5x+ z51y8rw9cRz(O#Pk4Ax(v=cXe0kWotk2K6s6`#cY4=mX_i0le00+IOuMG8m3N8(9oI z@|M8G{|Kzy0a{h9G8`dZI{=7*22l2iQ18j$3s|f@1c$>3z=ItP^-I(qg9qYZ7@rq| z0>ggz><;_X0~gI4SiQ{wrGo2l{YrHJtVma68F1AvLtX!d^JIaF!vL){9A^S3Ip`st z`;n*cIu3}+DiEVhz@T3QWxNDll@)-~UI&SRs8TR8hJk-34{nZnaP>?BJ&F`K%L}-Q zIPBF3A~gX@8;eu~wS#NG4;luV4l}_m(yq0J5&S-K7s{{^=>p@*IyiG{tp<#n+d)&I z4EQ^4hcRw5)UxA$zkUW~f22JCF4#5r?;7|e-quo~71u&~f)I8p(=uLLg}w2TlaZy2~m)`8O3fW8wAnV(|du>*+%_EsT!^^pb;kLJj) z(0h2es&C*t54F#*T>(GFhLAgI174d=;CM!GTrGe-Uc>c&2znFguulNA9x6dwtPd@; z0qmi`QBy$6A_+>B3|HqH>{04?S__U;8}^KVwrYo#^8;d+3Eq`BTyY!tNwV8%aFlceL|2wY%*f*acmInbPd;;2p6BHi0LVKA9*LygWoq#+173huIpiR92 zpHmySj`jg1jW$rT@(2U|r^RrF(&Gvc7Xf@ueefy4IU*shpaTH=*Mi!Wx_W+q5#uqu zzd?V&!6&smQVnWug`;NIYb$hlxkX}Key1yqD~SPp(I z!S`v}M~Hd~#9IbtVRdL0FbWny;pIjf*u?XHl_%0M;UlWd15!#RjuCwoFC)$0e^?Ptz6~PU*B5=l=!K)#ZF&cVIspoDE)cps% zOW?d1{Hn}S^xK0`nNO|rH;-ZC-dLF srQ1k2G7Cp2bxSVYTI%dv>e^gdfAqhtrSYR+EB@aZO1*wdBP4782jBR2tN;K2 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/9.wav b/examples/ffva/filesystem_support/mandarin_mainland/9.wav new file mode 100644 index 0000000000000000000000000000000000000000..28355dd55a1f1baab6291e8a52e928bc638c7534 GIT binary patch literal 20050 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 Cu5FtD literal 0 HcmV?d00001 diff --git a/examples/ffva/src/device_memory_impl.c b/examples/ffva/src/device_memory_impl.c new file mode 100644 index 00000000..11976438 --- /dev/null +++ b/examples/ffva/src/device_memory_impl.c @@ -0,0 +1,68 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include + +/* System headers */ +#include +#include +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" + +/* Library headers */ +#include "rtos_printf.h" +#include "rtos_qspi_flash.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "device_memory.h" +#include "device_memory_impl.h" + +void asr_printf(const char * format, ...) { + va_list args; + va_start(args, format); + xcore_utils_vprintf(format, args); + va_end(args); +} + +__attribute__((fptrgroup("devmem_malloc_fptr_grp"))) +void * devmem_malloc_local(size_t size) { + //rtos_printf("devmem_malloc_local size=%d\n", size); + return pvPortMalloc(size); +} + +__attribute__((fptrgroup("devmem_free_fptr_grp"))) +void devmem_free_local(void *ptr) { + vPortFree(ptr); +} + +__attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) +void devmem_read_ext_local(void *dest, const void *src, size_t n) { + //rtos_printf("devmem_read_ext_local dest=0x%x src=0x%x size=%d\n", dest, src, n); + if (IS_FLASH(src)) { + //uint32_t s = get_reference_time(); + int retval = -1; + while (retval == -1) { + // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset + retval = rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); + } + //uint32_t d = get_reference_time() - s; + //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); + } else { + memcpy(dest, src, n); + } +} + +void devmem_init(devmem_manager_t *devmem_ctx) { + xassert(devmem_ctx); + devmem_ctx->malloc = devmem_malloc_local; + devmem_ctx->free = devmem_free_local; + devmem_ctx->read_ext = devmem_read_ext_local; + devmem_ctx->read_ext_async = NULL; // not supported in this application + devmem_ctx->read_ext_wait = NULL; // not supported in this application +} diff --git a/examples/ffva/src/device_memory_impl.h b/examples/ffva/src/device_memory_impl.h new file mode 100644 index 00000000..0822a303 --- /dev/null +++ b/examples/ffva/src/device_memory_impl.h @@ -0,0 +1,10 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef DEVICE_MEMORY_IMPL_H +#define DEVICE_MEMORY_IMPL_H + +#include "device_memory.h" + +void devmem_init(devmem_manager_t *devmem_ctx); + +#endif // DEVICE_MEMORY_IMPL_H \ No newline at end of file From 1d4e5e8fc2eb184f64fb77b04d565b91b1894230 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Sun, 3 Mar 2024 21:40:34 +0000 Subject: [PATCH 018/288] XMOS can be detected --- examples/ffva/src/app_conf.h | 15 +++------ examples/ffva/src/main.c | 29 ++++++++++------ .../ffva/src/ww_model_runner/model_runner.c | 33 ++++++++++--------- .../src/ww_model_runner/ww_model_runner.c | 22 ++++++++++--- modules/asr/Cyberon/DSpotter_asr.c | 16 +++++---- .../reference/fixed_delay/audio_pipeline_t0.c | 5 +++ .../reference/fixed_delay/audio_pipeline_t1.c | 9 ++++- 7 files changed, 83 insertions(+), 46 deletions(-) diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index d7eedbc6..e3ce44c1 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -15,6 +15,10 @@ #define appconfAUDIOPIPELINE_PORT 7 #define appconfI2S_OUTPUT_SLAVE_PORT 8 +#ifndef appconfINTENT_ENGINE_READY_SYNC_PORT +#define appconfINTENT_ENGINE_READY_SYNC_PORT 18 +#endif /* appconfINTENT_ENGINE_READY_SYNC_PORT */ + /* Application tile specifiers */ #include "platform/driver_instances.h" #define FS_TILE_NO FLASH_TILE_NO @@ -37,11 +41,6 @@ #define appconfINTENT_FRAME_BUFFER_MULT (8*2) /* total buffer size is this value * MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME */ #define appconfINTENT_SAMPLE_BLOCK_LENGTH 240 -/* Enable inference engine */ -#ifndef appconfINTENT_ENABLED -#define appconfINTENT_ENABLED 1 -#endif - /* Maximum delay between a wake up phrase and command phrase */ #ifndef appconfINTENT_RESET_DELAY_MS #if appconfAUDIO_PLAYBACK_ENABLED @@ -88,10 +87,6 @@ #define appconfUART_BAUD_RATE 9600 #endif -#ifndef appconfINTENT_ENGINE_READY_SYNC_PORT -#define appconfINTENT_ENGINE_READY_SYNC_PORT 15 -#endif /* appconfINTENT_ENGINE_READY_SYNC_PORT */ - /** * A positive delay will delay mics * A negative delay will delay ref @@ -225,6 +220,6 @@ #define appconfUSB_AUDIO_TASK_PRIORITY (configMAX_PRIORITIES/2 + 1) #define appconfSPI_TASK_PRIORITY (configMAX_PRIORITIES/2 + 1) #define appconfQSPI_FLASH_TASK_PRIORITY (configMAX_PRIORITIES/2 + 0) -#define appconfWW_TASK_PRIORITY (configMAX_PRIORITIES/2 - 1) +#define appconfWW_TASK_PRIORITY (configMAX_PRIORITIES-2) #endif /* APP_CONF_H_ */ diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index ccf0c3c8..88ed28fd 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -64,6 +64,7 @@ void audio_pipeline_input(void *input_app_data, size_t ch_count, size_t frame_count) { + printintln(888); (void) input_app_data; int32_t **mic_ptr = (int32_t **)(input_audio_frames + (2 * frame_count)); @@ -82,6 +83,7 @@ void audio_pipeline_input(void *input_app_data, flushed = 1; } } + printintln(890); /* * NOTE: ALWAYS receive the next frame from the PDM mics, @@ -93,6 +95,7 @@ void audio_pipeline_input(void *input_app_data, mic_ptr, frame_count, portMAX_DELAY); + //printintln(891); #if appconfUSB_ENABLED int32_t **usb_mic_audio_frame = NULL; @@ -115,6 +118,7 @@ void audio_pipeline_input(void *input_app_data, usb_mic_audio_frame, ch_cnt); #endif + //printintln(897); #if appconfI2S_ENABLED if (!appconfUSB_ENABLED || aec_ref_source == appconfAEC_REF_I2S) { @@ -139,6 +143,8 @@ void audio_pipeline_input(void *input_app_data, } } #endif + //printintln(898); + } int audio_pipeline_output(void *output_app_data, @@ -160,6 +166,7 @@ int audio_pipeline_output(void *output_app_data, tmp[j][0] = *(tmpptr+j+(2*frame_count)); // ref 0 tmp[j][1] = *(tmpptr+j+(3*frame_count)); // ref 1 } +printintln(440); rtos_i2s_tx(i2s_ctx, (int32_t*) tmp, @@ -188,6 +195,8 @@ int audio_pipeline_output(void *output_app_data, portMAX_DELAY); } #endif +printintln(441); + #elif appconfI2S_MODE == appconfI2S_MODE_SLAVE /* I2S expects sample channel format */ int32_t tmp[appconfAUDIO_PIPELINE_FRAME_ADVANCE][appconfAUDIO_PIPELINE_CHANNELS]; @@ -211,7 +220,7 @@ int audio_pipeline_output(void *output_app_data, output_audio_frames, 6); #endif - +printintln(444); #if appconfWW_ENABLED ww_audio_send(intertile_ctx, frame_count, @@ -336,14 +345,6 @@ void startup_task(void *arg) gpio_test(gpio_ctx_t0); #endif -#if appconfINTENT_ENABLED && !ON_TILE(WW_TILE_NO) - // Wait until the intent engine is initialized before starting the - // audio pipeline. - intent_engine_ready_sync(); -#endif - - audio_pipeline_init(NULL, NULL); - #if ON_TILE(FS_TILE_NO) rtos_fatfs_init(qspi_flash_ctx); //rtos_dfu_image_print_debug(dfu_image_ctx); @@ -351,11 +352,19 @@ void startup_task(void *arg) // NOTE: must call rtos_qspi_flash_fast_read_shutdown_ll to use non low-level mode calls rtos_qspi_flash_fast_read_setup_ll(qspi_flash_ctx); #endif -vTaskDelay(pdMS_TO_TICKS(100)); +//vTaskDelay(pdMS_TO_TICKS(100)); #if appconfWW_ENABLED && ON_TILE(WW_TILE_NO) ww_task_create(appconfWW_TASK_PRIORITY); #endif +#if appconfWW_ENABLED && !ON_TILE(WW_TILE_NO) + // Wait until the intent engine is initialized before starting the + // audio pipeline. + intent_engine_ready_sync(); +#endif + + audio_pipeline_init(NULL, NULL); + mem_analysis(); } diff --git a/examples/ffva/src/ww_model_runner/model_runner.c b/examples/ffva/src/ww_model_runner/model_runner.c index 2d1eb1a2..c6555fd5 100644 --- a/examples/ffva/src/ww_model_runner/model_runner.c +++ b/examples/ffva/src/ww_model_runner/model_runner.c @@ -35,7 +35,7 @@ void* grammar = (void*)gs_grammarLabel; #else void* grammar = NULL; #endif - +#include "print.h" // Model file is in flash at the offset specified in the CMakeLists // QSPI_FLASH_MODEL_START_ADDRESS variable. The XS1_SWMEM_BASE value needs // to be added so the address in in the SwMem range. @@ -61,19 +61,19 @@ void model_runner_manager(void *args) NULL, vIntentTimerCallback); */ + /* Alert other tile to start the audio pipeline */ + intent_engine_ready_sync(); devmem_init(&devmem_ctx); printf("Call asr_init(). model = 0x%x, grammar = 0x%x\n", (unsigned int) model, (unsigned int) grammar); asr_ctx = asr_init((int32_t *)model, (int32_t *)grammar, &devmem_ctx); + printintln(11111); - int32_t buf[appconfINTENT_SAMPLE_BLOCK_LENGTH] = {0}; - int16_t buf_short[SAMPLES_PER_ASR] = {0}; + int16_t buf[appconfINTENT_SAMPLE_BLOCK_LENGTH] = {0}; + //int16_t buf_short[SAMPLES_PER_ASR] = {0}; asr_reset(asr_ctx); - /* Alert other tile to start the audio pipeline */ - intent_engine_ready_sync(); - - size_t buf_short_index = 0; + //size_t buf_short_index = 0; asr_error_t asr_error; asr_result_t asr_result; int word_id; @@ -94,13 +94,13 @@ void model_runner_manager(void *args) } while(buf_len > 0); printintln(222); - for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { - buf_short[buf_short_index++] = buf[i] >> 16; - } - if (buf_short_index < SAMPLES_PER_ASR) - continue; + //for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { + // buf_short[buf_short_index++] = buf[i] >> 16; + //} + //if (buf_short_index < SAMPLES_PER_ASR) + // continue; - buf_short_index = 0; // reset the offset into the buffer of int16s. + //buf_short_index = 0; // reset the offset into the buffer of int16s. // Note, we do not need to overlap the window of samples. // This is handled in the ASR ports. @@ -108,16 +108,19 @@ void model_runner_manager(void *args) // so, we need to check if an audio response is playing and skip to the next // audio frame because the playback may trigger the ASR. //if (intent_handler_response_playing()) continue; - - asr_error = asr_process(asr_ctx, buf_short, SAMPLES_PER_ASR); + printintln(222); + asr_error = asr_process(asr_ctx, buf, SAMPLES_PER_ASR); + printintln(asr_error); if (asr_error == ASR_EVALUATION_EXPIRED) { //led_indicate_end_of_eval(); continue; } if (asr_error != ASR_OK) continue; + printintln(225); asr_error = asr_get_result(asr_ctx, &asr_result); + printintln(asr_error); if (asr_error != ASR_OK) continue; word_id = asr_result.id; diff --git a/examples/ffva/src/ww_model_runner/ww_model_runner.c b/examples/ffva/src/ww_model_runner/ww_model_runner.c index 335bd61c..a7ebd600 100644 --- a/examples/ffva/src/ww_model_runner/ww_model_runner.c +++ b/examples/ffva/src/ww_model_runner/ww_model_runner.c @@ -20,11 +20,13 @@ extern configSTACK_DEPTH_TYPE model_runner_manager_stack_size; static StreamBufferHandle_t audio_stream = NULL; - +#include "print.h" void ww_audio_send(rtos_intertile_t *intertile_ctx, size_t frame_count, int32_t (*processed_audio_frame)[2]) { + printintln(550); + configASSERT(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); uint16_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; @@ -47,7 +49,7 @@ void ww_audio_send(rtos_intertile_t *intertile_ctx, void ww_task_create(unsigned priority) { - audio_stream = xStreamBufferCreate(2 * appconfAUDIO_PIPELINE_FRAME_ADVANCE, + audio_stream = xStreamBufferCreate(8*2 * appconfAUDIO_PIPELINE_FRAME_ADVANCE, appconfWW_FRAMES_PER_INFERENCE); xTaskCreate((TaskFunction_t)model_runner_manager, @@ -60,18 +62,30 @@ void ww_task_create(unsigned priority) void intent_engine_ready_sync(void) { - int sync = 0; + //return; + int sync = 3456; #if ON_TILE(WW_TILE_NO) printintln(123); + printintln(get_local_tile_id()); size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); + printintln(124); + + printintln(sizeof(sync)); + xassert(len == sizeof(sync)); rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); - printintln(124); + printintln(sync); + printintln(127); #else printintln(125); + printintln(get_local_tile_id()); rtos_intertile_tx(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, &sync, sizeof(sync)); + printintln(sync); + + printintln(126); + #endif } diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index f37058d6..7345f66b 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -39,7 +39,7 @@ devmem_manager_t *devmem_ctx = NULL; //https://www.xmos.ai/documentation/XM-014363-PC-4/html/prog-guide/prog-ref/xcc-pragma-directives/pragmas.html //#pragma stackfunction n. This pragma allocates n words ( int s) of stack space for the next function declaration in the current translation unit. //pragma stackfunction 1500 => Stack size is 1500*sizeof(int) = 6000 -#pragma stackfunction 1500 +#pragma stackfunction 2500 asr_port_t asr_init(int32_t *model, int32_t *grammar, devmem_manager_t *devmem) { DSpotterInitData oDSpotterInitData; @@ -86,27 +86,30 @@ asr_port_t asr_init(int32_t *model, int32_t *grammar, devmem_manager_t *devmem) g_lpbyDSpotterMem = NULL; return NULL; } + printintln(1110); - DBG_TRACE("The list of trigger word: \r\n"); + DBG_TRACE("The list of trigger words: \r\n"); nCount = DSpotterHL_GetDisplayCommandCount(DSPOTTER_HL_TRIGGER_STAGE); for (int i = 0; i < nCount; i++) { DSpotterHL_GetDisplayCommand(DSPOTTER_HL_TRIGGER_STAGE, i, szCommand, sizeof(szCommand), &nCmdID); DBG_TRACE(" %s, ID = %d\r\n", szCommand, nCmdID); } + printintln(1111); nCount = DSpotterHL_GetDisplayCommandCount(DSPOTTER_HL_COMMAND_STAGE); + printintln(nCount); if (nCount > 0) { - DBG_TRACE("The list of command word: \r\n"); + DBG_TRACE("The list of command words: \r\n"); for (int i = 0; i < nCount; i++) { DSpotterHL_GetDisplayCommand(DSPOTTER_HL_COMMAND_STAGE, i, szCommand, sizeof(szCommand), &nCmdID); - DBG_TRACE(" %s, ID = %d\r\n", szCommand, nCmdID); + DBG_TRACE("%d %s, ID = %d\r\n", i, szCommand, nCmdID); } } DBG_TRACE("\r\n"); - + printintln(1112); return (asr_port_t)100; } @@ -140,7 +143,8 @@ asr_error_t asr_process(asr_port_t *ctx, int16_t *audio_buf, size_t buf_len) // uint32_t timer_start = get_reference_time(); int nRet = DSpotterHL_AddSampleNoFlow(audio_buf, buf_len); - + printintln(555); + printintln(nRet); // Uncomment the two lines below to compute MIPS usage. // uint32_t timer_end = get_reference_time(); // asr_printf("DSpotter processing time: %lu (us)\n", (timer_end - timer_start) / 100); diff --git a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c index 5c2b85a8..399e9b11 100644 --- a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c +++ b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c @@ -47,10 +47,12 @@ static void *audio_pipeline_input_i(void *input_app_data) memset(frame_data, 0x00, sizeof(frame_data_t)); size_t bytes_received = 0; + //printintln(1005); bytes_received = rtos_intertile_rx_len( intertile_ctx, appconfAUDIOPIPELINE_PORT, portMAX_DELAY); + //printintln(1006); xassert(bytes_received == sizeof(frame_data_t)); @@ -58,6 +60,7 @@ static void *audio_pipeline_input_i(void *input_app_data) intertile_ctx, frame_data, bytes_received); + //printintln(1007); return frame_data; } @@ -65,6 +68,8 @@ static void *audio_pipeline_input_i(void *input_app_data) static int audio_pipeline_output_i(frame_data_t *frame_data, void *output_app_data) { + printintln(1002); + return audio_pipeline_output(output_app_data, (int32_t **)frame_data->samples, 6, diff --git a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c index b8565046..9f18ef66 100644 --- a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c +++ b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c @@ -35,18 +35,21 @@ static aec_ctx_t DWORD_ALIGNED aec_state = {}; static void *audio_pipeline_input_i(void *input_app_data) { frame_data_t *frame_data; - + //printintln(789); frame_data = pvPortMalloc(sizeof(frame_data_t)); memset(frame_data, 0x00, sizeof(frame_data_t)); + //printintln(790); audio_pipeline_input(input_app_data, (int32_t **)frame_data->aec_reference_audio_samples, 4, appconfAUDIO_PIPELINE_FRAME_ADVANCE); + //printintln(791); frame_data->vnr_pred_flag = 0; memcpy(frame_data->samples, frame_data->mic_samples_passthrough, sizeof(frame_data->samples)); + //printintln(795); return frame_data; } @@ -54,10 +57,14 @@ static void *audio_pipeline_input_i(void *input_app_data) static int audio_pipeline_output_i(frame_data_t *frame_data, void *output_app_data) { + printintln(1000); + rtos_intertile_tx(intertile_ctx, appconfAUDIOPIPELINE_PORT, frame_data, sizeof(frame_data_t)); + printintln(1001); + return AUDIO_PIPELINE_FREE_FRAME; } From 5eb146095f01313e0642a1b2eb590958e43ee5f2 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Mar 2024 08:41:45 +0000 Subject: [PATCH 019/288] Remove some debug prints --- examples/ffva/src/main.c | 9 -------- .../ffva/src/ww_model_runner/model_runner.c | 7 ------ .../src/ww_model_runner/ww_model_runner.c | 22 +++++-------------- modules/asr/Cyberon/DSpotter_asr.c | 9 +++----- .../reference/fixed_delay/audio_pipeline_t0.c | 9 -------- .../reference/fixed_delay/audio_pipeline_t1.c | 10 --------- 6 files changed, 8 insertions(+), 58 deletions(-) diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 88ed28fd..6005fd1d 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -64,7 +64,6 @@ void audio_pipeline_input(void *input_app_data, size_t ch_count, size_t frame_count) { - printintln(888); (void) input_app_data; int32_t **mic_ptr = (int32_t **)(input_audio_frames + (2 * frame_count)); @@ -83,7 +82,6 @@ void audio_pipeline_input(void *input_app_data, flushed = 1; } } - printintln(890); /* * NOTE: ALWAYS receive the next frame from the PDM mics, @@ -95,7 +93,6 @@ void audio_pipeline_input(void *input_app_data, mic_ptr, frame_count, portMAX_DELAY); - //printintln(891); #if appconfUSB_ENABLED int32_t **usb_mic_audio_frame = NULL; @@ -118,7 +115,6 @@ void audio_pipeline_input(void *input_app_data, usb_mic_audio_frame, ch_cnt); #endif - //printintln(897); #if appconfI2S_ENABLED if (!appconfUSB_ENABLED || aec_ref_source == appconfAEC_REF_I2S) { @@ -143,7 +139,6 @@ void audio_pipeline_input(void *input_app_data, } } #endif - //printintln(898); } @@ -153,7 +148,6 @@ int audio_pipeline_output(void *output_app_data, size_t frame_count) { (void) output_app_data; - printintln(777); #if appconfI2S_ENABLED #if appconfI2S_MODE == appconfI2S_MODE_MASTER #if !appconfI2S_TDM_ENABLED @@ -166,7 +160,6 @@ int audio_pipeline_output(void *output_app_data, tmp[j][0] = *(tmpptr+j+(2*frame_count)); // ref 0 tmp[j][1] = *(tmpptr+j+(3*frame_count)); // ref 1 } -printintln(440); rtos_i2s_tx(i2s_ctx, (int32_t*) tmp, @@ -195,7 +188,6 @@ printintln(440); portMAX_DELAY); } #endif -printintln(441); #elif appconfI2S_MODE == appconfI2S_MODE_SLAVE /* I2S expects sample channel format */ @@ -220,7 +212,6 @@ printintln(441); output_audio_frames, 6); #endif -printintln(444); #if appconfWW_ENABLED ww_audio_send(intertile_ctx, frame_count, diff --git a/examples/ffva/src/ww_model_runner/model_runner.c b/examples/ffva/src/ww_model_runner/model_runner.c index c6555fd5..18547ec5 100644 --- a/examples/ffva/src/ww_model_runner/model_runner.c +++ b/examples/ffva/src/ww_model_runner/model_runner.c @@ -66,7 +66,6 @@ void model_runner_manager(void *args) devmem_init(&devmem_ctx); printf("Call asr_init(). model = 0x%x, grammar = 0x%x\n", (unsigned int) model, (unsigned int) grammar); asr_ctx = asr_init((int32_t *)model, (int32_t *)grammar, &devmem_ctx); - printintln(11111); int16_t buf[appconfINTENT_SAMPLE_BLOCK_LENGTH] = {0}; //int16_t buf_short[SAMPLES_PER_ASR] = {0}; @@ -82,7 +81,6 @@ void model_runner_manager(void *args) { /* Receive audio frames */ uint8_t *buf_ptr = (uint8_t*)buf; - printintln(111); size_t buf_len = appconfWW_FRAMES_PER_INFERENCE * sizeof(int16_t); do { size_t bytes_rxed = xStreamBufferReceive(input_queue, @@ -92,7 +90,6 @@ void model_runner_manager(void *args) buf_len -= bytes_rxed; buf_ptr += bytes_rxed; } while(buf_len > 0); - printintln(222); //for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { // buf_short[buf_short_index++] = buf[i] >> 16; @@ -108,19 +105,15 @@ void model_runner_manager(void *args) // so, we need to check if an audio response is playing and skip to the next // audio frame because the playback may trigger the ASR. //if (intent_handler_response_playing()) continue; - printintln(222); asr_error = asr_process(asr_ctx, buf, SAMPLES_PER_ASR); - printintln(asr_error); if (asr_error == ASR_EVALUATION_EXPIRED) { //led_indicate_end_of_eval(); continue; } if (asr_error != ASR_OK) continue; - printintln(225); asr_error = asr_get_result(asr_ctx, &asr_result); - printintln(asr_error); if (asr_error != ASR_OK) continue; word_id = asr_result.id; diff --git a/examples/ffva/src/ww_model_runner/ww_model_runner.c b/examples/ffva/src/ww_model_runner/ww_model_runner.c index a7ebd600..d1ecd631 100644 --- a/examples/ffva/src/ww_model_runner/ww_model_runner.c +++ b/examples/ffva/src/ww_model_runner/ww_model_runner.c @@ -25,16 +25,16 @@ void ww_audio_send(rtos_intertile_t *intertile_ctx, size_t frame_count, int32_t (*processed_audio_frame)[2]) { - printintln(550); configASSERT(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); - uint16_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; + int16_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; for (int i = 0; i < frame_count; i++) { - ww_samples[i] = (uint16_t)(processed_audio_frame[i][ASR_CHANNEL] >> 16); + ww_samples[i] = (int16_t)(processed_audio_frame[i][ASR_CHANNEL] >> 16); } - printintln(555); + printintln(processed_audio_frame[0][ASR_CHANNEL] ); + printintln(ww_samples[0]); if(audio_stream != NULL) { if (xStreamBufferSend(audio_stream, ww_samples, sizeof(ww_samples), 0) != sizeof(ww_samples)) { rtos_printf("lost output samples for ww\n"); @@ -49,7 +49,7 @@ void ww_audio_send(rtos_intertile_t *intertile_ctx, void ww_task_create(unsigned priority) { - audio_stream = xStreamBufferCreate(8*2 * appconfAUDIO_PIPELINE_FRAME_ADVANCE, + audio_stream = xStreamBufferCreate(appconfINTENT_FRAME_BUFFER_MULT * appconfAUDIO_PIPELINE_FRAME_ADVANCE, appconfWW_FRAMES_PER_INFERENCE); xTaskCreate((TaskFunction_t)model_runner_manager, @@ -65,26 +65,14 @@ void intent_engine_ready_sync(void) //return; int sync = 3456; #if ON_TILE(WW_TILE_NO) - printintln(123); - printintln(get_local_tile_id()); size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); - printintln(124); - - printintln(sizeof(sync)); xassert(len == sizeof(sync)); rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); - printintln(sync); - printintln(127); #else - printintln(125); - printintln(get_local_tile_id()); rtos_intertile_tx(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, &sync, sizeof(sync)); - printintln(sync); - - printintln(126); #endif } diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index 7345f66b..bfb21cb1 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -86,7 +86,6 @@ asr_port_t asr_init(int32_t *model, int32_t *grammar, devmem_manager_t *devmem) g_lpbyDSpotterMem = NULL; return NULL; } - printintln(1110); DBG_TRACE("The list of trigger words: \r\n"); nCount = DSpotterHL_GetDisplayCommandCount(DSPOTTER_HL_TRIGGER_STAGE); @@ -95,10 +94,8 @@ asr_port_t asr_init(int32_t *model, int32_t *grammar, devmem_manager_t *devmem) DSpotterHL_GetDisplayCommand(DSPOTTER_HL_TRIGGER_STAGE, i, szCommand, sizeof(szCommand), &nCmdID); DBG_TRACE(" %s, ID = %d\r\n", szCommand, nCmdID); } - printintln(1111); nCount = DSpotterHL_GetDisplayCommandCount(DSPOTTER_HL_COMMAND_STAGE); - printintln(nCount); if (nCount > 0) { DBG_TRACE("The list of command words: \r\n"); @@ -109,7 +106,6 @@ asr_port_t asr_init(int32_t *model, int32_t *grammar, devmem_manager_t *devmem) } } DBG_TRACE("\r\n"); - printintln(1112); return (asr_port_t)100; } @@ -134,6 +130,9 @@ asr_error_t asr_process(asr_port_t *ctx, int16_t *audio_buf, size_t buf_len) DBG_TRACE("."); } #endif + //printintln(buf_len); + printintln(audio_buf[0]); +// printintln(audio_buf[1]); #ifdef SKIP_DSPOTTER_RECOG return ASR_ERROR; @@ -143,8 +142,6 @@ asr_error_t asr_process(asr_port_t *ctx, int16_t *audio_buf, size_t buf_len) // uint32_t timer_start = get_reference_time(); int nRet = DSpotterHL_AddSampleNoFlow(audio_buf, buf_len); - printintln(555); - printintln(nRet); // Uncomment the two lines below to compute MIPS usage. // uint32_t timer_end = get_reference_time(); // asr_printf("DSpotter processing time: %lu (us)\n", (timer_end - timer_start) / 100); diff --git a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c index 399e9b11..2ec19776 100644 --- a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c +++ b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c @@ -47,12 +47,10 @@ static void *audio_pipeline_input_i(void *input_app_data) memset(frame_data, 0x00, sizeof(frame_data_t)); size_t bytes_received = 0; - //printintln(1005); bytes_received = rtos_intertile_rx_len( intertile_ctx, appconfAUDIOPIPELINE_PORT, portMAX_DELAY); - //printintln(1006); xassert(bytes_received == sizeof(frame_data_t)); @@ -60,7 +58,6 @@ static void *audio_pipeline_input_i(void *input_app_data) intertile_ctx, frame_data, bytes_received); - //printintln(1007); return frame_data; } @@ -68,7 +65,6 @@ static void *audio_pipeline_input_i(void *input_app_data) static int audio_pipeline_output_i(frame_data_t *frame_data, void *output_app_data) { - printintln(1002); return audio_pipeline_output(output_app_data, (int32_t **)frame_data->samples, @@ -148,7 +144,6 @@ void audio_pipeline_init( void *input_app_data, void *output_app_data) { - printintln(611); const int stage_count = 3; const pipeline_stage_t stages[] = { (pipeline_stage_t)stage_vnr_and_ic, @@ -161,12 +156,9 @@ void audio_pipeline_init( configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(stage_ns), configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(stage_agc) + RTOS_THREAD_STACK_SIZE(audio_pipeline_output_i), }; - printintln(612); initialize_pipeline_stages(); - printintln(613); - generic_pipeline_init((pipeline_input_t)audio_pipeline_input_i, (pipeline_output_t)audio_pipeline_output_i, @@ -176,7 +168,6 @@ void audio_pipeline_init( (const size_t*) stage_stack_sizes, appconfAUDIO_PIPELINE_TASK_PRIORITY, stage_count); - printintln(614); } diff --git a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c index 9f18ef66..44f262c2 100644 --- a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c +++ b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c @@ -35,21 +35,17 @@ static aec_ctx_t DWORD_ALIGNED aec_state = {}; static void *audio_pipeline_input_i(void *input_app_data) { frame_data_t *frame_data; - //printintln(789); frame_data = pvPortMalloc(sizeof(frame_data_t)); memset(frame_data, 0x00, sizeof(frame_data_t)); - //printintln(790); audio_pipeline_input(input_app_data, (int32_t **)frame_data->aec_reference_audio_samples, 4, appconfAUDIO_PIPELINE_FRAME_ADVANCE); - //printintln(791); frame_data->vnr_pred_flag = 0; memcpy(frame_data->samples, frame_data->mic_samples_passthrough, sizeof(frame_data->samples)); - //printintln(795); return frame_data; } @@ -57,13 +53,11 @@ static void *audio_pipeline_input_i(void *input_app_data) static int audio_pipeline_output_i(frame_data_t *frame_data, void *output_app_data) { - printintln(1000); rtos_intertile_tx(intertile_ctx, appconfAUDIOPIPELINE_PORT, frame_data, sizeof(frame_data_t)); - printintln(1001); return AUDIO_PIPELINE_FREE_FRAME; } @@ -159,7 +153,6 @@ void audio_pipeline_init( void *output_app_data) { const int stage_count = 2; - printintln(456); const pipeline_stage_t stages[] = { (pipeline_stage_t)stage_delay, (pipeline_stage_t)stage_aec, @@ -169,10 +162,8 @@ void audio_pipeline_init( configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(stage_delay) + RTOS_THREAD_STACK_SIZE(audio_pipeline_input_i), configMINIMAL_STACK_SIZE + RTOS_THREAD_STACK_SIZE(stage_aec) + RTOS_THREAD_STACK_SIZE(audio_pipeline_output_i), }; - printintln(457); initialize_pipeline_stages(); - printintln(458); generic_pipeline_init((pipeline_input_t)audio_pipeline_input_i, (pipeline_output_t)audio_pipeline_output_i, @@ -182,7 +173,6 @@ void audio_pipeline_init( (const size_t*) stage_stack_sizes, appconfAUDIO_PIPELINE_TASK_PRIORITY, stage_count); - printintln(459); } #endif /* ON_TILE(1) */ From ecf0a14076effacd4ccd421457836eeada2206f0 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Mar 2024 10:26:41 +0000 Subject: [PATCH 020/288] Copy files from FFD example --- examples/ffva/src/device_memory_impl.c | 7 +- .../src/intent_engine/.intent_engine.c.swo | Bin 0 -> 20480 bytes .../ffva/src/intent_engine/intent_engine.c | 221 + .../ffva/src/intent_engine/intent_engine.h | 35 + .../ffva/src/intent_engine/intent_engine_io.c | 131 + .../src/intent_engine/intent_engine_support.c | 133 + .../audio_response/audio_response.c | 112 + .../audio_response/audio_response.h | 13 + .../intent_handler/audio_response/dr_wav.h | 8309 +++++++++++++++++ .../audio_response/dr_wav_freertos_port.h | 94 + .../ffva/src/intent_handler/intent_handler.c | 105 + .../ffva/src/intent_handler/intent_handler.h | 26 + .../ffva/src/ww_model_runner/model_runner.c | 9 +- .../src/ww_model_runner/ww_model_runner.c | 6 +- 14 files changed, 9192 insertions(+), 9 deletions(-) create mode 100644 examples/ffva/src/intent_engine/.intent_engine.c.swo create mode 100644 examples/ffva/src/intent_engine/intent_engine.c create mode 100644 examples/ffva/src/intent_engine/intent_engine.h create mode 100644 examples/ffva/src/intent_engine/intent_engine_io.c create mode 100644 examples/ffva/src/intent_engine/intent_engine_support.c create mode 100644 examples/ffva/src/intent_handler/audio_response/audio_response.c create mode 100644 examples/ffva/src/intent_handler/audio_response/audio_response.h create mode 100644 examples/ffva/src/intent_handler/audio_response/dr_wav.h create mode 100644 examples/ffva/src/intent_handler/audio_response/dr_wav_freertos_port.h create mode 100644 examples/ffva/src/intent_handler/intent_handler.c create mode 100644 examples/ffva/src/intent_handler/intent_handler.h diff --git a/examples/ffva/src/device_memory_impl.c b/examples/ffva/src/device_memory_impl.c index 11976438..bba3e45e 100644 --- a/examples/ffva/src/device_memory_impl.c +++ b/examples/ffva/src/device_memory_impl.c @@ -46,20 +46,21 @@ void devmem_read_ext_local(void *dest, const void *src, size_t n) { //rtos_printf("devmem_read_ext_local dest=0x%x src=0x%x size=%d\n", dest, src, n); if (IS_FLASH(src)) { //uint32_t s = get_reference_time(); - int retval = -1; + int retval = -1; while (retval == -1) { // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset retval = rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); + //printintln(retval); } //uint32_t d = get_reference_time() - s; //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); } else { memcpy(dest, src, n); - } + } } void devmem_init(devmem_manager_t *devmem_ctx) { - xassert(devmem_ctx); + xassert(devmem_ctx); devmem_ctx->malloc = devmem_malloc_local; devmem_ctx->free = devmem_free_local; devmem_ctx->read_ext = devmem_read_ext_local; diff --git a/examples/ffva/src/intent_engine/.intent_engine.c.swo b/examples/ffva/src/intent_engine/.intent_engine.c.swo new file mode 100644 index 0000000000000000000000000000000000000000..3ae72edd3e851cf740af0399baf59f4ebeee52cf GIT binary patch literal 20480 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 L-Cm}Se!=@+)zDf8 literal 0 HcmV?d00001 diff --git a/examples/ffva/src/intent_engine/intent_engine.c b/examples/ffva/src/intent_engine/intent_engine.c new file mode 100644 index 00000000..33f0c421 --- /dev/null +++ b/examples/ffva/src/intent_engine/intent_engine.c @@ -0,0 +1,221 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* STD headers */ +#include +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" +#include "task.h" +#include "stream_buffer.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "intent_engine/intent_engine.h" +#include "intent_handler/intent_handler.h" +#include "asr.h" +#include "device_memory_impl.h" +#include "gpio_ctrl/leds.h" + +#if ON_TILE(ASR_TILE_NO) + +#if ASR_SENSORY + #define IS_KEYWORD(id) (id == 17) + #define IS_COMMAND(id) (id > 0 && id != 17) +#elif ASR_CYBERON + #define IS_KEYWORD(id) (id == 1) + #define IS_COMMAND(id) (id >= 2) +#else +#error "Model has to be either Sensory or Cyberon" +#endif + + +#define SAMPLES_PER_ASR (appconfINTENT_SAMPLE_BLOCK_LENGTH) +#define STOP_LISTENING_SOUND_WAV_ID (0) + +// SEARCH model file is specified in the CMakeLists SENSORY_COMMAND_SEARCH_SOURCE_FILE variable +#ifdef COMMAND_SEARCH_SOURCE_FILE +extern const unsigned short gs_grammarLabel[]; +void* grammar = (void*)gs_grammarLabel; +#else +void* grammar = NULL; +#endif + +// Model file is in flash at the offset specified in the CMakeLists +// QSPI_FLASH_MODEL_START_ADDRESS variable. The XS1_SWMEM_BASE value needs +// to be added so the address in in the SwMem range. +uint16_t *model = (uint16_t *) (XS1_SWMEM_BASE + QSPI_FLASH_MODEL_START_ADDRESS); + +typedef enum intent_state { + STATE_EXPECTING_WAKEWORD, + STATE_EXPECTING_COMMAND, + STATE_PROCESSING_COMMAND +} intent_state_t; + +enum timeout_event { + TIMEOUT_EVENT_NONE = 0, + TIMEOUT_EVENT_INTENT = 1 +}; + +static intent_state_t intent_state; +static asr_port_t asr_ctx; +static devmem_manager_t devmem_ctx; + +static uint32_t timeout_event = TIMEOUT_EVENT_NONE; + +static void vIntentTimerCallback(TimerHandle_t pxTimer); +static void receive_audio_frames(StreamBufferHandle_t input_queue, int32_t *buf, + int16_t *buf_short, size_t *buf_short_index); +static void timeout_event_handler(TimerHandle_t pxTimer); + +static void vIntentTimerCallback(TimerHandle_t pxTimer) +{ + switch (intent_state) { + case STATE_EXPECTING_COMMAND: + case STATE_PROCESSING_COMMAND: + timeout_event |= TIMEOUT_EVENT_INTENT; + break; + default: + break; + } +} + +static void receive_audio_frames(StreamBufferHandle_t input_queue, int32_t *buf, + int16_t *buf_short, size_t *buf_short_index) +{ + uint8_t *buf_ptr = (uint8_t*)buf; + size_t buf_len = appconfINTENT_SAMPLE_BLOCK_LENGTH * sizeof(int32_t); + + do { + size_t bytes_rxed = xStreamBufferReceive(input_queue, + buf_ptr, + buf_len, + portMAX_DELAY); + buf_len -= bytes_rxed; + buf_ptr += bytes_rxed; + } while (buf_len > 0); + + for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { + buf_short[(*buf_short_index)++] = buf[i] >> 16; + } +} + +static void timeout_event_handler(TimerHandle_t pxTimer) +{ + if (timeout_event & TIMEOUT_EVENT_INTENT) { + timeout_event &= ~TIMEOUT_EVENT_INTENT; + intent_engine_play_response(STOP_LISTENING_SOUND_WAV_ID); + led_indicate_waiting(); + intent_state = STATE_EXPECTING_WAKEWORD; + } +} + +#pragma stackfunction 1000 +void intent_engine_task(void *args) +{ + intent_state = STATE_EXPECTING_WAKEWORD; + + StreamBufferHandle_t input_queue = (StreamBufferHandle_t)args; + TimerHandle_t int_eng_tmr = xTimerCreate( + "int_eng_tmr", + pdMS_TO_TICKS(appconfINTENT_RESET_DELAY_MS), + pdFALSE, + NULL, + vIntentTimerCallback); + + devmem_init(&devmem_ctx); + printf("Call asr_init(). model = 0x%x, grammar = 0x%x\n", (unsigned int) model, (unsigned int) grammar); + asr_ctx = asr_init((int32_t *)model, (int32_t *)grammar, &devmem_ctx); + + int32_t buf[appconfINTENT_SAMPLE_BLOCK_LENGTH] = {0}; + int16_t buf_short[SAMPLES_PER_ASR] = {0}; + + asr_reset(asr_ctx); + + /* Alert other tile to start the audio pipeline */ + intent_engine_ready_sync(); + + asr_error_t asr_error; + asr_result_t asr_result; + int word_id; + + size_t buf_short_index = 0; + + while (1) + { + timeout_event_handler(int_eng_tmr); + receive_audio_frames(input_queue, buf, buf_short, &buf_short_index); + + if (buf_short_index < SAMPLES_PER_ASR) + continue; + + buf_short_index = 0; // reset the offset into the buffer of int16s. + // Note, we do not need to overlap the window of samples. + // This is handled in the ASR ports. + + // this application does not support barge-in + // so, we need to check if an audio response is playing and skip to the next + // audio frame because the playback may trigger the ASR. + if (intent_handler_response_playing()) continue; + + asr_error = asr_process(asr_ctx, buf_short, SAMPLES_PER_ASR); + + if (asr_error == ASR_EVALUATION_EXPIRED) { + led_indicate_end_of_eval(); + continue; + } + if (asr_error != ASR_OK) continue; + + asr_error = asr_get_result(asr_ctx, &asr_result); + if (asr_error != ASR_OK) continue; + + word_id = asr_result.id; + + if (!IS_KEYWORD(word_id) && !IS_COMMAND(word_id)) continue; + + + #if appconfINTENT_RAW_OUTPUT + intent_engine_process_asr_result(word_id); + #else + if (intent_state == STATE_EXPECTING_WAKEWORD && IS_KEYWORD(word_id)) { + led_indicate_listening(); + xTimerStart(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + intent_state = STATE_EXPECTING_COMMAND; + } else if (intent_state == STATE_EXPECTING_COMMAND && IS_COMMAND(word_id)) { + xTimerReset(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + intent_state = STATE_PROCESSING_COMMAND; + } else if (intent_state == STATE_EXPECTING_COMMAND && IS_KEYWORD(word_id)) { + xTimerReset(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + // remain in STATE_EXPECTING_COMMAND state + } else if (intent_state == STATE_PROCESSING_COMMAND && IS_KEYWORD(word_id)) { + xTimerReset(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + intent_state = STATE_EXPECTING_COMMAND; + } else if (intent_state == STATE_PROCESSING_COMMAND && IS_COMMAND(word_id)) { + xTimerReset(int_eng_tmr, 0); + intent_engine_process_asr_result(word_id); + // remain in STATE_PROCESSING_COMMAND state + } + #endif + } +} + +#endif /* ON_TILE(ASR_TILE_NO) */ + +void intent_engine_ready_sync(void) +{ + int sync = 0; +#if ON_TILE(AUDIO_PIPELINE_TILE_NO) + size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); + xassert(len == sizeof(sync)); + rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); +#else + rtos_intertile_tx(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, &sync, sizeof(sync)); +#endif +} diff --git a/examples/ffva/src/intent_engine/intent_engine.h b/examples/ffva/src/intent_engine/intent_engine.h new file mode 100644 index 00000000..1cf46d39 --- /dev/null +++ b/examples/ffva/src/intent_engine/intent_engine.h @@ -0,0 +1,35 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef INTENT_ENGINE_H_ +#define INTENT_ENGINE_H_ + +#include +#include +#include + +#include "asr.h" +#include "rtos_intertile.h" + +int32_t intent_engine_create(uint32_t priority, void *args); +void intent_engine_ready_sync(void); + +void intent_engine_task(void *args); +void intent_engine_task_create(unsigned priority); +void intent_engine_intertile_task_create(uint32_t priority); + +int32_t intent_engine_sample_push(int32_t *buf, size_t frames); +void intent_engine_samples_send_local( + size_t frame_count, + int32_t *processed_audio_frame); +void intent_engine_samples_send_remote( + rtos_intertile_t *intertile, + size_t frame_count, + int32_t *processed_audio_frame); + + +void intent_engine_stream_buf_reset(void); +void intent_engine_play_response(int wav_id); +void intent_engine_process_asr_result(int word_id); + +#endif /* INTENT_ENGINE_H_ */ diff --git a/examples/ffva/src/intent_engine/intent_engine_io.c b/examples/ffva/src/intent_engine/intent_engine_io.c new file mode 100644 index 00000000..9077af51 --- /dev/null +++ b/examples/ffva/src/intent_engine/intent_engine_io.c @@ -0,0 +1,131 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* STD headers */ +#include +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" +#include "task.h" +#include "stream_buffer.h" +#include "queue.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "intent_engine.h" + +static QueueHandle_t q_intent = 0; + +// look up table to converting ASR IDs to wav file IDs or strings +#define ASR_NUMBER_OF_COMMANDS (17) + +typedef struct asr_lut_struct +{ + int asr_id; // ASR response IDs + int wav_id; // Wav file IDs corresponding to audio_files_en[] array in audio_response.c + const char* text; // String output +} asr_lut_t; + +#if ASR_CYBERON +static asr_lut_t asr_lut[ASR_NUMBER_OF_COMMANDS] = { + {1, 1, "Hello XMOS"}, + {2, 2, "Switch on the TV"}, + {3, 3, "Switch off the TV"}, + {4, 4, "Channel up"}, + {5, 5, "Channel down"}, + {6, 6, "Volume up"}, + {7, 7, "Volume down"}, + {8, 8, "Switch on the lights"}, + {9, 9, "Switch off the lights"}, + {10, 10, "Brightness up"}, + {11, 11, "Brightness down"}, + {12, 12, "Switch on the fan"}, + {13, 13, "Switch off the fan"}, + {14, 14, "Speed up the fan"}, + {15, 15, "Slow down the fan"}, + {16, 16, "Set higher temperature"}, + {17, 17, "Set lower temperature"} +}; +#elif ASR_SENSORY +static asr_lut_t asr_lut[ASR_NUMBER_OF_COMMANDS] = { + {1, 2, "Switch on the TV"}, + {2, 4, "Channel up"}, + {3, 5, "Channel down"}, + {4, 6, "Volume up"}, + {5, 7, "Volume down"}, + {6, 3, "Switch off the TV"}, + {7, 8, "Switch on the lights"}, + {8, 10, "Brightness up"}, + {9, 11, "Brightness down"}, + {10, 9, "Switch off the lights"}, + {11, 12, "Switch on the fan"}, + {12, 14, "Speed up the fan"}, + {13, 15, "Slow down the fan"}, + {14, 16, "Set higher temperature"}, + {15, 17, "Set lower temperature"}, + {16, 13, "Switch off the fan"}, + {17, 1, "Hello XMOS"} +}; +#else +#error "Model has to be either Sensory or Cyberon" +#endif + + + +void intent_engine_play_response(int wav_id) +{ + if(q_intent != 0) { + if(xQueueSend(q_intent, (void *)&wav_id, (TickType_t)0) != pdPASS) { + rtos_printf("Lost wav playback. Queue was full.\n"); + } + } +} + +void intent_engine_process_asr_result(int word_id) +{ + int wav_id = 0; + const char* text = ""; + + for (int i=0; i +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" +#include "task.h" +#include "stream_buffer.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "intent_engine/intent_engine.h" + +#if ON_TILE(ASR_TILE_NO) + +static StreamBufferHandle_t samples_to_engine_stream_buf = 0; + +void intent_engine_stream_buf_reset(void) +{ + if (samples_to_engine_stream_buf) + while (xStreamBufferReset(samples_to_engine_stream_buf) == pdFAIL) + vTaskDelay(pdMS_TO_TICKS(1)); +} + +#endif /* ON_TILE(ASR_TILE_NO) */ + +#if ASR_TILE_NO != AUDIO_PIPELINE_TILE_NO +#if ON_TILE(AUDIO_PIPELINE_TILE_NO) + +void intent_engine_samples_send_remote( + rtos_intertile_t *intertile, + size_t frame_count, + int32_t *processed_audio_frame) +{ + configASSERT(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); + + rtos_intertile_tx(intertile, + appconfINTENT_MODEL_RUNNER_SAMPLES_PORT, + processed_audio_frame, + sizeof(int32_t) * frame_count); +} + +#else /* ON_TILE(AUDIO_PIPELINE_TILE_NO) */ + +static void intent_engine_intertile_samples_in_task(void *arg) +{ + (void) arg; + + for (;;) { + int32_t samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; + size_t bytes_received; + + bytes_received = rtos_intertile_rx_len( + intertile_ap_ctx, + appconfINTENT_MODEL_RUNNER_SAMPLES_PORT, + portMAX_DELAY); + + xassert(bytes_received == sizeof(samples)); + + rtos_intertile_rx_data( + intertile_ap_ctx, + samples, + bytes_received); + + if (xStreamBufferSend(samples_to_engine_stream_buf, samples, sizeof(samples), 0) != sizeof(samples)) { + rtos_printf("lost output samples for intent\n"); + } + } +} + +void intent_engine_intertile_task_create(uint32_t priority) +{ + samples_to_engine_stream_buf = xStreamBufferCreate( + appconfINTENT_FRAME_BUFFER_MULT * appconfAUDIO_PIPELINE_FRAME_ADVANCE, + appconfINTENT_SAMPLE_BLOCK_LENGTH); + + xTaskCreate((TaskFunction_t)intent_engine_intertile_samples_in_task, + "int_intertile_rx", + RTOS_THREAD_STACK_SIZE(intent_engine_intertile_samples_in_task), + NULL, + priority-1, + NULL); + xTaskCreate((TaskFunction_t)intent_engine_task, + "intent_eng", + RTOS_THREAD_STACK_SIZE(intent_engine_task), + samples_to_engine_stream_buf, + uxTaskPriorityGet(NULL), + NULL); +} + +#endif /* ON_TILE(AUDIO_PIPELINE_TILE_NO) */ +#endif /* ASR_TILE_NO != AUDIO_PIPELINE_TILE_NO */ + +#if ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO +#if ON_TILE(ASR_TILE_NO) + +void intent_engine_samples_send_local( + size_t frame_count, + int32_t *processed_audio_frame) +{ + configASSERT(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); + + if(samples_to_engine_stream_buf != NULL) { + size_t bytes_to_send = sizeof(int32_t) * frame_count; + if (xStreamBufferSend(samples_to_engine_stream_buf, processed_audio_frame, bytes_to_send, 0) != bytes_to_send) { + rtos_printf("lost local output samples for intent\n"); + } + } else { + rtos_printf("intent engine streambuffer not ready\n"); + } +} + +void intent_engine_task_create(unsigned priority) +{ + samples_to_engine_stream_buf = xStreamBufferCreate( + appconfINTENT_FRAME_BUFFER_MULT * appconfAUDIO_PIPELINE_FRAME_ADVANCE, + appconfINTENT_SAMPLE_BLOCK_LENGTH); + + xTaskCreate((TaskFunction_t)intent_engine_task, + "intent_eng", + RTOS_THREAD_STACK_SIZE(intent_engine_task), + samples_to_engine_stream_buf, + uxTaskPriorityGet(NULL), + NULL); +} + +#endif /* ON_TILE(ASR_TILE_NO) */ +#endif /* ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO */ diff --git a/examples/ffva/src/intent_handler/audio_response/audio_response.c b/examples/ffva/src/intent_handler/audio_response/audio_response.c new file mode 100644 index 00000000..f28024fe --- /dev/null +++ b/examples/ffva/src/intent_handler/audio_response/audio_response.c @@ -0,0 +1,112 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* STD headers */ +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "intent_handler/intent_handler.h" +#include "audio_response.h" +#include "fs_support.h" +#include "ff.h" +#include "dr_wav_freertos_port.h" + +static const char *audio_files_en[] = { + "50.wav", /* sleep */ + "1.wav", /* wakeup */ + "3.wav", /* tv_on */ + "4.wav", /* tv_off */ + "5.wav", /* ch_up */ + "6.wav", /* ch_down */ + "7.wav", /* vol_up */ + "8.wav", /* vol_down */ + "9.wav", /* lights_on */ + "10.wav", /* lights_off */ + "11.wav", /* lights_up*/ + "12.wav", /* lights_down */ + "13.wav", /* fan_on */ + "14.wav", /* fan_off */ + "15.wav", /* fan_up */ + "16.wav", /* fan_down */ + "17.wav", /* temp_up */ + "18.wav", /* temp_down */ +}; + +#define NUM_FILES (sizeof(audio_files_en) / sizeof(char *)) + +static int16_t file_audio[appconfAUDIO_PIPELINE_FRAME_ADVANCE * sizeof(int16_t)]; +static int32_t i2s_audio[2*(appconfAUDIO_PIPELINE_FRAME_ADVANCE * sizeof(int32_t))]; +static drwav *wav_files = NULL; + +#pragma stackfunction 3000 + +int32_t audio_response_init(void) { + FRESULT result = 0; + FIL *files = pvPortMalloc(NUM_FILES * sizeof(FIL)); + wav_files = pvPortMalloc(NUM_FILES * sizeof(drwav)); + + configASSERT(files); + configASSERT(wav_files); + configASSERT(file_audio); + configASSERT(i2s_audio); + + for (int i=0; i + +int32_t audio_response_init(void); + +void audio_response_play(int32_t id); + +#endif /* AUDIO_RESPONSE_H_ */ diff --git a/examples/ffva/src/intent_handler/audio_response/dr_wav.h b/examples/ffva/src/intent_handler/audio_response/dr_wav.h new file mode 100644 index 00000000..94edf4ae --- /dev/null +++ b/examples/ffva/src/intent_handler/audio_response/dr_wav.h @@ -0,0 +1,8309 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +/* +WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. +dr_wav - v0.13.6 - 2022-04-10 + +David Reid - mackron@gmail.com + +GitHub: https://github.com/mackron/dr_libs +*/ + +/* +Introduction +============ +This is a single file library. To use it, do something like the following in one .c file. + + ```c + #define DR_WAV_IMPLEMENTATION + #include "dr_wav.h" + ``` + +You can then #include this file in other parts of the program as you would with any other header file. Do something like the following to read audio data: + + ```c + drwav wav; + if (!drwav_init_file(&wav, "my_song.wav", NULL)) { + // Error opening WAV file. + } + + drwav_int32* pDecodedInterleavedPCMFrames = malloc(wav.totalPCMFrameCount * wav.channels * sizeof(drwav_int32)); + size_t numberOfSamplesActuallyDecoded = drwav_read_pcm_frames_s32(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); + + ... + + drwav_uninit(&wav); + ``` + +If you just want to quickly open and read the audio data in a single operation you can do something like this: + + ```c + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalPCMFrameCount; + float* pSampleData = drwav_open_file_and_read_pcm_frames_f32("my_song.wav", &channels, &sampleRate, &totalPCMFrameCount, NULL); + if (pSampleData == NULL) { + // Error opening and reading WAV file. + } + + ... + + drwav_free(pSampleData, NULL); + ``` + +The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in this case), but you can still output the +audio data in its internal format (see notes below for supported formats): + + ```c + size_t framesRead = drwav_read_pcm_frames(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); + ``` + +You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for a particular data format: + + ```c + size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); + ``` + +dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at `drwav_init_write()`, +`drwav_init_file_write()`, etc. Use `drwav_write_pcm_frames()` to write samples, or `drwav_write_raw()` to write raw data in the "data" chunk. + + ```c + drwav_data_format format; + format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. + format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. + format.channels = 2; + format.sampleRate = 44100; + format.bitsPerSample = 16; + drwav_init_file_write(&wav, "data/recording.wav", &format, NULL); + + ... + + drwav_uint64 framesWritten = drwav_write_pcm_frames(pWav, frameCount, pSamples); + ``` + +dr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work without any manual intervention. + + +Build Options +============= +#define these options before including this file. + +#define DR_WAV_NO_CONVERSION_API + Disables conversion APIs such as `drwav_read_pcm_frames_f32()` and `drwav_s16_to_f32()`. + +#define DR_WAV_NO_STDIO + Disables APIs that initialize a decoder from a file such as `drwav_init_file()`, `drwav_init_file_write()`, etc. + + + +Notes +===== +- Samples are always interleaved. +- The default read function does not do any data conversion. Use `drwav_read_pcm_frames_f32()`, `drwav_read_pcm_frames_s32()` and `drwav_read_pcm_frames_s16()` + to read and convert audio data to 32-bit floating point, signed 32-bit integer and signed 16-bit integer samples respectively. Tested and supported internal + formats include the following: + - Unsigned 8-bit PCM + - Signed 12-bit PCM + - Signed 16-bit PCM + - Signed 24-bit PCM + - Signed 32-bit PCM + - IEEE 32-bit floating point + - IEEE 64-bit floating point + - A-law and u-law + - Microsoft ADPCM + - IMA ADPCM (DVI, format code 0x11) +- dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. +*/ + +#ifndef dr_wav_h +#define dr_wav_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define DRWAV_STRINGIFY(x) #x +#define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) + +#define DRWAV_VERSION_MAJOR 0 +#define DRWAV_VERSION_MINOR 13 +#define DRWAV_VERSION_REVISION 6 +#define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized types. */ +typedef signed char drwav_int8; +typedef unsigned char drwav_uint8; +typedef signed short drwav_int16; +typedef unsigned short drwav_uint16; +typedef signed int drwav_int32; +typedef unsigned int drwav_uint32; +#if defined(_MSC_VER) && !defined(__clang__) + typedef signed __int64 drwav_int64; + typedef unsigned __int64 drwav_uint64; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drwav_int64; + typedef unsigned long long drwav_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + typedef drwav_uint64 drwav_uintptr; +#else + typedef drwav_uint32 drwav_uintptr; +#endif +typedef drwav_uint8 drwav_bool8; +typedef drwav_uint32 drwav_bool32; +#define DRWAV_TRUE 1 +#define DRWAV_FALSE 0 + +#if !defined(DRWAV_API) + #if defined(DRWAV_DLL) + #if defined(_WIN32) + #define DRWAV_DLL_IMPORT __declspec(dllimport) + #define DRWAV_DLL_EXPORT __declspec(dllexport) + #define DRWAV_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRWAV_DLL_IMPORT __attribute__((visibility("default"))) + #define DRWAV_DLL_EXPORT __attribute__((visibility("default"))) + #define DRWAV_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRWAV_DLL_IMPORT + #define DRWAV_DLL_EXPORT + #define DRWAV_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) + #define DRWAV_API DRWAV_DLL_EXPORT + #else + #define DRWAV_API DRWAV_DLL_IMPORT + #endif + #define DRWAV_PRIVATE DRWAV_DLL_PRIVATE + #else + #define DRWAV_API extern + #define DRWAV_PRIVATE static + #endif +#endif + +typedef drwav_int32 drwav_result; +#define DRWAV_SUCCESS 0 +#define DRWAV_ERROR -1 /* A generic error. */ +#define DRWAV_INVALID_ARGS -2 +#define DRWAV_INVALID_OPERATION -3 +#define DRWAV_OUT_OF_MEMORY -4 +#define DRWAV_OUT_OF_RANGE -5 +#define DRWAV_ACCESS_DENIED -6 +#define DRWAV_DOES_NOT_EXIST -7 +#define DRWAV_ALREADY_EXISTS -8 +#define DRWAV_TOO_MANY_OPEN_FILES -9 +#define DRWAV_INVALID_FILE -10 +#define DRWAV_TOO_BIG -11 +#define DRWAV_PATH_TOO_LONG -12 +#define DRWAV_NAME_TOO_LONG -13 +#define DRWAV_NOT_DIRECTORY -14 +#define DRWAV_IS_DIRECTORY -15 +#define DRWAV_DIRECTORY_NOT_EMPTY -16 +#define DRWAV_END_OF_FILE -17 +#define DRWAV_NO_SPACE -18 +#define DRWAV_BUSY -19 +#define DRWAV_IO_ERROR -20 +#define DRWAV_INTERRUPT -21 +#define DRWAV_UNAVAILABLE -22 +#define DRWAV_ALREADY_IN_USE -23 +#define DRWAV_BAD_ADDRESS -24 +#define DRWAV_BAD_SEEK -25 +#define DRWAV_BAD_PIPE -26 +#define DRWAV_DEADLOCK -27 +#define DRWAV_TOO_MANY_LINKS -28 +#define DRWAV_NOT_IMPLEMENTED -29 +#define DRWAV_NO_MESSAGE -30 +#define DRWAV_BAD_MESSAGE -31 +#define DRWAV_NO_DATA_AVAILABLE -32 +#define DRWAV_INVALID_DATA -33 +#define DRWAV_TIMEOUT -34 +#define DRWAV_NO_NETWORK -35 +#define DRWAV_NOT_UNIQUE -36 +#define DRWAV_NOT_SOCKET -37 +#define DRWAV_NO_ADDRESS -38 +#define DRWAV_BAD_PROTOCOL -39 +#define DRWAV_PROTOCOL_UNAVAILABLE -40 +#define DRWAV_PROTOCOL_NOT_SUPPORTED -41 +#define DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRWAV_SOCKET_NOT_SUPPORTED -44 +#define DRWAV_CONNECTION_RESET -45 +#define DRWAV_ALREADY_CONNECTED -46 +#define DRWAV_NOT_CONNECTED -47 +#define DRWAV_CONNECTION_REFUSED -48 +#define DRWAV_NO_HOST -49 +#define DRWAV_IN_PROGRESS -50 +#define DRWAV_CANCELLED -51 +#define DRWAV_MEMORY_ALREADY_MAPPED -52 +#define DRWAV_AT_END -53 + +/* Common data formats. */ +#define DR_WAVE_FORMAT_PCM 0x1 +#define DR_WAVE_FORMAT_ADPCM 0x2 +#define DR_WAVE_FORMAT_IEEE_FLOAT 0x3 +#define DR_WAVE_FORMAT_ALAW 0x6 +#define DR_WAVE_FORMAT_MULAW 0x7 +#define DR_WAVE_FORMAT_DVI_ADPCM 0x11 +#define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE + +/* Flags to pass into drwav_init_ex(), etc. */ +#define DRWAV_SEQUENTIAL 0x00000001 + +DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision); +DRWAV_API const char* drwav_version_string(void); + +typedef enum +{ + drwav_seek_origin_start, + drwav_seek_origin_current +} drwav_seek_origin; + +typedef enum +{ + drwav_container_riff, + drwav_container_w64, + drwav_container_rf64 +} drwav_container; + +typedef struct +{ + union + { + drwav_uint8 fourcc[4]; + drwav_uint8 guid[16]; + } id; + + /* The size in bytes of the chunk. */ + drwav_uint64 sizeInBytes; + + /* + RIFF = 2 byte alignment. + W64 = 8 byte alignment. + */ + unsigned int paddingSize; +} drwav_chunk_header; + +typedef struct +{ + /* + The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications + that require support for data formats not natively supported by dr_wav. + */ + drwav_uint16 formatTag; + + /* The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. */ + drwav_uint16 channels; + + /* The sample rate. Usually set to something like 44100. */ + drwav_uint32 sampleRate; + + /* Average bytes per second. You probably don't need this, but it's left here for informational purposes. */ + drwav_uint32 avgBytesPerSec; + + /* Block align. This is equal to the number of channels * bytes per sample. */ + drwav_uint16 blockAlign; + + /* Bits per sample. */ + drwav_uint16 bitsPerSample; + + /* The size of the extended data. Only used internally for validation, but left here for informational purposes. */ + drwav_uint16 extendedSize; + + /* + The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, + is always rounded up to the nearest multiple of 8. This variable contains information about exactly how + many bits are valid per sample. Mainly used for informational purposes. + */ + drwav_uint16 validBitsPerSample; + + /* The channel mask. Not used at the moment. */ + drwav_uint32 channelMask; + + /* The sub-format, exactly as specified by the wave file. */ + drwav_uint8 subFormat[16]; +} drwav_fmt; + +DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT); + + +/* +Callback for when data is read. Return value is the number of bytes actually read. + +pUserData [in] The user data that was passed to drwav_init() and family. +pBufferOut [out] The output buffer. +bytesToRead [in] The number of bytes to read. + +Returns the number of bytes actually read. + +A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +either the entire bytesToRead is filled or you have reached the end of the stream. +*/ +typedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +/* +Callback for when data is written. Returns value is the number of bytes actually written. + +pUserData [in] The user data that was passed to drwav_init_write() and family. +pData [out] A pointer to the data to write. +bytesToWrite [in] The number of bytes to write. + +Returns the number of bytes actually written. + +If the return value differs from bytesToWrite, it indicates an error. +*/ +typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); + +/* +Callback for when data needs to be seeked. + +pUserData [in] The user data that was passed to drwav_init() and family. +offset [in] The number of bytes to move, relative to the origin. Will never be negative. +origin [in] The origin of the seek - the current position or the start of the stream. + +Returns whether or not the seek was successful. + +Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be either drwav_seek_origin_start or +drwav_seek_origin_current. +*/ +typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); + +/* +Callback for when drwav_init_ex() finds a chunk. + +pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex() and family. +onRead [in] A pointer to the function to call when reading. +onSeek [in] A pointer to the function to call when seeking. +pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex() and family. +pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk. +container [in] Whether or not the WAV file is a RIFF or Wave64 container. If you're unsure of the difference, assume RIFF. +pFMT [in] A pointer to the object containing the contents of the "fmt" chunk. + +Returns the number of bytes read + seeked. + +To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same for seeking with onSeek(). The return value must +be the total number of bytes you have read _plus_ seeked. + +Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should +use `id.fourcc`, otherwise you should use `id.guid`. + +The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the +`DR_WAVE_FORMAT_*` identifiers. + +The read pointer will be sitting on the first byte after the chunk's header. You must not attempt to read beyond the boundary of the chunk. +*/ +typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader, drwav_container container, const drwav_fmt* pFMT); + +typedef struct +{ + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} drwav_allocation_callbacks; + +/* Structure for internal use. Only used for loaders opened with drwav_init_memory(). */ +typedef struct +{ + const drwav_uint8* data; + size_t dataSize; + size_t currentReadPos; +} drwav__memory_stream; + +/* Structure for internal use. Only used for writers opened with drwav_init_memory_write(). */ +typedef struct +{ + void** ppData; + size_t* pDataSize; + size_t dataSize; + size_t dataCapacity; + size_t currentWritePos; +} drwav__memory_stream_write; + +typedef struct +{ + drwav_container container; /* RIFF, W64. */ + drwav_uint32 format; /* DR_WAVE_FORMAT_* */ + drwav_uint32 channels; + drwav_uint32 sampleRate; + drwav_uint32 bitsPerSample; +} drwav_data_format; + +typedef enum +{ + drwav_metadata_type_none = 0, + + /* + Unknown simply means a chunk that drwav does not handle specifically. You can still ask to + receive these chunks as metadata objects. It is then up to you to interpret the chunk's data. + You can also write unknown metadata to a wav file. Be careful writing unknown chunks if you + have also edited the audio data. The unknown chunks could represent offsets/sizes that no + longer correctly correspond to the audio data. + */ + drwav_metadata_type_unknown = 1 << 0, + + /* Only 1 of each of these metadata items are allowed in a wav file. */ + drwav_metadata_type_smpl = 1 << 1, + drwav_metadata_type_inst = 1 << 2, + drwav_metadata_type_cue = 1 << 3, + drwav_metadata_type_acid = 1 << 4, + drwav_metadata_type_bext = 1 << 5, + + /* + Wav files often have a LIST chunk. This is a chunk that contains a set of subchunks. For this + higher-level metadata API, we don't make a distinction between a regular chunk and a LIST + subchunk. Instead, they are all just 'metadata' items. + + There can be multiple of these metadata items in a wav file. + */ + drwav_metadata_type_list_label = 1 << 6, + drwav_metadata_type_list_note = 1 << 7, + drwav_metadata_type_list_labelled_cue_region = 1 << 8, + + drwav_metadata_type_list_info_software = 1 << 9, + drwav_metadata_type_list_info_copyright = 1 << 10, + drwav_metadata_type_list_info_title = 1 << 11, + drwav_metadata_type_list_info_artist = 1 << 12, + drwav_metadata_type_list_info_comment = 1 << 13, + drwav_metadata_type_list_info_date = 1 << 14, + drwav_metadata_type_list_info_genre = 1 << 15, + drwav_metadata_type_list_info_album = 1 << 16, + drwav_metadata_type_list_info_tracknumber = 1 << 17, + + /* Other type constants for convenience. */ + drwav_metadata_type_list_all_info_strings = drwav_metadata_type_list_info_software + | drwav_metadata_type_list_info_copyright + | drwav_metadata_type_list_info_title + | drwav_metadata_type_list_info_artist + | drwav_metadata_type_list_info_comment + | drwav_metadata_type_list_info_date + | drwav_metadata_type_list_info_genre + | drwav_metadata_type_list_info_album + | drwav_metadata_type_list_info_tracknumber, + + drwav_metadata_type_list_all_adtl = drwav_metadata_type_list_label + | drwav_metadata_type_list_note + | drwav_metadata_type_list_labelled_cue_region, + + drwav_metadata_type_all = -2, /*0xFFFFFFFF & ~drwav_metadata_type_unknown,*/ + drwav_metadata_type_all_including_unknown = -1 /*0xFFFFFFFF,*/ +} drwav_metadata_type; + +/* +Sampler Metadata + +The sampler chunk contains information about how a sound should be played in the context of a whole +audio production, and when used in a sampler. See https://en.wikipedia.org/wiki/Sample-based_synthesis. +*/ +typedef enum +{ + drwav_smpl_loop_type_forward = 0, + drwav_smpl_loop_type_pingpong = 1, + drwav_smpl_loop_type_backward = 2 +} drwav_smpl_loop_type; + +typedef struct +{ + /* The ID of the associated cue point, see drwav_cue and drwav_cue_point. As with all cue point IDs, this can correspond to a label chunk to give this loop a name, see drwav_list_label_or_note. */ + drwav_uint32 cuePointId; + + /* See drwav_smpl_loop_type. */ + drwav_uint32 type; + + /* The byte offset of the first sample to be played in the loop. */ + drwav_uint32 firstSampleByteOffset; + + /* The byte offset into the audio data of the last sample to be played in the loop. */ + drwav_uint32 lastSampleByteOffset; + + /* A value to represent that playback should occur at a point between samples. This value ranges from 0 to UINT32_MAX. Where a value of 0 means no fraction, and a value of (UINT32_MAX / 2) would mean half a sample. */ + drwav_uint32 sampleFraction; + + /* Number of times to play the loop. 0 means loop infinitely. */ + drwav_uint32 playCount; +} drwav_smpl_loop; + +typedef struct +{ + /* IDs for a particular MIDI manufacturer. 0 if not used. */ + drwav_uint32 manufacturerId; + drwav_uint32 productId; + + /* The period of 1 sample in nanoseconds. */ + drwav_uint32 samplePeriodNanoseconds; + + /* The MIDI root note of this file. 0 to 127. */ + drwav_uint32 midiUnityNote; + + /* The fraction of a semitone up from the given MIDI note. This is a value from 0 to UINT32_MAX, where 0 means no change and (UINT32_MAX / 2) is half a semitone (AKA 50 cents). */ + drwav_uint32 midiPitchFraction; + + /* Data relating to SMPTE standards which are used for syncing audio and video. 0 if not used. */ + drwav_uint32 smpteFormat; + drwav_uint32 smpteOffset; + + /* drwav_smpl_loop loops. */ + drwav_uint32 sampleLoopCount; + + /* Optional sampler-specific data. */ + drwav_uint32 samplerSpecificDataSizeInBytes; + + drwav_smpl_loop* pLoops; + drwav_uint8* pSamplerSpecificData; +} drwav_smpl; + +/* +Instrument Metadata + +The inst metadata contains data about how a sound should be played as part of an instrument. This +commonly read by samplers. See https://en.wikipedia.org/wiki/Sample-based_synthesis. +*/ +typedef struct +{ + drwav_int8 midiUnityNote; /* The root note of the audio as a MIDI note number. 0 to 127. */ + drwav_int8 fineTuneCents; /* -50 to +50 */ + drwav_int8 gainDecibels; /* -64 to +64 */ + drwav_int8 lowNote; /* 0 to 127 */ + drwav_int8 highNote; /* 0 to 127 */ + drwav_int8 lowVelocity; /* 1 to 127 */ + drwav_int8 highVelocity; /* 1 to 127 */ +} drwav_inst; + +/* +Cue Metadata + +Cue points are markers at specific points in the audio. They often come with an associated piece of +drwav_list_label_or_note metadata which contains the text for the marker. +*/ +typedef struct +{ + /* Unique identification value. */ + drwav_uint32 id; + + /* Set to 0. This is only relevant if there is a 'playlist' chunk - which is not supported by dr_wav. */ + drwav_uint32 playOrderPosition; + + /* Should always be "data". This represents the fourcc value of the chunk that this cue point corresponds to. dr_wav only supports a single data chunk so this should always be "data". */ + drwav_uint8 dataChunkId[4]; + + /* Set to 0. This is only relevant if there is a wave list chunk. dr_wav, like lots of readers/writers, do not support this. */ + drwav_uint32 chunkStart; + + /* Set to 0 for uncompressed formats. Else the last byte in compressed wave data where decompression can begin to find the value of the corresponding sample value. */ + drwav_uint32 blockStart; + + /* For uncompressed formats this is the byte offset of the cue point into the audio data. For compressed formats this is relative to the block specified with blockStart. */ + drwav_uint32 sampleByteOffset; +} drwav_cue_point; + +typedef struct +{ + drwav_uint32 cuePointCount; + drwav_cue_point *pCuePoints; +} drwav_cue; + +/* +Acid Metadata + +This chunk contains some information about the time signature and the tempo of the audio. +*/ +typedef enum +{ + drwav_acid_flag_one_shot = 1, /* If this is not set, then it is a loop instead of a one-shot. */ + drwav_acid_flag_root_note_set = 2, + drwav_acid_flag_stretch = 4, + drwav_acid_flag_disk_based = 8, + drwav_acid_flag_acidizer = 16 /* Not sure what this means. */ +} drwav_acid_flag; + +typedef struct +{ + /* A bit-field, see drwav_acid_flag. */ + drwav_uint32 flags; + + /* Valid if flags contains drwav_acid_flag_root_note_set. It represents the MIDI root note the file - a value from 0 to 127. */ + drwav_uint16 midiUnityNote; + + /* Reserved values that should probably be ignored. reserved1 seems to often be 128 and reserved2 is 0. */ + drwav_uint16 reserved1; + float reserved2; + + /* Number of beats. */ + drwav_uint32 numBeats; + + /* The time signature of the audio. */ + drwav_uint16 meterDenominator; + drwav_uint16 meterNumerator; + + /* Beats per minute of the track. Setting a value of 0 suggests that there is no tempo. */ + float tempo; +} drwav_acid; + +/* +Cue Label or Note metadata + +These are 2 different types of metadata, but they have the exact same format. Labels tend to be the +more common and represent a short name for a cue point. Notes might be used to represent a longer +comment. +*/ +typedef struct +{ + /* The ID of a cue point that this label or note corresponds to. */ + drwav_uint32 cuePointId; + + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_label_or_note; + +/* +BEXT metadata, also known as Broadcast Wave Format (BWF) + +This metadata adds some extra description to an audio file. You must check the version field to +determine if the UMID or the loudness fields are valid. +*/ +typedef struct +{ + /* + These top 3 fields, and the umid field are actually defined in the standard as a statically + sized buffers. In order to reduce the size of this struct (and therefore the union in the + metadata struct), we instead store these as pointers. + */ + char* pDescription; /* Can be NULL or a null-terminated string, must be <= 256 characters. */ + char* pOriginatorName; /* Can be NULL or a null-terminated string, must be <= 32 characters. */ + char* pOriginatorReference; /* Can be NULL or a null-terminated string, must be <= 32 characters. */ + char pOriginationDate[10]; /* ASCII "yyyy:mm:dd". */ + char pOriginationTime[8]; /* ASCII "hh:mm:ss". */ + drwav_uint64 timeReference; /* First sample count since midnight. */ + drwav_uint16 version; /* Version of the BWF, check this to see if the fields below are valid. */ + + /* + Unrestricted ASCII characters containing a collection of strings terminated by CR/LF. Each + string shall contain a description of a coding process applied to the audio data. + */ + char* pCodingHistory; + drwav_uint32 codingHistorySize; + + /* Fields below this point are only valid if the version is 1 or above. */ + drwav_uint8* pUMID; /* Exactly 64 bytes of SMPTE UMID */ + + /* Fields below this point are only valid if the version is 2 or above. */ + drwav_uint16 loudnessValue; /* Integrated Loudness Value of the file in LUFS (multiplied by 100). */ + drwav_uint16 loudnessRange; /* Loudness Range of the file in LU (multiplied by 100). */ + drwav_uint16 maxTruePeakLevel; /* Maximum True Peak Level of the file expressed as dBTP (multiplied by 100). */ + drwav_uint16 maxMomentaryLoudness; /* Highest value of the Momentary Loudness Level of the file in LUFS (multiplied by 100). */ + drwav_uint16 maxShortTermLoudness; /* Highest value of the Short-Term Loudness Level of the file in LUFS (multiplied by 100). */ +} drwav_bext; + +/* +Info Text Metadata + +There a many different types of information text that can be saved in this format. This is where +things like the album name, the artists, the year it was produced, etc are saved. See +drwav_metadata_type for the full list of types that dr_wav supports. +*/ +typedef struct +{ + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_info_text; + +/* +Labelled Cue Region Metadata + +The labelled cue region metadata is used to associate some region of audio with text. The region +starts at a cue point, and extends for the given number of samples. +*/ +typedef struct +{ + /* The ID of a cue point that this object corresponds to. */ + drwav_uint32 cuePointId; + + /* The number of samples from the cue point forwards that should be considered this region */ + drwav_uint32 sampleLength; + + /* Four characters used to say what the purpose of this region is. */ + drwav_uint8 purposeId[4]; + + /* Unsure of the exact meanings of these. It appears to be acceptable to set them all to 0. */ + drwav_uint16 country; + drwav_uint16 language; + drwav_uint16 dialect; + drwav_uint16 codePage; + + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_labelled_cue_region; + +/* +Unknown Metadata + +This chunk just represents a type of chunk that dr_wav does not understand. + +Unknown metadata has a location attached to it. This is because wav files can have a LIST chunk +that contains subchunks. These LIST chunks can be one of two types. An adtl list, or an INFO +list. This enum is used to specify the location of a chunk that dr_wav currently doesn't support. +*/ +typedef enum +{ + drwav_metadata_location_invalid, + drwav_metadata_location_top_level, + drwav_metadata_location_inside_info_list, + drwav_metadata_location_inside_adtl_list +} drwav_metadata_location; + +typedef struct +{ + drwav_uint8 id[4]; + drwav_metadata_location chunkLocation; + drwav_uint32 dataSizeInBytes; + drwav_uint8* pData; +} drwav_unknown_metadata; + +/* +Metadata is saved as a union of all the supported types. +*/ +typedef struct +{ + /* Determines which item in the union is valid. */ + drwav_metadata_type type; + + union + { + drwav_cue cue; + drwav_smpl smpl; + drwav_acid acid; + drwav_inst inst; + drwav_bext bext; + drwav_list_label_or_note labelOrNote; /* List label or list note. */ + drwav_list_labelled_cue_region labelledCueRegion; + drwav_list_info_text infoText; /* Any of the list info types. */ + drwav_unknown_metadata unknown; + } data; +} drwav_metadata; + +typedef struct +{ + /* A pointer to the function to call when more data is needed. */ + drwav_read_proc onRead; + + /* A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. */ + drwav_write_proc onWrite; + + /* A pointer to the function to call when the wav file needs to be seeked. */ + drwav_seek_proc onSeek; + + /* The user data to pass to callbacks. */ + void* pUserData; + + /* Allocation callbacks. */ + drwav_allocation_callbacks allocationCallbacks; + + + /* Whether or not the WAV file is formatted as a standard RIFF file or W64. */ + drwav_container container; + + + /* Structure containing format information exactly as specified by the wav file. */ + drwav_fmt fmt; + + /* The sample rate. Will be set to something like 44100. */ + drwav_uint32 sampleRate; + + /* The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. */ + drwav_uint16 channels; + + /* The bits per sample. Will be set to something like 16, 24, etc. */ + drwav_uint16 bitsPerSample; + + /* Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). */ + drwav_uint16 translatedFormatTag; + + /* The total number of PCM frames making up the audio data. */ + drwav_uint64 totalPCMFrameCount; + + + /* The size in bytes of the data chunk. */ + drwav_uint64 dataChunkDataSize; + + /* The position in the stream of the first data byte of the data chunk. This is used for seeking. */ + drwav_uint64 dataChunkDataPos; + + /* The number of bytes remaining in the data chunk. */ + drwav_uint64 bytesRemaining; + + /* The current read position in PCM frames. */ + drwav_uint64 readCursorInPCMFrames; + + + /* + Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always + set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. + */ + drwav_uint64 dataChunkDataSizeTargetWrite; + + /* Keeps track of whether or not the wav writer was initialized in sequential mode. */ + drwav_bool32 isSequentialWrite; + + + /* A bit-field of drwav_metadata_type values, only bits set in this variable are parsed and saved */ + drwav_metadata_type allowedMetadataTypes; + + /* A array of metadata. This is valid after the *init_with_metadata call returns. It will be valid until drwav_uninit() is called. You can take ownership of this data with drwav_take_ownership_of_metadata(). */ + drwav_metadata* pMetadata; + drwav_uint32 metadataCount; + + + /* A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_init_memory(). */ + drwav__memory_stream memoryStream; + drwav__memory_stream_write memoryStreamWrite; + + + /* Microsoft ADPCM specific data. */ + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_uint16 predictor[2]; + drwav_int32 delta[2]; + drwav_int32 cachedFrames[4]; /* Samples are stored in this cache during decoding. */ + drwav_uint32 cachedFrameCount; + drwav_int32 prevFrames[2][2]; /* The previous 2 samples for each channel (2 channels at most). */ + } msadpcm; + + /* IMA ADPCM specific data. */ + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_int32 predictor[2]; + drwav_int32 stepIndex[2]; + drwav_int32 cachedFrames[16]; /* Samples are stored in this cache during decoding. */ + drwav_uint32 cachedFrameCount; + } ima; +} drwav; + + +/* +Initializes a pre-allocated drwav object for reading. + +pWav [out] A pointer to the drwav object being initialized. +onRead [in] The function to call when data needs to be read from the client. +onSeek [in] The function to call when the read position of the client data needs to move. +onChunk [in, optional] The function to call when a chunk is enumerated at initialized time. +pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk. +flags [in, optional] A set of flags for controlling how things are loaded. + +Returns true if successful; false otherwise. + +Close the loader with drwav_uninit(). + +This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +to open the stream from a file or from a block of memory respectively. + +Possible values for flags: + DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function + to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored. + +drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);". + +The onChunk callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt +after the function returns. + +See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() +*/ +DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* +Initializes a pre-allocated drwav object for writing. + +onWrite [in] The function to call when data needs to be written. +onSeek [in] The function to call when the write position needs to move. +pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. +metadata, numMetadata [in, optional] An array of metadata objects that should be written to the file. The array is not edited. You are responsible for this metadata memory and it must maintain valid until drwav_uninit() is called. + +Returns true if successful; false otherwise. + +Close the writer with drwav_uninit(). + +This is the lowest level function for initializing a WAV file. You can also use drwav_init_file_write() and drwav_init_memory_write() +to open the stream from a file or from a block of memory respectively. + +If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform +a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. + +See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() +*/ +DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_with_metadata(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks, drwav_metadata* pMetadata, drwav_uint32 metadataCount); + +/* +Utility function to determine the target size of the entire data to be written (including all headers and chunks). + +Returns the target size in bytes. + +The metadata argument can be NULL meaning no metadata exists. + +Useful if the application needs to know the size to allocate. + +Only writing to the RIFF chunk and one data chunk is currently supported. + +See also: drwav_init_write(), drwav_init_file_write(), drwav_init_memory_write() +*/ +DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalFrameCount, drwav_metadata* pMetadata, drwav_uint32 metadataCount); + +/* +Take ownership of the metadata objects that were allocated via one of the init_with_metadata() function calls. The init_with_metdata functions perform a single heap allocation for this metadata. + +Useful if you want the data to persist beyond the lifetime of the drwav object. + +You must free the data returned from this function using drwav_free(). +*/ +DRWAV_API drwav_metadata* drwav_take_ownership_of_metadata(drwav* pWav); + +/* +Uninitializes the given drwav object. + +Use this only for objects initialized with drwav_init*() functions (drwav_init(), drwav_init_ex(), drwav_init_write(), drwav_init_write_sequential()). +*/ +DRWAV_API drwav_result drwav_uninit(drwav* pWav); + + +/* +Reads raw audio data. + +This is the lowest level function for reading audio data. It simply reads the given number of +bytes of the raw internal sample data. + +Consider using drwav_read_pcm_frames_s16(), drwav_read_pcm_frames_s32() or drwav_read_pcm_frames_f32() for +reading sample data in a consistent format. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of bytes actually read. +*/ +DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); + +/* +Reads up to the specified number of PCM frames from the WAV file. + +The output data will be in the file's internal format, converted to native-endian byte order. Use +drwav_read_pcm_frames_s16/f32/s32() to read data in a specific format. + +If the return value is less than it means the end of the file has been reached or +you have requested more PCM frames than can possibly fit in the output buffer. + +This function will only work when sample data is of a fixed size and uncompressed. If you are +using a compressed format consider using drwav_read_raw() or drwav_read_pcm_frames_s16/s32/f32(). + +pBufferOut can be NULL in which case a seek will be performed. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); + +/* +Seeks to the given PCM frame. + +Returns true if successful; false otherwise. +*/ +DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex); + +/* +Retrieves the current read position in pcm frames. +*/ +DRWAV_API drwav_result drwav_get_cursor_in_pcm_frames(drwav* pWav, drwav_uint64* pCursor); + +/* +Retrieves the length of the file. +*/ +DRWAV_API drwav_result drwav_get_length_in_pcm_frames(drwav* pWav, drwav_uint64* pLength); + + +/* +Writes raw audio data. + +Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. +*/ +DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData); + +/* +Writes PCM frames. + +Returns the number of PCM frames written. + +Input samples need to be in native-endian byte order. On big-endian architectures the input data will be converted to +little-endian. Use drwav_write_raw() to write raw audio data without performing any conversion. +*/ +DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); +DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); +DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); + +/* Conversion Utilities */ +#ifndef DR_WAV_NO_CONVERSION_API + +/* +Reads a chunk of audio data and converts it to signed 16-bit PCM samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + + +/* +Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + + +/* +Reads a chunk of audio data and converts it to signed 32-bit PCM samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +#endif /* DR_WAV_NO_CONVERSION_API */ + + +/* High-Level Convenience Helpers */ + +#ifndef DR_WAV_NO_STDIO +/* +Helper for initializing a wave file for reading using stdio. + +This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ +DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_with_metadata(drwav* pWav, const char* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_with_metadata_w(drwav* pWav, const wchar_t* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + + +/* +Helper for initializing a wave file for writing using stdio. + +This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ +DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif /* DR_WAV_NO_STDIO */ + +/* +Helper for initializing a loader from a pre-allocated memory buffer. + +This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +the lifetime of the drwav object. + +The buffer should contain the contents of the entire wave file, not just the sample data. +*/ +DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* data, size_t dataSize, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* +Helper for initializing a writer which outputs data to a memory buffer. + +dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). + +The buffer will remain allocated even after drwav_uninit() is called. The buffer should not be considered valid +until after drwav_uninit() has been called. +*/ +DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); + + +#ifndef DR_WAV_NO_CONVERSION_API +/* +Opens and reads an entire wav file in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#ifndef DR_WAV_NO_STDIO +/* +Opens and decodes an entire wav file in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif +/* +Opens and decodes an entire wav file from a block of memory in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif + +/* Frees data that was allocated internally by dr_wav. */ +DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* Converts bytes from a wav stream to a sized type of native endian. */ +DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data); +DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data); +DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data); +DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data); +DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data); +DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data); +DRWAV_API float drwav_bytes_to_f32(const drwav_uint8* data); + +/* Compares a GUID for the purpose of checking the type of a Wave64 chunk. */ +DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]); + +/* Compares a four-character-code for the purpose of checking the type of a RIFF chunk. */ +DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b); + +#ifdef __cplusplus +} +#endif +#endif /* dr_wav_h */ + + +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ +#if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) +#ifndef dr_wav_c +#define dr_wav_c + +#include +#include +#include /* For INT_MAX */ + +#ifndef DR_WAV_NO_STDIO +#include +#include +#endif + +/* Standard library stuff. */ +#ifndef DRWAV_ASSERT +#include +#define DRWAV_ASSERT(expression) assert(expression) +#endif +#ifndef DRWAV_MALLOC +#define DRWAV_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRWAV_REALLOC +#define DRWAV_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRWAV_FREE +#define DRWAV_FREE(p) free((p)) +#endif +#ifndef DRWAV_COPY_MEMORY +#define DRWAV_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRWAV_ZERO_MEMORY +#define DRWAV_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#ifndef DRWAV_ZERO_OBJECT +#define DRWAV_ZERO_OBJECT(p) DRWAV_ZERO_MEMORY((p), sizeof(*p)) +#endif + +#define drwav_countof(x) (sizeof(x) / sizeof(x[0])) +#define drwav_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) +#define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) +#define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) +#define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) +#define drwav_offset_ptr(p, offset) (((drwav_uint8*)(p)) + (offset)) + +#define DRWAV_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */ + +/* CPU architecture. */ +#if defined(__x86_64__) || defined(_M_X64) + #define DRWAV_X64 +#elif defined(__i386) || defined(_M_IX86) + #define DRWAV_X86 +#elif defined(__arm__) || defined(_M_ARM) + #define DRWAV_ARM +#endif + +#ifdef _MSC_VER + #define DRWAV_INLINE __forceinline +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DRWAV_GNUC_INLINE_HINT __inline__ + #else + #define DRWAV_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT + #endif +#elif defined(__WATCOMC__) + #define DRWAV_INLINE __inline +#else + #define DRWAV_INLINE +#endif + +#if defined(SIZE_MAX) + #define DRWAV_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRWAV_SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRWAV_SIZE_MAX 0xFFFFFFFF + #endif +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #define DRWAV_HAS_BYTESWAP64_INTRINSIC +#elif defined(__clang__) + #if defined(__has_builtin) + #if __has_builtin(__builtin_bswap16) + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap32) + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap64) + #define DRWAV_HAS_BYTESWAP64_INTRINSIC + #endif + #endif +#elif defined(__GNUC__) + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #define DRWAV_HAS_BYTESWAP64_INTRINSIC + #endif + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #endif +#endif + +DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision) +{ + if (pMajor) { + *pMajor = DRWAV_VERSION_MAJOR; + } + + if (pMinor) { + *pMinor = DRWAV_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRWAV_VERSION_REVISION; + } +} + +DRWAV_API const char* drwav_version_string(void) +{ + return DRWAV_VERSION_STRING; +} + +/* +These limits are used for basic validation when initializing the decoder. If you exceed these limits, first of all: what on Earth are +you doing?! (Let me know, I'd be curious!) Second, you can adjust these by #define-ing them before the dr_wav implementation. +*/ +#ifndef DRWAV_MAX_SAMPLE_RATE +#define DRWAV_MAX_SAMPLE_RATE 384000 +#endif +#ifndef DRWAV_MAX_CHANNELS +#define DRWAV_MAX_CHANNELS 256 +#endif +#ifndef DRWAV_MAX_BITS_PER_SAMPLE +#define DRWAV_MAX_BITS_PER_SAMPLE 64 +#endif + +static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; /* 66666972-912E-11CF-A5D6-28DB04C10000 */ +static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 65766177-ACF3-11D3-8CD1-00C04F8EDB8A */ +/*static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 74636166-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 61746164-ACF3-11D3-8CD1-00C04F8EDB8A */ +/*static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A */ + + +static DRWAV_INLINE int drwav__is_little_endian(void) +{ +#if defined(DRWAV_X86) || defined(DRWAV_X64) + return DRWAV_TRUE; +#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN + return DRWAV_TRUE; +#else + int n = 1; + return (*(char*)&n) == 1; +#endif +} + + +static DRWAV_INLINE void drwav_bytes_to_guid(const drwav_uint8* data, drwav_uint8* guid) +{ + int i; + for (i = 0; i < 16; ++i) { + guid[i] = data[i]; + } +} + + +static DRWAV_INLINE drwav_uint16 drwav__bswap16(drwav_uint16 n) +{ +#ifdef DRWAV_HAS_BYTESWAP16_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ushort(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF00) >> 8) | + ((n & 0x00FF) << 8); +#endif +} + +static DRWAV_INLINE drwav_uint32 drwav__bswap32(drwav_uint32 n) +{ +#ifdef DRWAV_HAS_BYTESWAP32_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ulong(n); + #elif defined(__GNUC__) || defined(__clang__) + #if defined(DRWAV_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(DRWAV_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ + /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ + drwav_uint32 r; + __asm__ __volatile__ ( + #if defined(DRWAV_64BIT) + "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) + #endif + ); + return r; + #else + return __builtin_bswap32(n); + #endif + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF000000) >> 24) | + ((n & 0x00FF0000) >> 8) | + ((n & 0x0000FF00) << 8) | + ((n & 0x000000FF) << 24); +#endif +} + +static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) +{ +#ifdef DRWAV_HAS_BYTESWAP64_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_uint64(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drwav_uint64)0xFF000000 )) << 8) | + ((n & ((drwav_uint64)0x00FF0000 )) << 24) | + ((n & ((drwav_uint64)0x0000FF00 )) << 40) | + ((n & ((drwav_uint64)0x000000FF )) << 56); +#endif +} + + +static DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n) +{ + return (drwav_int16)drwav__bswap16((drwav_uint16)n); +} + +static DRWAV_INLINE void drwav__bswap_samples_s16(drwav_int16* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_s16(pSamples[iSample]); + } +} + + +static DRWAV_INLINE void drwav__bswap_s24(drwav_uint8* p) +{ + drwav_uint8 t; + t = p[0]; + p[0] = p[2]; + p[2] = t; +} + +static DRWAV_INLINE void drwav__bswap_samples_s24(drwav_uint8* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + drwav_uint8* pSample = pSamples + (iSample*3); + drwav__bswap_s24(pSample); + } +} + + +static DRWAV_INLINE drwav_int32 drwav__bswap_s32(drwav_int32 n) +{ + return (drwav_int32)drwav__bswap32((drwav_uint32)n); +} + +static DRWAV_INLINE void drwav__bswap_samples_s32(drwav_int32* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_s32(pSamples[iSample]); + } +} + + +static DRWAV_INLINE float drwav__bswap_f32(float n) +{ + union { + drwav_uint32 i; + float f; + } x; + x.f = n; + x.i = drwav__bswap32(x.i); + + return x.f; +} + +static DRWAV_INLINE void drwav__bswap_samples_f32(float* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_f32(pSamples[iSample]); + } +} + + +static DRWAV_INLINE double drwav__bswap_f64(double n) +{ + union { + drwav_uint64 i; + double f; + } x; + x.f = n; + x.i = drwav__bswap64(x.i); + + return x.f; +} + +static DRWAV_INLINE void drwav__bswap_samples_f64(double* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_f64(pSamples[iSample]); + } +} + + +static DRWAV_INLINE void drwav__bswap_samples_pcm(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample) +{ + /* Assumes integer PCM. Floating point PCM is done in drwav__bswap_samples_ieee(). */ + switch (bytesPerSample) + { + case 1: /* u8 */ + { + /* no-op. */ + } break; + case 2: /* s16, s12 (loosely packed) */ + { + drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); + } break; + case 3: /* s24 */ + { + drwav__bswap_samples_s24((drwav_uint8*)pSamples, sampleCount); + } break; + case 4: /* s32 */ + { + drwav__bswap_samples_s32((drwav_int32*)pSamples, sampleCount); + } break; + default: + { + /* Unsupported format. */ + DRWAV_ASSERT(DRWAV_FALSE); + } break; + } +} + +static DRWAV_INLINE void drwav__bswap_samples_ieee(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample) +{ + switch (bytesPerSample) + { + #if 0 /* Contributions welcome for f16 support. */ + case 2: /* f16 */ + { + drwav__bswap_samples_f16((drwav_float16*)pSamples, sampleCount); + } break; + #endif + case 4: /* f32 */ + { + drwav__bswap_samples_f32((float*)pSamples, sampleCount); + } break; + case 8: /* f64 */ + { + drwav__bswap_samples_f64((double*)pSamples, sampleCount); + } break; + default: + { + /* Unsupported format. */ + DRWAV_ASSERT(DRWAV_FALSE); + } break; + } +} + +static DRWAV_INLINE void drwav__bswap_samples(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample, drwav_uint16 format) +{ + switch (format) + { + case DR_WAVE_FORMAT_PCM: + { + drwav__bswap_samples_pcm(pSamples, sampleCount, bytesPerSample); + } break; + + case DR_WAVE_FORMAT_IEEE_FLOAT: + { + drwav__bswap_samples_ieee(pSamples, sampleCount, bytesPerSample); + } break; + + case DR_WAVE_FORMAT_ALAW: + case DR_WAVE_FORMAT_MULAW: + { + drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); + } break; + + case DR_WAVE_FORMAT_ADPCM: + case DR_WAVE_FORMAT_DVI_ADPCM: + default: + { + /* Unsupported format. */ + DRWAV_ASSERT(DRWAV_FALSE); + } break; + } +} + + +DRWAV_PRIVATE void* drwav__malloc_default(size_t sz, void* pUserData) +{ + (void)pUserData; + return DRWAV_MALLOC(sz); +} + +DRWAV_PRIVATE void* drwav__realloc_default(void* p, size_t sz, void* pUserData) +{ + (void)pUserData; + return DRWAV_REALLOC(p, sz); +} + +DRWAV_PRIVATE void drwav__free_default(void* p, void* pUserData) +{ + (void)pUserData; + DRWAV_FREE(p); +} + + +DRWAV_PRIVATE void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } + + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } + + return NULL; +} + +DRWAV_PRIVATE void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); + } + + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; + + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; + } + + if (p != NULL) { + DRWAV_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } + + return p2; + } + + return NULL; +} + +DRWAV_PRIVATE void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +DRWAV_PRIVATE drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + /* Copy. */ + return *pAllocationCallbacks; + } else { + /* Defaults. */ + drwav_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = drwav__malloc_default; + allocationCallbacks.onRealloc = drwav__realloc_default; + allocationCallbacks.onFree = drwav__free_default; + return allocationCallbacks; + } +} + + +static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 formatTag) +{ + return + formatTag == DR_WAVE_FORMAT_ADPCM || + formatTag == DR_WAVE_FORMAT_DVI_ADPCM; +} + +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) +{ + return (unsigned int)(chunkSize % 2); +} + +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) +{ + return (unsigned int)(chunkSize % 8); +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +DRWAV_PRIVATE drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) +{ + if (container == drwav_container_riff || container == drwav_container_rf64) { + drwav_uint8 sizeInBytes[4]; + + if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { + return DRWAV_AT_END; + } + + if (onRead(pUserData, sizeInBytes, 4) != 4) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav_bytes_to_u32(sizeInBytes); + pHeaderOut->paddingSize = drwav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); + *pRunningBytesReadOut += 8; + } else { + drwav_uint8 sizeInBytes[8]; + + if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { + return DRWAV_AT_END; + } + + if (onRead(pUserData, sizeInBytes, 8) != 8) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav_bytes_to_u64(sizeInBytes) - 24; /* <-- Subtract 24 because w64 includes the size of the header. */ + pHeaderOut->paddingSize = drwav__chunk_padding_size_w64(pHeaderOut->sizeInBytes); + *pRunningBytesReadOut += 24; + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + drwav_uint64 bytesRemainingToSeek = offset; + while (bytesRemainingToSeek > 0) { + if (bytesRemainingToSeek > 0x7FFFFFFF) { + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek -= 0x7FFFFFFF; + } else { + if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek = 0; + } + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_start); + } + + /* Larger than 32-bit seek. */ + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + + for (;;) { + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_current); + } + + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + } + + /* Should never get here. */ + /*return DRWAV_TRUE; */ +} + + +DRWAV_PRIVATE drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) +{ + drwav_chunk_header header; + drwav_uint8 fmt[16]; + + if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + + /* Skip non-fmt chunks. */ + while (((container == drwav_container_riff || container == drwav_container_rf64) && !drwav_fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += header.sizeInBytes + header.paddingSize; + + /* Try the next header. */ + if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + } + + + /* Validation. */ + if (container == drwav_container_riff || container == drwav_container_rf64) { + if (!drwav_fourcc_equal(header.id.fourcc, "fmt ")) { + return DRWAV_FALSE; + } + } else { + if (!drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT)) { + return DRWAV_FALSE; + } + } + + + if (onRead(pUserData, fmt, sizeof(fmt)) != sizeof(fmt)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += sizeof(fmt); + + fmtOut->formatTag = drwav_bytes_to_u16(fmt + 0); + fmtOut->channels = drwav_bytes_to_u16(fmt + 2); + fmtOut->sampleRate = drwav_bytes_to_u32(fmt + 4); + fmtOut->avgBytesPerSec = drwav_bytes_to_u32(fmt + 8); + fmtOut->blockAlign = drwav_bytes_to_u16(fmt + 12); + fmtOut->bitsPerSample = drwav_bytes_to_u16(fmt + 14); + + fmtOut->extendedSize = 0; + fmtOut->validBitsPerSample = 0; + fmtOut->channelMask = 0; + DRWAV_ZERO_MEMORY(fmtOut->subFormat, sizeof(fmtOut->subFormat)); + + if (header.sizeInBytes > 16) { + drwav_uint8 fmt_cbSize[2]; + int bytesReadSoFar = 0; + + if (onRead(pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) { + return DRWAV_FALSE; /* Expecting more data. */ + } + *pRunningBytesReadOut += sizeof(fmt_cbSize); + + bytesReadSoFar = 18; + + fmtOut->extendedSize = drwav_bytes_to_u16(fmt_cbSize); + if (fmtOut->extendedSize > 0) { + /* Simple validation. */ + if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + if (fmtOut->extendedSize != 22) { + return DRWAV_FALSE; + } + } + + if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + drwav_uint8 fmtext[22]; + if (onRead(pUserData, fmtext, fmtOut->extendedSize) != fmtOut->extendedSize) { + return DRWAV_FALSE; /* Expecting more data. */ + } + + fmtOut->validBitsPerSample = drwav_bytes_to_u16(fmtext + 0); + fmtOut->channelMask = drwav_bytes_to_u32(fmtext + 2); + drwav_bytes_to_guid(fmtext + 6, fmtOut->subFormat); + } else { + if (!onSeek(pUserData, fmtOut->extendedSize, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + } + *pRunningBytesReadOut += fmtOut->extendedSize; + + bytesReadSoFar += fmtOut->extendedSize; + } + + /* Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. */ + if (!onSeek(pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += (header.sizeInBytes - bytesReadSoFar); + } + + if (header.paddingSize > 0) { + if (!onSeek(pUserData, header.paddingSize, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += header.paddingSize; + } + + return DRWAV_TRUE; +} + + +DRWAV_PRIVATE size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +{ + size_t bytesRead; + + DRWAV_ASSERT(onRead != NULL); + DRWAV_ASSERT(pCursor != NULL); + + bytesRead = onRead(pUserData, pBufferOut, bytesToRead); + *pCursor += bytesRead; + return bytesRead; +} + +#if 0 +DRWAV_PRIVATE drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) +{ + DRWAV_ASSERT(onSeek != NULL); + DRWAV_ASSERT(pCursor != NULL); + + if (!onSeek(pUserData, offset, origin)) { + return DRWAV_FALSE; + } + + if (origin == drwav_seek_origin_start) { + *pCursor = offset; + } else { + *pCursor += offset; + } + + return DRWAV_TRUE; +} +#endif + + +#define DRWAV_SMPL_BYTES 36 +#define DRWAV_SMPL_LOOP_BYTES 24 +#define DRWAV_INST_BYTES 7 +#define DRWAV_ACID_BYTES 24 +#define DRWAV_CUE_BYTES 4 +#define DRWAV_BEXT_BYTES 602 +#define DRWAV_BEXT_DESCRIPTION_BYTES 256 +#define DRWAV_BEXT_ORIGINATOR_NAME_BYTES 32 +#define DRWAV_BEXT_ORIGINATOR_REF_BYTES 32 +#define DRWAV_BEXT_RESERVED_BYTES 180 +#define DRWAV_BEXT_UMID_BYTES 64 +#define DRWAV_CUE_POINT_BYTES 24 +#define DRWAV_LIST_LABEL_OR_NOTE_BYTES 4 +#define DRWAV_LIST_LABELLED_TEXT_BYTES 20 + +#define DRWAV_METADATA_ALIGNMENT 8 + +typedef enum +{ + drwav__metadata_parser_stage_count, + drwav__metadata_parser_stage_read +} drwav__metadata_parser_stage; + +typedef struct +{ + drwav_read_proc onRead; + drwav_seek_proc onSeek; + void *pReadSeekUserData; + drwav__metadata_parser_stage stage; + drwav_metadata *pMetadata; + drwav_uint32 metadataCount; + drwav_uint8 *pData; + drwav_uint8 *pDataCursor; + drwav_uint64 metadataCursor; + drwav_uint64 extraCapacity; +} drwav__metadata_parser; + +DRWAV_PRIVATE size_t drwav__metadata_memory_capacity(drwav__metadata_parser* pParser) +{ + drwav_uint64 cap = sizeof(drwav_metadata) * (drwav_uint64)pParser->metadataCount + pParser->extraCapacity; + if (cap > DRWAV_SIZE_MAX) { + return 0; /* Too big. */ + } + + return (size_t)cap; /* Safe cast thanks to the check above. */ +} + +DRWAV_PRIVATE drwav_uint8* drwav__metadata_get_memory(drwav__metadata_parser* pParser, size_t size, size_t align) +{ + drwav_uint8* pResult; + + if (align) { + drwav_uintptr modulo = (drwav_uintptr)pParser->pDataCursor % align; + if (modulo != 0) { + pParser->pDataCursor += align - modulo; + } + } + + pResult = pParser->pDataCursor; + + /* + Getting to the point where this function is called means there should always be memory + available. Out of memory checks should have been done at an earlier stage. + */ + DRWAV_ASSERT((pResult + size) <= (pParser->pData + drwav__metadata_memory_capacity(pParser))); + + pParser->pDataCursor += size; + return pResult; +} + +DRWAV_PRIVATE void drwav__metadata_request_extra_memory_for_stage_2(drwav__metadata_parser* pParser, size_t bytes, size_t align) +{ + size_t extra = bytes + (align ? (align - 1) : 0); + pParser->extraCapacity += extra; +} + +DRWAV_PRIVATE drwav_result drwav__metadata_alloc(drwav__metadata_parser* pParser, drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pParser->extraCapacity != 0 || pParser->metadataCount != 0) { + pAllocationCallbacks->onFree(pParser->pData, pAllocationCallbacks->pUserData); + + pParser->pData = (drwav_uint8*)pAllocationCallbacks->onMalloc(drwav__metadata_memory_capacity(pParser), pAllocationCallbacks->pUserData); + pParser->pDataCursor = pParser->pData; + + if (pParser->pData == NULL) { + return DRWAV_OUT_OF_MEMORY; + } + + /* + We don't need to worry about specifying an alignment here because malloc always returns something + of suitable alignment. This also means than pParser->pMetadata is all that we need to store in order + for us to free when we are done. + */ + pParser->pMetadata = (drwav_metadata*)drwav__metadata_get_memory(pParser, sizeof(drwav_metadata) * pParser->metadataCount, 1); + pParser->metadataCursor = 0; + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE size_t drwav__metadata_parser_read(drwav__metadata_parser* pParser, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +{ + if (pCursor != NULL) { + return drwav__on_read(pParser->onRead, pParser->pReadSeekUserData, pBufferOut, bytesToRead, pCursor); + } else { + return pParser->onRead(pParser->pReadSeekUserData, pBufferOut, bytesToRead); + } +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) +{ + drwav_uint8 smplHeaderData[DRWAV_SMPL_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead = drwav__metadata_parser_read(pParser, smplHeaderData, sizeof(smplHeaderData), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + DRWAV_ASSERT(pChunkHeader != NULL); + + if (bytesJustRead == sizeof(smplHeaderData)) { + drwav_uint32 iSampleLoop; + + pMetadata->type = drwav_metadata_type_smpl; + pMetadata->data.smpl.manufacturerId = drwav_bytes_to_u32(smplHeaderData + 0); + pMetadata->data.smpl.productId = drwav_bytes_to_u32(smplHeaderData + 4); + pMetadata->data.smpl.samplePeriodNanoseconds = drwav_bytes_to_u32(smplHeaderData + 8); + pMetadata->data.smpl.midiUnityNote = drwav_bytes_to_u32(smplHeaderData + 12); + pMetadata->data.smpl.midiPitchFraction = drwav_bytes_to_u32(smplHeaderData + 16); + pMetadata->data.smpl.smpteFormat = drwav_bytes_to_u32(smplHeaderData + 20); + pMetadata->data.smpl.smpteOffset = drwav_bytes_to_u32(smplHeaderData + 24); + pMetadata->data.smpl.sampleLoopCount = drwav_bytes_to_u32(smplHeaderData + 28); + pMetadata->data.smpl.samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(smplHeaderData + 32); + + /* + The loop count needs to be validated against the size of the chunk for safety so we don't + attempt to read over the boundary of the chunk. + */ + if (pMetadata->data.smpl.sampleLoopCount == (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES) { + pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); + + for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { + drwav_uint8 smplLoopData[DRWAV_SMPL_LOOP_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); + + if (bytesJustRead == sizeof(smplLoopData)) { + pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); + pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); + pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); + pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); + pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); + pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); + } else { + break; + } + } + + if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { + pMetadata->data.smpl.pSamplerSpecificData = drwav__metadata_get_memory(pParser, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, 1); + DRWAV_ASSERT(pMetadata->data.smpl.pSamplerSpecificData != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); + } + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) +{ + drwav_uint8 cueHeaderSectionData[DRWAV_CUE_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead = drwav__metadata_parser_read(pParser, cueHeaderSectionData, sizeof(cueHeaderSectionData), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(cueHeaderSectionData)) { + pMetadata->type = drwav_metadata_type_cue; + pMetadata->data.cue.cuePointCount = drwav_bytes_to_u32(cueHeaderSectionData); + + /* + We need to validate the cue point count against the size of the chunk so we don't read + beyond the chunk. + */ + if (pMetadata->data.cue.cuePointCount == (pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES) { + pMetadata->data.cue.pCuePoints = (drwav_cue_point*)drwav__metadata_get_memory(pParser, sizeof(drwav_cue_point) * pMetadata->data.cue.cuePointCount, DRWAV_METADATA_ALIGNMENT); + DRWAV_ASSERT(pMetadata->data.cue.pCuePoints != NULL); + + if (pMetadata->data.cue.cuePointCount > 0) { + drwav_uint32 iCuePoint; + + for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { + drwav_uint8 cuePointData[DRWAV_CUE_POINT_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, cuePointData, sizeof(cuePointData), &totalBytesRead); + + if (bytesJustRead == sizeof(cuePointData)) { + pMetadata->data.cue.pCuePoints[iCuePoint].id = drwav_bytes_to_u32(cuePointData + 0); + pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition = drwav_bytes_to_u32(cuePointData + 4); + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[0] = cuePointData[8]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[1] = cuePointData[9]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[2] = cuePointData[10]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; + pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); + pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); + pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); + } else { + break; + } + } + } + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_inst_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +{ + drwav_uint8 instData[DRWAV_INST_BYTES]; + drwav_uint64 bytesRead = drwav__metadata_parser_read(pParser, instData, sizeof(instData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(instData)) { + pMetadata->type = drwav_metadata_type_inst; + pMetadata->data.inst.midiUnityNote = (drwav_int8)instData[0]; + pMetadata->data.inst.fineTuneCents = (drwav_int8)instData[1]; + pMetadata->data.inst.gainDecibels = (drwav_int8)instData[2]; + pMetadata->data.inst.lowNote = (drwav_int8)instData[3]; + pMetadata->data.inst.highNote = (drwav_int8)instData[4]; + pMetadata->data.inst.lowVelocity = (drwav_int8)instData[5]; + pMetadata->data.inst.highVelocity = (drwav_int8)instData[6]; + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_acid_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +{ + drwav_uint8 acidData[DRWAV_ACID_BYTES]; + drwav_uint64 bytesRead = drwav__metadata_parser_read(pParser, acidData, sizeof(acidData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(acidData)) { + pMetadata->type = drwav_metadata_type_acid; + pMetadata->data.acid.flags = drwav_bytes_to_u32(acidData + 0); + pMetadata->data.acid.midiUnityNote = drwav_bytes_to_u16(acidData + 4); + pMetadata->data.acid.reserved1 = drwav_bytes_to_u16(acidData + 6); + pMetadata->data.acid.reserved2 = drwav_bytes_to_f32(acidData + 8); + pMetadata->data.acid.numBeats = drwav_bytes_to_u32(acidData + 12); + pMetadata->data.acid.meterDenominator = drwav_bytes_to_u16(acidData + 16); + pMetadata->data.acid.meterNumerator = drwav_bytes_to_u16(acidData + 18); + pMetadata->data.acid.tempo = drwav_bytes_to_f32(acidData + 20); + } + + return bytesRead; +} + +DRWAV_PRIVATE size_t drwav__strlen(const char* str) +{ + size_t result = 0; + + while (*str++) { + result += 1; + } + + return result; +} + +DRWAV_PRIVATE size_t drwav__strlen_clamped(const char* str, size_t maxToRead) +{ + size_t result = 0; + + while (*str++ && result < maxToRead) { + result += 1; + } + + return result; +} + +DRWAV_PRIVATE char* drwav__metadata_copy_string(drwav__metadata_parser* pParser, const char* str, size_t maxToRead) +{ + size_t len = drwav__strlen_clamped(str, maxToRead); + + if (len) { + char* result = (char*)drwav__metadata_get_memory(pParser, len + 1, 1); + DRWAV_ASSERT(result != NULL); + + DRWAV_COPY_MEMORY(result, str, len); + result[len] = '\0'; + + return result; + } else { + return NULL; + } +} + +typedef struct +{ + const void* pBuffer; + size_t sizeInBytes; + size_t cursor; +} drwav_buffer_reader; + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_init(const void* pBuffer, size_t sizeInBytes, drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pBuffer != NULL); + DRWAV_ASSERT(pReader != NULL); + + DRWAV_ZERO_OBJECT(pReader); + + pReader->pBuffer = pBuffer; + pReader->sizeInBytes = sizeInBytes; + pReader->cursor = 0; + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE const void* drwav_buffer_reader_ptr(const drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pReader != NULL); + + return drwav_offset_ptr(pReader->pBuffer, pReader->cursor); +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_seek(drwav_buffer_reader* pReader, size_t bytesToSeek) +{ + DRWAV_ASSERT(pReader != NULL); + + if (pReader->cursor + bytesToSeek > pReader->sizeInBytes) { + return DRWAV_BAD_SEEK; /* Seeking too far forward. */ + } + + pReader->cursor += bytesToSeek; + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read(drwav_buffer_reader* pReader, void* pDst, size_t bytesToRead, size_t* pBytesRead) +{ + drwav_result result = DRWAV_SUCCESS; + size_t bytesRemaining; + + DRWAV_ASSERT(pReader != NULL); + + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + + bytesRemaining = (pReader->sizeInBytes - pReader->cursor); + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (pDst == NULL) { + /* Seek. */ + result = drwav_buffer_reader_seek(pReader, bytesToRead); + } else { + /* Read. */ + DRWAV_COPY_MEMORY(pDst, drwav_buffer_reader_ptr(pReader), bytesToRead); + pReader->cursor += bytesToRead; + } + + DRWAV_ASSERT(pReader->cursor <= pReader->sizeInBytes); + + if (result == DRWAV_SUCCESS) { + if (pBytesRead != NULL) { + *pBytesRead = bytesToRead; + } + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u16(drwav_buffer_reader* pReader, drwav_uint16* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[2]; + + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + + *pDst = 0; /* Safety. */ + + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + + *pDst = drwav_bytes_to_u16(data); + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u32(drwav_buffer_reader* pReader, drwav_uint32* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[4]; + + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + + *pDst = 0; /* Safety. */ + + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + + *pDst = drwav_bytes_to_u32(data); + + return DRWAV_SUCCESS; +} + + + +DRWAV_PRIVATE drwav_uint64 drwav__read_bext_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) +{ + drwav_uint8 bextData[DRWAV_BEXT_BYTES]; + size_t bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(bextData)) { + drwav_buffer_reader reader; + drwav_uint32 timeReferenceLow; + drwav_uint32 timeReferenceHigh; + size_t extraBytes; + + pMetadata->type = drwav_metadata_type_bext; + + if (drwav_buffer_reader_init(bextData, bytesRead, &reader) == DRWAV_SUCCESS) { + pMetadata->data.bext.pDescription = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_DESCRIPTION_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_DESCRIPTION_BYTES); + + pMetadata->data.bext.pOriginatorName = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + + pMetadata->data.bext.pOriginatorReference = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_REF_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_REF_BYTES); + + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate), NULL); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime), NULL); + + drwav_buffer_reader_read_u32(&reader, &timeReferenceLow); + drwav_buffer_reader_read_u32(&reader, &timeReferenceHigh); + pMetadata->data.bext.timeReference = ((drwav_uint64)timeReferenceHigh << 32) + timeReferenceLow; + + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.version); + + pMetadata->data.bext.pUMID = drwav__metadata_get_memory(pParser, DRWAV_BEXT_UMID_BYTES, 1); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES, NULL); + + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessValue); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessRange); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxTruePeakLevel); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxMomentaryLoudness); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxShortTermLoudness); + + DRWAV_ASSERT((drwav_offset_ptr(drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_RESERVED_BYTES)) == (bextData + DRWAV_BEXT_BYTES)); + + extraBytes = (size_t)(chunkSize - DRWAV_BEXT_BYTES); + if (extraBytes > 0) { + pMetadata->data.bext.pCodingHistory = (char*)drwav__metadata_get_memory(pParser, extraBytes + 1, 1); + DRWAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); + + bytesRead += drwav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); + pMetadata->data.bext.codingHistorySize = (drwav_uint32)drwav__strlen(pMetadata->data.bext.pCodingHistory); + } else { + pMetadata->data.bext.pCodingHistory = NULL; + pMetadata->data.bext.codingHistorySize = 0; + } + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_list_label_or_note_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize, drwav_metadata_type type) +{ + drwav_uint8 cueIDBuffer[DRWAV_LIST_LABEL_OR_NOTE_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead = drwav__metadata_parser_read(pParser, cueIDBuffer, sizeof(cueIDBuffer), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(cueIDBuffer)) { + drwav_uint32 sizeIncludingNullTerminator; + + pMetadata->type = type; + pMetadata->data.labelOrNote.cuePointId = drwav_bytes_to_u32(cueIDBuffer); + + sizeIncludingNullTerminator = (drwav_uint32)chunkSize - DRWAV_LIST_LABEL_OR_NOTE_BYTES; + if (sizeIncludingNullTerminator > 0) { + pMetadata->data.labelOrNote.stringLength = sizeIncludingNullTerminator - 1; + pMetadata->data.labelOrNote.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.labelOrNote.pString, sizeIncludingNullTerminator, &totalBytesRead); + } else { + pMetadata->data.labelOrNote.stringLength = 0; + pMetadata->data.labelOrNote.pString = NULL; + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_list_labelled_cue_region_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) +{ + drwav_uint8 buffer[DRWAV_LIST_LABELLED_TEXT_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 sizeIncludingNullTerminator; + + pMetadata->type = drwav_metadata_type_list_labelled_cue_region; + pMetadata->data.labelledCueRegion.cuePointId = drwav_bytes_to_u32(buffer + 0); + pMetadata->data.labelledCueRegion.sampleLength = drwav_bytes_to_u32(buffer + 4); + pMetadata->data.labelledCueRegion.purposeId[0] = buffer[8]; + pMetadata->data.labelledCueRegion.purposeId[1] = buffer[9]; + pMetadata->data.labelledCueRegion.purposeId[2] = buffer[10]; + pMetadata->data.labelledCueRegion.purposeId[3] = buffer[11]; + pMetadata->data.labelledCueRegion.country = drwav_bytes_to_u16(buffer + 12); + pMetadata->data.labelledCueRegion.language = drwav_bytes_to_u16(buffer + 14); + pMetadata->data.labelledCueRegion.dialect = drwav_bytes_to_u16(buffer + 16); + pMetadata->data.labelledCueRegion.codePage = drwav_bytes_to_u16(buffer + 18); + + sizeIncludingNullTerminator = (drwav_uint32)chunkSize - DRWAV_LIST_LABELLED_TEXT_BYTES; + if (sizeIncludingNullTerminator > 0) { + pMetadata->data.labelledCueRegion.stringLength = sizeIncludingNullTerminator - 1; + pMetadata->data.labelledCueRegion.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.labelledCueRegion.pString, sizeIncludingNullTerminator, &totalBytesRead); + } else { + pMetadata->data.labelledCueRegion.stringLength = 0; + pMetadata->data.labelledCueRegion.pString = NULL; + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_info_text_chunk(drwav__metadata_parser* pParser, drwav_uint64 chunkSize, drwav_metadata_type type) +{ + drwav_uint64 bytesRead = 0; + drwav_uint32 stringSizeWithNullTerminator = (drwav_uint32)chunkSize; + + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, stringSizeWithNullTerminator, 1); + } else { + drwav_metadata* pMetadata = &pParser->pMetadata[pParser->metadataCursor]; + pMetadata->type = type; + if (stringSizeWithNullTerminator > 0) { + pMetadata->data.infoText.stringLength = stringSizeWithNullTerminator - 1; + pMetadata->data.infoText.pString = (char*)drwav__metadata_get_memory(pParser, stringSizeWithNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.infoText.pString != NULL); + + bytesRead = drwav__metadata_parser_read(pParser, pMetadata->data.infoText.pString, (size_t)stringSizeWithNullTerminator, NULL); + if (bytesRead == chunkSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } else { + pMetadata->data.infoText.stringLength = 0; + pMetadata->data.infoText.pString = NULL; + pParser->metadataCursor += 1; + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_unknown_chunk(drwav__metadata_parser* pParser, const drwav_uint8* pChunkId, drwav_uint64 chunkSize, drwav_metadata_location location) +{ + drwav_uint64 bytesRead = 0; + + if (location == drwav_metadata_location_invalid) { + return 0; + } + + if (drwav_fourcc_equal(pChunkId, "data") || drwav_fourcc_equal(pChunkId, "fmt") || drwav_fourcc_equal(pChunkId, "fact")) { + return 0; + } + + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)chunkSize, 1); + } else { + drwav_metadata* pMetadata = &pParser->pMetadata[pParser->metadataCursor]; + pMetadata->type = drwav_metadata_type_unknown; + pMetadata->data.unknown.chunkLocation = location; + pMetadata->data.unknown.id[0] = pChunkId[0]; + pMetadata->data.unknown.id[1] = pChunkId[1]; + pMetadata->data.unknown.id[2] = pChunkId[2]; + pMetadata->data.unknown.id[3] = pChunkId[3]; + pMetadata->data.unknown.dataSizeInBytes = (drwav_uint32)chunkSize; + pMetadata->data.unknown.pData = (drwav_uint8 *)drwav__metadata_get_memory(pParser, (size_t)chunkSize, 1); + DRWAV_ASSERT(pMetadata->data.unknown.pData != NULL); + + bytesRead = drwav__metadata_parser_read(pParser, pMetadata->data.unknown.pData, pMetadata->data.unknown.dataSizeInBytes, NULL); + if (bytesRead == pMetadata->data.unknown.dataSizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to read. */ + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_bool32 drwav__chunk_matches(drwav_metadata_type allowedMetadataTypes, const drwav_uint8* pChunkID, drwav_metadata_type type, const char* pID) +{ + return (allowedMetadataTypes & type) && drwav_fourcc_equal(pChunkID, pID); +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata_type allowedMetadataTypes) +{ + const drwav_uint8 *pChunkID = pChunkHeader->id.fourcc; + drwav_uint64 bytesRead = 0; + + if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_smpl, "smpl")) { + if (pChunkHeader->sizeInBytes >= DRWAV_SMPL_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + drwav_uint8 buffer[4]; + size_t bytesJustRead; + + if (!pParser->onSeek(pParser->pReadSeekUserData, 28, drwav_seek_origin_current)) { + return bytesRead; + } + bytesRead += 28; + + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 loopCount = drwav_bytes_to_u32(buffer); + drwav_uint64 calculatedLoopCount; + + /* The loop count must be validated against the size of the chunk. */ + calculatedLoopCount = (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES; + if (calculatedLoopCount == loopCount) { + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(buffer); + + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_smpl_loop) * loopCount, DRWAV_METADATA_ALIGNMENT); + drwav__metadata_request_extra_memory_for_stage_2(pParser, samplerSpecificDataSizeInBytes, 1); + } + } else { + /* Loop count in header does not match the size of the chunk. */ + } + } + } else { + bytesRead = drwav__read_smpl_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_inst, "inst")) { + if (pChunkHeader->sizeInBytes == DRWAV_INST_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_inst_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_acid, "acid")) { + if (pChunkHeader->sizeInBytes == DRWAV_ACID_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_acid_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_cue, "cue ")) { + if (pChunkHeader->sizeInBytes >= DRWAV_CUE_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + size_t cueCount; + + pParser->metadataCount += 1; + cueCount = (size_t)(pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES; + drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_cue_point) * cueCount, DRWAV_METADATA_ALIGNMENT); + } else { + bytesRead = drwav__read_cue_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_bext, "bext")) { + if (pChunkHeader->sizeInBytes >= DRWAV_BEXT_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + /* The description field is the largest one in a bext chunk, so that is the max size of this temporary buffer. */ + char buffer[DRWAV_BEXT_DESCRIPTION_BYTES + 1]; + size_t allocSizeNeeded = DRWAV_BEXT_UMID_BYTES; /* We know we will need SMPTE umid size. */ + size_t bytesJustRead; + + buffer[DRWAV_BEXT_DESCRIPTION_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_DESCRIPTION_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_DESCRIPTION_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + + buffer[DRWAV_BEXT_ORIGINATOR_NAME_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_NAME_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_NAME_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + + buffer[DRWAV_BEXT_ORIGINATOR_REF_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_REF_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_REF_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - DRWAV_BEXT_BYTES; /* Coding history. */ + + drwav__metadata_request_extra_memory_for_stage_2(pParser, allocSizeNeeded, 1); + + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_bext_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], pChunkHeader->sizeInBytes); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav_fourcc_equal(pChunkID, "LIST") || drwav_fourcc_equal(pChunkID, "list")) { + drwav_metadata_location listType = drwav_metadata_location_invalid; + while (bytesRead < pChunkHeader->sizeInBytes) { + drwav_uint8 subchunkId[4]; + drwav_uint8 subchunkSizeBuffer[4]; + drwav_uint64 subchunkDataSize; + drwav_uint64 subchunkBytesRead = 0; + drwav_uint64 bytesJustRead = drwav__metadata_parser_read(pParser, subchunkId, sizeof(subchunkId), &bytesRead); + if (bytesJustRead != sizeof(subchunkId)) { + break; + } + + /* + The first thing in a list chunk should be "adtl" or "INFO". + + - adtl means this list is a Associated Data List Chunk and will contain labels, notes + or labelled cue regions. + - INFO means this list is an Info List Chunk containing info text chunks such as IPRD + which would specifies the album of this wav file. + + No data follows the adtl or INFO id so we just make note of what type this list is and + continue. + */ + if (drwav_fourcc_equal(subchunkId, "adtl")) { + listType = drwav_metadata_location_inside_adtl_list; + continue; + } else if (drwav_fourcc_equal(subchunkId, "INFO")) { + listType = drwav_metadata_location_inside_info_list; + continue; + } + + bytesJustRead = drwav__metadata_parser_read(pParser, subchunkSizeBuffer, sizeof(subchunkSizeBuffer), &bytesRead); + if (bytesJustRead != sizeof(subchunkSizeBuffer)) { + break; + } + subchunkDataSize = drwav_bytes_to_u32(subchunkSizeBuffer); + + if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_label, "labl") || drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_note, "note")) { + if (subchunkDataSize >= DRWAV_LIST_LABEL_OR_NOTE_BYTES) { + drwav_uint64 stringSizeWithNullTerm = subchunkDataSize - DRWAV_LIST_LABEL_OR_NOTE_BYTES; + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)stringSizeWithNullTerm, 1); + } else { + subchunkBytesRead = drwav__read_list_label_or_note_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], subchunkDataSize, drwav_fourcc_equal(subchunkId, "labl") ? drwav_metadata_type_list_label : drwav_metadata_type_list_note); + if (subchunkBytesRead == subchunkDataSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_labelled_cue_region, "ltxt")) { + if (subchunkDataSize >= DRWAV_LIST_LABELLED_TEXT_BYTES) { + drwav_uint64 stringSizeWithNullTerminator = subchunkDataSize - DRWAV_LIST_LABELLED_TEXT_BYTES; + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)stringSizeWithNullTerminator, 1); + } else { + subchunkBytesRead = drwav__read_list_labelled_cue_region_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], subchunkDataSize); + if (subchunkBytesRead == subchunkDataSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_software, "ISFT")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_software); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_copyright, "ICOP")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_copyright); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_title, "INAM")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_title); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_artist, "IART")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_artist); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_comment, "ICMT")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_comment); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_date, "ICRD")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_date); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_genre, "IGNR")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_genre); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_album, "IPRD")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_album); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_tracknumber, "ITRK")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_tracknumber); + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { + subchunkBytesRead = drwav__metadata_process_unknown_chunk(pParser, subchunkId, subchunkDataSize, listType); + } + + bytesRead += subchunkBytesRead; + DRWAV_ASSERT(subchunkBytesRead <= subchunkDataSize); + + if (subchunkBytesRead < subchunkDataSize) { + drwav_uint64 bytesToSeek = subchunkDataSize - subchunkBytesRead; + + if (!pParser->onSeek(pParser->pReadSeekUserData, (int)bytesToSeek, drwav_seek_origin_current)) { + break; + } + bytesRead += bytesToSeek; + } + + if ((subchunkDataSize % 2) == 1) { + if (!pParser->onSeek(pParser->pReadSeekUserData, 1, drwav_seek_origin_current)) { + break; + } + bytesRead += 1; + } + } + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { + bytesRead = drwav__metadata_process_unknown_chunk(pParser, pChunkID, pChunkHeader->sizeInBytes, drwav_metadata_location_top_level); + } + + return bytesRead; +} + + +DRWAV_PRIVATE drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) +{ + drwav_uint32 bytesPerFrame; + + /* + The bytes per frame is a bit ambiguous. It can be either be based on the bits per sample, or the block align. The way I'm doing it here + is that if the bits per sample is a multiple of 8, use floor(bitsPerSample*channels/8), otherwise fall back to the block align. + */ + if ((pWav->bitsPerSample & 0x7) == 0) { + /* Bits per sample is a multiple of 8. */ + bytesPerFrame = (pWav->bitsPerSample * pWav->fmt.channels) >> 3; + } else { + bytesPerFrame = pWav->fmt.blockAlign; + } + + /* Validation for known formats. a-law and mu-law should be 1 byte per channel. If it's not, it's not decodable. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW || pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + if (bytesPerFrame != pWav->fmt.channels) { + return 0; /* Invalid file. */ + } + } + + return bytesPerFrame; +} + +DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) +{ + if (pFMT == NULL) { + return 0; + } + + if (pFMT->formatTag != DR_WAVE_FORMAT_EXTENSIBLE) { + return pFMT->formatTag; + } else { + return drwav_bytes_to_u16(pFMT->subFormat); /* Only the first two bytes are required. */ + } +} + +DRWAV_PRIVATE drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pWav == NULL || onRead == NULL || onSeek == NULL) { + return DRWAV_FALSE; + } + + DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); + pWav->onRead = onRead; + pWav->onSeek = onSeek; + pWav->pUserData = pReadSeekUserData; + pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); + + if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { + return DRWAV_FALSE; /* Invalid allocation callbacks. */ + } + + return DRWAV_TRUE; +} + +#pragma stackfunction 200 +DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + /* This function assumes drwav_preinit() has been called beforehand. */ + + drwav_uint64 cursor; /* <-- Keeps track of the byte position so we can seek to specific locations. */ + drwav_bool32 sequential; + drwav_uint8 riff[4]; + drwav_fmt fmt; + unsigned short translatedFormatTag; + drwav_bool32 foundDataChunk; + drwav_uint64 dataChunkSize = 0; /* <-- Important! Don't explicitly set this to 0 anywhere else. Calculation of the size of the data chunk is performed in different paths depending on the container. */ + drwav_uint64 sampleCountFromFactChunk = 0; /* Same as dataChunkSize - make sure this is the only place this is initialized to 0. */ + drwav_uint64 chunkSize; + drwav__metadata_parser metadataParser; + + cursor = 0; + sequential = (flags & DRWAV_SEQUENTIAL) != 0; + + /* The first 4 bytes should be the RIFF identifier. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { + return DRWAV_FALSE; + } + + /* + The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for + w64 it will start with "riff". + */ + if (drwav_fourcc_equal(riff, "RIFF")) { + pWav->container = drwav_container_riff; + } else if (drwav_fourcc_equal(riff, "riff")) { + int i; + drwav_uint8 riff2[12]; + + pWav->container = drwav_container_w64; + + /* Check the rest of the GUID for validity. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) { + return DRWAV_FALSE; + } + + for (i = 0; i < 12; ++i) { + if (riff2[i] != drwavGUID_W64_RIFF[i+4]) { + return DRWAV_FALSE; + } + } + } else if (drwav_fourcc_equal(riff, "RF64")) { + pWav->container = drwav_container_rf64; + } else { + return DRWAV_FALSE; /* Unknown or unsupported container. */ + } + + + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + drwav_uint8 chunkSizeBytes[4]; + drwav_uint8 wave[4]; + + /* RIFF/WAVE */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (pWav->container == drwav_container_riff) { + if (drwav_bytes_to_u32(chunkSizeBytes) < 36) { + return DRWAV_FALSE; /* Chunk size should always be at least 36 bytes. */ + } + } else { + if (drwav_bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) { + return DRWAV_FALSE; /* Chunk size should always be set to -1/0xFFFFFFFF for RF64. The actual size is retrieved later. */ + } + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav_fourcc_equal(wave, "WAVE")) { + return DRWAV_FALSE; /* Expecting "WAVE". */ + } + } else { + drwav_uint8 chunkSizeBytes[8]; + drwav_uint8 wave[16]; + + /* W64 */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (drwav_bytes_to_u64(chunkSizeBytes) < 80) { + return DRWAV_FALSE; + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav_guid_equal(wave, drwavGUID_W64_WAVE)) { + return DRWAV_FALSE; + } + } + + + /* For RF64, the "ds64" chunk must come next, before the "fmt " chunk. */ + if (pWav->container == drwav_container_rf64) { + drwav_uint8 sizeBytes[8]; + drwav_uint64 bytesRemainingInChunk; + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + if (!drwav_fourcc_equal(header.id.fourcc, "ds64")) { + return DRWAV_FALSE; /* Expecting "ds64". */ + } + + bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; + + /* We don't care about the size of the RIFF chunk - skip it. */ + if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + cursor += 8; + + + /* Next 8 bytes is the size of the "data" chunk. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + dataChunkSize = drwav_bytes_to_u64(sizeBytes); + + + /* Next 8 bytes is the same count which we would usually derived from the FACT chunk if it was available. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + sampleCountFromFactChunk = drwav_bytes_to_u64(sizeBytes); + + + /* Skip over everything else. */ + if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor += bytesRemainingInChunk; + } + + + /* The next bytes should be the "fmt " chunk. */ + if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) { + return DRWAV_FALSE; /* Failed to read the "fmt " chunk. */ + } + + /* Basic validation. */ + if ((fmt.sampleRate == 0 || fmt.sampleRate > DRWAV_MAX_SAMPLE_RATE) || + (fmt.channels == 0 || fmt.channels > DRWAV_MAX_CHANNELS) || + (fmt.bitsPerSample == 0 || fmt.bitsPerSample > DRWAV_MAX_BITS_PER_SAMPLE) || + fmt.blockAlign == 0) { + return DRWAV_FALSE; /* Probably an invalid WAV file. */ + } + + + /* Translate the internal format. */ + translatedFormatTag = fmt.formatTag; + if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + translatedFormatTag = drwav_bytes_to_u16(fmt.subFormat + 0); + } + + DRWAV_ZERO_MEMORY(&metadataParser, sizeof(metadataParser)); + + /* Not tested on W64. */ + if (!sequential && pWav->allowedMetadataTypes != drwav_metadata_type_none && (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64)) { + drwav_uint64 cursorForMetadata = cursor; + + metadataParser.onRead = pWav->onRead; + metadataParser.onSeek = pWav->onSeek; + metadataParser.pReadSeekUserData = pWav->pUserData; + metadataParser.stage = drwav__metadata_parser_stage_count; + + for (;;) { + drwav_result result; + drwav_uint64 bytesRead; + drwav_uint64 remainingBytes; + drwav_chunk_header header; + + result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursorForMetadata, &header); + if (result != DRWAV_SUCCESS) { + break; + } + + bytesRead = drwav__metadata_process_chunk(&metadataParser, &header, pWav->allowedMetadataTypes); + DRWAV_ASSERT(bytesRead <= header.sizeInBytes); + + remainingBytes = header.sizeInBytes - bytesRead + header.paddingSize; + if (!drwav__seek_forward(pWav->onSeek, remainingBytes, pWav->pUserData)) { + break; + } + cursorForMetadata += remainingBytes; + } + + if (!drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData)) { + return DRWAV_FALSE; + } + + drwav__metadata_alloc(&metadataParser, &pWav->allocationCallbacks); + metadataParser.stage = drwav__metadata_parser_stage_read; + } + + /* + We need to enumerate over each chunk for two reasons: + 1) The "data" chunk may not be the next one + 2) We may want to report each chunk back to the client + + In order to correctly report each chunk back to the client we will need to keep looping until the end of the file. + */ + foundDataChunk = DRWAV_FALSE; + + /* The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop. */ + for (;;) { + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + if (!foundDataChunk) { + return DRWAV_FALSE; + } else { + break; /* Probably at the end of the file. Get out of the loop. */ + } + } + + /* Tell the client about this chunk. */ + if (!sequential && onChunk != NULL) { + drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, pWav->onRead, pWav->onSeek, pWav->pUserData, &header, pWav->container, &fmt); + + /* + dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before + we called the callback. + */ + if (callbackBytesRead > 0) { + if (!drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData)) { + return DRWAV_FALSE; + } + } + } + + if (!sequential && pWav->allowedMetadataTypes != drwav_metadata_type_none && (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64)) { + drwav_uint64 bytesRead = drwav__metadata_process_chunk(&metadataParser, &header, pWav->allowedMetadataTypes); + + if (bytesRead > 0) { + if (!drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData)) { + return DRWAV_FALSE; + } + } + } + + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + + chunkSize = header.sizeInBytes; + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + if (drwav_fourcc_equal(header.id.fourcc, "data")) { + foundDataChunk = DRWAV_TRUE; + if (pWav->container != drwav_container_rf64) { /* The data chunk size for RF64 will always be set to 0xFFFFFFFF here. It was set to it's true value earlier. */ + dataChunkSize = chunkSize; + } + } + } else { + if (drwav_guid_equal(header.id.guid, drwavGUID_W64_DATA)) { + foundDataChunk = DRWAV_TRUE; + dataChunkSize = chunkSize; + } + } + + /* + If at this point we have found the data chunk and we're running in sequential mode, we need to break out of this loop. The reason for + this is that we would otherwise require a backwards seek which sequential mode forbids. + */ + if (foundDataChunk && sequential) { + break; + } + + /* Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats. */ + if (pWav->container == drwav_container_riff) { + if (drwav_fourcc_equal(header.id.fourcc, "fact")) { + drwav_uint32 sampleCount; + if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) { + return DRWAV_FALSE; + } + chunkSize -= 4; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + + /* + The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this + for Microsoft ADPCM formats. + */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + sampleCountFromFactChunk = sampleCount; + } else { + sampleCountFromFactChunk = 0; + } + } + } else if (pWav->container == drwav_container_w64) { + if (drwav_guid_equal(header.id.guid, drwavGUID_W64_FACT)) { + if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { + return DRWAV_FALSE; + } + chunkSize -= 8; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + } + } else if (pWav->container == drwav_container_rf64) { + /* We retrieved the sample count from the ds64 chunk earlier so no need to do that here. */ + } + + /* Make sure we seek past the padding. */ + chunkSize += header.paddingSize; + if (!drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData)) { + break; + } + cursor += chunkSize; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + } + + pWav->pMetadata = metadataParser.pMetadata; + pWav->metadataCount = metadataParser.metadataCount; + + /* If we haven't found a data chunk, return an error. */ + if (!foundDataChunk) { + return DRWAV_FALSE; + } + + /* We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk. */ + if (!sequential) { + if (!drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor = pWav->dataChunkDataPos; + } + + + /* At this point we should be sitting on the first byte of the raw audio data. */ + + pWav->fmt = fmt; + pWav->sampleRate = fmt.sampleRate; + pWav->channels = fmt.channels; + pWav->bitsPerSample = fmt.bitsPerSample; + pWav->bytesRemaining = dataChunkSize; + pWav->translatedFormatTag = translatedFormatTag; + pWav->dataChunkDataSize = dataChunkSize; + + if (sampleCountFromFactChunk != 0) { + pWav->totalPCMFrameCount = sampleCountFromFactChunk; + } else { + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return DRWAV_FALSE; /* Invalid file. */ + } + + pWav->totalPCMFrameCount = dataChunkSize / bytesPerFrame; + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 totalBlockHeaderSizeInBytes; + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + + /* Make sure any trailing partial block is accounted for. */ + if ((blockCount * fmt.blockAlign) < dataChunkSize) { + blockCount += 1; + } + + /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ + totalBlockHeaderSizeInBytes = blockCount * (6*fmt.channels); + pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 totalBlockHeaderSizeInBytes; + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + + /* Make sure any trailing partial block is accounted for. */ + if ((blockCount * fmt.blockAlign) < dataChunkSize) { + blockCount += 1; + } + + /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ + totalBlockHeaderSizeInBytes = blockCount * (4*fmt.channels); + pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; + + /* The header includes a decoded sample for each channel which acts as the initial predictor sample. */ + pWav->totalPCMFrameCount += blockCount; + } + } + + /* Some formats only support a certain number of channels. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + if (pWav->channels > 2) { + return DRWAV_FALSE; + } + } + + /* The number of bytes per frame must be known. If not, it's an invalid file and not decodable. */ + if (drwav_get_bytes_per_pcm_frame(pWav) == 0) { + return DRWAV_FALSE; + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + /* + I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), + it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count + from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct + way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should + always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my + correctness tests against libsndfile, and is disabled by default. + */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; /* x2 because two samples per byte. */ + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; + } +#endif + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit(pWav, onRead, onSeek, pReadSeekUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); +} + +DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit(pWav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->allowedMetadataTypes = drwav_metadata_type_all_including_unknown; /* <-- Needs to be set to tell drwav_init_ex() that we need to process metadata. */ + return drwav_init__internal(pWav, NULL, NULL, flags); +} + +DRWAV_API drwav_metadata* drwav_take_ownership_of_metadata(drwav* pWav) +{ + drwav_metadata *result = pWav->pMetadata; + + pWav->pMetadata = NULL; + pWav->metadataCount = 0; + + return result; +} + + +DRWAV_PRIVATE size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + /* Generic write. Assumes no byte reordering required. */ + return pWav->onWrite(pWav->pUserData, pData, dataSize); +} + +DRWAV_PRIVATE size_t drwav__write_byte(drwav* pWav, drwav_uint8 byte) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + return pWav->onWrite(pWav->pUserData, &byte, 1); +} + +DRWAV_PRIVATE size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap16(value); + } + + return drwav__write(pWav, &value, 2); +} + +DRWAV_PRIVATE size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap32(value); + } + + return drwav__write(pWav, &value, 4); +} + +DRWAV_PRIVATE size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap64(value); + } + + return drwav__write(pWav, &value, 8); +} + +DRWAV_PRIVATE size_t drwav__write_f32ne_to_le(drwav* pWav, float value) +{ + union { + drwav_uint32 u32; + float f32; + } u; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + u.f32 = value; + + if (!drwav__is_little_endian()) { + u.u32 = drwav__bswap32(u.u32); + } + + return drwav__write(pWav, &u.u32, 4); +} + +DRWAV_PRIVATE size_t drwav__write_or_count(drwav* pWav, const void* pData, size_t dataSize) +{ + if (pWav == NULL) { + return dataSize; + } + + return drwav__write(pWav, pData, dataSize); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_byte(drwav* pWav, drwav_uint8 byte) +{ + if (pWav == NULL) { + return 1; + } + + return drwav__write_byte(pWav, byte); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_u16ne_to_le(drwav* pWav, drwav_uint16 value) +{ + if (pWav == NULL) { + return 2; + } + + return drwav__write_u16ne_to_le(pWav, value); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_u32ne_to_le(drwav* pWav, drwav_uint32 value) +{ + if (pWav == NULL) { + return 4; + } + + return drwav__write_u32ne_to_le(pWav, value); +} + +#if 0 /* Unused for now. */ +DRWAV_PRIVATE size_t drwav__write_or_count_u64ne_to_le(drwav* pWav, drwav_uint64 value) +{ + if (pWav == NULL) { + return 8; + } + + return drwav__write_u64ne_to_le(pWav, value); +} +#endif + +DRWAV_PRIVATE size_t drwav__write_or_count_f32ne_to_le(drwav* pWav, float value) +{ + if (pWav == NULL) { + return 4; + } + + return drwav__write_f32ne_to_le(pWav, value); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_string_to_fixed_size_buf(drwav* pWav, char* str, size_t bufFixedSize) +{ + size_t len; + + if (pWav == NULL) { + return bufFixedSize; + } + + len = drwav__strlen_clamped(str, bufFixedSize); + drwav__write_or_count(pWav, str, len); + + if (len < bufFixedSize) { + size_t i; + for (i = 0; i < bufFixedSize - len; ++i) { + drwav__write_byte(pWav, 0); + } + } + + return bufFixedSize; +} + + +/* pWav can be NULL meaning just count the bytes that would be written. */ +DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* pMetadatas, drwav_uint32 metadataCount) +{ + size_t bytesWritten = 0; + drwav_bool32 hasListAdtl = DRWAV_FALSE; + drwav_bool32 hasListInfo = DRWAV_FALSE; + drwav_uint32 iMetadata; + + if (pMetadatas == NULL || metadataCount == 0) { + return 0; + } + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 chunkSize = 0; + + if ((pMetadata->type & drwav_metadata_type_list_all_info_strings) || (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list)) { + hasListInfo = DRWAV_TRUE; + } + + if ((pMetadata->type & drwav_metadata_type_list_all_adtl) || (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list)) { + hasListAdtl = DRWAV_TRUE; + } + + switch (pMetadata->type) { + case drwav_metadata_type_smpl: + { + drwav_uint32 iLoop; + + chunkSize = DRWAV_SMPL_BYTES + DRWAV_SMPL_LOOP_BYTES * pMetadata->data.smpl.sampleLoopCount + pMetadata->data.smpl.samplerSpecificDataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, "smpl", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.manufacturerId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.productId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.samplePeriodNanoseconds); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.midiUnityNote); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.midiPitchFraction); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.smpteFormat); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.smpteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.sampleLoopCount); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.samplerSpecificDataSizeInBytes); + + for (iLoop = 0; iLoop < pMetadata->data.smpl.sampleLoopCount; ++iLoop) { + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].cuePointId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].type); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].firstSampleByteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].lastSampleByteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].sampleFraction); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].playCount); + } + + if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { + bytesWritten += drwav__write(pWav, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes); + } + } break; + + case drwav_metadata_type_inst: + { + chunkSize = DRWAV_INST_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "inst", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.midiUnityNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.fineTuneCents, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.gainDecibels, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.lowNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.highNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.lowVelocity, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.highVelocity, 1); + } break; + + case drwav_metadata_type_cue: + { + drwav_uint32 iCuePoint; + + chunkSize = DRWAV_CUE_BYTES + DRWAV_CUE_POINT_BYTES * pMetadata->data.cue.cuePointCount; + + bytesWritten += drwav__write_or_count(pWav, "cue ", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.cuePointCount); + for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].id); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].blockStart); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset); + } + } break; + + case drwav_metadata_type_acid: + { + chunkSize = DRWAV_ACID_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "acid", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.acid.flags); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.midiUnityNote); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.reserved1); + bytesWritten += drwav__write_or_count_f32ne_to_le(pWav, pMetadata->data.acid.reserved2); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.acid.numBeats); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.meterDenominator); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.meterNumerator); + bytesWritten += drwav__write_or_count_f32ne_to_le(pWav, pMetadata->data.acid.tempo); + } break; + + case drwav_metadata_type_bext: + { + char reservedBuf[DRWAV_BEXT_RESERVED_BYTES]; + drwav_uint32 timeReferenceLow; + drwav_uint32 timeReferenceHigh; + + chunkSize = DRWAV_BEXT_BYTES + pMetadata->data.bext.codingHistorySize; + + bytesWritten += drwav__write_or_count(pWav, "bext", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pDescription, DRWAV_BEXT_DESCRIPTION_BYTES); + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pOriginatorName, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pOriginatorReference, DRWAV_BEXT_ORIGINATOR_REF_BYTES); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate)); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime)); + + timeReferenceLow = (drwav_uint32)(pMetadata->data.bext.timeReference & 0xFFFFFFFF); + timeReferenceHigh = (drwav_uint32)(pMetadata->data.bext.timeReference >> 32); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, timeReferenceLow); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, timeReferenceHigh); + + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.version); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.loudnessValue); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.loudnessRange); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxTruePeakLevel); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxMomentaryLoudness); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxShortTermLoudness); + + DRWAV_ZERO_MEMORY(reservedBuf, sizeof(reservedBuf)); + bytesWritten += drwav__write_or_count(pWav, reservedBuf, sizeof(reservedBuf)); + + if (pMetadata->data.bext.codingHistorySize > 0) { + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pCodingHistory, pMetadata->data.bext.codingHistorySize); + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_top_level) { + chunkSize = pMetadata->data.unknown.dataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, pMetadata->data.unknown.dataSizeInBytes); + } + } break; + + default: break; + } + if ((chunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + + if (hasListInfo) { + drwav_uint32 chunkSize = 4; /* Start with 4 bytes for "INFO". */ + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + + if ((pMetadata->type & drwav_metadata_type_list_all_info_strings)) { + chunkSize += 8; /* For id and string size. */ + chunkSize += pMetadata->data.infoText.stringLength + 1; /* Include null terminator. */ + } else if (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list) { + chunkSize += 8; /* For id string size. */ + chunkSize += pMetadata->data.unknown.dataSizeInBytes; + } + + if ((chunkSize % 2) != 0) { + chunkSize += 1; + } + } + + bytesWritten += drwav__write_or_count(pWav, "LIST", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, "INFO", 4); + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 subchunkSize = 0; + + if (pMetadata->type & drwav_metadata_type_list_all_info_strings) { + const char* pID = NULL; + + switch (pMetadata->type) { + case drwav_metadata_type_list_info_software: pID = "ISFT"; break; + case drwav_metadata_type_list_info_copyright: pID = "ICOP"; break; + case drwav_metadata_type_list_info_title: pID = "INAM"; break; + case drwav_metadata_type_list_info_artist: pID = "IART"; break; + case drwav_metadata_type_list_info_comment: pID = "ICMT"; break; + case drwav_metadata_type_list_info_date: pID = "ICRD"; break; + case drwav_metadata_type_list_info_genre: pID = "IGNR"; break; + case drwav_metadata_type_list_info_album: pID = "IPRD"; break; + case drwav_metadata_type_list_info_tracknumber: pID = "ITRK"; break; + default: break; + } + + DRWAV_ASSERT(pID != NULL); + + if (pMetadata->data.infoText.stringLength) { + subchunkSize = pMetadata->data.infoText.stringLength + 1; + bytesWritten += drwav__write_or_count(pWav, pID, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.infoText.pString, pMetadata->data.infoText.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } else if (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list) { + if (pMetadata->data.unknown.dataSizeInBytes) { + subchunkSize = pMetadata->data.unknown.dataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.unknown.dataSizeInBytes); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, subchunkSize); + } + } + + if ((subchunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + } + + if (hasListAdtl) { + drwav_uint32 chunkSize = 4; /* start with 4 bytes for "adtl" */ + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + + switch (pMetadata->type) + { + case drwav_metadata_type_list_label: + case drwav_metadata_type_list_note: + { + chunkSize += 8; /* for id and chunk size */ + chunkSize += DRWAV_LIST_LABEL_OR_NOTE_BYTES; + + if (pMetadata->data.labelOrNote.stringLength > 0) { + chunkSize += pMetadata->data.labelOrNote.stringLength + 1; + } + } break; + + case drwav_metadata_type_list_labelled_cue_region: + { + chunkSize += 8; /* for id and chunk size */ + chunkSize += DRWAV_LIST_LABELLED_TEXT_BYTES; + + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + chunkSize += pMetadata->data.labelledCueRegion.stringLength + 1; + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list) { + chunkSize += 8; /* for id and chunk size */ + chunkSize += pMetadata->data.unknown.dataSizeInBytes; + } + } break; + + default: break; + } + + if ((chunkSize % 2) != 0) { + chunkSize += 1; + } + } + + bytesWritten += drwav__write_or_count(pWav, "LIST", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, "adtl", 4); + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 subchunkSize = 0; + + switch (pMetadata->type) + { + case drwav_metadata_type_list_label: + case drwav_metadata_type_list_note: + { + if (pMetadata->data.labelOrNote.stringLength > 0) { + const char *pID = NULL; + + if (pMetadata->type == drwav_metadata_type_list_label) { + pID = "labl"; + } + else if (pMetadata->type == drwav_metadata_type_list_note) { + pID = "note"; + } + + DRWAV_ASSERT(pID != NULL); + DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); + + subchunkSize = DRWAV_LIST_LABEL_OR_NOTE_BYTES; + + bytesWritten += drwav__write_or_count(pWav, pID, 4); + subchunkSize += pMetadata->data.labelOrNote.stringLength + 1; + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelOrNote.cuePointId); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelOrNote.pString, pMetadata->data.labelOrNote.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } break; + + case drwav_metadata_type_list_labelled_cue_region: + { + subchunkSize = DRWAV_LIST_LABELLED_TEXT_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "ltxt", 4); + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + subchunkSize += pMetadata->data.labelledCueRegion.stringLength + 1; + } + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelledCueRegion.cuePointId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelledCueRegion.sampleLength); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelledCueRegion.purposeId, 4); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.country); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.language); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.dialect); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.codePage); + + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelledCueRegion.pString, pMetadata->data.labelledCueRegion.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list) { + subchunkSize = pMetadata->data.unknown.dataSizeInBytes; + + DRWAV_ASSERT(pMetadata->data.unknown.pData != NULL); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, subchunkSize); + } + } break; + + default: break; + } + + if ((subchunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + } + + DRWAV_ASSERT((bytesWritten % 2) == 0); + + return bytesWritten; +} + +DRWAV_PRIVATE drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + drwav_uint64 chunkSize = 4 + 24 + (drwav_uint64)drwav__write_or_count_metadata(NULL, pMetadata, metadataCount) + 8 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 24 = "fmt " chunk. 8 = "data" + u32 data size. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + + return (drwav_uint32)chunkSize; /* Safe cast due to the clamp above. */ +} + +DRWAV_PRIVATE drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) +{ + if (dataChunkSize <= 0xFFFFFFFFUL) { + return (drwav_uint32)dataChunkSize; + } else { + return 0xFFFFFFFFUL; + } +} + +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + drwav_uint64 dataSubchunkPaddingSize = drwav__chunk_padding_size_w64(dataChunkSize); + + return 80 + 24 + dataChunkSize + dataSubchunkPaddingSize; /* +24 because W64 includes the size of the GUID and size fields. */ +} + +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + return 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ +} + +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize, drwav_metadata *metadata, drwav_uint32 numMetadata) +{ + drwav_uint64 chunkSize = 4 + 36 + 24 + (drwav_uint64)drwav__write_or_count_metadata(NULL, metadata, numMetadata) + 8 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 36 = "ds64" chunk. 24 = "fmt " chunk. 8 = "data" + u32 data size. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + + return chunkSize; +} + +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + return dataChunkSize; +} + + + +DRWAV_PRIVATE drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pWav == NULL || onWrite == NULL) { + return DRWAV_FALSE; + } + + if (!isSequential && onSeek == NULL) { + return DRWAV_FALSE; /* <-- onSeek is required when in non-sequential mode. */ + } + + /* Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. */ + if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) { + return DRWAV_FALSE; + } + if (pFormat->format == DR_WAVE_FORMAT_ADPCM || pFormat->format == DR_WAVE_FORMAT_DVI_ADPCM) { + return DRWAV_FALSE; + } + + DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); + pWav->onWrite = onWrite; + pWav->onSeek = onSeek; + pWav->pUserData = pUserData; + pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); + + if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { + return DRWAV_FALSE; /* Invalid allocation callbacks. */ + } + + pWav->fmt.formatTag = (drwav_uint16)pFormat->format; + pWav->fmt.channels = (drwav_uint16)pFormat->channels; + pWav->fmt.sampleRate = pFormat->sampleRate; + pWav->fmt.avgBytesPerSec = (drwav_uint32)((pFormat->bitsPerSample * pFormat->sampleRate * pFormat->channels) / 8); + pWav->fmt.blockAlign = (drwav_uint16)((pFormat->channels * pFormat->bitsPerSample) / 8); + pWav->fmt.bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->fmt.extendedSize = 0; + pWav->isSequentialWrite = isSequential; + + return DRWAV_TRUE; +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + /* The function assumes drwav_preinit_write() was called beforehand. */ + + size_t runningPos = 0; + drwav_uint64 initialDataChunkSize = 0; + drwav_uint64 chunkSizeFMT; + + /* + The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In + sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- + sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. + */ + if (pWav->isSequentialWrite) { + initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8; + + /* + The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 + so for the sake of simplicity I'm not doing any validation for that. + */ + if (pFormat->container == drwav_container_riff) { + if (initialDataChunkSize > (0xFFFFFFFFUL - 36)) { + return DRWAV_FALSE; /* Not enough room to store every sample. */ + } + } + } + + pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; + + + /* "RIFF" chunk. */ + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; /* +28 = "WAVE" + [sizeof "fmt " chunk] */ + runningPos += drwav__write(pWav, "RIFF", 4); + runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); + runningPos += drwav__write(pWav, "WAVE", 4); + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); + runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "RF64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always 0xFFFFFFFF for RF64. Set to a proper value in the "ds64" chunk. */ + runningPos += drwav__write(pWav, "WAVE", 4); + } + + + /* "ds64" chunk (RF64 only). */ + if (pFormat->container == drwav_container_rf64) { + drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */ + drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; /* +8 for the ds64 header. */ + + runningPos += drwav__write(pWav, "ds64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); /* Size of ds64. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); /* Size of RIFF. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); /* Size of DATA. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); /* Sample count. */ + runningPos += drwav__write_u32ne_to_le(pWav, 0); /* Table length. Always set to zero in our case since we're not doing any other chunks than "DATA". */ + } + + + /* "fmt " chunk. */ + if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { + chunkSizeFMT = 16; + runningPos += drwav__write(pWav, "fmt ", 4); + runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); + } else if (pFormat->container == drwav_container_w64) { + chunkSizeFMT = 40; + runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); + } + + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.formatTag); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.channels); + runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.sampleRate); + runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample); + + /* TODO: is a 'fact' chunk required for DR_WAVE_FORMAT_IEEE_FLOAT? */ + + if (!pWav->isSequentialWrite && pWav->pMetadata != NULL && pWav->metadataCount > 0 && (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64)) { + runningPos += drwav__write_or_count_metadata(pWav, pWav->pMetadata, pWav->metadataCount); + } + + pWav->dataChunkDataPos = runningPos; + + /* "data" chunk. */ + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always set to 0xFFFFFFFF for RF64. The true size of the data chunk is specified in the ds64 chunk. */ + } + + /* Set some properties for the client's convenience. */ + pWav->container = pFormat->container; + pWav->channels = (drwav_uint16)pFormat->channels; + pWav->sampleRate = pFormat->sampleRate; + pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->translatedFormatTag = (drwav_uint16)pFormat->format; + pWav->dataChunkDataPos = runningPos; + + return DRWAV_TRUE; +} + + +DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init_write__internal(pWav, pFormat, 0); /* DRWAV_FALSE = Not Sequential */ +} + +DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_TRUE, onWrite, NULL, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init_write__internal(pWav, pFormat, totalSampleCount); /* DRWAV_TRUE = Sequential */ +} + +DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_write_sequential(pWav, pFormat, totalPCMFrameCount*pFormat->channels, onWrite, pUserData, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_write_with_metadata(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->pMetadata = pMetadata; + pWav->metadataCount = metadataCount; + + return drwav_init_write__internal(pWav, pFormat, 0); +} + + +DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalFrameCount, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + /* Casting totalFrameCount to drwav_int64 for VC6 compatibility. No issues in practice because nobody is going to exhaust the whole 63 bits. */ + drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalFrameCount * pFormat->channels * pFormat->bitsPerSample/8.0); + drwav_uint64 riffChunkSizeBytes; + drwav_uint64 fileSizeBytes = 0; + + if (pFormat->container == drwav_container_riff) { + riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes, pMetadata, metadataCount); + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ + } else if (pFormat->container == drwav_container_w64) { + riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); + fileSizeBytes = riffChunkSizeBytes; + } else if (pFormat->container == drwav_container_rf64) { + riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes, pMetadata, metadataCount); + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ + } + + return fileSizeBytes; +} + + +#ifndef DR_WAV_NO_STDIO + +/* drwav_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ +#include +DRWAV_PRIVATE drwav_result drwav_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRWAV_SUCCESS; + #ifdef EPERM + case EPERM: return DRWAV_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRWAV_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRWAV_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRWAV_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRWAV_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRWAV_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRWAV_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRWAV_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRWAV_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRWAV_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRWAV_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRWAV_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRWAV_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRWAV_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRWAV_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRWAV_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRWAV_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRWAV_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRWAV_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRWAV_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRWAV_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRWAV_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRWAV_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRWAV_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRWAV_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRWAV_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRWAV_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRWAV_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRWAV_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRWAV_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRWAV_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRWAV_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DRWAV_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRWAV_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRWAV_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRWAV_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRWAV_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRWAV_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRWAV_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRWAV_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRWAV_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRWAV_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRWAV_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRWAV_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRWAV_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRWAV_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRWAV_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRWAV_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRWAV_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRWAV_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRWAV_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRWAV_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRWAV_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRWAV_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRWAV_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRWAV_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRWAV_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRWAV_ERROR; + #endif + #ifdef EADV + case EADV: return DRWAV_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRWAV_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRWAV_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRWAV_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRWAV_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRWAV_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRWAV_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRWAV_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRWAV_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRWAV_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRWAV_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRWAV_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRWAV_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRWAV_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRWAV_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRWAV_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRWAV_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRWAV_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRWAV_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRWAV_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRWAV_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRWAV_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRWAV_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRWAV_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRWAV_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRWAV_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRWAV_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRWAV_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRWAV_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRWAV_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRWAV_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRWAV_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRWAV_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRWAV_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRWAV_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRWAV_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRWAV_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRWAV_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRWAV_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRWAV_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRWAV_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRWAV_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRWAV_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRWAV_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRWAV_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRWAV_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRWAV_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRWAV_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRWAV_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRWAV_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRWAV_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRWAV_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRWAV_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRWAV_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRWAV_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRWAV_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRWAV_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRWAV_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRWAV_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRWAV_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRWAV_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRWAV_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRWAV_ERROR; + #endif + default: return DRWAV_ERROR; + } +} + +DRWAV_PRIVATE drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRWAV_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drwav_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drwav_result result = drwav_result_from_errno(errno); + if (result == DRWAV_SUCCESS) { + result = DRWAV_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRWAV_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRWAV_HAS_WFOPEN + #endif +#endif + +DRWAV_PRIVATE drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRWAV_INVALID_ARGS; + } + +#if defined(DRWAV_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drwav_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drwav_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can + think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. + */ + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRWAV_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drwav_result_from_errno(errno); + } + + pFilePathMB = (char*)drwav__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRWAV_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRWAV_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drwav__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + + if (*ppFile == NULL) { + return DRWAV_ERROR; + } +#endif + + return DRWAV_SUCCESS; +} + + +DRWAV_PRIVATE size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +DRWAV_PRIVATE size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) +{ + return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_ex(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, drwav_metadata_type allowedMetadataTypes, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav_bool32 result; + + result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + pWav->allowedMetadataTypes = allowedMetadataTypes; + + result = drwav_init__internal(pWav, onChunk, pChunkUserData, flags); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, drwav_metadata_type_none, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_ex_w(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, drwav_metadata_type_none, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_with_metadata(drwav* pWav, const char* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, NULL, NULL, flags, drwav_metadata_type_all_including_unknown, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_with_metadata_w(drwav* pWav, const wchar_t* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, NULL, NULL, flags, drwav_metadata_type_all_including_unknown, pAllocationCallbacks); +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav_bool32 result; + + result = drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + result = drwav_init_write__internal(pWav, pFormat, totalSampleCount); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "wb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); +} + +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"wb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_file_write_sequential(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write_w__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write_w__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_file_write_sequential_w(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} +#endif /* DR_WAV_NO_STDIO */ + + +DRWAV_PRIVATE size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + drwav* pWav = (drwav*)pUserData; + size_t bytesRemaining; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->memoryStream.dataSize >= pWav->memoryStream.currentReadPos); + + bytesRemaining = pWav->memoryStream.dataSize - pWav->memoryStream.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DRWAV_COPY_MEMORY(pBufferOut, pWav->memoryStream.data + pWav->memoryStream.currentReadPos, bytesToRead); + pWav->memoryStream.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav* pWav = (drwav*)pUserData; + DRWAV_ASSERT(pWav != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (pWav->memoryStream.currentReadPos + offset > pWav->memoryStream.dataSize) { + return DRWAV_FALSE; /* Trying to seek too far forward. */ + } + } else { + if (pWav->memoryStream.currentReadPos < (size_t)-offset) { + return DRWAV_FALSE; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pWav->memoryStream.currentReadPos += offset; + } else { + if ((drwav_uint32)offset <= pWav->memoryStream.dataSize) { + pWav->memoryStream.currentReadPos = offset; + } else { + return DRWAV_FALSE; /* Trying to seek too far forward. */ + } + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) +{ + drwav* pWav = (drwav*)pUserData; + size_t bytesRemaining; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->memoryStreamWrite.dataCapacity >= pWav->memoryStreamWrite.currentWritePos); + + bytesRemaining = pWav->memoryStreamWrite.dataCapacity - pWav->memoryStreamWrite.currentWritePos; + if (bytesRemaining < bytesToWrite) { + /* Need to reallocate. */ + void* pNewData; + size_t newDataCapacity = (pWav->memoryStreamWrite.dataCapacity == 0) ? 256 : pWav->memoryStreamWrite.dataCapacity * 2; + + /* If doubling wasn't enough, just make it the minimum required size to write the data. */ + if ((newDataCapacity - pWav->memoryStreamWrite.currentWritePos) < bytesToWrite) { + newDataCapacity = pWav->memoryStreamWrite.currentWritePos + bytesToWrite; + } + + pNewData = drwav__realloc_from_callbacks(*pWav->memoryStreamWrite.ppData, newDataCapacity, pWav->memoryStreamWrite.dataCapacity, &pWav->allocationCallbacks); + if (pNewData == NULL) { + return 0; + } + + *pWav->memoryStreamWrite.ppData = pNewData; + pWav->memoryStreamWrite.dataCapacity = newDataCapacity; + } + + DRWAV_COPY_MEMORY(((drwav_uint8*)(*pWav->memoryStreamWrite.ppData)) + pWav->memoryStreamWrite.currentWritePos, pDataIn, bytesToWrite); + + pWav->memoryStreamWrite.currentWritePos += bytesToWrite; + if (pWav->memoryStreamWrite.dataSize < pWav->memoryStreamWrite.currentWritePos) { + pWav->memoryStreamWrite.dataSize = pWav->memoryStreamWrite.currentWritePos; + } + + *pWav->memoryStreamWrite.pDataSize = pWav->memoryStreamWrite.dataSize; + + return bytesToWrite; +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav* pWav = (drwav*)pUserData; + DRWAV_ASSERT(pWav != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (pWav->memoryStreamWrite.currentWritePos + offset > pWav->memoryStreamWrite.dataSize) { + offset = (int)(pWav->memoryStreamWrite.dataSize - pWav->memoryStreamWrite.currentWritePos); /* Trying to seek too far forward. */ + } + } else { + if (pWav->memoryStreamWrite.currentWritePos < (size_t)-offset) { + offset = -(int)pWav->memoryStreamWrite.currentWritePos; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pWav->memoryStreamWrite.currentWritePos += offset; + } else { + if ((drwav_uint32)offset <= pWav->memoryStreamWrite.dataSize) { + pWav->memoryStreamWrite.currentWritePos = offset; + } else { + pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; /* Trying to seek too far forward. */ + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (data == NULL || dataSize == 0) { + return DRWAV_FALSE; + } + + if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStream.data = (const drwav_uint8*)data; + pWav->memoryStream.dataSize = dataSize; + pWav->memoryStream.currentReadPos = 0; + + return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); +} + +DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* data, size_t dataSize, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (data == NULL || dataSize == 0) { + return DRWAV_FALSE; + } + + if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStream.data = (const drwav_uint8*)data; + pWav->memoryStream.dataSize = dataSize; + pWav->memoryStream.currentReadPos = 0; + + pWav->allowedMetadataTypes = drwav_metadata_type_all_including_unknown; + + return drwav_init__internal(pWav, NULL, NULL, flags); +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (ppData == NULL || pDataSize == NULL) { + return DRWAV_FALSE; + } + + *ppData = NULL; /* Important because we're using realloc()! */ + *pDataSize = 0; + + if (!drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStreamWrite.ppData = ppData; + pWav->memoryStreamWrite.pDataSize = pDataSize; + pWav->memoryStreamWrite.dataSize = 0; + pWav->memoryStreamWrite.dataCapacity = 0; + pWav->memoryStreamWrite.currentWritePos = 0; + + return drwav_init_write__internal(pWav, pFormat, totalSampleCount); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_memory_write_sequential(pWav, ppData, pDataSize, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} + + +#pragma stackfunction 200 +DRWAV_API drwav_result drwav_uninit(drwav* pWav) +{ + drwav_result result = DRWAV_SUCCESS; + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + /* + If the drwav object was opened in write mode we'll need to finalize a few things: + - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. + - Set the size of the "data" chunk. + */ + if (pWav->onWrite != NULL) { + drwav_uint32 paddingSize = 0; + + /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */ + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); + } else { + paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); + } + + if (paddingSize > 0) { + drwav_uint64 paddingData = 0; + drwav__write(pWav, &paddingData, paddingSize); /* Byte order does not matter for this. */ + } + + /* + Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need + to do this when using non-sequential mode. + */ + if (pWav->onSeek && !pWav->isSequentialWrite) { + if (pWav->container == drwav_container_riff) { + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { + drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); + drwav__write_u32ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) { + drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); + drwav__write_u32ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == drwav_container_w64) { + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == drwav_container_rf64) { + /* We only need to update the ds64 chunk. The "RIFF" and "data" chunks always have their sizes set to 0xFFFFFFFF for RF64. */ + int ds64BodyPos = 12 + 8; + + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } + } + } + + /* Validation for sequential mode. */ + if (pWav->isSequentialWrite) { + if (pWav->dataChunkDataSize != pWav->dataChunkDataSizeTargetWrite) { + result = DRWAV_INVALID_FILE; + } + } + } else { + if (pWav->pMetadata != NULL) { + pWav->allocationCallbacks.onFree(pWav->pMetadata, pWav->allocationCallbacks.pUserData); + } + } + +#ifndef DR_WAV_NO_STDIO + /* + If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() + was used by looking at the onRead and onSeek callbacks. + */ + if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) { + fclose((FILE*)pWav->pUserData); + } +#endif + + return result; +} + + +#pragma stackfunction 800 +DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) +{ + size_t bytesRead; + drwav_uint32 bytesPerFrame; + + if (pWav == NULL || bytesToRead == 0) { + return 0; /* Invalid args. */ + } + + if (bytesToRead > pWav->bytesRemaining) { + bytesToRead = (size_t)pWav->bytesRemaining; + } + + if (bytesToRead == 0) { + return 0; /* At end. */ + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; /* Could not determine the bytes per frame. */ + } + + if (pBufferOut != NULL) { + bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); + } else { + /* We need to seek. If we fail, we need to read-and-discard to make sure we get a good byte count. */ + bytesRead = 0; + while (bytesRead < bytesToRead) { + size_t bytesToSeek = (bytesToRead - bytesRead); + if (bytesToSeek > 0x7FFFFFFF) { + bytesToSeek = 0x7FFFFFFF; + } + + if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, drwav_seek_origin_current) == DRWAV_FALSE) { + break; + } + + bytesRead += bytesToSeek; + } + + /* When we get here we may need to read-and-discard some data. */ + while (bytesRead < bytesToRead) { + drwav_uint8 buffer[4096]; + size_t bytesSeeked; + size_t bytesToSeek = (bytesToRead - bytesRead); + if (bytesToSeek > sizeof(buffer)) { + bytesToSeek = sizeof(buffer); + } + + bytesSeeked = pWav->onRead(pWav->pUserData, buffer, bytesToSeek); + bytesRead += bytesSeeked; + + if (bytesSeeked < bytesToSeek) { + break; /* Reached the end. */ + } + } + } + + pWav->readCursorInPCMFrames += bytesRead / bytesPerFrame; + + pWav->bytesRemaining -= bytesRead; + return bytesRead; +} + + + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint32 bytesPerFrame; + drwav_uint64 bytesToRead; /* Intentionally uint64 instead of size_t so we can do a check that we're not reading too much on 32-bit builds. */ + + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + /* Cannot use this function for compressed formats. */ + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + return 0; + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + bytesToRead = framesToRead * bytesPerFrame; + if (bytesToRead > DRWAV_SIZE_MAX) { + bytesToRead = (DRWAV_SIZE_MAX / bytesPerFrame) * bytesPerFrame; /* Round the number of bytes to read to a clean frame boundary. */ + } + + /* + Doing an explicit check here just to make it clear that we don't want to be attempt to read anything if there's no bytes to read. There + *could* be a time where it evaluates to 0 due to overflowing. + */ + if (bytesToRead == 0) { + return 0; + } + + return drwav_read_raw(pWav, (size_t)bytesToRead, pBufferOut) / bytesPerFrame; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + + if (pBufferOut != NULL) { + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; /* Could not get the bytes per frame which means bytes per sample cannot be determined and we don't know how to byte swap. */ + } + + drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, bytesPerFrame/pWav->channels, pWav->translatedFormatTag); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + if (drwav__is_little_endian()) { + return drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + } else { + return drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); + } +} + + + +DRWAV_PRIVATE drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) +{ + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; /* No seeking in write mode. */ + } + + if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + /* Cached data needs to be cleared for compressed formats. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + DRWAV_ZERO_OBJECT(&pWav->msadpcm); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + DRWAV_ZERO_OBJECT(&pWav->ima); + } else { + DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ + } + } + + pWav->readCursorInPCMFrames = 0; + pWav->bytesRemaining = pWav->dataChunkDataSize; + + return DRWAV_TRUE; +} + +#pragma stackfunction 1200 +DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex) +{ + /* Seeking should be compatible with wave files > 2GB. */ + + if (pWav == NULL || pWav->onSeek == NULL) { + return DRWAV_FALSE; + } + + /* No seeking in write mode. */ + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; + } + + /* If there are no samples, just return DRWAV_TRUE without doing anything. */ + if (pWav->totalPCMFrameCount == 0) { + return DRWAV_TRUE; + } + + /* Make sure the sample is clamped. */ + if (targetFrameIndex > pWav->totalPCMFrameCount) { + targetFrameIndex = pWav->totalPCMFrameCount; + } + + /* + For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need + to seek back to the start. + */ + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + /* TODO: This can be optimized. */ + + /* + If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, + we first need to seek back to the start and then just do the same thing as a forward seek. + */ + if (targetFrameIndex < pWav->readCursorInPCMFrames) { + if (!drwav_seek_to_first_pcm_frame(pWav)) { + return DRWAV_FALSE; + } + } + + if (targetFrameIndex > pWav->readCursorInPCMFrames) { + drwav_uint64 offsetInFrames = targetFrameIndex - pWav->readCursorInPCMFrames; + + drwav_int16 devnull[2048]; + while (offsetInFrames > 0) { + drwav_uint64 framesRead = 0; + drwav_uint64 framesToRead = offsetInFrames; + if (framesToRead > drwav_countof(devnull)/pWav->channels) { + framesToRead = drwav_countof(devnull)/pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + framesRead = drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, devnull); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + framesRead = drwav_read_pcm_frames_s16__ima(pWav, framesToRead, devnull); + } else { + DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ + } + + if (framesRead != framesToRead) { + return DRWAV_FALSE; + } + + offsetInFrames -= framesRead; + } + } + } else { + drwav_uint64 totalSizeInBytes; + drwav_uint64 currentBytePos; + drwav_uint64 targetBytePos; + drwav_uint64 offset; + drwav_uint32 bytesPerFrame; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return DRWAV_FALSE; /* Not able to calculate offset. */ + } + + totalSizeInBytes = pWav->totalPCMFrameCount * bytesPerFrame; + DRWAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining); + + currentBytePos = totalSizeInBytes - pWav->bytesRemaining; + targetBytePos = targetFrameIndex * bytesPerFrame; + + if (currentBytePos < targetBytePos) { + /* Offset forwards. */ + offset = (targetBytePos - currentBytePos); + } else { + /* Offset backwards. */ + if (!drwav_seek_to_first_pcm_frame(pWav)) { + return DRWAV_FALSE; + } + offset = targetBytePos; + } + + while (offset > 0) { + int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); + if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + + pWav->readCursorInPCMFrames += offset32 / bytesPerFrame; + pWav->bytesRemaining -= offset32; + offset -= offset32; + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_result drwav_get_cursor_in_pcm_frames(drwav* pWav, drwav_uint64* pCursor) +{ + if (pCursor == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pCursor = 0; /* Safety. */ + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pCursor = pWav->readCursorInPCMFrames; + + return DRWAV_SUCCESS; +} + +DRWAV_API drwav_result drwav_get_length_in_pcm_frames(drwav* pWav, drwav_uint64* pLength) +{ + if (pLength == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pLength = 0; /* Safety. */ + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pLength = pWav->totalPCMFrameCount; + + return DRWAV_SUCCESS; +} + + +DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) +{ + size_t bytesWritten; + + if (pWav == NULL || bytesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); + pWav->dataChunkDataSize += bytesWritten; + + return bytesWritten; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + drwav_uint64 bytesToWrite; + drwav_uint64 bytesWritten; + const drwav_uint8* pRunningData; + + if (pWav == NULL || framesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { + return 0; + } + + bytesWritten = 0; + pRunningData = (const drwav_uint8*)pData; + + while (bytesToWrite > 0) { + size_t bytesJustWritten; + drwav_uint64 bytesToWriteThisIteration; + + bytesToWriteThisIteration = bytesToWrite; + DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ + + bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); + if (bytesJustWritten == 0) { + break; + } + + bytesToWrite -= bytesJustWritten; + bytesWritten += bytesJustWritten; + pRunningData += bytesJustWritten; + } + + return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + drwav_uint64 bytesToWrite; + drwav_uint64 bytesWritten; + drwav_uint32 bytesPerSample; + const drwav_uint8* pRunningData; + + if (pWav == NULL || framesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { + return 0; + } + + bytesWritten = 0; + pRunningData = (const drwav_uint8*)pData; + + bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels; + if (bytesPerSample == 0) { + return 0; /* Cannot determine bytes per sample, or bytes per sample is less than one byte. */ + } + + while (bytesToWrite > 0) { + drwav_uint8 temp[4096]; + drwav_uint32 sampleCount; + size_t bytesJustWritten; + drwav_uint64 bytesToWriteThisIteration; + + bytesToWriteThisIteration = bytesToWrite; + DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ + + /* + WAV files are always little-endian. We need to byte swap on big-endian architectures. Since our input buffer is read-only we need + to use an intermediary buffer for the conversion. + */ + sampleCount = sizeof(temp)/bytesPerSample; + + if (bytesToWriteThisIteration > ((drwav_uint64)sampleCount)*bytesPerSample) { + bytesToWriteThisIteration = ((drwav_uint64)sampleCount)*bytesPerSample; + } + + DRWAV_COPY_MEMORY(temp, pRunningData, (size_t)bytesToWriteThisIteration); + drwav__bswap_samples(temp, sampleCount, bytesPerSample, pWav->translatedFormatTag); + + bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, temp); + if (bytesJustWritten == 0) { + break; + } + + bytesToWrite -= bytesJustWritten; + bytesWritten += bytesJustWritten; + pRunningData += bytesJustWritten; + } + + return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + if (drwav__is_little_endian()) { + return drwav_write_pcm_frames_le(pWav, framesToWrite, pData); + } else { + return drwav_write_pcm_frames_be(pWav, framesToWrite, pData); + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead = 0; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(framesToRead > 0); + + /* TODO: Lots of room for optimization here. */ + + while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ + + /* If there are no cached frames we need to load a new block. */ + if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + /* Mono. */ + drwav_uint8 header[7]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 1); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 3); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 5); + pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; + pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.cachedFrameCount = 2; + } else { + /* Stereo. */ + drwav_uint8 header[14]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.predictor[1] = header[1]; + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 2); + pWav->msadpcm.delta[1] = drwav_bytes_to_s16(header + 4); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 6); + pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav_bytes_to_s16(header + 8); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 10); + pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav_bytes_to_s16(header + 12); + + pWav->msadpcm.cachedFrames[0] = pWav->msadpcm.prevFrames[0][0]; + pWav->msadpcm.cachedFrames[1] = pWav->msadpcm.prevFrames[1][0]; + pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; + pWav->msadpcm.cachedFrameCount = 2; + } + } + + /* Output anything that's cached. */ + while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + if (pBufferOut != NULL) { + drwav_uint32 iSample = 0; + for (iSample = 0; iSample < pWav->channels; iSample += 1) { + pBufferOut[iSample] = (drwav_int16)pWav->msadpcm.cachedFrames[(drwav_countof(pWav->msadpcm.cachedFrames) - (pWav->msadpcm.cachedFrameCount*pWav->channels)) + iSample]; + } + + pBufferOut += pWav->channels; + } + + framesToRead -= 1; + totalFramesRead += 1; + pWav->readCursorInPCMFrames += 1; + pWav->msadpcm.cachedFrameCount -= 1; + } + + if (framesToRead == 0) { + break; + } + + + /* + If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + loop iteration which will trigger the loading of a new block. + */ + if (pWav->msadpcm.cachedFrameCount == 0) { + if (pWav->msadpcm.bytesRemainingInBlock == 0) { + continue; + } else { + static drwav_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; + + drwav_uint8 nibbles; + drwav_int32 nibble0; + drwav_int32 nibble1; + + if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock -= 1; + + /* TODO: Optimize away these if statements. */ + nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } + nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } + + if (pWav->channels == 1) { + /* Mono. */ + drwav_int32 newSample0; + drwav_int32 newSample1; + + newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample0; + + + newSample1 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[0]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample1; + + + pWav->msadpcm.cachedFrames[2] = newSample0; + pWav->msadpcm.cachedFrames[3] = newSample1; + pWav->msadpcm.cachedFrameCount = 2; + } else { + /* Stereo. */ + drwav_int32 newSample0; + drwav_int32 newSample1; + + /* Left. */ + newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample0; + + + /* Right. */ + newSample1 = ((pWav->msadpcm.prevFrames[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevFrames[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[1]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8; + if (pWav->msadpcm.delta[1] < 16) { + pWav->msadpcm.delta[1] = 16; + } + + pWav->msadpcm.prevFrames[1][0] = pWav->msadpcm.prevFrames[1][1]; + pWav->msadpcm.prevFrames[1][1] = newSample1; + + pWav->msadpcm.cachedFrames[2] = newSample0; + pWav->msadpcm.cachedFrames[3] = newSample1; + pWav->msadpcm.cachedFrameCount = 1; + } + } + } + } + + return totalFramesRead; +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead = 0; + drwav_uint32 iChannel; + + static drwav_int32 indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + static drwav_int32 stepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(framesToRead > 0); + + /* TODO: Lots of room for optimization here. */ + + while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ + + /* If there are no cached samples we need to load a new block. */ + if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + /* Mono. */ + drwav_uint8 header[4]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + if (header[2] >= drwav_countof(stepTable)) { + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->ima.bytesRemainingInBlock = 0; + return totalFramesRead; /* Invalid data. */ + } + + pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; + pWav->ima.cachedFrameCount = 1; + } else { + /* Stereo. */ + drwav_uint8 header[8]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + if (header[2] >= drwav_countof(stepTable) || header[6] >= drwav_countof(stepTable)) { + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->ima.bytesRemainingInBlock = 0; + return totalFramesRead; /* Invalid data. */ + } + + pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + pWav->ima.predictor[1] = drwav_bytes_to_s16(header + 4); + pWav->ima.stepIndex[1] = drwav_clamp(header[6], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1]; + pWav->ima.cachedFrameCount = 1; + } + } + + /* Output anything that's cached. */ + while (framesToRead > 0 && pWav->ima.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + if (pBufferOut != NULL) { + drwav_uint32 iSample; + for (iSample = 0; iSample < pWav->channels; iSample += 1) { + pBufferOut[iSample] = (drwav_int16)pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + iSample]; + } + pBufferOut += pWav->channels; + } + + framesToRead -= 1; + totalFramesRead += 1; + pWav->readCursorInPCMFrames += 1; + pWav->ima.cachedFrameCount -= 1; + } + + if (framesToRead == 0) { + break; + } + + /* + If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + loop iteration which will trigger the loading of a new block. + */ + if (pWav->ima.cachedFrameCount == 0) { + if (pWav->ima.bytesRemainingInBlock == 0) { + continue; + } else { + /* + From what I can tell with stereo streams, it looks like every 4 bytes (8 samples) is for one channel. So it goes 4 bytes for the + left channel, 4 bytes for the right channel. + */ + pWav->ima.cachedFrameCount = 8; + for (iChannel = 0; iChannel < pWav->channels; ++iChannel) { + drwav_uint32 iByte; + drwav_uint8 nibbles[4]; + if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) { + pWav->ima.cachedFrameCount = 0; + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock -= 4; + + for (iByte = 0; iByte < 4; ++iByte) { + drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0); + drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4); + + drwav_int32 step = stepTable[pWav->ima.stepIndex[iChannel]]; + drwav_int32 predictor = pWav->ima.predictor[iChannel]; + + drwav_int32 diff = step >> 3; + if (nibble0 & 1) diff += step >> 2; + if (nibble0 & 2) diff += step >> 1; + if (nibble0 & 4) diff += step; + if (nibble0 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble0], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+0)*pWav->channels + iChannel] = predictor; + + + step = stepTable[pWav->ima.stepIndex[iChannel]]; + predictor = pWav->ima.predictor[iChannel]; + + diff = step >> 3; + if (nibble1 & 1) diff += step >> 2; + if (nibble1 & 2) diff += step >> 1; + if (nibble1 & 4) diff += step; + if (nibble1 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble1], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+1)*pWav->channels + iChannel] = predictor; + } + } + } + } + } + + return totalFramesRead; +} + + +#ifndef DR_WAV_NO_CONVERSION_API +static unsigned short g_drwavAlawTable[256] = { + 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, + 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, + 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, + 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, + 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, + 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, + 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, + 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, + 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, + 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, + 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, + 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, + 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, + 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, + 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, + 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 +}; + +static unsigned short g_drwavMulawTable[256] = { + 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, + 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, + 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, + 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, + 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, + 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, + 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, + 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, + 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, + 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, + 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, + 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, + 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, + 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavAlawTable[sampleIn]; +} + +static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavMulawTable[sampleIn]; +} + + + +DRWAV_PRIVATE void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + size_t i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_s16(pOut, pIn, totalSampleCount); + return; + } + + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + for (i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int16*)pIn)[i]; + } + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s16(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); /* Safe cast. */ + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_s16__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_s16__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_s16__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_s16__mulaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_s16__ima(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x << 8; + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = ((int)(((unsigned int)(((const drwav_uint8*)pIn)[i*3+0]) << 8) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+1]) << 16) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+2])) << 24)) >> 8; + r = x >> 8; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x >> 16; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + float x = pIn[i]; + float c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5f); + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + double x = pIn[i]; + double c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5); + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + for (i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__alaw_to_s16(pIn[i]); + } +} + +DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + for (i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__mulaw_to_s16(pIn[i]); + } +} + + + +DRWAV_PRIVATE void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +{ + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_f32(pOut, pIn, sampleCount); + return; + } + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_f32(pOut, pIn, sampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_f32(pOut, (const drwav_int32*)pIn, sampleCount); + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < sampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + unsigned int i; + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((const float*)pIn)[i]; + } + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); + return; + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + /* + We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + want to duplicate that code. + */ + drwav_uint64 totalFramesRead; + drwav_int16 samples16[2048]; + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ + + pBufferOut += framesRead*pWav->channels; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(float) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(float) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_f32__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_f32__msadpcm_ima(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_f32__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_f32__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_f32__mulaw(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + /* + It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears + libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note + the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated + correctness testing. This is disabled by default. + */ + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (pIn[i] / 256.0f) * 2 - 1; + } +#else + for (i = 0; i < sampleCount; ++i) { + float x = pIn[i]; + x = x * 0.00784313725490196078f; /* 0..255 to 0..2 */ + x = x - 1; /* 0..2 to -1..1 */ + + *pOut++ = x; + } +#endif +} + +DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] * 0.000030517578125f; + } +} + +DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + double x; + drwav_uint32 a = ((drwav_uint32)(pIn[i*3+0]) << 8); + drwav_uint32 b = ((drwav_uint32)(pIn[i*3+1]) << 16); + drwav_uint32 c = ((drwav_uint32)(pIn[i*3+2]) << 24); + + x = (double)((drwav_int32)(a | b | c) >> 8); + *pOut++ = (float)(x * 0.00000011920928955078125); + } +} + +DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + size_t i; + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (float)(pIn[i] / 2147483648.0); + } +} + +DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (float)pIn[i]; + } +} + +DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f; + } +} + +DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; + } +} + + + +DRWAV_PRIVATE void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_s32(pOut, pIn, totalSampleCount); + return; + } + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s32(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + for (i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int32*)pIn)[i]; + } + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + /* + We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + want to duplicate that code. + */ + drwav_uint64 totalFramesRead = 0; + drwav_int16 samples16[2048]; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ + + pBufferOut += framesRead*pWav->channels; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_s32__msadpcm_ima(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_s32__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_s32__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_s32__mulaw(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((int)pIn[i] - 128) << 24; + } +} + +DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] << 16; + } +} + +DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + unsigned int s0 = pIn[i*3 + 0]; + unsigned int s1 = pIn[i*3 + 1]; + unsigned int s2 = pIn[i*3 + 2]; + + drwav_int32 sample32 = (drwav_int32)((s0 << 8) | (s1 << 16) | (s2 << 24)); + *pOut++ = sample32; + } +} + +DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); + } +} + +DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); + } +} + +DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16; + } +} + +DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i= 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; + } +} + + + +DRWAV_PRIVATE drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + drwav_int16* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int16); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (drwav_int16*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_s16(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + +DRWAV_PRIVATE float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + float* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(float); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (float*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_f32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + +DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + drwav_int32* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int32); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (drwav_int32*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_s32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + + + +DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +#ifndef DR_WAV_NO_STDIO +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + + +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} +#endif + +DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} +#endif /* DR_WAV_NO_CONVERSION_API */ + + +DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + drwav__free_from_callbacks(p, pAllocationCallbacks); + } else { + drwav__free_default(p, NULL); + } +} + +DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data) +{ + return ((drwav_uint16)data[0] << 0) | ((drwav_uint16)data[1] << 8); +} + +DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data) +{ + return (drwav_int16)drwav_bytes_to_u16(data); +} + +DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data) +{ + return ((drwav_uint32)data[0] << 0) | ((drwav_uint32)data[1] << 8) | ((drwav_uint32)data[2] << 16) | ((drwav_uint32)data[3] << 24); +} + +DRWAV_API float drwav_bytes_to_f32(const drwav_uint8* data) +{ + union { + drwav_uint32 u32; + float f32; + } value; + + value.u32 = drwav_bytes_to_u32(data); + return value.f32; +} + +DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data) +{ + return (drwav_int32)drwav_bytes_to_u32(data); +} + +DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data) +{ + return + ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | + ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); +} + +DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data) +{ + return (drwav_int64)drwav_bytes_to_u64(data); +} + + +DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) +{ + int i; + for (i = 0; i < 16; i += 1) { + if (a[i] != b[i]) { + return DRWAV_FALSE; + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) +{ + return + a[0] == b[0] && + a[1] == b[1] && + a[2] == b[2] && + a[3] == b[3]; +} + +#endif /* dr_wav_c */ +#endif /* DR_WAV_IMPLEMENTATION */ + +/* +REVISION HISTORY +================ +v0.13.6 - 2022-04-10 + - Fix compilation error on older versions of GCC. + - Remove some dependencies on the standard library. + +v0.13.5 - 2022-01-26 + - Fix an error when seeking to the end of the file. + +v0.13.4 - 2021-12-08 + - Fix some static analysis warnings. + +v0.13.3 - 2021-11-24 + - Fix an incorrect assertion when trying to endian swap 1-byte sample formats. This is now a no-op + rather than a failed assertion. + - Fix a bug with parsing of the bext chunk. + - Fix some static analysis warnings. + +v0.13.2 - 2021-10-02 + - Fix a possible buffer overflow when reading from compressed formats. + +v0.13.1 - 2021-07-31 + - Fix platform detection for ARM64. + +v0.13.0 - 2021-07-01 + - Improve support for reading and writing metadata. Use the `_with_metadata()` APIs to initialize + a WAV decoder and store the metadata within the `drwav` object. Use the `pMetadata` and + `metadataCount` members of the `drwav` object to read the data. The old way of handling metadata + via a callback is still usable and valid. + - API CHANGE: drwav_target_write_size_bytes() now takes extra parameters for calculating the + required write size when writing metadata. + - Add drwav_get_cursor_in_pcm_frames() + - Add drwav_get_length_in_pcm_frames() + - Fix a bug where drwav_read_raw() can call the read callback with a byte count of zero. + +v0.12.20 - 2021-06-11 + - Fix some undefined behavior. + +v0.12.19 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + - Minor improvements to the management of some internal state concerning the data chunk cursor. + +v0.12.18 - 2021-01-31 + - Clean up some static analysis warnings. + +v0.12.17 - 2021-01-17 + - Minor fix to sample code in documentation. + - Correctly qualify a private API as private rather than public. + - Code cleanup. + +v0.12.16 - 2020-12-02 + - Fix a bug when trying to read more bytes than can fit in a size_t. + +v0.12.15 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.12.14 - 2020-11-13 + - Minor code clean up. + +v0.12.13 - 2020-11-01 + - Improve compiler support for older versions of GCC. + +v0.12.12 - 2020-09-28 + - Add support for RF64. + - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section. + +v0.12.11 - 2020-09-08 + - Fix a compilation error on older compilers. + +v0.12.10 - 2020-08-24 + - Fix a bug when seeking with ADPCM formats. + +v0.12.9 - 2020-08-02 + - Simplify sized types. + +v0.12.8 - 2020-07-25 + - Fix a compilation warning. + +v0.12.7 - 2020-07-15 + - Fix some bugs on big-endian architectures. + - Fix an error in s24 to f32 conversion. + +v0.12.6 - 2020-06-23 + - Change drwav_read_*() to allow NULL to be passed in as the output buffer which is equivalent to a forward seek. + - Fix a buffer overflow when trying to decode invalid IMA-ADPCM files. + - Add include guard for the implementation section. + +v0.12.5 - 2020-05-27 + - Minor documentation fix. + +v0.12.4 - 2020-05-16 + - Replace assert() with DRWAV_ASSERT(). + - Add compile-time and run-time version querying. + - DRWAV_VERSION_MINOR + - DRWAV_VERSION_MAJOR + - DRWAV_VERSION_REVISION + - DRWAV_VERSION_STRING + - drwav_version() + - drwav_version_string() + +v0.12.3 - 2020-04-30 + - Fix compilation errors with VC6. + +v0.12.2 - 2020-04-21 + - Fix a bug where drwav_init_file() does not close the file handle after attempting to load an erroneous file. + +v0.12.1 - 2020-04-13 + - Fix some pedantic warnings. + +v0.12.0 - 2020-04-04 + - API CHANGE: Add container and format parameters to the chunk callback. + - Minor documentation updates. + +v0.11.5 - 2020-03-07 + - Fix compilation error with Visual Studio .NET 2003. + +v0.11.4 - 2020-01-29 + - Fix some static analysis warnings. + - Fix a bug when reading f32 samples from an A-law encoded stream. + +v0.11.3 - 2020-01-12 + - Minor changes to some f32 format conversion routines. + - Minor bug fix for ADPCM conversion when end of file is reached. + +v0.11.2 - 2019-12-02 + - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. + - Fix an integer overflow bug. + - Fix a null pointer dereference bug. + - Add limits to sample rate, channels and bits per sample to tighten up some validation. + +v0.11.1 - 2019-10-07 + - Internal code clean up. + +v0.11.0 - 2019-10-06 + - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation + routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: + - drwav_init() + - drwav_init_ex() + - drwav_init_file() + - drwav_init_file_ex() + - drwav_init_file_w() + - drwav_init_file_w_ex() + - drwav_init_memory() + - drwav_init_memory_ex() + - drwav_init_write() + - drwav_init_write_sequential() + - drwav_init_write_sequential_pcm_frames() + - drwav_init_file_write() + - drwav_init_file_write_sequential() + - drwav_init_file_write_sequential_pcm_frames() + - drwav_init_file_write_w() + - drwav_init_file_write_sequential_w() + - drwav_init_file_write_sequential_pcm_frames_w() + - drwav_init_memory_write() + - drwav_init_memory_write_sequential() + - drwav_init_memory_write_sequential_pcm_frames() + - drwav_open_and_read_pcm_frames_s16() + - drwav_open_and_read_pcm_frames_f32() + - drwav_open_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_s16() + - drwav_open_file_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_s16_w() + - drwav_open_file_and_read_pcm_frames_f32_w() + - drwav_open_file_and_read_pcm_frames_s32_w() + - drwav_open_memory_and_read_pcm_frames_s16() + - drwav_open_memory_and_read_pcm_frames_f32() + - drwav_open_memory_and_read_pcm_frames_s32() + Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use + DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE. + - Add support for reading and writing PCM frames in an explicit endianness. New APIs: + - drwav_read_pcm_frames_le() + - drwav_read_pcm_frames_be() + - drwav_read_pcm_frames_s16le() + - drwav_read_pcm_frames_s16be() + - drwav_read_pcm_frames_f32le() + - drwav_read_pcm_frames_f32be() + - drwav_read_pcm_frames_s32le() + - drwav_read_pcm_frames_s32be() + - drwav_write_pcm_frames_le() + - drwav_write_pcm_frames_be() + - Remove deprecated APIs. + - API CHANGE: The following APIs now return native-endian data. Previously they returned little-endian data. + - drwav_read_pcm_frames() + - drwav_read_pcm_frames_s16() + - drwav_read_pcm_frames_s32() + - drwav_read_pcm_frames_f32() + - drwav_open_and_read_pcm_frames_s16() + - drwav_open_and_read_pcm_frames_s32() + - drwav_open_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s16() + - drwav_open_file_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s16_w() + - drwav_open_file_and_read_pcm_frames_s32_w() + - drwav_open_file_and_read_pcm_frames_f32_w() + - drwav_open_memory_and_read_pcm_frames_s16() + - drwav_open_memory_and_read_pcm_frames_s32() + - drwav_open_memory_and_read_pcm_frames_f32() + +v0.10.1 - 2019-08-31 + - Correctly handle partial trailing ADPCM blocks. + +v0.10.0 - 2019-08-04 + - Remove deprecated APIs. + - Add wchar_t variants for file loading APIs: + drwav_init_file_w() + drwav_init_file_ex_w() + drwav_init_file_write_w() + drwav_init_file_write_sequential_w() + - Add drwav_target_write_size_bytes() which calculates the total size in bytes of a WAV file given a format and sample count. + - Add APIs for specifying the PCM frame count instead of the sample count when opening in sequential write mode: + drwav_init_write_sequential_pcm_frames() + drwav_init_file_write_sequential_pcm_frames() + drwav_init_file_write_sequential_pcm_frames_w() + drwav_init_memory_write_sequential_pcm_frames() + - Deprecate drwav_open*() and drwav_close(): + drwav_open() + drwav_open_ex() + drwav_open_write() + drwav_open_write_sequential() + drwav_open_file() + drwav_open_file_ex() + drwav_open_file_write() + drwav_open_file_write_sequential() + drwav_open_memory() + drwav_open_memory_ex() + drwav_open_memory_write() + drwav_open_memory_write_sequential() + drwav_close() + - Minor documentation updates. + +v0.9.2 - 2019-05-21 + - Fix warnings. + +v0.9.1 - 2019-05-05 + - Add support for C89. + - Change license to choice of public domain or MIT-0. + +v0.9.0 - 2018-12-16 + - API CHANGE: Add new reading APIs for reading by PCM frames instead of samples. Old APIs have been deprecated and + will be removed in v0.10.0. Deprecated APIs and their replacements: + drwav_read() -> drwav_read_pcm_frames() + drwav_read_s16() -> drwav_read_pcm_frames_s16() + drwav_read_f32() -> drwav_read_pcm_frames_f32() + drwav_read_s32() -> drwav_read_pcm_frames_s32() + drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() + drwav_write() -> drwav_write_pcm_frames() + drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() + drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() + drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() + drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() + drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() + drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() + drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() + drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() + drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() + drwav::totalSampleCount -> drwav::totalPCMFrameCount + - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*(). + - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*(). + - Add built-in support for smpl chunks. + - Add support for firing a callback for each chunk in the file at initialization time. + - This is enabled through the drwav_init_ex(), etc. family of APIs. + - Handle invalid FMT chunks more robustly. + +v0.8.5 - 2018-09-11 + - Const correctness. + - Fix a potential stack overflow. + +v0.8.4 - 2018-08-07 + - Improve 64-bit detection. + +v0.8.3 - 2018-08-05 + - Fix C++ build on older versions of GCC. + +v0.8.2 - 2018-08-02 + - Fix some big-endian bugs. + +v0.8.1 - 2018-06-29 + - Add support for sequential writing APIs. + - Disable seeking in write mode. + - Fix bugs with Wave64. + - Fix typos. + +v0.8 - 2018-04-27 + - Bug fix. + - Start using major.minor.revision versioning. + +v0.7f - 2018-02-05 + - Restrict ADPCM formats to a maximum of 2 channels. + +v0.7e - 2018-02-02 + - Fix a crash. + +v0.7d - 2018-02-01 + - Fix a crash. + +v0.7c - 2018-02-01 + - Set drwav.bytesPerSample to 0 for all compressed formats. + - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for + all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). + - Fix some divide-by-zero errors. + +v0.7b - 2018-01-22 + - Fix errors with seeking of compressed formats. + - Fix compilation error when DR_WAV_NO_CONVERSION_API + +v0.7a - 2017-11-17 + - Fix some GCC warnings. + +v0.7 - 2017-11-04 + - Add writing APIs. + +v0.6 - 2017-08-16 + - API CHANGE: Rename dr_* types to drwav_*. + - Add support for custom implementations of malloc(), realloc(), etc. + - Add support for Microsoft ADPCM. + - Add support for IMA ADPCM (DVI, format code 0x11). + - Optimizations to drwav_read_s16(). + - Bug fixes. + +v0.5g - 2017-07-16 + - Change underlying type for booleans to unsigned. + +v0.5f - 2017-04-04 + - Fix a minor bug with drwav_open_and_read_s16() and family. + +v0.5e - 2016-12-29 + - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. + - Minor fixes to documentation. + +v0.5d - 2016-12-28 + - Use drwav_int* and drwav_uint* sized types to improve compiler support. + +v0.5c - 2016-11-11 + - Properly handle JUNK chunks that come before the FMT chunk. + +v0.5b - 2016-10-23 + - A minor change to drwav_bool8 and drwav_bool32 types. + +v0.5a - 2016-10-11 + - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. + - Improve A-law and mu-law efficiency. + +v0.5 - 2016-09-29 + - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to + keep it consistent with dr_audio and dr_flac. + +v0.4b - 2016-09-18 + - Fixed a typo in documentation. + +v0.4a - 2016-09-18 + - Fixed a typo. + - Change date format to ISO 8601 (YYYY-MM-DD) + +v0.4 - 2016-07-13 + - API CHANGE. Make onSeek consistent with dr_flac. + - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. + - Added support for Sony Wave64. + +v0.3a - 2016-05-28 + - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. + - Fixed a memory leak. + +v0.3 - 2016-05-22 + - Lots of API changes for consistency. + +v0.2a - 2016-05-16 + - Fixed Linux/GCC build. + +v0.2 - 2016-05-11 + - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. + +v0.1a - 2016-05-07 + - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. + +v0.1 - 2016-05-04 + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +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 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. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2020 David Reid + +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. + +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/examples/ffva/src/intent_handler/audio_response/dr_wav_freertos_port.h b/examples/ffva/src/intent_handler/audio_response/dr_wav_freertos_port.h new file mode 100644 index 00000000..e530ab7c --- /dev/null +++ b/examples/ffva/src/intent_handler/audio_response/dr_wav_freertos_port.h @@ -0,0 +1,94 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef DR_WAV_FREERTOS_PORT_H_ +#define DR_WAV_FREERTOS_PORT_H_ + +/* STD headers */ +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "fs_support.h" +#include "ff.h" + +/* dr_wav Options */ +#undef DR_WAV_NO_CONVERSION_API +#define DR_WAV_NO_STDIO + +/* dr_wav STD lib port */ +// #define DRWAV_ASSERT(expression) xassert(expression) +// #define DRWAV_MALLOC(sz) pvPortMalloc((sz)) +// #define DRWAV_REALLOC(sz) pvPortMalloc((sz)) +// #define DRWAV_FREE(p) vPortFree(p) + +void *dr_wav_malloc_port(size_t sz, void* pUserData); +void *dr_wav_realloc_port(void* p, size_t sz, void* pUserData); +void dr_wav_free_port(void* p, void* pUserData); + +#define DR_WAV_IMPLEMENTATION +#include "dr_wav.h" + +drwav_allocation_callbacks drwav_memory_cbs = { + .pUserData = NULL, + .onMalloc = dr_wav_malloc_port, + .onRealloc = dr_wav_realloc_port, + .onFree = dr_wav_free_port, +}; + +void *dr_wav_malloc_port(size_t sz, void* pUserData) { + (void) pUserData; + rtos_printf("**malloc was called\n"); + return pvPortMalloc(sz); +} + +void *dr_wav_realloc_port(void* p, size_t sz, void* pUserData) { + rtos_printf("**realloc was called\n"); + xassert(0); /* Not implemented in FreeRTOS */ + return NULL; +} + +void dr_wav_free_port(void* p, void* pUserData) { + (void) pUserData; + rtos_printf("**free was called\n"); + vPortFree(p); +} + +size_t drwav_read_proc_port(void* pUserData, void* pBufferOut, size_t bytesToRead) { + FIL *file = (FIL*)pUserData; + FRESULT result; + uint32_t bytes_read = 0; + + result = f_read(file, + (uint8_t*)pBufferOut, + bytesToRead, + (unsigned int*)&bytes_read); + + return (result == FR_OK) ? bytes_read : 0; +} + +size_t drwav_write_proc_port(void* pUserData, const void* pData, size_t bytesToWrite) { + rtos_printf("drwav_write_proc_port not implemented\n"); + return 0; +} + +drwav_bool32 drwav_seek_proc_port(void* pUserData, int offset, drwav_seek_origin origin) { + FIL *file = (FIL*)pUserData; + FRESULT result; + + result = f_lseek(file, offset); + + return (result == FR_OK) ? DRWAV_TRUE : DRWAV_FALSE;; +} + +drwav_uint64 drwav_chunk_proc_port(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader, drwav_container container, const drwav_fmt* pFMT) { + rtos_printf("drwav_chunk_proc_port not implemented\n"); + return (drwav_uint64)-1; +} + +#endif /* DR_WAV_FREERTOS_PORT_H_ */ diff --git a/examples/ffva/src/intent_handler/intent_handler.c b/examples/ffva/src/intent_handler/intent_handler.c new file mode 100644 index 00000000..8d052c9c --- /dev/null +++ b/examples/ffva/src/intent_handler/intent_handler.c @@ -0,0 +1,105 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* STD headers */ +#include +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "intent_handler/intent_handler.h" +#include "fs_support.h" +#include "ff.h" +#include "audio_response.h" +#include "intent_engine/intent_engine.h" + +#define WAKEUP_LOW (appconfINTENT_WAKEUP_EDGE_TYPE) +#define WAKEUP_HIGH (appconfINTENT_WAKEUP_EDGE_TYPE == 0) + +#if ON_TILE(ASR_TILE_NO) + +static bool audio_response_playing = 0; + +static void proc_keyword_res(void *args) { + QueueHandle_t q_intent = (QueueHandle_t) args; + int32_t id = 0; + int32_t host_status = 0; + + configASSERT(q_intent != 0); + + const rtos_gpio_port_id_t p_out_wakeup = rtos_gpio_port(GPIO_OUT_HOST_WAKEUP_PORT); + const rtos_gpio_port_id_t p_in_host_status = rtos_gpio_port(GPIO_IN_HOST_STATUS_PORT); + + rtos_gpio_port_enable(gpio_ctx_t0, p_out_wakeup); + rtos_gpio_port_enable(gpio_ctx_t0, p_in_host_status); + + rtos_gpio_port_out(gpio_ctx_t0, p_out_wakeup, WAKEUP_LOW); + +#if appconfAUDIO_PLAYBACK_ENABLED + audio_response_init(); +#endif + while(1) { + xQueueReceive(q_intent, &id, portMAX_DELAY); + + host_status = rtos_gpio_port_in(gpio_ctx_t0, p_in_host_status); + + if (host_status == 0) { /* Host is not awake */ + rtos_gpio_port_out(gpio_ctx_t0, p_out_wakeup, WAKEUP_HIGH); + rtos_printf("Delay for host wake up\n"); + vTaskDelay(pdMS_TO_TICKS(appconfINTENT_TRANSPORT_DELAY_MS)); + rtos_gpio_port_out(gpio_ctx_t0, p_out_wakeup, WAKEUP_LOW); + } +#if appconfINTENT_I2C_OUTPUT_ENABLED + i2c_res_t ret; + uint32_t buf = id; + size_t sent = 0; + + ret = rtos_i2c_master_write( + i2c_master_ctx, + appconfINTENT_I2C_OUTPUT_DEVICE_ADDR, + (uint8_t*)&buf, + sizeof(uint32_t), + &sent, + 1 + ); + + if (ret != I2C_ACK) { + rtos_printf("I2C inference output was not acknowledged\n\tSent %d bytes\n", sent); + } +#endif +#if appconfINTENT_UART_OUTPUT_ENABLED && (UART_TILE_NO == ASR_TILE_NO) + uint32_t buf_uart = id; + rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)&buf_uart, sizeof(uint32_t)); +#endif +#if appconfAUDIO_PLAYBACK_ENABLED + audio_response_playing = true; + audio_response_play(id); + audio_response_playing = false; +#endif + } +} + +bool intent_handler_response_playing() { + return audio_response_playing; +} + +int32_t intent_handler_create(uint32_t priority, void *args) +{ + xTaskCreate((TaskFunction_t)proc_keyword_res, + "proc_keyword_res", + RTOS_THREAD_STACK_SIZE(proc_keyword_res), + args, + priority, + NULL); + + return 0; +} + +#endif /* ON_TILE(ASR_TILE_NO) */ diff --git a/examples/ffva/src/intent_handler/intent_handler.h b/examples/ffva/src/intent_handler/intent_handler.h new file mode 100644 index 00000000..4d3d8b78 --- /dev/null +++ b/examples/ffva/src/intent_handler/intent_handler.h @@ -0,0 +1,26 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef INTENT_HANDLER_H_ +#define INTENT_HANDLER_H_ + +#include + +#if XK_VOICE_L71 +#define GPIO_OUT_HOST_WAKEUP_PORT XS1_PORT_1D /* PORT_SPI_MOSI */ +#define GPIO_IN_HOST_STATUS_PORT XS1_PORT_1P /* PORT_SPI_MISO */ + +#elif XCOREAI_EXPLORER +#define GPIO_OUT_HOST_WAKEUP_PORT XS1_PORT_1M /* X0D36 */ +#define GPIO_IN_HOST_STATUS_PORT XS1_PORT_1P /* X0D39 */ + +#else +#define GPIO_OUT_HOST_WAKEUP_PORT 0 +#define GPIO_IN_HOST_STATUS_PORT 0 +#endif + +int32_t intent_handler_create(uint32_t priority, void *args); + +bool intent_handler_response_playing(); + +#endif /* INTENT_HANDLER_H_ */ diff --git a/examples/ffva/src/ww_model_runner/model_runner.c b/examples/ffva/src/ww_model_runner/model_runner.c index 18547ec5..6c0c0d32 100644 --- a/examples/ffva/src/ww_model_runner/model_runner.c +++ b/examples/ffva/src/ww_model_runner/model_runner.c @@ -117,10 +117,14 @@ void model_runner_manager(void *args) if (asr_error != ASR_OK) continue; word_id = asr_result.id; + printintln(555); + printintln(55555); + + printintln(word_id); if (!IS_KEYWORD(word_id) && !IS_COMMAND(word_id)) continue; -/* +#if 0 #if appconfINTENT_RAW_OUTPUT intent_engine_process_asr_result(word_id); #else @@ -147,8 +151,7 @@ void model_runner_manager(void *args) // remain in STATE_PROCESSING_COMMAND state } #endif - } -*/ +#endif /* Perform inference here */ // rtos_printf("inference\n"); } diff --git a/examples/ffva/src/ww_model_runner/ww_model_runner.c b/examples/ffva/src/ww_model_runner/ww_model_runner.c index d1ecd631..9b11fb2c 100644 --- a/examples/ffva/src/ww_model_runner/ww_model_runner.c +++ b/examples/ffva/src/ww_model_runner/ww_model_runner.c @@ -31,10 +31,10 @@ void ww_audio_send(rtos_intertile_t *intertile_ctx, int16_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; for (int i = 0; i < frame_count; i++) { - ww_samples[i] = (int16_t)(processed_audio_frame[i][ASR_CHANNEL] >> 16); + ww_samples[i] = (int16_t)((processed_audio_frame[i][ASR_CHANNEL] >> 16) & 0xFFFF); } - printintln(processed_audio_frame[0][ASR_CHANNEL] ); - printintln(ww_samples[0]); + //printintln(processed_audio_frame[0][ASR_CHANNEL] ); + //printintln(ww_samples[0]); if(audio_stream != NULL) { if (xStreamBufferSend(audio_stream, ww_samples, sizeof(ww_samples), 0) != sizeof(ww_samples)) { rtos_printf("lost output samples for ww\n"); From 22e9bf3215430b722ff7e32593d4eb776c483550 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Mar 2024 14:44:27 +0000 Subject: [PATCH 021/288] Use intent handler --- .../ffd/src/intent_engine/intent_engine.c | 19 +- .../src/intent_engine/intent_engine_support.c | 3 +- .../ffd/src/intent_handler/intent_handler.c | 6 +- .../XK_VOICE_L71/platform/driver_instances.c | 3 + .../XK_VOICE_L71/platform/driver_instances.h | 3 + .../XK_VOICE_L71/platform/platform_init.c | 17 + .../XK_VOICE_L71/platform/platform_start.c | 12 +- examples/ffva/ffva.cmake | 7 +- examples/ffva/ffva_int_dev.cmake | 2 +- examples/ffva/src/app_conf.h | 7 +- examples/ffva/src/device_memory_impl.c | 1 - .../src/intent_engine/.intent_engine.c.swo | Bin 20480 -> 0 bytes .../ffva/src/intent_engine/intent_engine.c | 221 - .../ffva/src/intent_engine/intent_engine.h | 35 - .../ffva/src/intent_engine/intent_engine_io.c | 131 - .../src/intent_engine/intent_engine_support.c | 133 - .../audio_response/audio_response.c | 112 - .../audio_response/audio_response.h | 13 - .../intent_handler/audio_response/dr_wav.h | 8309 ----------------- .../audio_response/dr_wav_freertos_port.h | 94 - .../ffva/src/intent_handler/intent_handler.c | 105 - .../ffva/src/intent_handler/intent_handler.h | 26 - examples/ffva/src/main.c | 29 +- .../ffva/src/ww_model_runner/model_runner.c | 158 - .../src/ww_model_runner/ww_model_runner.c | 80 - .../src/ww_model_runner/ww_model_runner.h | 19 - 26 files changed, 78 insertions(+), 9467 deletions(-) delete mode 100644 examples/ffva/src/intent_engine/.intent_engine.c.swo delete mode 100644 examples/ffva/src/intent_engine/intent_engine.c delete mode 100644 examples/ffva/src/intent_engine/intent_engine.h delete mode 100644 examples/ffva/src/intent_engine/intent_engine_io.c delete mode 100644 examples/ffva/src/intent_engine/intent_engine_support.c delete mode 100644 examples/ffva/src/intent_handler/audio_response/audio_response.c delete mode 100644 examples/ffva/src/intent_handler/audio_response/audio_response.h delete mode 100644 examples/ffva/src/intent_handler/audio_response/dr_wav.h delete mode 100644 examples/ffva/src/intent_handler/audio_response/dr_wav_freertos_port.h delete mode 100644 examples/ffva/src/intent_handler/intent_handler.c delete mode 100644 examples/ffva/src/intent_handler/intent_handler.h delete mode 100644 examples/ffva/src/ww_model_runner/model_runner.c delete mode 100644 examples/ffva/src/ww_model_runner/ww_model_runner.c delete mode 100644 examples/ffva/src/ww_model_runner/ww_model_runner.h diff --git a/examples/ffd/src/intent_engine/intent_engine.c b/examples/ffd/src/intent_engine/intent_engine.c index 33f0c421..788d8e55 100644 --- a/examples/ffd/src/intent_engine/intent_engine.c +++ b/examples/ffd/src/intent_engine/intent_engine.c @@ -14,11 +14,11 @@ /* App headers */ #include "app_conf.h" #include "platform/driver_instances.h" -#include "intent_engine/intent_engine.h" -#include "intent_handler/intent_handler.h" +#include "intent_engine.h" +#include "intent_handler.h" #include "asr.h" #include "device_memory_impl.h" -#include "gpio_ctrl/leds.h" +//#include "gpio_ctrl/leds.h" #if ON_TILE(ASR_TILE_NO) @@ -108,7 +108,8 @@ static void timeout_event_handler(TimerHandle_t pxTimer) if (timeout_event & TIMEOUT_EVENT_INTENT) { timeout_event &= ~TIMEOUT_EVENT_INTENT; intent_engine_play_response(STOP_LISTENING_SOUND_WAV_ID); - led_indicate_waiting(); + //TODO: Enable this line + //led_indicate_waiting(); intent_state = STATE_EXPECTING_WAKEWORD; } } @@ -159,12 +160,13 @@ void intent_engine_task(void *args) // this application does not support barge-in // so, we need to check if an audio response is playing and skip to the next // audio frame because the playback may trigger the ASR. - if (intent_handler_response_playing()) continue; + //TODO: Enable this line + //if (intent_handler_response_playing()) continue; asr_error = asr_process(asr_ctx, buf_short, SAMPLES_PER_ASR); - if (asr_error == ASR_EVALUATION_EXPIRED) { - led_indicate_end_of_eval(); + //TODO: Enable this line + //led_indicate_end_of_eval(); continue; } if (asr_error != ASR_OK) continue; @@ -181,7 +183,8 @@ void intent_engine_task(void *args) intent_engine_process_asr_result(word_id); #else if (intent_state == STATE_EXPECTING_WAKEWORD && IS_KEYWORD(word_id)) { - led_indicate_listening(); + //TODO: Enable this line + //led_indicate_listening(); xTimerStart(int_eng_tmr, 0); intent_engine_process_asr_result(word_id); intent_state = STATE_EXPECTING_COMMAND; diff --git a/examples/ffd/src/intent_engine/intent_engine_support.c b/examples/ffd/src/intent_engine/intent_engine_support.c index c393b881..a21497a8 100644 --- a/examples/ffd/src/intent_engine/intent_engine_support.c +++ b/examples/ffd/src/intent_engine/intent_engine_support.c @@ -14,7 +14,7 @@ /* App headers */ #include "app_conf.h" #include "platform/driver_instances.h" -#include "intent_engine/intent_engine.h" +#include "intent_engine.h" #if ON_TILE(ASR_TILE_NO) @@ -110,6 +110,7 @@ void intent_engine_samples_send_local( if (xStreamBufferSend(samples_to_engine_stream_buf, processed_audio_frame, bytes_to_send, 0) != bytes_to_send) { rtos_printf("lost local output samples for intent\n"); } + } else { rtos_printf("intent engine streambuffer not ready\n"); } diff --git a/examples/ffd/src/intent_handler/intent_handler.c b/examples/ffd/src/intent_handler/intent_handler.c index 8d052c9c..1110668e 100644 --- a/examples/ffd/src/intent_handler/intent_handler.c +++ b/examples/ffd/src/intent_handler/intent_handler.c @@ -1,6 +1,6 @@ // Copyright 2022-2023 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. - +fddsfsdf /* STD headers */ #include #include @@ -14,7 +14,7 @@ /* App headers */ #include "app_conf.h" #include "platform/driver_instances.h" -#include "intent_handler/intent_handler.h" +#include "intent_handler.h" #include "fs_support.h" #include "ff.h" #include "audio_response.h" @@ -79,7 +79,7 @@ static void proc_keyword_res(void *args) { rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)&buf_uart, sizeof(uint32_t)); #endif #if appconfAUDIO_PLAYBACK_ENABLED - audio_response_playing = true; + audio_response_playing = true; audio_response_play(id); audio_response_playing = false; #endif diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c index e7265da0..32fab8a0 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c @@ -35,3 +35,6 @@ rtos_spi_slave_t *spi_slave_ctx = &spi_slave_ctx_s; static rtos_dfu_image_t dfu_image_ctx_s; rtos_dfu_image_t *dfu_image_ctx = &dfu_image_ctx_s; + +static rtos_uart_tx_t uart_tx_ctx_s; +rtos_uart_tx_t *uart_tx_ctx = &uart_tx_ctx_s; diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h index 72abcf37..5807bfd7 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h @@ -13,6 +13,7 @@ #include "rtos_qspi_flash.h" #include "rtos_dfu_image.h" #include "rtos_spi_slave.h" +#include "rtos_uart_tx.h" /* Tile specifiers */ #define FLASH_TILE_NO 0 @@ -21,6 +22,7 @@ #define SPI_OUTPUT_TILE_NO 0 #define MICARRAY_TILE_NO 1 #define I2S_TILE_NO 1 +#define UART_TILE_NO 0 /** TILE 0 Clock Blocks */ #define FLASH_CLKBLK XS1_CLKBLK_1 @@ -59,5 +61,6 @@ extern rtos_i2c_slave_t *i2c_slave_ctx; extern rtos_spi_slave_t *spi_slave_ctx; extern rtos_i2s_t *i2s_ctx; extern rtos_dfu_image_t *dfu_image_ctx; +extern rtos_uart_tx_t *uart_tx_ctx; #endif /* DRIVER_INSTANCES_H_ */ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index 36d5b168..b1ffc658 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -239,6 +239,22 @@ static void usb_init(void) #endif } +static void uart_init(void) +{ +#if ON_TILE(UART_TILE_NO) + hwtimer_t tmr_tx = hwtimer_alloc(); + + rtos_uart_tx_init( + uart_tx_ctx, + XS1_PORT_1A, /* J4:24*/ + appconfUART_BAUD_RATE, + 8, + UART_PARITY_NONE, + 1, + tmr_tx); +#endif +} + void platform_init(chanend_t other_tile_c) { rtos_intertile_init(intertile_ctx, other_tile_c); @@ -252,4 +268,5 @@ void platform_init(chanend_t other_tile_c) mics_init(); i2s_init(); usb_init(); + uart_init(); } diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index 7ab795a1..8e51798d 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -40,10 +40,6 @@ static void flash_start(void) { #if ON_TILE(FLASH_TILE_NO) rtos_qspi_flash_start(qspi_flash_ctx, appconfQSPI_FLASH_TASK_PRIORITY); - - //uint32_t flash_core_map = ~((1 << appconfUSB_INTERRUPT_CORE) | (1 << appconfUSB_SOF_INTERRUPT_CORE)); - //rtos_qspi_flash_start(qspi_flash_ctx, appconfQSPI_FLASH_TASK_PRIORITY); - //rtos_qspi_flash_op_core_affinity_set(qspi_flash_ctx, flash_core_map); #endif } @@ -143,6 +139,13 @@ static void usb_start(void) #endif } +static void uart_start(void) +{ +#if ON_TILE(UART_TILE_NO) + rtos_uart_tx_start(uart_tx_ctx); +#endif +} + void platform_start(void) { rtos_intertile_start(intertile_ctx); @@ -157,4 +160,5 @@ void platform_start(void) mics_start(); i2s_start(); usb_start(); + uart_start(); } diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index 5424652d..44347102 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -5,11 +5,13 @@ set(MODEL_LANGUAGE "english_usa") set(CYBERON_COMMAND_NET_FILE "${CMAKE_CURRENT_LIST_DIR}/../ffd/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap") -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ) +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine/*.c) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/usb - ${CMAKE_CURRENT_LIST_DIR}/src/ww_model_runner + #${CMAKE_CURRENT_LIST_DIR}/src/ww_model_runner + ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine + ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler ) include(${CMAKE_CURRENT_LIST_DIR}/bsp_config/bsp_config.cmake) @@ -86,6 +88,7 @@ endif() # XMOS Example Design Targets #********************** include(${CMAKE_CURRENT_LIST_DIR}/ffva_int.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/ffva_int_cyberon.cmake) include(${CMAKE_CURRENT_LIST_DIR}/ffva_ua.cmake) #********************** diff --git a/examples/ffva/ffva_int_dev.cmake b/examples/ffva/ffva_int_dev.cmake index 88cbc5ea..ca74e5ac 100644 --- a/examples/ffva/ffva_int_dev.cmake +++ b/examples/ffva/ffva_int_dev.cmake @@ -3,7 +3,7 @@ set(FFVA_INT_COMPILE_DEFINITIONS ${APP_COMPILE_DEFINITIONS} appconfEXTERNAL_MCLK=1 appconfI2S_ENABLED=1 - appconfUSB_ENABLED=0 + =0 appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=480000 diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index e3ce44c1..63d7590d 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -21,8 +21,9 @@ /* Application tile specifiers */ #include "platform/driver_instances.h" +#define ASR_TILE_NO FLASH_TILE_NO #define FS_TILE_NO FLASH_TILE_NO -#define AUDIO_PIPELINE_TILE_NO MICARRAY_TILE_NO +#define AUDIO_PIPELINE_TILE_NO FLASH_TILE_NO /* Audio Pipeline Configuration */ #define appconfAUDIO_CLOCK_FREQUENCY MIC_ARRAY_CONFIG_MCLK_FREQ @@ -129,8 +130,8 @@ #define appconfUSB_ENABLED 0 #endif -#ifndef appconfWW_ENABLED -#define appconfWW_ENABLED 1 +#ifndef appconfINTENT_ENABLED +#define appconfINTENT_ENABLED 1 #endif #ifndef appconfUSB_AUDIO_SAMPLE_RATE diff --git a/examples/ffva/src/device_memory_impl.c b/examples/ffva/src/device_memory_impl.c index bba3e45e..71fd30ca 100644 --- a/examples/ffva/src/device_memory_impl.c +++ b/examples/ffva/src/device_memory_impl.c @@ -50,7 +50,6 @@ void devmem_read_ext_local(void *dest, const void *src, size_t n) { while (retval == -1) { // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset retval = rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); - //printintln(retval); } //uint32_t d = get_reference_time() - s; //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); diff --git a/examples/ffva/src/intent_engine/.intent_engine.c.swo b/examples/ffva/src/intent_engine/.intent_engine.c.swo deleted file mode 100644 index 3ae72edd3e851cf740af0399baf59f4ebeee52cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeHOTZ|i58J^I4Xu0&FKJ@q_S-*L_y6=uB_i4>e%`$DLMO9b2 z!gf>3rKytZ9Cw^^Q-==COr1PBRd9S!rRP#c)1C+$ro5)jHyx`cQle|N+D+l58jX6& zb89Kf_J!?pVXs@Zh(1l%u0%y#7fT?Pz^)SL_>HNfw`ljx&CXDp17mly+iqRmwRv0- zOCXj&EP+@8u>@iX#1e=l5KADI!2h2F{Ps24=fKuS6l+~|-#2u>Mm<;6eS4_>TJ;>Z z`@>MZdW>(e1Y!xq5{M-bOCXj&EP+@8u>@iX#1e=l5KADI!2du3W?j?vW8Uj20f6`a zVf=sTxD31mbbu_d47~MGP5U|UEZ_hZU;sH_1-Jot5ez*A z90KkG?gajI6KDi}0z3~~1fB)X0}lghz=ObH;4a{wH)`5HfXl#3z!!ldz*{$H+6%z* zKnL)E)4(jSANcL{n)WjAec)TbH-P7WXMq#IyVqe{;8ox$;0wUxz#?!h@Cp(sUjiNh zR)85G1^g9>l^+6^faigWfCoGZq=8QYzl07i0Z#y3U=!#74+4jQL%H0cg;9|Jc zmlvLI`hqPmqmr)ZTt8FNGnH)NBtM;A(N7o4OY_=!4c~6m!}h_K8r(O%a|u~KZo2DU zQajLg&GnYaFkbCkqhr^6%dy$)%naT?U(jg6;4l5U>Zz$QT}@HvmR+}Mpp|c#mT%eX ziR8Q{EAc1t6$x)Q&28=qukF~LNEnr3iRZFLMK2IvMsc;U1ny4p?9#vpzSR;=2Opwr z;&T*saDhEh$>#OqYK7~k^g@Ma3+P`7X0#eCG1Q)oYMo@~Av_YWRbx_CkzRbmwChdb zCMuZQU6v2rXYHdKlJ;W9l+3ewf)`sJ< z1jNHERL{)gc8ro26bXqcU*|3HM7 zp*GYlvd7rt3nRB?f7qJo31$3Bdl1PyPQk#O(gK$Y zD5~yZZ5N6Kz2B)WHd~^_Tc&NU3m2css|r2$Av)Z zqYeV>$XSil5UnKQ$SO~2!tS(&7A}nMg~$}#4<-l>>YZy$QzQ|gUr~}*3a_XWPnwOv`$r5LbS#F%p>v?`WZRl)*ea0wd`H5WGSmya6 z>@~;4y<~yt^m|3rjI`bUnZ^#E6+8&sue^g>@`Vh^|eu*%8bZqVCaHdC5zI4mL~9*=U;H1~Yv*v(spJ!e?Gv)U1XD z8WlyEym?OKEYJ7wO`U*8(vZ&4I9rF|Ynfzw3#+-@JSv->*ik1vrt##u$5s2BSryHB zO>}+X+62?l(Il) zA)i-Z2ptMWv3xePeIthLNf^w70DQ3v%ekv}MGKxKX5bozZKbDS@RDBUX`?Lb2md7Y zbWGqBguns(HQ#Nf;7EJZ8xTdW z4ki#EW^>p@h>KhJIy+hqBN#1E=0Q2H;V0Ybab(kxA9qKHfXD8`Q*EW{F5D&;eg@3E zT@ypJVtsJ(TxpinI7_$NY(toJ;d*R5HOR0heK8K_2hLcOCJJ1lB_~`V%9Wy_MhzcF z4}Gj#b6kLofD4d93TrMhil=s1)=Xm8Hm_*raFjMT3<()g~f@9Q1&ILa2CI~JH# zSeBN@oCeu7jpG0Nv59_9FZ>bzS5;8>W5o7f1-d{6m;>$st^=+G{*0J@8<+;BfWISl z{~7Qya1r<(-~$za;(L60tlb6d2W|rXLa{vXI`C`YJHQ(7ATR;k0o)G!0`dH#Knj=! z-bF0`ZQx?!Pl(sw0KN-62{eH!a0H-u{@;k(UjtqRE&(q9j{p|10h|PG2lfLm zB8GnkXae^G`+$px+dmKFfIET9h||9gRDlWLH;B(K0v#X?yp34=GVo=<0J6X$a1=NU z+z;Fa+zR{+arisH>%fnIH1H|lP3VJk^1JXu45gQ=z6IwE*(}$0JZvDS-N)gEXpGlQ z-=vs9mB6K$WCy)%yB4_k2q@?wslth_>3L|GphyoEPK6wUEmY_t8;qC^<-@Z@4Lf4Y zY(eK`J-u|68)pj{UMiL=lZ-YYFB<6_!t-o}pD30QdzV30#Kx^IWsAI&E$KNLq-XAk zb@0fDP;#nv$y*?%sBd#>B+uexc%Crq&rxPn8kWKQg*x?VI=cEv4SK?Ubw$ezJeb`a zHAXJ1(5QWQDVwTtD{a?->GL@GpYVtf^dz<%7a5ScQc>6~$P|)(RM^f(O~4m$!U1F@5bo}z3$-i z!=~1q1O{5&i`S7_-5G9th}60pr=jDun^s41x?`{pkWs@oVOTNk=$%S?Dw64JBG3@_ zaISOLjoc?ajO@tC#75pt<^4lrU^`!}5h9};wd>{dX^xC(X|;l5i$7Ek2)o|5iPILcjJ*~#4b=rv0laGRg^MdmimB9S;b8q{!Eo!s z56vu91;=B59@yOXGfVVf*hvLZ6HzvzN$xa|fHs?v)OLh17;R!qKb6j{rYl*TSWtSl ztS|MlwKVQ9hw4C)0+Q@eMpGsEg7j}MC>!Qh?GfeP_p<6T$1WG^(0%7ZrJXfA z%`rB-Z2F^&ObADEzZh+jUR8uk@L=z^N-+0LZX~2 zN;^Rz@WfC?Y6^Bs+iD}5fV2vQv(QFp?%>}k(-iJcsXP$U6W!X7M(B0W`5BdzL~EHW zMeDVw%^vagr`NMb0c?gcy_RkH2|B@omOw0lSOT#GVhO|&h$Rq9AeO)fEP)H*M=ccL552$jyYjVN@G@5>Z=(sE L-Cm}Se!=@+)zDf8 diff --git a/examples/ffva/src/intent_engine/intent_engine.c b/examples/ffva/src/intent_engine/intent_engine.c deleted file mode 100644 index 33f0c421..00000000 --- a/examples/ffva/src/intent_engine/intent_engine.c +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -/* STD headers */ -#include -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" -#include "task.h" -#include "stream_buffer.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "intent_engine/intent_engine.h" -#include "intent_handler/intent_handler.h" -#include "asr.h" -#include "device_memory_impl.h" -#include "gpio_ctrl/leds.h" - -#if ON_TILE(ASR_TILE_NO) - -#if ASR_SENSORY - #define IS_KEYWORD(id) (id == 17) - #define IS_COMMAND(id) (id > 0 && id != 17) -#elif ASR_CYBERON - #define IS_KEYWORD(id) (id == 1) - #define IS_COMMAND(id) (id >= 2) -#else -#error "Model has to be either Sensory or Cyberon" -#endif - - -#define SAMPLES_PER_ASR (appconfINTENT_SAMPLE_BLOCK_LENGTH) -#define STOP_LISTENING_SOUND_WAV_ID (0) - -// SEARCH model file is specified in the CMakeLists SENSORY_COMMAND_SEARCH_SOURCE_FILE variable -#ifdef COMMAND_SEARCH_SOURCE_FILE -extern const unsigned short gs_grammarLabel[]; -void* grammar = (void*)gs_grammarLabel; -#else -void* grammar = NULL; -#endif - -// Model file is in flash at the offset specified in the CMakeLists -// QSPI_FLASH_MODEL_START_ADDRESS variable. The XS1_SWMEM_BASE value needs -// to be added so the address in in the SwMem range. -uint16_t *model = (uint16_t *) (XS1_SWMEM_BASE + QSPI_FLASH_MODEL_START_ADDRESS); - -typedef enum intent_state { - STATE_EXPECTING_WAKEWORD, - STATE_EXPECTING_COMMAND, - STATE_PROCESSING_COMMAND -} intent_state_t; - -enum timeout_event { - TIMEOUT_EVENT_NONE = 0, - TIMEOUT_EVENT_INTENT = 1 -}; - -static intent_state_t intent_state; -static asr_port_t asr_ctx; -static devmem_manager_t devmem_ctx; - -static uint32_t timeout_event = TIMEOUT_EVENT_NONE; - -static void vIntentTimerCallback(TimerHandle_t pxTimer); -static void receive_audio_frames(StreamBufferHandle_t input_queue, int32_t *buf, - int16_t *buf_short, size_t *buf_short_index); -static void timeout_event_handler(TimerHandle_t pxTimer); - -static void vIntentTimerCallback(TimerHandle_t pxTimer) -{ - switch (intent_state) { - case STATE_EXPECTING_COMMAND: - case STATE_PROCESSING_COMMAND: - timeout_event |= TIMEOUT_EVENT_INTENT; - break; - default: - break; - } -} - -static void receive_audio_frames(StreamBufferHandle_t input_queue, int32_t *buf, - int16_t *buf_short, size_t *buf_short_index) -{ - uint8_t *buf_ptr = (uint8_t*)buf; - size_t buf_len = appconfINTENT_SAMPLE_BLOCK_LENGTH * sizeof(int32_t); - - do { - size_t bytes_rxed = xStreamBufferReceive(input_queue, - buf_ptr, - buf_len, - portMAX_DELAY); - buf_len -= bytes_rxed; - buf_ptr += bytes_rxed; - } while (buf_len > 0); - - for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { - buf_short[(*buf_short_index)++] = buf[i] >> 16; - } -} - -static void timeout_event_handler(TimerHandle_t pxTimer) -{ - if (timeout_event & TIMEOUT_EVENT_INTENT) { - timeout_event &= ~TIMEOUT_EVENT_INTENT; - intent_engine_play_response(STOP_LISTENING_SOUND_WAV_ID); - led_indicate_waiting(); - intent_state = STATE_EXPECTING_WAKEWORD; - } -} - -#pragma stackfunction 1000 -void intent_engine_task(void *args) -{ - intent_state = STATE_EXPECTING_WAKEWORD; - - StreamBufferHandle_t input_queue = (StreamBufferHandle_t)args; - TimerHandle_t int_eng_tmr = xTimerCreate( - "int_eng_tmr", - pdMS_TO_TICKS(appconfINTENT_RESET_DELAY_MS), - pdFALSE, - NULL, - vIntentTimerCallback); - - devmem_init(&devmem_ctx); - printf("Call asr_init(). model = 0x%x, grammar = 0x%x\n", (unsigned int) model, (unsigned int) grammar); - asr_ctx = asr_init((int32_t *)model, (int32_t *)grammar, &devmem_ctx); - - int32_t buf[appconfINTENT_SAMPLE_BLOCK_LENGTH] = {0}; - int16_t buf_short[SAMPLES_PER_ASR] = {0}; - - asr_reset(asr_ctx); - - /* Alert other tile to start the audio pipeline */ - intent_engine_ready_sync(); - - asr_error_t asr_error; - asr_result_t asr_result; - int word_id; - - size_t buf_short_index = 0; - - while (1) - { - timeout_event_handler(int_eng_tmr); - receive_audio_frames(input_queue, buf, buf_short, &buf_short_index); - - if (buf_short_index < SAMPLES_PER_ASR) - continue; - - buf_short_index = 0; // reset the offset into the buffer of int16s. - // Note, we do not need to overlap the window of samples. - // This is handled in the ASR ports. - - // this application does not support barge-in - // so, we need to check if an audio response is playing and skip to the next - // audio frame because the playback may trigger the ASR. - if (intent_handler_response_playing()) continue; - - asr_error = asr_process(asr_ctx, buf_short, SAMPLES_PER_ASR); - - if (asr_error == ASR_EVALUATION_EXPIRED) { - led_indicate_end_of_eval(); - continue; - } - if (asr_error != ASR_OK) continue; - - asr_error = asr_get_result(asr_ctx, &asr_result); - if (asr_error != ASR_OK) continue; - - word_id = asr_result.id; - - if (!IS_KEYWORD(word_id) && !IS_COMMAND(word_id)) continue; - - - #if appconfINTENT_RAW_OUTPUT - intent_engine_process_asr_result(word_id); - #else - if (intent_state == STATE_EXPECTING_WAKEWORD && IS_KEYWORD(word_id)) { - led_indicate_listening(); - xTimerStart(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - intent_state = STATE_EXPECTING_COMMAND; - } else if (intent_state == STATE_EXPECTING_COMMAND && IS_COMMAND(word_id)) { - xTimerReset(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - intent_state = STATE_PROCESSING_COMMAND; - } else if (intent_state == STATE_EXPECTING_COMMAND && IS_KEYWORD(word_id)) { - xTimerReset(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - // remain in STATE_EXPECTING_COMMAND state - } else if (intent_state == STATE_PROCESSING_COMMAND && IS_KEYWORD(word_id)) { - xTimerReset(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - intent_state = STATE_EXPECTING_COMMAND; - } else if (intent_state == STATE_PROCESSING_COMMAND && IS_COMMAND(word_id)) { - xTimerReset(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - // remain in STATE_PROCESSING_COMMAND state - } - #endif - } -} - -#endif /* ON_TILE(ASR_TILE_NO) */ - -void intent_engine_ready_sync(void) -{ - int sync = 0; -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) - size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); - xassert(len == sizeof(sync)); - rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); -#else - rtos_intertile_tx(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, &sync, sizeof(sync)); -#endif -} diff --git a/examples/ffva/src/intent_engine/intent_engine.h b/examples/ffva/src/intent_engine/intent_engine.h deleted file mode 100644 index 1cf46d39..00000000 --- a/examples/ffva/src/intent_engine/intent_engine.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#ifndef INTENT_ENGINE_H_ -#define INTENT_ENGINE_H_ - -#include -#include -#include - -#include "asr.h" -#include "rtos_intertile.h" - -int32_t intent_engine_create(uint32_t priority, void *args); -void intent_engine_ready_sync(void); - -void intent_engine_task(void *args); -void intent_engine_task_create(unsigned priority); -void intent_engine_intertile_task_create(uint32_t priority); - -int32_t intent_engine_sample_push(int32_t *buf, size_t frames); -void intent_engine_samples_send_local( - size_t frame_count, - int32_t *processed_audio_frame); -void intent_engine_samples_send_remote( - rtos_intertile_t *intertile, - size_t frame_count, - int32_t *processed_audio_frame); - - -void intent_engine_stream_buf_reset(void); -void intent_engine_play_response(int wav_id); -void intent_engine_process_asr_result(int word_id); - -#endif /* INTENT_ENGINE_H_ */ diff --git a/examples/ffva/src/intent_engine/intent_engine_io.c b/examples/ffva/src/intent_engine/intent_engine_io.c deleted file mode 100644 index 9077af51..00000000 --- a/examples/ffva/src/intent_engine/intent_engine_io.c +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -/* STD headers */ -#include -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" -#include "task.h" -#include "stream_buffer.h" -#include "queue.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "intent_engine.h" - -static QueueHandle_t q_intent = 0; - -// look up table to converting ASR IDs to wav file IDs or strings -#define ASR_NUMBER_OF_COMMANDS (17) - -typedef struct asr_lut_struct -{ - int asr_id; // ASR response IDs - int wav_id; // Wav file IDs corresponding to audio_files_en[] array in audio_response.c - const char* text; // String output -} asr_lut_t; - -#if ASR_CYBERON -static asr_lut_t asr_lut[ASR_NUMBER_OF_COMMANDS] = { - {1, 1, "Hello XMOS"}, - {2, 2, "Switch on the TV"}, - {3, 3, "Switch off the TV"}, - {4, 4, "Channel up"}, - {5, 5, "Channel down"}, - {6, 6, "Volume up"}, - {7, 7, "Volume down"}, - {8, 8, "Switch on the lights"}, - {9, 9, "Switch off the lights"}, - {10, 10, "Brightness up"}, - {11, 11, "Brightness down"}, - {12, 12, "Switch on the fan"}, - {13, 13, "Switch off the fan"}, - {14, 14, "Speed up the fan"}, - {15, 15, "Slow down the fan"}, - {16, 16, "Set higher temperature"}, - {17, 17, "Set lower temperature"} -}; -#elif ASR_SENSORY -static asr_lut_t asr_lut[ASR_NUMBER_OF_COMMANDS] = { - {1, 2, "Switch on the TV"}, - {2, 4, "Channel up"}, - {3, 5, "Channel down"}, - {4, 6, "Volume up"}, - {5, 7, "Volume down"}, - {6, 3, "Switch off the TV"}, - {7, 8, "Switch on the lights"}, - {8, 10, "Brightness up"}, - {9, 11, "Brightness down"}, - {10, 9, "Switch off the lights"}, - {11, 12, "Switch on the fan"}, - {12, 14, "Speed up the fan"}, - {13, 15, "Slow down the fan"}, - {14, 16, "Set higher temperature"}, - {15, 17, "Set lower temperature"}, - {16, 13, "Switch off the fan"}, - {17, 1, "Hello XMOS"} -}; -#else -#error "Model has to be either Sensory or Cyberon" -#endif - - - -void intent_engine_play_response(int wav_id) -{ - if(q_intent != 0) { - if(xQueueSend(q_intent, (void *)&wav_id, (TickType_t)0) != pdPASS) { - rtos_printf("Lost wav playback. Queue was full.\n"); - } - } -} - -void intent_engine_process_asr_result(int word_id) -{ - int wav_id = 0; - const char* text = ""; - - for (int i=0; i -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" -#include "task.h" -#include "stream_buffer.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "intent_engine/intent_engine.h" - -#if ON_TILE(ASR_TILE_NO) - -static StreamBufferHandle_t samples_to_engine_stream_buf = 0; - -void intent_engine_stream_buf_reset(void) -{ - if (samples_to_engine_stream_buf) - while (xStreamBufferReset(samples_to_engine_stream_buf) == pdFAIL) - vTaskDelay(pdMS_TO_TICKS(1)); -} - -#endif /* ON_TILE(ASR_TILE_NO) */ - -#if ASR_TILE_NO != AUDIO_PIPELINE_TILE_NO -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) - -void intent_engine_samples_send_remote( - rtos_intertile_t *intertile, - size_t frame_count, - int32_t *processed_audio_frame) -{ - configASSERT(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); - - rtos_intertile_tx(intertile, - appconfINTENT_MODEL_RUNNER_SAMPLES_PORT, - processed_audio_frame, - sizeof(int32_t) * frame_count); -} - -#else /* ON_TILE(AUDIO_PIPELINE_TILE_NO) */ - -static void intent_engine_intertile_samples_in_task(void *arg) -{ - (void) arg; - - for (;;) { - int32_t samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; - size_t bytes_received; - - bytes_received = rtos_intertile_rx_len( - intertile_ap_ctx, - appconfINTENT_MODEL_RUNNER_SAMPLES_PORT, - portMAX_DELAY); - - xassert(bytes_received == sizeof(samples)); - - rtos_intertile_rx_data( - intertile_ap_ctx, - samples, - bytes_received); - - if (xStreamBufferSend(samples_to_engine_stream_buf, samples, sizeof(samples), 0) != sizeof(samples)) { - rtos_printf("lost output samples for intent\n"); - } - } -} - -void intent_engine_intertile_task_create(uint32_t priority) -{ - samples_to_engine_stream_buf = xStreamBufferCreate( - appconfINTENT_FRAME_BUFFER_MULT * appconfAUDIO_PIPELINE_FRAME_ADVANCE, - appconfINTENT_SAMPLE_BLOCK_LENGTH); - - xTaskCreate((TaskFunction_t)intent_engine_intertile_samples_in_task, - "int_intertile_rx", - RTOS_THREAD_STACK_SIZE(intent_engine_intertile_samples_in_task), - NULL, - priority-1, - NULL); - xTaskCreate((TaskFunction_t)intent_engine_task, - "intent_eng", - RTOS_THREAD_STACK_SIZE(intent_engine_task), - samples_to_engine_stream_buf, - uxTaskPriorityGet(NULL), - NULL); -} - -#endif /* ON_TILE(AUDIO_PIPELINE_TILE_NO) */ -#endif /* ASR_TILE_NO != AUDIO_PIPELINE_TILE_NO */ - -#if ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO -#if ON_TILE(ASR_TILE_NO) - -void intent_engine_samples_send_local( - size_t frame_count, - int32_t *processed_audio_frame) -{ - configASSERT(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); - - if(samples_to_engine_stream_buf != NULL) { - size_t bytes_to_send = sizeof(int32_t) * frame_count; - if (xStreamBufferSend(samples_to_engine_stream_buf, processed_audio_frame, bytes_to_send, 0) != bytes_to_send) { - rtos_printf("lost local output samples for intent\n"); - } - } else { - rtos_printf("intent engine streambuffer not ready\n"); - } -} - -void intent_engine_task_create(unsigned priority) -{ - samples_to_engine_stream_buf = xStreamBufferCreate( - appconfINTENT_FRAME_BUFFER_MULT * appconfAUDIO_PIPELINE_FRAME_ADVANCE, - appconfINTENT_SAMPLE_BLOCK_LENGTH); - - xTaskCreate((TaskFunction_t)intent_engine_task, - "intent_eng", - RTOS_THREAD_STACK_SIZE(intent_engine_task), - samples_to_engine_stream_buf, - uxTaskPriorityGet(NULL), - NULL); -} - -#endif /* ON_TILE(ASR_TILE_NO) */ -#endif /* ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO */ diff --git a/examples/ffva/src/intent_handler/audio_response/audio_response.c b/examples/ffva/src/intent_handler/audio_response/audio_response.c deleted file mode 100644 index f28024fe..00000000 --- a/examples/ffva/src/intent_handler/audio_response/audio_response.c +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -/* STD headers */ -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" -#include "task.h" -#include "queue.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "intent_handler/intent_handler.h" -#include "audio_response.h" -#include "fs_support.h" -#include "ff.h" -#include "dr_wav_freertos_port.h" - -static const char *audio_files_en[] = { - "50.wav", /* sleep */ - "1.wav", /* wakeup */ - "3.wav", /* tv_on */ - "4.wav", /* tv_off */ - "5.wav", /* ch_up */ - "6.wav", /* ch_down */ - "7.wav", /* vol_up */ - "8.wav", /* vol_down */ - "9.wav", /* lights_on */ - "10.wav", /* lights_off */ - "11.wav", /* lights_up*/ - "12.wav", /* lights_down */ - "13.wav", /* fan_on */ - "14.wav", /* fan_off */ - "15.wav", /* fan_up */ - "16.wav", /* fan_down */ - "17.wav", /* temp_up */ - "18.wav", /* temp_down */ -}; - -#define NUM_FILES (sizeof(audio_files_en) / sizeof(char *)) - -static int16_t file_audio[appconfAUDIO_PIPELINE_FRAME_ADVANCE * sizeof(int16_t)]; -static int32_t i2s_audio[2*(appconfAUDIO_PIPELINE_FRAME_ADVANCE * sizeof(int32_t))]; -static drwav *wav_files = NULL; - -#pragma stackfunction 3000 - -int32_t audio_response_init(void) { - FRESULT result = 0; - FIL *files = pvPortMalloc(NUM_FILES * sizeof(FIL)); - wav_files = pvPortMalloc(NUM_FILES * sizeof(drwav)); - - configASSERT(files); - configASSERT(wav_files); - configASSERT(file_audio); - configASSERT(i2s_audio); - - for (int i=0; i - -int32_t audio_response_init(void); - -void audio_response_play(int32_t id); - -#endif /* AUDIO_RESPONSE_H_ */ diff --git a/examples/ffva/src/intent_handler/audio_response/dr_wav.h b/examples/ffva/src/intent_handler/audio_response/dr_wav.h deleted file mode 100644 index 94edf4ae..00000000 --- a/examples/ffva/src/intent_handler/audio_response/dr_wav.h +++ /dev/null @@ -1,8309 +0,0 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -/* -WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_wav - v0.13.6 - 2022-04-10 - -David Reid - mackron@gmail.com - -GitHub: https://github.com/mackron/dr_libs -*/ - -/* -Introduction -============ -This is a single file library. To use it, do something like the following in one .c file. - - ```c - #define DR_WAV_IMPLEMENTATION - #include "dr_wav.h" - ``` - -You can then #include this file in other parts of the program as you would with any other header file. Do something like the following to read audio data: - - ```c - drwav wav; - if (!drwav_init_file(&wav, "my_song.wav", NULL)) { - // Error opening WAV file. - } - - drwav_int32* pDecodedInterleavedPCMFrames = malloc(wav.totalPCMFrameCount * wav.channels * sizeof(drwav_int32)); - size_t numberOfSamplesActuallyDecoded = drwav_read_pcm_frames_s32(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); - - ... - - drwav_uninit(&wav); - ``` - -If you just want to quickly open and read the audio data in a single operation you can do something like this: - - ```c - unsigned int channels; - unsigned int sampleRate; - drwav_uint64 totalPCMFrameCount; - float* pSampleData = drwav_open_file_and_read_pcm_frames_f32("my_song.wav", &channels, &sampleRate, &totalPCMFrameCount, NULL); - if (pSampleData == NULL) { - // Error opening and reading WAV file. - } - - ... - - drwav_free(pSampleData, NULL); - ``` - -The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in this case), but you can still output the -audio data in its internal format (see notes below for supported formats): - - ```c - size_t framesRead = drwav_read_pcm_frames(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); - ``` - -You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for a particular data format: - - ```c - size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); - ``` - -dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at `drwav_init_write()`, -`drwav_init_file_write()`, etc. Use `drwav_write_pcm_frames()` to write samples, or `drwav_write_raw()` to write raw data in the "data" chunk. - - ```c - drwav_data_format format; - format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. - format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. - format.channels = 2; - format.sampleRate = 44100; - format.bitsPerSample = 16; - drwav_init_file_write(&wav, "data/recording.wav", &format, NULL); - - ... - - drwav_uint64 framesWritten = drwav_write_pcm_frames(pWav, frameCount, pSamples); - ``` - -dr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work without any manual intervention. - - -Build Options -============= -#define these options before including this file. - -#define DR_WAV_NO_CONVERSION_API - Disables conversion APIs such as `drwav_read_pcm_frames_f32()` and `drwav_s16_to_f32()`. - -#define DR_WAV_NO_STDIO - Disables APIs that initialize a decoder from a file such as `drwav_init_file()`, `drwav_init_file_write()`, etc. - - - -Notes -===== -- Samples are always interleaved. -- The default read function does not do any data conversion. Use `drwav_read_pcm_frames_f32()`, `drwav_read_pcm_frames_s32()` and `drwav_read_pcm_frames_s16()` - to read and convert audio data to 32-bit floating point, signed 32-bit integer and signed 16-bit integer samples respectively. Tested and supported internal - formats include the following: - - Unsigned 8-bit PCM - - Signed 12-bit PCM - - Signed 16-bit PCM - - Signed 24-bit PCM - - Signed 32-bit PCM - - IEEE 32-bit floating point - - IEEE 64-bit floating point - - A-law and u-law - - Microsoft ADPCM - - IMA ADPCM (DVI, format code 0x11) -- dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. -*/ - -#ifndef dr_wav_h -#define dr_wav_h - -#ifdef __cplusplus -extern "C" { -#endif - -#define DRWAV_STRINGIFY(x) #x -#define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) - -#define DRWAV_VERSION_MAJOR 0 -#define DRWAV_VERSION_MINOR 13 -#define DRWAV_VERSION_REVISION 6 -#define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) - -#include /* For size_t. */ - -/* Sized types. */ -typedef signed char drwav_int8; -typedef unsigned char drwav_uint8; -typedef signed short drwav_int16; -typedef unsigned short drwav_uint16; -typedef signed int drwav_int32; -typedef unsigned int drwav_uint32; -#if defined(_MSC_VER) && !defined(__clang__) - typedef signed __int64 drwav_int64; - typedef unsigned __int64 drwav_uint64; -#else - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wlong-long" - #if defined(__clang__) - #pragma GCC diagnostic ignored "-Wc++11-long-long" - #endif - #endif - typedef signed long long drwav_int64; - typedef unsigned long long drwav_uint64; - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) - #pragma GCC diagnostic pop - #endif -#endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) - typedef drwav_uint64 drwav_uintptr; -#else - typedef drwav_uint32 drwav_uintptr; -#endif -typedef drwav_uint8 drwav_bool8; -typedef drwav_uint32 drwav_bool32; -#define DRWAV_TRUE 1 -#define DRWAV_FALSE 0 - -#if !defined(DRWAV_API) - #if defined(DRWAV_DLL) - #if defined(_WIN32) - #define DRWAV_DLL_IMPORT __declspec(dllimport) - #define DRWAV_DLL_EXPORT __declspec(dllexport) - #define DRWAV_DLL_PRIVATE static - #else - #if defined(__GNUC__) && __GNUC__ >= 4 - #define DRWAV_DLL_IMPORT __attribute__((visibility("default"))) - #define DRWAV_DLL_EXPORT __attribute__((visibility("default"))) - #define DRWAV_DLL_PRIVATE __attribute__((visibility("hidden"))) - #else - #define DRWAV_DLL_IMPORT - #define DRWAV_DLL_EXPORT - #define DRWAV_DLL_PRIVATE static - #endif - #endif - - #if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) - #define DRWAV_API DRWAV_DLL_EXPORT - #else - #define DRWAV_API DRWAV_DLL_IMPORT - #endif - #define DRWAV_PRIVATE DRWAV_DLL_PRIVATE - #else - #define DRWAV_API extern - #define DRWAV_PRIVATE static - #endif -#endif - -typedef drwav_int32 drwav_result; -#define DRWAV_SUCCESS 0 -#define DRWAV_ERROR -1 /* A generic error. */ -#define DRWAV_INVALID_ARGS -2 -#define DRWAV_INVALID_OPERATION -3 -#define DRWAV_OUT_OF_MEMORY -4 -#define DRWAV_OUT_OF_RANGE -5 -#define DRWAV_ACCESS_DENIED -6 -#define DRWAV_DOES_NOT_EXIST -7 -#define DRWAV_ALREADY_EXISTS -8 -#define DRWAV_TOO_MANY_OPEN_FILES -9 -#define DRWAV_INVALID_FILE -10 -#define DRWAV_TOO_BIG -11 -#define DRWAV_PATH_TOO_LONG -12 -#define DRWAV_NAME_TOO_LONG -13 -#define DRWAV_NOT_DIRECTORY -14 -#define DRWAV_IS_DIRECTORY -15 -#define DRWAV_DIRECTORY_NOT_EMPTY -16 -#define DRWAV_END_OF_FILE -17 -#define DRWAV_NO_SPACE -18 -#define DRWAV_BUSY -19 -#define DRWAV_IO_ERROR -20 -#define DRWAV_INTERRUPT -21 -#define DRWAV_UNAVAILABLE -22 -#define DRWAV_ALREADY_IN_USE -23 -#define DRWAV_BAD_ADDRESS -24 -#define DRWAV_BAD_SEEK -25 -#define DRWAV_BAD_PIPE -26 -#define DRWAV_DEADLOCK -27 -#define DRWAV_TOO_MANY_LINKS -28 -#define DRWAV_NOT_IMPLEMENTED -29 -#define DRWAV_NO_MESSAGE -30 -#define DRWAV_BAD_MESSAGE -31 -#define DRWAV_NO_DATA_AVAILABLE -32 -#define DRWAV_INVALID_DATA -33 -#define DRWAV_TIMEOUT -34 -#define DRWAV_NO_NETWORK -35 -#define DRWAV_NOT_UNIQUE -36 -#define DRWAV_NOT_SOCKET -37 -#define DRWAV_NO_ADDRESS -38 -#define DRWAV_BAD_PROTOCOL -39 -#define DRWAV_PROTOCOL_UNAVAILABLE -40 -#define DRWAV_PROTOCOL_NOT_SUPPORTED -41 -#define DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED -42 -#define DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED -43 -#define DRWAV_SOCKET_NOT_SUPPORTED -44 -#define DRWAV_CONNECTION_RESET -45 -#define DRWAV_ALREADY_CONNECTED -46 -#define DRWAV_NOT_CONNECTED -47 -#define DRWAV_CONNECTION_REFUSED -48 -#define DRWAV_NO_HOST -49 -#define DRWAV_IN_PROGRESS -50 -#define DRWAV_CANCELLED -51 -#define DRWAV_MEMORY_ALREADY_MAPPED -52 -#define DRWAV_AT_END -53 - -/* Common data formats. */ -#define DR_WAVE_FORMAT_PCM 0x1 -#define DR_WAVE_FORMAT_ADPCM 0x2 -#define DR_WAVE_FORMAT_IEEE_FLOAT 0x3 -#define DR_WAVE_FORMAT_ALAW 0x6 -#define DR_WAVE_FORMAT_MULAW 0x7 -#define DR_WAVE_FORMAT_DVI_ADPCM 0x11 -#define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE - -/* Flags to pass into drwav_init_ex(), etc. */ -#define DRWAV_SEQUENTIAL 0x00000001 - -DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision); -DRWAV_API const char* drwav_version_string(void); - -typedef enum -{ - drwav_seek_origin_start, - drwav_seek_origin_current -} drwav_seek_origin; - -typedef enum -{ - drwav_container_riff, - drwav_container_w64, - drwav_container_rf64 -} drwav_container; - -typedef struct -{ - union - { - drwav_uint8 fourcc[4]; - drwav_uint8 guid[16]; - } id; - - /* The size in bytes of the chunk. */ - drwav_uint64 sizeInBytes; - - /* - RIFF = 2 byte alignment. - W64 = 8 byte alignment. - */ - unsigned int paddingSize; -} drwav_chunk_header; - -typedef struct -{ - /* - The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications - that require support for data formats not natively supported by dr_wav. - */ - drwav_uint16 formatTag; - - /* The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. */ - drwav_uint16 channels; - - /* The sample rate. Usually set to something like 44100. */ - drwav_uint32 sampleRate; - - /* Average bytes per second. You probably don't need this, but it's left here for informational purposes. */ - drwav_uint32 avgBytesPerSec; - - /* Block align. This is equal to the number of channels * bytes per sample. */ - drwav_uint16 blockAlign; - - /* Bits per sample. */ - drwav_uint16 bitsPerSample; - - /* The size of the extended data. Only used internally for validation, but left here for informational purposes. */ - drwav_uint16 extendedSize; - - /* - The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, - is always rounded up to the nearest multiple of 8. This variable contains information about exactly how - many bits are valid per sample. Mainly used for informational purposes. - */ - drwav_uint16 validBitsPerSample; - - /* The channel mask. Not used at the moment. */ - drwav_uint32 channelMask; - - /* The sub-format, exactly as specified by the wave file. */ - drwav_uint8 subFormat[16]; -} drwav_fmt; - -DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT); - - -/* -Callback for when data is read. Return value is the number of bytes actually read. - -pUserData [in] The user data that was passed to drwav_init() and family. -pBufferOut [out] The output buffer. -bytesToRead [in] The number of bytes to read. - -Returns the number of bytes actually read. - -A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until -either the entire bytesToRead is filled or you have reached the end of the stream. -*/ -typedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); - -/* -Callback for when data is written. Returns value is the number of bytes actually written. - -pUserData [in] The user data that was passed to drwav_init_write() and family. -pData [out] A pointer to the data to write. -bytesToWrite [in] The number of bytes to write. - -Returns the number of bytes actually written. - -If the return value differs from bytesToWrite, it indicates an error. -*/ -typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); - -/* -Callback for when data needs to be seeked. - -pUserData [in] The user data that was passed to drwav_init() and family. -offset [in] The number of bytes to move, relative to the origin. Will never be negative. -origin [in] The origin of the seek - the current position or the start of the stream. - -Returns whether or not the seek was successful. - -Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be either drwav_seek_origin_start or -drwav_seek_origin_current. -*/ -typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); - -/* -Callback for when drwav_init_ex() finds a chunk. - -pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex() and family. -onRead [in] A pointer to the function to call when reading. -onSeek [in] A pointer to the function to call when seeking. -pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex() and family. -pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk. -container [in] Whether or not the WAV file is a RIFF or Wave64 container. If you're unsure of the difference, assume RIFF. -pFMT [in] A pointer to the object containing the contents of the "fmt" chunk. - -Returns the number of bytes read + seeked. - -To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same for seeking with onSeek(). The return value must -be the total number of bytes you have read _plus_ seeked. - -Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should -use `id.fourcc`, otherwise you should use `id.guid`. - -The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the -`DR_WAVE_FORMAT_*` identifiers. - -The read pointer will be sitting on the first byte after the chunk's header. You must not attempt to read beyond the boundary of the chunk. -*/ -typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader, drwav_container container, const drwav_fmt* pFMT); - -typedef struct -{ - void* pUserData; - void* (* onMalloc)(size_t sz, void* pUserData); - void* (* onRealloc)(void* p, size_t sz, void* pUserData); - void (* onFree)(void* p, void* pUserData); -} drwav_allocation_callbacks; - -/* Structure for internal use. Only used for loaders opened with drwav_init_memory(). */ -typedef struct -{ - const drwav_uint8* data; - size_t dataSize; - size_t currentReadPos; -} drwav__memory_stream; - -/* Structure for internal use. Only used for writers opened with drwav_init_memory_write(). */ -typedef struct -{ - void** ppData; - size_t* pDataSize; - size_t dataSize; - size_t dataCapacity; - size_t currentWritePos; -} drwav__memory_stream_write; - -typedef struct -{ - drwav_container container; /* RIFF, W64. */ - drwav_uint32 format; /* DR_WAVE_FORMAT_* */ - drwav_uint32 channels; - drwav_uint32 sampleRate; - drwav_uint32 bitsPerSample; -} drwav_data_format; - -typedef enum -{ - drwav_metadata_type_none = 0, - - /* - Unknown simply means a chunk that drwav does not handle specifically. You can still ask to - receive these chunks as metadata objects. It is then up to you to interpret the chunk's data. - You can also write unknown metadata to a wav file. Be careful writing unknown chunks if you - have also edited the audio data. The unknown chunks could represent offsets/sizes that no - longer correctly correspond to the audio data. - */ - drwav_metadata_type_unknown = 1 << 0, - - /* Only 1 of each of these metadata items are allowed in a wav file. */ - drwav_metadata_type_smpl = 1 << 1, - drwav_metadata_type_inst = 1 << 2, - drwav_metadata_type_cue = 1 << 3, - drwav_metadata_type_acid = 1 << 4, - drwav_metadata_type_bext = 1 << 5, - - /* - Wav files often have a LIST chunk. This is a chunk that contains a set of subchunks. For this - higher-level metadata API, we don't make a distinction between a regular chunk and a LIST - subchunk. Instead, they are all just 'metadata' items. - - There can be multiple of these metadata items in a wav file. - */ - drwav_metadata_type_list_label = 1 << 6, - drwav_metadata_type_list_note = 1 << 7, - drwav_metadata_type_list_labelled_cue_region = 1 << 8, - - drwav_metadata_type_list_info_software = 1 << 9, - drwav_metadata_type_list_info_copyright = 1 << 10, - drwav_metadata_type_list_info_title = 1 << 11, - drwav_metadata_type_list_info_artist = 1 << 12, - drwav_metadata_type_list_info_comment = 1 << 13, - drwav_metadata_type_list_info_date = 1 << 14, - drwav_metadata_type_list_info_genre = 1 << 15, - drwav_metadata_type_list_info_album = 1 << 16, - drwav_metadata_type_list_info_tracknumber = 1 << 17, - - /* Other type constants for convenience. */ - drwav_metadata_type_list_all_info_strings = drwav_metadata_type_list_info_software - | drwav_metadata_type_list_info_copyright - | drwav_metadata_type_list_info_title - | drwav_metadata_type_list_info_artist - | drwav_metadata_type_list_info_comment - | drwav_metadata_type_list_info_date - | drwav_metadata_type_list_info_genre - | drwav_metadata_type_list_info_album - | drwav_metadata_type_list_info_tracknumber, - - drwav_metadata_type_list_all_adtl = drwav_metadata_type_list_label - | drwav_metadata_type_list_note - | drwav_metadata_type_list_labelled_cue_region, - - drwav_metadata_type_all = -2, /*0xFFFFFFFF & ~drwav_metadata_type_unknown,*/ - drwav_metadata_type_all_including_unknown = -1 /*0xFFFFFFFF,*/ -} drwav_metadata_type; - -/* -Sampler Metadata - -The sampler chunk contains information about how a sound should be played in the context of a whole -audio production, and when used in a sampler. See https://en.wikipedia.org/wiki/Sample-based_synthesis. -*/ -typedef enum -{ - drwav_smpl_loop_type_forward = 0, - drwav_smpl_loop_type_pingpong = 1, - drwav_smpl_loop_type_backward = 2 -} drwav_smpl_loop_type; - -typedef struct -{ - /* The ID of the associated cue point, see drwav_cue and drwav_cue_point. As with all cue point IDs, this can correspond to a label chunk to give this loop a name, see drwav_list_label_or_note. */ - drwav_uint32 cuePointId; - - /* See drwav_smpl_loop_type. */ - drwav_uint32 type; - - /* The byte offset of the first sample to be played in the loop. */ - drwav_uint32 firstSampleByteOffset; - - /* The byte offset into the audio data of the last sample to be played in the loop. */ - drwav_uint32 lastSampleByteOffset; - - /* A value to represent that playback should occur at a point between samples. This value ranges from 0 to UINT32_MAX. Where a value of 0 means no fraction, and a value of (UINT32_MAX / 2) would mean half a sample. */ - drwav_uint32 sampleFraction; - - /* Number of times to play the loop. 0 means loop infinitely. */ - drwav_uint32 playCount; -} drwav_smpl_loop; - -typedef struct -{ - /* IDs for a particular MIDI manufacturer. 0 if not used. */ - drwav_uint32 manufacturerId; - drwav_uint32 productId; - - /* The period of 1 sample in nanoseconds. */ - drwav_uint32 samplePeriodNanoseconds; - - /* The MIDI root note of this file. 0 to 127. */ - drwav_uint32 midiUnityNote; - - /* The fraction of a semitone up from the given MIDI note. This is a value from 0 to UINT32_MAX, where 0 means no change and (UINT32_MAX / 2) is half a semitone (AKA 50 cents). */ - drwav_uint32 midiPitchFraction; - - /* Data relating to SMPTE standards which are used for syncing audio and video. 0 if not used. */ - drwav_uint32 smpteFormat; - drwav_uint32 smpteOffset; - - /* drwav_smpl_loop loops. */ - drwav_uint32 sampleLoopCount; - - /* Optional sampler-specific data. */ - drwav_uint32 samplerSpecificDataSizeInBytes; - - drwav_smpl_loop* pLoops; - drwav_uint8* pSamplerSpecificData; -} drwav_smpl; - -/* -Instrument Metadata - -The inst metadata contains data about how a sound should be played as part of an instrument. This -commonly read by samplers. See https://en.wikipedia.org/wiki/Sample-based_synthesis. -*/ -typedef struct -{ - drwav_int8 midiUnityNote; /* The root note of the audio as a MIDI note number. 0 to 127. */ - drwav_int8 fineTuneCents; /* -50 to +50 */ - drwav_int8 gainDecibels; /* -64 to +64 */ - drwav_int8 lowNote; /* 0 to 127 */ - drwav_int8 highNote; /* 0 to 127 */ - drwav_int8 lowVelocity; /* 1 to 127 */ - drwav_int8 highVelocity; /* 1 to 127 */ -} drwav_inst; - -/* -Cue Metadata - -Cue points are markers at specific points in the audio. They often come with an associated piece of -drwav_list_label_or_note metadata which contains the text for the marker. -*/ -typedef struct -{ - /* Unique identification value. */ - drwav_uint32 id; - - /* Set to 0. This is only relevant if there is a 'playlist' chunk - which is not supported by dr_wav. */ - drwav_uint32 playOrderPosition; - - /* Should always be "data". This represents the fourcc value of the chunk that this cue point corresponds to. dr_wav only supports a single data chunk so this should always be "data". */ - drwav_uint8 dataChunkId[4]; - - /* Set to 0. This is only relevant if there is a wave list chunk. dr_wav, like lots of readers/writers, do not support this. */ - drwav_uint32 chunkStart; - - /* Set to 0 for uncompressed formats. Else the last byte in compressed wave data where decompression can begin to find the value of the corresponding sample value. */ - drwav_uint32 blockStart; - - /* For uncompressed formats this is the byte offset of the cue point into the audio data. For compressed formats this is relative to the block specified with blockStart. */ - drwav_uint32 sampleByteOffset; -} drwav_cue_point; - -typedef struct -{ - drwav_uint32 cuePointCount; - drwav_cue_point *pCuePoints; -} drwav_cue; - -/* -Acid Metadata - -This chunk contains some information about the time signature and the tempo of the audio. -*/ -typedef enum -{ - drwav_acid_flag_one_shot = 1, /* If this is not set, then it is a loop instead of a one-shot. */ - drwav_acid_flag_root_note_set = 2, - drwav_acid_flag_stretch = 4, - drwav_acid_flag_disk_based = 8, - drwav_acid_flag_acidizer = 16 /* Not sure what this means. */ -} drwav_acid_flag; - -typedef struct -{ - /* A bit-field, see drwav_acid_flag. */ - drwav_uint32 flags; - - /* Valid if flags contains drwav_acid_flag_root_note_set. It represents the MIDI root note the file - a value from 0 to 127. */ - drwav_uint16 midiUnityNote; - - /* Reserved values that should probably be ignored. reserved1 seems to often be 128 and reserved2 is 0. */ - drwav_uint16 reserved1; - float reserved2; - - /* Number of beats. */ - drwav_uint32 numBeats; - - /* The time signature of the audio. */ - drwav_uint16 meterDenominator; - drwav_uint16 meterNumerator; - - /* Beats per minute of the track. Setting a value of 0 suggests that there is no tempo. */ - float tempo; -} drwav_acid; - -/* -Cue Label or Note metadata - -These are 2 different types of metadata, but they have the exact same format. Labels tend to be the -more common and represent a short name for a cue point. Notes might be used to represent a longer -comment. -*/ -typedef struct -{ - /* The ID of a cue point that this label or note corresponds to. */ - drwav_uint32 cuePointId; - - /* Size of the string not including any null terminator. */ - drwav_uint32 stringLength; - - /* The string. The *init_with_metadata functions null terminate this for convenience. */ - char* pString; -} drwav_list_label_or_note; - -/* -BEXT metadata, also known as Broadcast Wave Format (BWF) - -This metadata adds some extra description to an audio file. You must check the version field to -determine if the UMID or the loudness fields are valid. -*/ -typedef struct -{ - /* - These top 3 fields, and the umid field are actually defined in the standard as a statically - sized buffers. In order to reduce the size of this struct (and therefore the union in the - metadata struct), we instead store these as pointers. - */ - char* pDescription; /* Can be NULL or a null-terminated string, must be <= 256 characters. */ - char* pOriginatorName; /* Can be NULL or a null-terminated string, must be <= 32 characters. */ - char* pOriginatorReference; /* Can be NULL or a null-terminated string, must be <= 32 characters. */ - char pOriginationDate[10]; /* ASCII "yyyy:mm:dd". */ - char pOriginationTime[8]; /* ASCII "hh:mm:ss". */ - drwav_uint64 timeReference; /* First sample count since midnight. */ - drwav_uint16 version; /* Version of the BWF, check this to see if the fields below are valid. */ - - /* - Unrestricted ASCII characters containing a collection of strings terminated by CR/LF. Each - string shall contain a description of a coding process applied to the audio data. - */ - char* pCodingHistory; - drwav_uint32 codingHistorySize; - - /* Fields below this point are only valid if the version is 1 or above. */ - drwav_uint8* pUMID; /* Exactly 64 bytes of SMPTE UMID */ - - /* Fields below this point are only valid if the version is 2 or above. */ - drwav_uint16 loudnessValue; /* Integrated Loudness Value of the file in LUFS (multiplied by 100). */ - drwav_uint16 loudnessRange; /* Loudness Range of the file in LU (multiplied by 100). */ - drwav_uint16 maxTruePeakLevel; /* Maximum True Peak Level of the file expressed as dBTP (multiplied by 100). */ - drwav_uint16 maxMomentaryLoudness; /* Highest value of the Momentary Loudness Level of the file in LUFS (multiplied by 100). */ - drwav_uint16 maxShortTermLoudness; /* Highest value of the Short-Term Loudness Level of the file in LUFS (multiplied by 100). */ -} drwav_bext; - -/* -Info Text Metadata - -There a many different types of information text that can be saved in this format. This is where -things like the album name, the artists, the year it was produced, etc are saved. See -drwav_metadata_type for the full list of types that dr_wav supports. -*/ -typedef struct -{ - /* Size of the string not including any null terminator. */ - drwav_uint32 stringLength; - - /* The string. The *init_with_metadata functions null terminate this for convenience. */ - char* pString; -} drwav_list_info_text; - -/* -Labelled Cue Region Metadata - -The labelled cue region metadata is used to associate some region of audio with text. The region -starts at a cue point, and extends for the given number of samples. -*/ -typedef struct -{ - /* The ID of a cue point that this object corresponds to. */ - drwav_uint32 cuePointId; - - /* The number of samples from the cue point forwards that should be considered this region */ - drwav_uint32 sampleLength; - - /* Four characters used to say what the purpose of this region is. */ - drwav_uint8 purposeId[4]; - - /* Unsure of the exact meanings of these. It appears to be acceptable to set them all to 0. */ - drwav_uint16 country; - drwav_uint16 language; - drwav_uint16 dialect; - drwav_uint16 codePage; - - /* Size of the string not including any null terminator. */ - drwav_uint32 stringLength; - - /* The string. The *init_with_metadata functions null terminate this for convenience. */ - char* pString; -} drwav_list_labelled_cue_region; - -/* -Unknown Metadata - -This chunk just represents a type of chunk that dr_wav does not understand. - -Unknown metadata has a location attached to it. This is because wav files can have a LIST chunk -that contains subchunks. These LIST chunks can be one of two types. An adtl list, or an INFO -list. This enum is used to specify the location of a chunk that dr_wav currently doesn't support. -*/ -typedef enum -{ - drwav_metadata_location_invalid, - drwav_metadata_location_top_level, - drwav_metadata_location_inside_info_list, - drwav_metadata_location_inside_adtl_list -} drwav_metadata_location; - -typedef struct -{ - drwav_uint8 id[4]; - drwav_metadata_location chunkLocation; - drwav_uint32 dataSizeInBytes; - drwav_uint8* pData; -} drwav_unknown_metadata; - -/* -Metadata is saved as a union of all the supported types. -*/ -typedef struct -{ - /* Determines which item in the union is valid. */ - drwav_metadata_type type; - - union - { - drwav_cue cue; - drwav_smpl smpl; - drwav_acid acid; - drwav_inst inst; - drwav_bext bext; - drwav_list_label_or_note labelOrNote; /* List label or list note. */ - drwav_list_labelled_cue_region labelledCueRegion; - drwav_list_info_text infoText; /* Any of the list info types. */ - drwav_unknown_metadata unknown; - } data; -} drwav_metadata; - -typedef struct -{ - /* A pointer to the function to call when more data is needed. */ - drwav_read_proc onRead; - - /* A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. */ - drwav_write_proc onWrite; - - /* A pointer to the function to call when the wav file needs to be seeked. */ - drwav_seek_proc onSeek; - - /* The user data to pass to callbacks. */ - void* pUserData; - - /* Allocation callbacks. */ - drwav_allocation_callbacks allocationCallbacks; - - - /* Whether or not the WAV file is formatted as a standard RIFF file or W64. */ - drwav_container container; - - - /* Structure containing format information exactly as specified by the wav file. */ - drwav_fmt fmt; - - /* The sample rate. Will be set to something like 44100. */ - drwav_uint32 sampleRate; - - /* The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. */ - drwav_uint16 channels; - - /* The bits per sample. Will be set to something like 16, 24, etc. */ - drwav_uint16 bitsPerSample; - - /* Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). */ - drwav_uint16 translatedFormatTag; - - /* The total number of PCM frames making up the audio data. */ - drwav_uint64 totalPCMFrameCount; - - - /* The size in bytes of the data chunk. */ - drwav_uint64 dataChunkDataSize; - - /* The position in the stream of the first data byte of the data chunk. This is used for seeking. */ - drwav_uint64 dataChunkDataPos; - - /* The number of bytes remaining in the data chunk. */ - drwav_uint64 bytesRemaining; - - /* The current read position in PCM frames. */ - drwav_uint64 readCursorInPCMFrames; - - - /* - Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always - set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. - */ - drwav_uint64 dataChunkDataSizeTargetWrite; - - /* Keeps track of whether or not the wav writer was initialized in sequential mode. */ - drwav_bool32 isSequentialWrite; - - - /* A bit-field of drwav_metadata_type values, only bits set in this variable are parsed and saved */ - drwav_metadata_type allowedMetadataTypes; - - /* A array of metadata. This is valid after the *init_with_metadata call returns. It will be valid until drwav_uninit() is called. You can take ownership of this data with drwav_take_ownership_of_metadata(). */ - drwav_metadata* pMetadata; - drwav_uint32 metadataCount; - - - /* A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_init_memory(). */ - drwav__memory_stream memoryStream; - drwav__memory_stream_write memoryStreamWrite; - - - /* Microsoft ADPCM specific data. */ - struct - { - drwav_uint32 bytesRemainingInBlock; - drwav_uint16 predictor[2]; - drwav_int32 delta[2]; - drwav_int32 cachedFrames[4]; /* Samples are stored in this cache during decoding. */ - drwav_uint32 cachedFrameCount; - drwav_int32 prevFrames[2][2]; /* The previous 2 samples for each channel (2 channels at most). */ - } msadpcm; - - /* IMA ADPCM specific data. */ - struct - { - drwav_uint32 bytesRemainingInBlock; - drwav_int32 predictor[2]; - drwav_int32 stepIndex[2]; - drwav_int32 cachedFrames[16]; /* Samples are stored in this cache during decoding. */ - drwav_uint32 cachedFrameCount; - } ima; -} drwav; - - -/* -Initializes a pre-allocated drwav object for reading. - -pWav [out] A pointer to the drwav object being initialized. -onRead [in] The function to call when data needs to be read from the client. -onSeek [in] The function to call when the read position of the client data needs to move. -onChunk [in, optional] The function to call when a chunk is enumerated at initialized time. -pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. -pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk. -flags [in, optional] A set of flags for controlling how things are loaded. - -Returns true if successful; false otherwise. - -Close the loader with drwav_uninit(). - -This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() -to open the stream from a file or from a block of memory respectively. - -Possible values for flags: - DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function - to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored. - -drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);". - -The onChunk callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt -after the function returns. - -See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() -*/ -DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); - -/* -Initializes a pre-allocated drwav object for writing. - -onWrite [in] The function to call when data needs to be written. -onSeek [in] The function to call when the write position needs to move. -pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. -metadata, numMetadata [in, optional] An array of metadata objects that should be written to the file. The array is not edited. You are responsible for this metadata memory and it must maintain valid until drwav_uninit() is called. - -Returns true if successful; false otherwise. - -Close the writer with drwav_uninit(). - -This is the lowest level function for initializing a WAV file. You can also use drwav_init_file_write() and drwav_init_memory_write() -to open the stream from a file or from a block of memory respectively. - -If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform -a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. - -See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() -*/ -DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_write_with_metadata(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks, drwav_metadata* pMetadata, drwav_uint32 metadataCount); - -/* -Utility function to determine the target size of the entire data to be written (including all headers and chunks). - -Returns the target size in bytes. - -The metadata argument can be NULL meaning no metadata exists. - -Useful if the application needs to know the size to allocate. - -Only writing to the RIFF chunk and one data chunk is currently supported. - -See also: drwav_init_write(), drwav_init_file_write(), drwav_init_memory_write() -*/ -DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalFrameCount, drwav_metadata* pMetadata, drwav_uint32 metadataCount); - -/* -Take ownership of the metadata objects that were allocated via one of the init_with_metadata() function calls. The init_with_metdata functions perform a single heap allocation for this metadata. - -Useful if you want the data to persist beyond the lifetime of the drwav object. - -You must free the data returned from this function using drwav_free(). -*/ -DRWAV_API drwav_metadata* drwav_take_ownership_of_metadata(drwav* pWav); - -/* -Uninitializes the given drwav object. - -Use this only for objects initialized with drwav_init*() functions (drwav_init(), drwav_init_ex(), drwav_init_write(), drwav_init_write_sequential()). -*/ -DRWAV_API drwav_result drwav_uninit(drwav* pWav); - - -/* -Reads raw audio data. - -This is the lowest level function for reading audio data. It simply reads the given number of -bytes of the raw internal sample data. - -Consider using drwav_read_pcm_frames_s16(), drwav_read_pcm_frames_s32() or drwav_read_pcm_frames_f32() for -reading sample data in a consistent format. - -pBufferOut can be NULL in which case a seek will be performed. - -Returns the number of bytes actually read. -*/ -DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); - -/* -Reads up to the specified number of PCM frames from the WAV file. - -The output data will be in the file's internal format, converted to native-endian byte order. Use -drwav_read_pcm_frames_s16/f32/s32() to read data in a specific format. - -If the return value is less than it means the end of the file has been reached or -you have requested more PCM frames than can possibly fit in the output buffer. - -This function will only work when sample data is of a fixed size and uncompressed. If you are -using a compressed format consider using drwav_read_raw() or drwav_read_pcm_frames_s16/s32/f32(). - -pBufferOut can be NULL in which case a seek will be performed. -*/ -DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); -DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); -DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); - -/* -Seeks to the given PCM frame. - -Returns true if successful; false otherwise. -*/ -DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex); - -/* -Retrieves the current read position in pcm frames. -*/ -DRWAV_API drwav_result drwav_get_cursor_in_pcm_frames(drwav* pWav, drwav_uint64* pCursor); - -/* -Retrieves the length of the file. -*/ -DRWAV_API drwav_result drwav_get_length_in_pcm_frames(drwav* pWav, drwav_uint64* pLength); - - -/* -Writes raw audio data. - -Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. -*/ -DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData); - -/* -Writes PCM frames. - -Returns the number of PCM frames written. - -Input samples need to be in native-endian byte order. On big-endian architectures the input data will be converted to -little-endian. Use drwav_write_raw() to write raw audio data without performing any conversion. -*/ -DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); -DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); -DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); - -/* Conversion Utilities */ -#ifndef DR_WAV_NO_CONVERSION_API - -/* -Reads a chunk of audio data and converts it to signed 16-bit PCM samples. - -pBufferOut can be NULL in which case a seek will be performed. - -Returns the number of PCM frames actually read. - -If the return value is less than it means the end of the file has been reached. -*/ -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); - -/* Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. */ -DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. */ -DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. */ -DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount); - -/* Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. */ -DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount); - -/* Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. */ -DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount); - -/* Low-level function for converting A-law samples to signed 16-bit PCM samples. */ -DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting u-law samples to signed 16-bit PCM samples. */ -DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); - - -/* -Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. - -pBufferOut can be NULL in which case a seek will be performed. - -Returns the number of PCM frames actually read. - -If the return value is less than it means the end of the file has been reached. -*/ -DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); -DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); -DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); - -/* Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. */ -DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. */ -DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount); - -/* Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. */ -DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. */ -DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount); - -/* Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. */ -DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount); - -/* Low-level function for converting A-law samples to IEEE 32-bit floating point samples. */ -DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting u-law samples to IEEE 32-bit floating point samples. */ -DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); - - -/* -Reads a chunk of audio data and converts it to signed 32-bit PCM samples. - -pBufferOut can be NULL in which case a seek will be performed. - -Returns the number of PCM frames actually read. - -If the return value is less than it means the end of the file has been reached. -*/ -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); - -/* Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. */ -DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. */ -DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount); - -/* Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. */ -DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. */ -DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount); - -/* Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. */ -DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount); - -/* Low-level function for converting A-law samples to signed 32-bit PCM samples. */ -DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); - -/* Low-level function for converting u-law samples to signed 32-bit PCM samples. */ -DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); - -#endif /* DR_WAV_NO_CONVERSION_API */ - - -/* High-Level Convenience Helpers */ - -#ifndef DR_WAV_NO_STDIO -/* -Helper for initializing a wave file for reading using stdio. - -This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav -objects because the operating system may restrict the number of file handles an application can have open at -any given time. -*/ -DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_with_metadata(drwav* pWav, const char* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_with_metadata_w(drwav* pWav, const wchar_t* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); - - -/* -Helper for initializing a wave file for writing using stdio. - -This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav -objects because the operating system may restrict the number of file handles an application can have open at -any given time. -*/ -DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); -#endif /* DR_WAV_NO_STDIO */ - -/* -Helper for initializing a loader from a pre-allocated memory buffer. - -This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for -the lifetime of the drwav object. - -The buffer should contain the contents of the entire wave file, not just the sample data. -*/ -DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* data, size_t dataSize, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); - -/* -Helper for initializing a writer which outputs data to a memory buffer. - -dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). - -The buffer will remain allocated even after drwav_uninit() is called. The buffer should not be considered valid -until after drwav_uninit() has been called. -*/ -DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); - - -#ifndef DR_WAV_NO_CONVERSION_API -/* -Opens and reads an entire wav file in a single operation. - -The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. -*/ -DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -#ifndef DR_WAV_NO_STDIO -/* -Opens and decodes an entire wav file in a single operation. - -The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. -*/ -DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -#endif -/* -Opens and decodes an entire wav file from a block of memory in a single operation. - -The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. -*/ -DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); -#endif - -/* Frees data that was allocated internally by dr_wav. */ -DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks); - -/* Converts bytes from a wav stream to a sized type of native endian. */ -DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data); -DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data); -DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data); -DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data); -DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data); -DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data); -DRWAV_API float drwav_bytes_to_f32(const drwav_uint8* data); - -/* Compares a GUID for the purpose of checking the type of a Wave64 chunk. */ -DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]); - -/* Compares a four-character-code for the purpose of checking the type of a RIFF chunk. */ -DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b); - -#ifdef __cplusplus -} -#endif -#endif /* dr_wav_h */ - - -/************************************************************************************************************************************************************ - ************************************************************************************************************************************************************ - - IMPLEMENTATION - - ************************************************************************************************************************************************************ - ************************************************************************************************************************************************************/ -#if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) -#ifndef dr_wav_c -#define dr_wav_c - -#include -#include -#include /* For INT_MAX */ - -#ifndef DR_WAV_NO_STDIO -#include -#include -#endif - -/* Standard library stuff. */ -#ifndef DRWAV_ASSERT -#include -#define DRWAV_ASSERT(expression) assert(expression) -#endif -#ifndef DRWAV_MALLOC -#define DRWAV_MALLOC(sz) malloc((sz)) -#endif -#ifndef DRWAV_REALLOC -#define DRWAV_REALLOC(p, sz) realloc((p), (sz)) -#endif -#ifndef DRWAV_FREE -#define DRWAV_FREE(p) free((p)) -#endif -#ifndef DRWAV_COPY_MEMORY -#define DRWAV_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) -#endif -#ifndef DRWAV_ZERO_MEMORY -#define DRWAV_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) -#endif -#ifndef DRWAV_ZERO_OBJECT -#define DRWAV_ZERO_OBJECT(p) DRWAV_ZERO_MEMORY((p), sizeof(*p)) -#endif - -#define drwav_countof(x) (sizeof(x) / sizeof(x[0])) -#define drwav_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) -#define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) -#define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) -#define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) -#define drwav_offset_ptr(p, offset) (((drwav_uint8*)(p)) + (offset)) - -#define DRWAV_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */ - -/* CPU architecture. */ -#if defined(__x86_64__) || defined(_M_X64) - #define DRWAV_X64 -#elif defined(__i386) || defined(_M_IX86) - #define DRWAV_X86 -#elif defined(__arm__) || defined(_M_ARM) - #define DRWAV_ARM -#endif - -#ifdef _MSC_VER - #define DRWAV_INLINE __forceinline -#elif defined(__GNUC__) - /* - I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when - the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some - case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the - command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue - I am using "__inline__" only when we're compiling in strict ANSI mode. - */ - #if defined(__STRICT_ANSI__) - #define DRWAV_GNUC_INLINE_HINT __inline__ - #else - #define DRWAV_GNUC_INLINE_HINT inline - #endif - - #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) - #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT __attribute__((always_inline)) - #else - #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT - #endif -#elif defined(__WATCOMC__) - #define DRWAV_INLINE __inline -#else - #define DRWAV_INLINE -#endif - -#if defined(SIZE_MAX) - #define DRWAV_SIZE_MAX SIZE_MAX -#else - #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) - #define DRWAV_SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) - #else - #define DRWAV_SIZE_MAX 0xFFFFFFFF - #endif -#endif - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - #define DRWAV_HAS_BYTESWAP16_INTRINSIC - #define DRWAV_HAS_BYTESWAP32_INTRINSIC - #define DRWAV_HAS_BYTESWAP64_INTRINSIC -#elif defined(__clang__) - #if defined(__has_builtin) - #if __has_builtin(__builtin_bswap16) - #define DRWAV_HAS_BYTESWAP16_INTRINSIC - #endif - #if __has_builtin(__builtin_bswap32) - #define DRWAV_HAS_BYTESWAP32_INTRINSIC - #endif - #if __has_builtin(__builtin_bswap64) - #define DRWAV_HAS_BYTESWAP64_INTRINSIC - #endif - #endif -#elif defined(__GNUC__) - #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) - #define DRWAV_HAS_BYTESWAP32_INTRINSIC - #define DRWAV_HAS_BYTESWAP64_INTRINSIC - #endif - #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) - #define DRWAV_HAS_BYTESWAP16_INTRINSIC - #endif -#endif - -DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision) -{ - if (pMajor) { - *pMajor = DRWAV_VERSION_MAJOR; - } - - if (pMinor) { - *pMinor = DRWAV_VERSION_MINOR; - } - - if (pRevision) { - *pRevision = DRWAV_VERSION_REVISION; - } -} - -DRWAV_API const char* drwav_version_string(void) -{ - return DRWAV_VERSION_STRING; -} - -/* -These limits are used for basic validation when initializing the decoder. If you exceed these limits, first of all: what on Earth are -you doing?! (Let me know, I'd be curious!) Second, you can adjust these by #define-ing them before the dr_wav implementation. -*/ -#ifndef DRWAV_MAX_SAMPLE_RATE -#define DRWAV_MAX_SAMPLE_RATE 384000 -#endif -#ifndef DRWAV_MAX_CHANNELS -#define DRWAV_MAX_CHANNELS 256 -#endif -#ifndef DRWAV_MAX_BITS_PER_SAMPLE -#define DRWAV_MAX_BITS_PER_SAMPLE 64 -#endif - -static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; /* 66666972-912E-11CF-A5D6-28DB04C10000 */ -static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 65766177-ACF3-11D3-8CD1-00C04F8EDB8A */ -/*static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A */ -static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A */ -static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 74636166-ACF3-11D3-8CD1-00C04F8EDB8A */ -static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 61746164-ACF3-11D3-8CD1-00C04F8EDB8A */ -/*static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A */ - - -static DRWAV_INLINE int drwav__is_little_endian(void) -{ -#if defined(DRWAV_X86) || defined(DRWAV_X64) - return DRWAV_TRUE; -#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN - return DRWAV_TRUE; -#else - int n = 1; - return (*(char*)&n) == 1; -#endif -} - - -static DRWAV_INLINE void drwav_bytes_to_guid(const drwav_uint8* data, drwav_uint8* guid) -{ - int i; - for (i = 0; i < 16; ++i) { - guid[i] = data[i]; - } -} - - -static DRWAV_INLINE drwav_uint16 drwav__bswap16(drwav_uint16 n) -{ -#ifdef DRWAV_HAS_BYTESWAP16_INTRINSIC - #if defined(_MSC_VER) - return _byteswap_ushort(n); - #elif defined(__GNUC__) || defined(__clang__) - return __builtin_bswap16(n); - #else - #error "This compiler does not support the byte swap intrinsic." - #endif -#else - return ((n & 0xFF00) >> 8) | - ((n & 0x00FF) << 8); -#endif -} - -static DRWAV_INLINE drwav_uint32 drwav__bswap32(drwav_uint32 n) -{ -#ifdef DRWAV_HAS_BYTESWAP32_INTRINSIC - #if defined(_MSC_VER) - return _byteswap_ulong(n); - #elif defined(__GNUC__) || defined(__clang__) - #if defined(DRWAV_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(DRWAV_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ - /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ - drwav_uint32 r; - __asm__ __volatile__ ( - #if defined(DRWAV_64BIT) - "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ - #else - "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) - #endif - ); - return r; - #else - return __builtin_bswap32(n); - #endif - #else - #error "This compiler does not support the byte swap intrinsic." - #endif -#else - return ((n & 0xFF000000) >> 24) | - ((n & 0x00FF0000) >> 8) | - ((n & 0x0000FF00) << 8) | - ((n & 0x000000FF) << 24); -#endif -} - -static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) -{ -#ifdef DRWAV_HAS_BYTESWAP64_INTRINSIC - #if defined(_MSC_VER) - return _byteswap_uint64(n); - #elif defined(__GNUC__) || defined(__clang__) - return __builtin_bswap64(n); - #else - #error "This compiler does not support the byte swap intrinsic." - #endif -#else - /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ - return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | - ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | - ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | - ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | - ((n & ((drwav_uint64)0xFF000000 )) << 8) | - ((n & ((drwav_uint64)0x00FF0000 )) << 24) | - ((n & ((drwav_uint64)0x0000FF00 )) << 40) | - ((n & ((drwav_uint64)0x000000FF )) << 56); -#endif -} - - -static DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n) -{ - return (drwav_int16)drwav__bswap16((drwav_uint16)n); -} - -static DRWAV_INLINE void drwav__bswap_samples_s16(drwav_int16* pSamples, drwav_uint64 sampleCount) -{ - drwav_uint64 iSample; - for (iSample = 0; iSample < sampleCount; iSample += 1) { - pSamples[iSample] = drwav__bswap_s16(pSamples[iSample]); - } -} - - -static DRWAV_INLINE void drwav__bswap_s24(drwav_uint8* p) -{ - drwav_uint8 t; - t = p[0]; - p[0] = p[2]; - p[2] = t; -} - -static DRWAV_INLINE void drwav__bswap_samples_s24(drwav_uint8* pSamples, drwav_uint64 sampleCount) -{ - drwav_uint64 iSample; - for (iSample = 0; iSample < sampleCount; iSample += 1) { - drwav_uint8* pSample = pSamples + (iSample*3); - drwav__bswap_s24(pSample); - } -} - - -static DRWAV_INLINE drwav_int32 drwav__bswap_s32(drwav_int32 n) -{ - return (drwav_int32)drwav__bswap32((drwav_uint32)n); -} - -static DRWAV_INLINE void drwav__bswap_samples_s32(drwav_int32* pSamples, drwav_uint64 sampleCount) -{ - drwav_uint64 iSample; - for (iSample = 0; iSample < sampleCount; iSample += 1) { - pSamples[iSample] = drwav__bswap_s32(pSamples[iSample]); - } -} - - -static DRWAV_INLINE float drwav__bswap_f32(float n) -{ - union { - drwav_uint32 i; - float f; - } x; - x.f = n; - x.i = drwav__bswap32(x.i); - - return x.f; -} - -static DRWAV_INLINE void drwav__bswap_samples_f32(float* pSamples, drwav_uint64 sampleCount) -{ - drwav_uint64 iSample; - for (iSample = 0; iSample < sampleCount; iSample += 1) { - pSamples[iSample] = drwav__bswap_f32(pSamples[iSample]); - } -} - - -static DRWAV_INLINE double drwav__bswap_f64(double n) -{ - union { - drwav_uint64 i; - double f; - } x; - x.f = n; - x.i = drwav__bswap64(x.i); - - return x.f; -} - -static DRWAV_INLINE void drwav__bswap_samples_f64(double* pSamples, drwav_uint64 sampleCount) -{ - drwav_uint64 iSample; - for (iSample = 0; iSample < sampleCount; iSample += 1) { - pSamples[iSample] = drwav__bswap_f64(pSamples[iSample]); - } -} - - -static DRWAV_INLINE void drwav__bswap_samples_pcm(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample) -{ - /* Assumes integer PCM. Floating point PCM is done in drwav__bswap_samples_ieee(). */ - switch (bytesPerSample) - { - case 1: /* u8 */ - { - /* no-op. */ - } break; - case 2: /* s16, s12 (loosely packed) */ - { - drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); - } break; - case 3: /* s24 */ - { - drwav__bswap_samples_s24((drwav_uint8*)pSamples, sampleCount); - } break; - case 4: /* s32 */ - { - drwav__bswap_samples_s32((drwav_int32*)pSamples, sampleCount); - } break; - default: - { - /* Unsupported format. */ - DRWAV_ASSERT(DRWAV_FALSE); - } break; - } -} - -static DRWAV_INLINE void drwav__bswap_samples_ieee(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample) -{ - switch (bytesPerSample) - { - #if 0 /* Contributions welcome for f16 support. */ - case 2: /* f16 */ - { - drwav__bswap_samples_f16((drwav_float16*)pSamples, sampleCount); - } break; - #endif - case 4: /* f32 */ - { - drwav__bswap_samples_f32((float*)pSamples, sampleCount); - } break; - case 8: /* f64 */ - { - drwav__bswap_samples_f64((double*)pSamples, sampleCount); - } break; - default: - { - /* Unsupported format. */ - DRWAV_ASSERT(DRWAV_FALSE); - } break; - } -} - -static DRWAV_INLINE void drwav__bswap_samples(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample, drwav_uint16 format) -{ - switch (format) - { - case DR_WAVE_FORMAT_PCM: - { - drwav__bswap_samples_pcm(pSamples, sampleCount, bytesPerSample); - } break; - - case DR_WAVE_FORMAT_IEEE_FLOAT: - { - drwav__bswap_samples_ieee(pSamples, sampleCount, bytesPerSample); - } break; - - case DR_WAVE_FORMAT_ALAW: - case DR_WAVE_FORMAT_MULAW: - { - drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); - } break; - - case DR_WAVE_FORMAT_ADPCM: - case DR_WAVE_FORMAT_DVI_ADPCM: - default: - { - /* Unsupported format. */ - DRWAV_ASSERT(DRWAV_FALSE); - } break; - } -} - - -DRWAV_PRIVATE void* drwav__malloc_default(size_t sz, void* pUserData) -{ - (void)pUserData; - return DRWAV_MALLOC(sz); -} - -DRWAV_PRIVATE void* drwav__realloc_default(void* p, size_t sz, void* pUserData) -{ - (void)pUserData; - return DRWAV_REALLOC(p, sz); -} - -DRWAV_PRIVATE void drwav__free_default(void* p, void* pUserData) -{ - (void)pUserData; - DRWAV_FREE(p); -} - - -DRWAV_PRIVATE void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks == NULL) { - return NULL; - } - - if (pAllocationCallbacks->onMalloc != NULL) { - return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); - } - - /* Try using realloc(). */ - if (pAllocationCallbacks->onRealloc != NULL) { - return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); - } - - return NULL; -} - -DRWAV_PRIVATE void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks == NULL) { - return NULL; - } - - if (pAllocationCallbacks->onRealloc != NULL) { - return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); - } - - /* Try emulating realloc() in terms of malloc()/free(). */ - if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { - void* p2; - - p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); - if (p2 == NULL) { - return NULL; - } - - if (p != NULL) { - DRWAV_COPY_MEMORY(p2, p, szOld); - pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); - } - - return p2; - } - - return NULL; -} - -DRWAV_PRIVATE void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (p == NULL || pAllocationCallbacks == NULL) { - return; - } - - if (pAllocationCallbacks->onFree != NULL) { - pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); - } -} - - -DRWAV_PRIVATE drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks != NULL) { - /* Copy. */ - return *pAllocationCallbacks; - } else { - /* Defaults. */ - drwav_allocation_callbacks allocationCallbacks; - allocationCallbacks.pUserData = NULL; - allocationCallbacks.onMalloc = drwav__malloc_default; - allocationCallbacks.onRealloc = drwav__realloc_default; - allocationCallbacks.onFree = drwav__free_default; - return allocationCallbacks; - } -} - - -static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 formatTag) -{ - return - formatTag == DR_WAVE_FORMAT_ADPCM || - formatTag == DR_WAVE_FORMAT_DVI_ADPCM; -} - -DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) -{ - return (unsigned int)(chunkSize % 2); -} - -DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) -{ - return (unsigned int)(chunkSize % 8); -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); -DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); - -DRWAV_PRIVATE drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) -{ - if (container == drwav_container_riff || container == drwav_container_rf64) { - drwav_uint8 sizeInBytes[4]; - - if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { - return DRWAV_AT_END; - } - - if (onRead(pUserData, sizeInBytes, 4) != 4) { - return DRWAV_INVALID_FILE; - } - - pHeaderOut->sizeInBytes = drwav_bytes_to_u32(sizeInBytes); - pHeaderOut->paddingSize = drwav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); - *pRunningBytesReadOut += 8; - } else { - drwav_uint8 sizeInBytes[8]; - - if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { - return DRWAV_AT_END; - } - - if (onRead(pUserData, sizeInBytes, 8) != 8) { - return DRWAV_INVALID_FILE; - } - - pHeaderOut->sizeInBytes = drwav_bytes_to_u64(sizeInBytes) - 24; /* <-- Subtract 24 because w64 includes the size of the header. */ - pHeaderOut->paddingSize = drwav__chunk_padding_size_w64(pHeaderOut->sizeInBytes); - *pRunningBytesReadOut += 24; - } - - return DRWAV_SUCCESS; -} - -DRWAV_PRIVATE drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) -{ - drwav_uint64 bytesRemainingToSeek = offset; - while (bytesRemainingToSeek > 0) { - if (bytesRemainingToSeek > 0x7FFFFFFF) { - if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { - return DRWAV_FALSE; - } - bytesRemainingToSeek -= 0x7FFFFFFF; - } else { - if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) { - return DRWAV_FALSE; - } - bytesRemainingToSeek = 0; - } - } - - return DRWAV_TRUE; -} - -DRWAV_PRIVATE drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) -{ - if (offset <= 0x7FFFFFFF) { - return onSeek(pUserData, (int)offset, drwav_seek_origin_start); - } - - /* Larger than 32-bit seek. */ - if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { - return DRWAV_FALSE; - } - offset -= 0x7FFFFFFF; - - for (;;) { - if (offset <= 0x7FFFFFFF) { - return onSeek(pUserData, (int)offset, drwav_seek_origin_current); - } - - if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { - return DRWAV_FALSE; - } - offset -= 0x7FFFFFFF; - } - - /* Should never get here. */ - /*return DRWAV_TRUE; */ -} - - -DRWAV_PRIVATE drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) -{ - drwav_chunk_header header; - drwav_uint8 fmt[16]; - - if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - - - /* Skip non-fmt chunks. */ - while (((container == drwav_container_riff || container == drwav_container_rf64) && !drwav_fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT))) { - if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { - return DRWAV_FALSE; - } - *pRunningBytesReadOut += header.sizeInBytes + header.paddingSize; - - /* Try the next header. */ - if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - } - - - /* Validation. */ - if (container == drwav_container_riff || container == drwav_container_rf64) { - if (!drwav_fourcc_equal(header.id.fourcc, "fmt ")) { - return DRWAV_FALSE; - } - } else { - if (!drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT)) { - return DRWAV_FALSE; - } - } - - - if (onRead(pUserData, fmt, sizeof(fmt)) != sizeof(fmt)) { - return DRWAV_FALSE; - } - *pRunningBytesReadOut += sizeof(fmt); - - fmtOut->formatTag = drwav_bytes_to_u16(fmt + 0); - fmtOut->channels = drwav_bytes_to_u16(fmt + 2); - fmtOut->sampleRate = drwav_bytes_to_u32(fmt + 4); - fmtOut->avgBytesPerSec = drwav_bytes_to_u32(fmt + 8); - fmtOut->blockAlign = drwav_bytes_to_u16(fmt + 12); - fmtOut->bitsPerSample = drwav_bytes_to_u16(fmt + 14); - - fmtOut->extendedSize = 0; - fmtOut->validBitsPerSample = 0; - fmtOut->channelMask = 0; - DRWAV_ZERO_MEMORY(fmtOut->subFormat, sizeof(fmtOut->subFormat)); - - if (header.sizeInBytes > 16) { - drwav_uint8 fmt_cbSize[2]; - int bytesReadSoFar = 0; - - if (onRead(pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) { - return DRWAV_FALSE; /* Expecting more data. */ - } - *pRunningBytesReadOut += sizeof(fmt_cbSize); - - bytesReadSoFar = 18; - - fmtOut->extendedSize = drwav_bytes_to_u16(fmt_cbSize); - if (fmtOut->extendedSize > 0) { - /* Simple validation. */ - if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { - if (fmtOut->extendedSize != 22) { - return DRWAV_FALSE; - } - } - - if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { - drwav_uint8 fmtext[22]; - if (onRead(pUserData, fmtext, fmtOut->extendedSize) != fmtOut->extendedSize) { - return DRWAV_FALSE; /* Expecting more data. */ - } - - fmtOut->validBitsPerSample = drwav_bytes_to_u16(fmtext + 0); - fmtOut->channelMask = drwav_bytes_to_u32(fmtext + 2); - drwav_bytes_to_guid(fmtext + 6, fmtOut->subFormat); - } else { - if (!onSeek(pUserData, fmtOut->extendedSize, drwav_seek_origin_current)) { - return DRWAV_FALSE; - } - } - *pRunningBytesReadOut += fmtOut->extendedSize; - - bytesReadSoFar += fmtOut->extendedSize; - } - - /* Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. */ - if (!onSeek(pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current)) { - return DRWAV_FALSE; - } - *pRunningBytesReadOut += (header.sizeInBytes - bytesReadSoFar); - } - - if (header.paddingSize > 0) { - if (!onSeek(pUserData, header.paddingSize, drwav_seek_origin_current)) { - return DRWAV_FALSE; - } - *pRunningBytesReadOut += header.paddingSize; - } - - return DRWAV_TRUE; -} - - -DRWAV_PRIVATE size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) -{ - size_t bytesRead; - - DRWAV_ASSERT(onRead != NULL); - DRWAV_ASSERT(pCursor != NULL); - - bytesRead = onRead(pUserData, pBufferOut, bytesToRead); - *pCursor += bytesRead; - return bytesRead; -} - -#if 0 -DRWAV_PRIVATE drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) -{ - DRWAV_ASSERT(onSeek != NULL); - DRWAV_ASSERT(pCursor != NULL); - - if (!onSeek(pUserData, offset, origin)) { - return DRWAV_FALSE; - } - - if (origin == drwav_seek_origin_start) { - *pCursor = offset; - } else { - *pCursor += offset; - } - - return DRWAV_TRUE; -} -#endif - - -#define DRWAV_SMPL_BYTES 36 -#define DRWAV_SMPL_LOOP_BYTES 24 -#define DRWAV_INST_BYTES 7 -#define DRWAV_ACID_BYTES 24 -#define DRWAV_CUE_BYTES 4 -#define DRWAV_BEXT_BYTES 602 -#define DRWAV_BEXT_DESCRIPTION_BYTES 256 -#define DRWAV_BEXT_ORIGINATOR_NAME_BYTES 32 -#define DRWAV_BEXT_ORIGINATOR_REF_BYTES 32 -#define DRWAV_BEXT_RESERVED_BYTES 180 -#define DRWAV_BEXT_UMID_BYTES 64 -#define DRWAV_CUE_POINT_BYTES 24 -#define DRWAV_LIST_LABEL_OR_NOTE_BYTES 4 -#define DRWAV_LIST_LABELLED_TEXT_BYTES 20 - -#define DRWAV_METADATA_ALIGNMENT 8 - -typedef enum -{ - drwav__metadata_parser_stage_count, - drwav__metadata_parser_stage_read -} drwav__metadata_parser_stage; - -typedef struct -{ - drwav_read_proc onRead; - drwav_seek_proc onSeek; - void *pReadSeekUserData; - drwav__metadata_parser_stage stage; - drwav_metadata *pMetadata; - drwav_uint32 metadataCount; - drwav_uint8 *pData; - drwav_uint8 *pDataCursor; - drwav_uint64 metadataCursor; - drwav_uint64 extraCapacity; -} drwav__metadata_parser; - -DRWAV_PRIVATE size_t drwav__metadata_memory_capacity(drwav__metadata_parser* pParser) -{ - drwav_uint64 cap = sizeof(drwav_metadata) * (drwav_uint64)pParser->metadataCount + pParser->extraCapacity; - if (cap > DRWAV_SIZE_MAX) { - return 0; /* Too big. */ - } - - return (size_t)cap; /* Safe cast thanks to the check above. */ -} - -DRWAV_PRIVATE drwav_uint8* drwav__metadata_get_memory(drwav__metadata_parser* pParser, size_t size, size_t align) -{ - drwav_uint8* pResult; - - if (align) { - drwav_uintptr modulo = (drwav_uintptr)pParser->pDataCursor % align; - if (modulo != 0) { - pParser->pDataCursor += align - modulo; - } - } - - pResult = pParser->pDataCursor; - - /* - Getting to the point where this function is called means there should always be memory - available. Out of memory checks should have been done at an earlier stage. - */ - DRWAV_ASSERT((pResult + size) <= (pParser->pData + drwav__metadata_memory_capacity(pParser))); - - pParser->pDataCursor += size; - return pResult; -} - -DRWAV_PRIVATE void drwav__metadata_request_extra_memory_for_stage_2(drwav__metadata_parser* pParser, size_t bytes, size_t align) -{ - size_t extra = bytes + (align ? (align - 1) : 0); - pParser->extraCapacity += extra; -} - -DRWAV_PRIVATE drwav_result drwav__metadata_alloc(drwav__metadata_parser* pParser, drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pParser->extraCapacity != 0 || pParser->metadataCount != 0) { - pAllocationCallbacks->onFree(pParser->pData, pAllocationCallbacks->pUserData); - - pParser->pData = (drwav_uint8*)pAllocationCallbacks->onMalloc(drwav__metadata_memory_capacity(pParser), pAllocationCallbacks->pUserData); - pParser->pDataCursor = pParser->pData; - - if (pParser->pData == NULL) { - return DRWAV_OUT_OF_MEMORY; - } - - /* - We don't need to worry about specifying an alignment here because malloc always returns something - of suitable alignment. This also means than pParser->pMetadata is all that we need to store in order - for us to free when we are done. - */ - pParser->pMetadata = (drwav_metadata*)drwav__metadata_get_memory(pParser, sizeof(drwav_metadata) * pParser->metadataCount, 1); - pParser->metadataCursor = 0; - } - - return DRWAV_SUCCESS; -} - -DRWAV_PRIVATE size_t drwav__metadata_parser_read(drwav__metadata_parser* pParser, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) -{ - if (pCursor != NULL) { - return drwav__on_read(pParser->onRead, pParser->pReadSeekUserData, pBufferOut, bytesToRead, pCursor); - } else { - return pParser->onRead(pParser->pReadSeekUserData, pBufferOut, bytesToRead); - } -} - -DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) -{ - drwav_uint8 smplHeaderData[DRWAV_SMPL_BYTES]; - drwav_uint64 totalBytesRead = 0; - size_t bytesJustRead = drwav__metadata_parser_read(pParser, smplHeaderData, sizeof(smplHeaderData), &totalBytesRead); - - DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); - DRWAV_ASSERT(pChunkHeader != NULL); - - if (bytesJustRead == sizeof(smplHeaderData)) { - drwav_uint32 iSampleLoop; - - pMetadata->type = drwav_metadata_type_smpl; - pMetadata->data.smpl.manufacturerId = drwav_bytes_to_u32(smplHeaderData + 0); - pMetadata->data.smpl.productId = drwav_bytes_to_u32(smplHeaderData + 4); - pMetadata->data.smpl.samplePeriodNanoseconds = drwav_bytes_to_u32(smplHeaderData + 8); - pMetadata->data.smpl.midiUnityNote = drwav_bytes_to_u32(smplHeaderData + 12); - pMetadata->data.smpl.midiPitchFraction = drwav_bytes_to_u32(smplHeaderData + 16); - pMetadata->data.smpl.smpteFormat = drwav_bytes_to_u32(smplHeaderData + 20); - pMetadata->data.smpl.smpteOffset = drwav_bytes_to_u32(smplHeaderData + 24); - pMetadata->data.smpl.sampleLoopCount = drwav_bytes_to_u32(smplHeaderData + 28); - pMetadata->data.smpl.samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(smplHeaderData + 32); - - /* - The loop count needs to be validated against the size of the chunk for safety so we don't - attempt to read over the boundary of the chunk. - */ - if (pMetadata->data.smpl.sampleLoopCount == (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES) { - pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); - - for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { - drwav_uint8 smplLoopData[DRWAV_SMPL_LOOP_BYTES]; - bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); - - if (bytesJustRead == sizeof(smplLoopData)) { - pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); - pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); - pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); - pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); - pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); - pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); - } else { - break; - } - } - - if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { - pMetadata->data.smpl.pSamplerSpecificData = drwav__metadata_get_memory(pParser, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, 1); - DRWAV_ASSERT(pMetadata->data.smpl.pSamplerSpecificData != NULL); - - drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); - } - } - } - - return totalBytesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) -{ - drwav_uint8 cueHeaderSectionData[DRWAV_CUE_BYTES]; - drwav_uint64 totalBytesRead = 0; - size_t bytesJustRead = drwav__metadata_parser_read(pParser, cueHeaderSectionData, sizeof(cueHeaderSectionData), &totalBytesRead); - - DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); - - if (bytesJustRead == sizeof(cueHeaderSectionData)) { - pMetadata->type = drwav_metadata_type_cue; - pMetadata->data.cue.cuePointCount = drwav_bytes_to_u32(cueHeaderSectionData); - - /* - We need to validate the cue point count against the size of the chunk so we don't read - beyond the chunk. - */ - if (pMetadata->data.cue.cuePointCount == (pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES) { - pMetadata->data.cue.pCuePoints = (drwav_cue_point*)drwav__metadata_get_memory(pParser, sizeof(drwav_cue_point) * pMetadata->data.cue.cuePointCount, DRWAV_METADATA_ALIGNMENT); - DRWAV_ASSERT(pMetadata->data.cue.pCuePoints != NULL); - - if (pMetadata->data.cue.cuePointCount > 0) { - drwav_uint32 iCuePoint; - - for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { - drwav_uint8 cuePointData[DRWAV_CUE_POINT_BYTES]; - bytesJustRead = drwav__metadata_parser_read(pParser, cuePointData, sizeof(cuePointData), &totalBytesRead); - - if (bytesJustRead == sizeof(cuePointData)) { - pMetadata->data.cue.pCuePoints[iCuePoint].id = drwav_bytes_to_u32(cuePointData + 0); - pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition = drwav_bytes_to_u32(cuePointData + 4); - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[0] = cuePointData[8]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[1] = cuePointData[9]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[2] = cuePointData[10]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; - pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); - pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); - pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); - } else { - break; - } - } - } - } - } - - return totalBytesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav__read_inst_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) -{ - drwav_uint8 instData[DRWAV_INST_BYTES]; - drwav_uint64 bytesRead = drwav__metadata_parser_read(pParser, instData, sizeof(instData), NULL); - - DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); - - if (bytesRead == sizeof(instData)) { - pMetadata->type = drwav_metadata_type_inst; - pMetadata->data.inst.midiUnityNote = (drwav_int8)instData[0]; - pMetadata->data.inst.fineTuneCents = (drwav_int8)instData[1]; - pMetadata->data.inst.gainDecibels = (drwav_int8)instData[2]; - pMetadata->data.inst.lowNote = (drwav_int8)instData[3]; - pMetadata->data.inst.highNote = (drwav_int8)instData[4]; - pMetadata->data.inst.lowVelocity = (drwav_int8)instData[5]; - pMetadata->data.inst.highVelocity = (drwav_int8)instData[6]; - } - - return bytesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav__read_acid_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) -{ - drwav_uint8 acidData[DRWAV_ACID_BYTES]; - drwav_uint64 bytesRead = drwav__metadata_parser_read(pParser, acidData, sizeof(acidData), NULL); - - DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); - - if (bytesRead == sizeof(acidData)) { - pMetadata->type = drwav_metadata_type_acid; - pMetadata->data.acid.flags = drwav_bytes_to_u32(acidData + 0); - pMetadata->data.acid.midiUnityNote = drwav_bytes_to_u16(acidData + 4); - pMetadata->data.acid.reserved1 = drwav_bytes_to_u16(acidData + 6); - pMetadata->data.acid.reserved2 = drwav_bytes_to_f32(acidData + 8); - pMetadata->data.acid.numBeats = drwav_bytes_to_u32(acidData + 12); - pMetadata->data.acid.meterDenominator = drwav_bytes_to_u16(acidData + 16); - pMetadata->data.acid.meterNumerator = drwav_bytes_to_u16(acidData + 18); - pMetadata->data.acid.tempo = drwav_bytes_to_f32(acidData + 20); - } - - return bytesRead; -} - -DRWAV_PRIVATE size_t drwav__strlen(const char* str) -{ - size_t result = 0; - - while (*str++) { - result += 1; - } - - return result; -} - -DRWAV_PRIVATE size_t drwav__strlen_clamped(const char* str, size_t maxToRead) -{ - size_t result = 0; - - while (*str++ && result < maxToRead) { - result += 1; - } - - return result; -} - -DRWAV_PRIVATE char* drwav__metadata_copy_string(drwav__metadata_parser* pParser, const char* str, size_t maxToRead) -{ - size_t len = drwav__strlen_clamped(str, maxToRead); - - if (len) { - char* result = (char*)drwav__metadata_get_memory(pParser, len + 1, 1); - DRWAV_ASSERT(result != NULL); - - DRWAV_COPY_MEMORY(result, str, len); - result[len] = '\0'; - - return result; - } else { - return NULL; - } -} - -typedef struct -{ - const void* pBuffer; - size_t sizeInBytes; - size_t cursor; -} drwav_buffer_reader; - -DRWAV_PRIVATE drwav_result drwav_buffer_reader_init(const void* pBuffer, size_t sizeInBytes, drwav_buffer_reader* pReader) -{ - DRWAV_ASSERT(pBuffer != NULL); - DRWAV_ASSERT(pReader != NULL); - - DRWAV_ZERO_OBJECT(pReader); - - pReader->pBuffer = pBuffer; - pReader->sizeInBytes = sizeInBytes; - pReader->cursor = 0; - - return DRWAV_SUCCESS; -} - -DRWAV_PRIVATE const void* drwav_buffer_reader_ptr(const drwav_buffer_reader* pReader) -{ - DRWAV_ASSERT(pReader != NULL); - - return drwav_offset_ptr(pReader->pBuffer, pReader->cursor); -} - -DRWAV_PRIVATE drwav_result drwav_buffer_reader_seek(drwav_buffer_reader* pReader, size_t bytesToSeek) -{ - DRWAV_ASSERT(pReader != NULL); - - if (pReader->cursor + bytesToSeek > pReader->sizeInBytes) { - return DRWAV_BAD_SEEK; /* Seeking too far forward. */ - } - - pReader->cursor += bytesToSeek; - - return DRWAV_SUCCESS; -} - -DRWAV_PRIVATE drwav_result drwav_buffer_reader_read(drwav_buffer_reader* pReader, void* pDst, size_t bytesToRead, size_t* pBytesRead) -{ - drwav_result result = DRWAV_SUCCESS; - size_t bytesRemaining; - - DRWAV_ASSERT(pReader != NULL); - - if (pBytesRead != NULL) { - *pBytesRead = 0; - } - - bytesRemaining = (pReader->sizeInBytes - pReader->cursor); - if (bytesToRead > bytesRemaining) { - bytesToRead = bytesRemaining; - } - - if (pDst == NULL) { - /* Seek. */ - result = drwav_buffer_reader_seek(pReader, bytesToRead); - } else { - /* Read. */ - DRWAV_COPY_MEMORY(pDst, drwav_buffer_reader_ptr(pReader), bytesToRead); - pReader->cursor += bytesToRead; - } - - DRWAV_ASSERT(pReader->cursor <= pReader->sizeInBytes); - - if (result == DRWAV_SUCCESS) { - if (pBytesRead != NULL) { - *pBytesRead = bytesToRead; - } - } - - return DRWAV_SUCCESS; -} - -DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u16(drwav_buffer_reader* pReader, drwav_uint16* pDst) -{ - drwav_result result; - size_t bytesRead; - drwav_uint8 data[2]; - - DRWAV_ASSERT(pReader != NULL); - DRWAV_ASSERT(pDst != NULL); - - *pDst = 0; /* Safety. */ - - result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); - if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { - return result; - } - - *pDst = drwav_bytes_to_u16(data); - - return DRWAV_SUCCESS; -} - -DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u32(drwav_buffer_reader* pReader, drwav_uint32* pDst) -{ - drwav_result result; - size_t bytesRead; - drwav_uint8 data[4]; - - DRWAV_ASSERT(pReader != NULL); - DRWAV_ASSERT(pDst != NULL); - - *pDst = 0; /* Safety. */ - - result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); - if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { - return result; - } - - *pDst = drwav_bytes_to_u32(data); - - return DRWAV_SUCCESS; -} - - - -DRWAV_PRIVATE drwav_uint64 drwav__read_bext_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) -{ - drwav_uint8 bextData[DRWAV_BEXT_BYTES]; - size_t bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); - - DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); - - if (bytesRead == sizeof(bextData)) { - drwav_buffer_reader reader; - drwav_uint32 timeReferenceLow; - drwav_uint32 timeReferenceHigh; - size_t extraBytes; - - pMetadata->type = drwav_metadata_type_bext; - - if (drwav_buffer_reader_init(bextData, bytesRead, &reader) == DRWAV_SUCCESS) { - pMetadata->data.bext.pDescription = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_DESCRIPTION_BYTES); - drwav_buffer_reader_seek(&reader, DRWAV_BEXT_DESCRIPTION_BYTES); - - pMetadata->data.bext.pOriginatorName = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_NAME_BYTES); - drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); - - pMetadata->data.bext.pOriginatorReference = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_REF_BYTES); - drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_REF_BYTES); - - drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate), NULL); - drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime), NULL); - - drwav_buffer_reader_read_u32(&reader, &timeReferenceLow); - drwav_buffer_reader_read_u32(&reader, &timeReferenceHigh); - pMetadata->data.bext.timeReference = ((drwav_uint64)timeReferenceHigh << 32) + timeReferenceLow; - - drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.version); - - pMetadata->data.bext.pUMID = drwav__metadata_get_memory(pParser, DRWAV_BEXT_UMID_BYTES, 1); - drwav_buffer_reader_read(&reader, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES, NULL); - - drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessValue); - drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessRange); - drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxTruePeakLevel); - drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxMomentaryLoudness); - drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxShortTermLoudness); - - DRWAV_ASSERT((drwav_offset_ptr(drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_RESERVED_BYTES)) == (bextData + DRWAV_BEXT_BYTES)); - - extraBytes = (size_t)(chunkSize - DRWAV_BEXT_BYTES); - if (extraBytes > 0) { - pMetadata->data.bext.pCodingHistory = (char*)drwav__metadata_get_memory(pParser, extraBytes + 1, 1); - DRWAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); - - bytesRead += drwav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); - pMetadata->data.bext.codingHistorySize = (drwav_uint32)drwav__strlen(pMetadata->data.bext.pCodingHistory); - } else { - pMetadata->data.bext.pCodingHistory = NULL; - pMetadata->data.bext.codingHistorySize = 0; - } - } - } - - return bytesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav__read_list_label_or_note_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize, drwav_metadata_type type) -{ - drwav_uint8 cueIDBuffer[DRWAV_LIST_LABEL_OR_NOTE_BYTES]; - drwav_uint64 totalBytesRead = 0; - size_t bytesJustRead = drwav__metadata_parser_read(pParser, cueIDBuffer, sizeof(cueIDBuffer), &totalBytesRead); - - DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); - - if (bytesJustRead == sizeof(cueIDBuffer)) { - drwav_uint32 sizeIncludingNullTerminator; - - pMetadata->type = type; - pMetadata->data.labelOrNote.cuePointId = drwav_bytes_to_u32(cueIDBuffer); - - sizeIncludingNullTerminator = (drwav_uint32)chunkSize - DRWAV_LIST_LABEL_OR_NOTE_BYTES; - if (sizeIncludingNullTerminator > 0) { - pMetadata->data.labelOrNote.stringLength = sizeIncludingNullTerminator - 1; - pMetadata->data.labelOrNote.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); - DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); - - drwav__metadata_parser_read(pParser, pMetadata->data.labelOrNote.pString, sizeIncludingNullTerminator, &totalBytesRead); - } else { - pMetadata->data.labelOrNote.stringLength = 0; - pMetadata->data.labelOrNote.pString = NULL; - } - } - - return totalBytesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav__read_list_labelled_cue_region_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) -{ - drwav_uint8 buffer[DRWAV_LIST_LABELLED_TEXT_BYTES]; - drwav_uint64 totalBytesRead = 0; - size_t bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &totalBytesRead); - - DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); - - if (bytesJustRead == sizeof(buffer)) { - drwav_uint32 sizeIncludingNullTerminator; - - pMetadata->type = drwav_metadata_type_list_labelled_cue_region; - pMetadata->data.labelledCueRegion.cuePointId = drwav_bytes_to_u32(buffer + 0); - pMetadata->data.labelledCueRegion.sampleLength = drwav_bytes_to_u32(buffer + 4); - pMetadata->data.labelledCueRegion.purposeId[0] = buffer[8]; - pMetadata->data.labelledCueRegion.purposeId[1] = buffer[9]; - pMetadata->data.labelledCueRegion.purposeId[2] = buffer[10]; - pMetadata->data.labelledCueRegion.purposeId[3] = buffer[11]; - pMetadata->data.labelledCueRegion.country = drwav_bytes_to_u16(buffer + 12); - pMetadata->data.labelledCueRegion.language = drwav_bytes_to_u16(buffer + 14); - pMetadata->data.labelledCueRegion.dialect = drwav_bytes_to_u16(buffer + 16); - pMetadata->data.labelledCueRegion.codePage = drwav_bytes_to_u16(buffer + 18); - - sizeIncludingNullTerminator = (drwav_uint32)chunkSize - DRWAV_LIST_LABELLED_TEXT_BYTES; - if (sizeIncludingNullTerminator > 0) { - pMetadata->data.labelledCueRegion.stringLength = sizeIncludingNullTerminator - 1; - pMetadata->data.labelledCueRegion.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); - DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); - - drwav__metadata_parser_read(pParser, pMetadata->data.labelledCueRegion.pString, sizeIncludingNullTerminator, &totalBytesRead); - } else { - pMetadata->data.labelledCueRegion.stringLength = 0; - pMetadata->data.labelledCueRegion.pString = NULL; - } - } - - return totalBytesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_info_text_chunk(drwav__metadata_parser* pParser, drwav_uint64 chunkSize, drwav_metadata_type type) -{ - drwav_uint64 bytesRead = 0; - drwav_uint32 stringSizeWithNullTerminator = (drwav_uint32)chunkSize; - - if (pParser->stage == drwav__metadata_parser_stage_count) { - pParser->metadataCount += 1; - drwav__metadata_request_extra_memory_for_stage_2(pParser, stringSizeWithNullTerminator, 1); - } else { - drwav_metadata* pMetadata = &pParser->pMetadata[pParser->metadataCursor]; - pMetadata->type = type; - if (stringSizeWithNullTerminator > 0) { - pMetadata->data.infoText.stringLength = stringSizeWithNullTerminator - 1; - pMetadata->data.infoText.pString = (char*)drwav__metadata_get_memory(pParser, stringSizeWithNullTerminator, 1); - DRWAV_ASSERT(pMetadata->data.infoText.pString != NULL); - - bytesRead = drwav__metadata_parser_read(pParser, pMetadata->data.infoText.pString, (size_t)stringSizeWithNullTerminator, NULL); - if (bytesRead == chunkSize) { - pParser->metadataCursor += 1; - } else { - /* Failed to parse. */ - } - } else { - pMetadata->data.infoText.stringLength = 0; - pMetadata->data.infoText.pString = NULL; - pParser->metadataCursor += 1; - } - } - - return bytesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_unknown_chunk(drwav__metadata_parser* pParser, const drwav_uint8* pChunkId, drwav_uint64 chunkSize, drwav_metadata_location location) -{ - drwav_uint64 bytesRead = 0; - - if (location == drwav_metadata_location_invalid) { - return 0; - } - - if (drwav_fourcc_equal(pChunkId, "data") || drwav_fourcc_equal(pChunkId, "fmt") || drwav_fourcc_equal(pChunkId, "fact")) { - return 0; - } - - if (pParser->stage == drwav__metadata_parser_stage_count) { - pParser->metadataCount += 1; - drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)chunkSize, 1); - } else { - drwav_metadata* pMetadata = &pParser->pMetadata[pParser->metadataCursor]; - pMetadata->type = drwav_metadata_type_unknown; - pMetadata->data.unknown.chunkLocation = location; - pMetadata->data.unknown.id[0] = pChunkId[0]; - pMetadata->data.unknown.id[1] = pChunkId[1]; - pMetadata->data.unknown.id[2] = pChunkId[2]; - pMetadata->data.unknown.id[3] = pChunkId[3]; - pMetadata->data.unknown.dataSizeInBytes = (drwav_uint32)chunkSize; - pMetadata->data.unknown.pData = (drwav_uint8 *)drwav__metadata_get_memory(pParser, (size_t)chunkSize, 1); - DRWAV_ASSERT(pMetadata->data.unknown.pData != NULL); - - bytesRead = drwav__metadata_parser_read(pParser, pMetadata->data.unknown.pData, pMetadata->data.unknown.dataSizeInBytes, NULL); - if (bytesRead == pMetadata->data.unknown.dataSizeInBytes) { - pParser->metadataCursor += 1; - } else { - /* Failed to read. */ - } - } - - return bytesRead; -} - -DRWAV_PRIVATE drwav_bool32 drwav__chunk_matches(drwav_metadata_type allowedMetadataTypes, const drwav_uint8* pChunkID, drwav_metadata_type type, const char* pID) -{ - return (allowedMetadataTypes & type) && drwav_fourcc_equal(pChunkID, pID); -} - -DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata_type allowedMetadataTypes) -{ - const drwav_uint8 *pChunkID = pChunkHeader->id.fourcc; - drwav_uint64 bytesRead = 0; - - if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_smpl, "smpl")) { - if (pChunkHeader->sizeInBytes >= DRWAV_SMPL_BYTES) { - if (pParser->stage == drwav__metadata_parser_stage_count) { - drwav_uint8 buffer[4]; - size_t bytesJustRead; - - if (!pParser->onSeek(pParser->pReadSeekUserData, 28, drwav_seek_origin_current)) { - return bytesRead; - } - bytesRead += 28; - - bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); - if (bytesJustRead == sizeof(buffer)) { - drwav_uint32 loopCount = drwav_bytes_to_u32(buffer); - drwav_uint64 calculatedLoopCount; - - /* The loop count must be validated against the size of the chunk. */ - calculatedLoopCount = (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES; - if (calculatedLoopCount == loopCount) { - bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); - if (bytesJustRead == sizeof(buffer)) { - drwav_uint32 samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(buffer); - - pParser->metadataCount += 1; - drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_smpl_loop) * loopCount, DRWAV_METADATA_ALIGNMENT); - drwav__metadata_request_extra_memory_for_stage_2(pParser, samplerSpecificDataSizeInBytes, 1); - } - } else { - /* Loop count in header does not match the size of the chunk. */ - } - } - } else { - bytesRead = drwav__read_smpl_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); - if (bytesRead == pChunkHeader->sizeInBytes) { - pParser->metadataCursor += 1; - } else { - /* Failed to parse. */ - } - } - } else { - /* Incorrectly formed chunk. */ - } - } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_inst, "inst")) { - if (pChunkHeader->sizeInBytes == DRWAV_INST_BYTES) { - if (pParser->stage == drwav__metadata_parser_stage_count) { - pParser->metadataCount += 1; - } else { - bytesRead = drwav__read_inst_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); - if (bytesRead == pChunkHeader->sizeInBytes) { - pParser->metadataCursor += 1; - } else { - /* Failed to parse. */ - } - } - } else { - /* Incorrectly formed chunk. */ - } - } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_acid, "acid")) { - if (pChunkHeader->sizeInBytes == DRWAV_ACID_BYTES) { - if (pParser->stage == drwav__metadata_parser_stage_count) { - pParser->metadataCount += 1; - } else { - bytesRead = drwav__read_acid_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); - if (bytesRead == pChunkHeader->sizeInBytes) { - pParser->metadataCursor += 1; - } else { - /* Failed to parse. */ - } - } - } else { - /* Incorrectly formed chunk. */ - } - } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_cue, "cue ")) { - if (pChunkHeader->sizeInBytes >= DRWAV_CUE_BYTES) { - if (pParser->stage == drwav__metadata_parser_stage_count) { - size_t cueCount; - - pParser->metadataCount += 1; - cueCount = (size_t)(pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES; - drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_cue_point) * cueCount, DRWAV_METADATA_ALIGNMENT); - } else { - bytesRead = drwav__read_cue_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); - if (bytesRead == pChunkHeader->sizeInBytes) { - pParser->metadataCursor += 1; - } else { - /* Failed to parse. */ - } - } - } else { - /* Incorrectly formed chunk. */ - } - } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_bext, "bext")) { - if (pChunkHeader->sizeInBytes >= DRWAV_BEXT_BYTES) { - if (pParser->stage == drwav__metadata_parser_stage_count) { - /* The description field is the largest one in a bext chunk, so that is the max size of this temporary buffer. */ - char buffer[DRWAV_BEXT_DESCRIPTION_BYTES + 1]; - size_t allocSizeNeeded = DRWAV_BEXT_UMID_BYTES; /* We know we will need SMPTE umid size. */ - size_t bytesJustRead; - - buffer[DRWAV_BEXT_DESCRIPTION_BYTES] = '\0'; - bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_DESCRIPTION_BYTES, &bytesRead); - if (bytesJustRead != DRWAV_BEXT_DESCRIPTION_BYTES) { - return bytesRead; - } - allocSizeNeeded += drwav__strlen(buffer) + 1; - - buffer[DRWAV_BEXT_ORIGINATOR_NAME_BYTES] = '\0'; - bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_NAME_BYTES, &bytesRead); - if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_NAME_BYTES) { - return bytesRead; - } - allocSizeNeeded += drwav__strlen(buffer) + 1; - - buffer[DRWAV_BEXT_ORIGINATOR_REF_BYTES] = '\0'; - bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_REF_BYTES, &bytesRead); - if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_REF_BYTES) { - return bytesRead; - } - allocSizeNeeded += drwav__strlen(buffer) + 1; - allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - DRWAV_BEXT_BYTES; /* Coding history. */ - - drwav__metadata_request_extra_memory_for_stage_2(pParser, allocSizeNeeded, 1); - - pParser->metadataCount += 1; - } else { - bytesRead = drwav__read_bext_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], pChunkHeader->sizeInBytes); - if (bytesRead == pChunkHeader->sizeInBytes) { - pParser->metadataCursor += 1; - } else { - /* Failed to parse. */ - } - } - } else { - /* Incorrectly formed chunk. */ - } - } else if (drwav_fourcc_equal(pChunkID, "LIST") || drwav_fourcc_equal(pChunkID, "list")) { - drwav_metadata_location listType = drwav_metadata_location_invalid; - while (bytesRead < pChunkHeader->sizeInBytes) { - drwav_uint8 subchunkId[4]; - drwav_uint8 subchunkSizeBuffer[4]; - drwav_uint64 subchunkDataSize; - drwav_uint64 subchunkBytesRead = 0; - drwav_uint64 bytesJustRead = drwav__metadata_parser_read(pParser, subchunkId, sizeof(subchunkId), &bytesRead); - if (bytesJustRead != sizeof(subchunkId)) { - break; - } - - /* - The first thing in a list chunk should be "adtl" or "INFO". - - - adtl means this list is a Associated Data List Chunk and will contain labels, notes - or labelled cue regions. - - INFO means this list is an Info List Chunk containing info text chunks such as IPRD - which would specifies the album of this wav file. - - No data follows the adtl or INFO id so we just make note of what type this list is and - continue. - */ - if (drwav_fourcc_equal(subchunkId, "adtl")) { - listType = drwav_metadata_location_inside_adtl_list; - continue; - } else if (drwav_fourcc_equal(subchunkId, "INFO")) { - listType = drwav_metadata_location_inside_info_list; - continue; - } - - bytesJustRead = drwav__metadata_parser_read(pParser, subchunkSizeBuffer, sizeof(subchunkSizeBuffer), &bytesRead); - if (bytesJustRead != sizeof(subchunkSizeBuffer)) { - break; - } - subchunkDataSize = drwav_bytes_to_u32(subchunkSizeBuffer); - - if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_label, "labl") || drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_note, "note")) { - if (subchunkDataSize >= DRWAV_LIST_LABEL_OR_NOTE_BYTES) { - drwav_uint64 stringSizeWithNullTerm = subchunkDataSize - DRWAV_LIST_LABEL_OR_NOTE_BYTES; - if (pParser->stage == drwav__metadata_parser_stage_count) { - pParser->metadataCount += 1; - drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)stringSizeWithNullTerm, 1); - } else { - subchunkBytesRead = drwav__read_list_label_or_note_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], subchunkDataSize, drwav_fourcc_equal(subchunkId, "labl") ? drwav_metadata_type_list_label : drwav_metadata_type_list_note); - if (subchunkBytesRead == subchunkDataSize) { - pParser->metadataCursor += 1; - } else { - /* Failed to parse. */ - } - } - } else { - /* Incorrectly formed chunk. */ - } - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_labelled_cue_region, "ltxt")) { - if (subchunkDataSize >= DRWAV_LIST_LABELLED_TEXT_BYTES) { - drwav_uint64 stringSizeWithNullTerminator = subchunkDataSize - DRWAV_LIST_LABELLED_TEXT_BYTES; - if (pParser->stage == drwav__metadata_parser_stage_count) { - pParser->metadataCount += 1; - drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)stringSizeWithNullTerminator, 1); - } else { - subchunkBytesRead = drwav__read_list_labelled_cue_region_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], subchunkDataSize); - if (subchunkBytesRead == subchunkDataSize) { - pParser->metadataCursor += 1; - } else { - /* Failed to parse. */ - } - } - } else { - /* Incorrectly formed chunk. */ - } - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_software, "ISFT")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_software); - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_copyright, "ICOP")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_copyright); - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_title, "INAM")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_title); - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_artist, "IART")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_artist); - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_comment, "ICMT")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_comment); - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_date, "ICRD")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_date); - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_genre, "IGNR")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_genre); - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_album, "IPRD")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_album); - } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_tracknumber, "ITRK")) { - subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_tracknumber); - } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { - subchunkBytesRead = drwav__metadata_process_unknown_chunk(pParser, subchunkId, subchunkDataSize, listType); - } - - bytesRead += subchunkBytesRead; - DRWAV_ASSERT(subchunkBytesRead <= subchunkDataSize); - - if (subchunkBytesRead < subchunkDataSize) { - drwav_uint64 bytesToSeek = subchunkDataSize - subchunkBytesRead; - - if (!pParser->onSeek(pParser->pReadSeekUserData, (int)bytesToSeek, drwav_seek_origin_current)) { - break; - } - bytesRead += bytesToSeek; - } - - if ((subchunkDataSize % 2) == 1) { - if (!pParser->onSeek(pParser->pReadSeekUserData, 1, drwav_seek_origin_current)) { - break; - } - bytesRead += 1; - } - } - } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { - bytesRead = drwav__metadata_process_unknown_chunk(pParser, pChunkID, pChunkHeader->sizeInBytes, drwav_metadata_location_top_level); - } - - return bytesRead; -} - - -DRWAV_PRIVATE drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) -{ - drwav_uint32 bytesPerFrame; - - /* - The bytes per frame is a bit ambiguous. It can be either be based on the bits per sample, or the block align. The way I'm doing it here - is that if the bits per sample is a multiple of 8, use floor(bitsPerSample*channels/8), otherwise fall back to the block align. - */ - if ((pWav->bitsPerSample & 0x7) == 0) { - /* Bits per sample is a multiple of 8. */ - bytesPerFrame = (pWav->bitsPerSample * pWav->fmt.channels) >> 3; - } else { - bytesPerFrame = pWav->fmt.blockAlign; - } - - /* Validation for known formats. a-law and mu-law should be 1 byte per channel. If it's not, it's not decodable. */ - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW || pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { - if (bytesPerFrame != pWav->fmt.channels) { - return 0; /* Invalid file. */ - } - } - - return bytesPerFrame; -} - -DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) -{ - if (pFMT == NULL) { - return 0; - } - - if (pFMT->formatTag != DR_WAVE_FORMAT_EXTENSIBLE) { - return pFMT->formatTag; - } else { - return drwav_bytes_to_u16(pFMT->subFormat); /* Only the first two bytes are required. */ - } -} - -DRWAV_PRIVATE drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pWav == NULL || onRead == NULL || onSeek == NULL) { - return DRWAV_FALSE; - } - - DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); - pWav->onRead = onRead; - pWav->onSeek = onSeek; - pWav->pUserData = pReadSeekUserData; - pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); - - if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { - return DRWAV_FALSE; /* Invalid allocation callbacks. */ - } - - return DRWAV_TRUE; -} - -#pragma stackfunction 200 -DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) -{ - /* This function assumes drwav_preinit() has been called beforehand. */ - - drwav_uint64 cursor; /* <-- Keeps track of the byte position so we can seek to specific locations. */ - drwav_bool32 sequential; - drwav_uint8 riff[4]; - drwav_fmt fmt; - unsigned short translatedFormatTag; - drwav_bool32 foundDataChunk; - drwav_uint64 dataChunkSize = 0; /* <-- Important! Don't explicitly set this to 0 anywhere else. Calculation of the size of the data chunk is performed in different paths depending on the container. */ - drwav_uint64 sampleCountFromFactChunk = 0; /* Same as dataChunkSize - make sure this is the only place this is initialized to 0. */ - drwav_uint64 chunkSize; - drwav__metadata_parser metadataParser; - - cursor = 0; - sequential = (flags & DRWAV_SEQUENTIAL) != 0; - - /* The first 4 bytes should be the RIFF identifier. */ - if (drwav__on_read(pWav->onRead, pWav->pUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { - return DRWAV_FALSE; - } - - /* - The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for - w64 it will start with "riff". - */ - if (drwav_fourcc_equal(riff, "RIFF")) { - pWav->container = drwav_container_riff; - } else if (drwav_fourcc_equal(riff, "riff")) { - int i; - drwav_uint8 riff2[12]; - - pWav->container = drwav_container_w64; - - /* Check the rest of the GUID for validity. */ - if (drwav__on_read(pWav->onRead, pWav->pUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) { - return DRWAV_FALSE; - } - - for (i = 0; i < 12; ++i) { - if (riff2[i] != drwavGUID_W64_RIFF[i+4]) { - return DRWAV_FALSE; - } - } - } else if (drwav_fourcc_equal(riff, "RF64")) { - pWav->container = drwav_container_rf64; - } else { - return DRWAV_FALSE; /* Unknown or unsupported container. */ - } - - - if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { - drwav_uint8 chunkSizeBytes[4]; - drwav_uint8 wave[4]; - - /* RIFF/WAVE */ - if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { - return DRWAV_FALSE; - } - - if (pWav->container == drwav_container_riff) { - if (drwav_bytes_to_u32(chunkSizeBytes) < 36) { - return DRWAV_FALSE; /* Chunk size should always be at least 36 bytes. */ - } - } else { - if (drwav_bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) { - return DRWAV_FALSE; /* Chunk size should always be set to -1/0xFFFFFFFF for RF64. The actual size is retrieved later. */ - } - } - - if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { - return DRWAV_FALSE; - } - - if (!drwav_fourcc_equal(wave, "WAVE")) { - return DRWAV_FALSE; /* Expecting "WAVE". */ - } - } else { - drwav_uint8 chunkSizeBytes[8]; - drwav_uint8 wave[16]; - - /* W64 */ - if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { - return DRWAV_FALSE; - } - - if (drwav_bytes_to_u64(chunkSizeBytes) < 80) { - return DRWAV_FALSE; - } - - if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { - return DRWAV_FALSE; - } - - if (!drwav_guid_equal(wave, drwavGUID_W64_WAVE)) { - return DRWAV_FALSE; - } - } - - - /* For RF64, the "ds64" chunk must come next, before the "fmt " chunk. */ - if (pWav->container == drwav_container_rf64) { - drwav_uint8 sizeBytes[8]; - drwav_uint64 bytesRemainingInChunk; - drwav_chunk_header header; - drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); - if (result != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - - if (!drwav_fourcc_equal(header.id.fourcc, "ds64")) { - return DRWAV_FALSE; /* Expecting "ds64". */ - } - - bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; - - /* We don't care about the size of the RIFF chunk - skip it. */ - if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { - return DRWAV_FALSE; - } - bytesRemainingInChunk -= 8; - cursor += 8; - - - /* Next 8 bytes is the size of the "data" chunk. */ - if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { - return DRWAV_FALSE; - } - bytesRemainingInChunk -= 8; - dataChunkSize = drwav_bytes_to_u64(sizeBytes); - - - /* Next 8 bytes is the same count which we would usually derived from the FACT chunk if it was available. */ - if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { - return DRWAV_FALSE; - } - bytesRemainingInChunk -= 8; - sampleCountFromFactChunk = drwav_bytes_to_u64(sizeBytes); - - - /* Skip over everything else. */ - if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { - return DRWAV_FALSE; - } - cursor += bytesRemainingInChunk; - } - - - /* The next bytes should be the "fmt " chunk. */ - if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) { - return DRWAV_FALSE; /* Failed to read the "fmt " chunk. */ - } - - /* Basic validation. */ - if ((fmt.sampleRate == 0 || fmt.sampleRate > DRWAV_MAX_SAMPLE_RATE) || - (fmt.channels == 0 || fmt.channels > DRWAV_MAX_CHANNELS) || - (fmt.bitsPerSample == 0 || fmt.bitsPerSample > DRWAV_MAX_BITS_PER_SAMPLE) || - fmt.blockAlign == 0) { - return DRWAV_FALSE; /* Probably an invalid WAV file. */ - } - - - /* Translate the internal format. */ - translatedFormatTag = fmt.formatTag; - if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { - translatedFormatTag = drwav_bytes_to_u16(fmt.subFormat + 0); - } - - DRWAV_ZERO_MEMORY(&metadataParser, sizeof(metadataParser)); - - /* Not tested on W64. */ - if (!sequential && pWav->allowedMetadataTypes != drwav_metadata_type_none && (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64)) { - drwav_uint64 cursorForMetadata = cursor; - - metadataParser.onRead = pWav->onRead; - metadataParser.onSeek = pWav->onSeek; - metadataParser.pReadSeekUserData = pWav->pUserData; - metadataParser.stage = drwav__metadata_parser_stage_count; - - for (;;) { - drwav_result result; - drwav_uint64 bytesRead; - drwav_uint64 remainingBytes; - drwav_chunk_header header; - - result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursorForMetadata, &header); - if (result != DRWAV_SUCCESS) { - break; - } - - bytesRead = drwav__metadata_process_chunk(&metadataParser, &header, pWav->allowedMetadataTypes); - DRWAV_ASSERT(bytesRead <= header.sizeInBytes); - - remainingBytes = header.sizeInBytes - bytesRead + header.paddingSize; - if (!drwav__seek_forward(pWav->onSeek, remainingBytes, pWav->pUserData)) { - break; - } - cursorForMetadata += remainingBytes; - } - - if (!drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData)) { - return DRWAV_FALSE; - } - - drwav__metadata_alloc(&metadataParser, &pWav->allocationCallbacks); - metadataParser.stage = drwav__metadata_parser_stage_read; - } - - /* - We need to enumerate over each chunk for two reasons: - 1) The "data" chunk may not be the next one - 2) We may want to report each chunk back to the client - - In order to correctly report each chunk back to the client we will need to keep looping until the end of the file. - */ - foundDataChunk = DRWAV_FALSE; - - /* The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop. */ - for (;;) { - drwav_chunk_header header; - drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); - if (result != DRWAV_SUCCESS) { - if (!foundDataChunk) { - return DRWAV_FALSE; - } else { - break; /* Probably at the end of the file. Get out of the loop. */ - } - } - - /* Tell the client about this chunk. */ - if (!sequential && onChunk != NULL) { - drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, pWav->onRead, pWav->onSeek, pWav->pUserData, &header, pWav->container, &fmt); - - /* - dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before - we called the callback. - */ - if (callbackBytesRead > 0) { - if (!drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData)) { - return DRWAV_FALSE; - } - } - } - - if (!sequential && pWav->allowedMetadataTypes != drwav_metadata_type_none && (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64)) { - drwav_uint64 bytesRead = drwav__metadata_process_chunk(&metadataParser, &header, pWav->allowedMetadataTypes); - - if (bytesRead > 0) { - if (!drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData)) { - return DRWAV_FALSE; - } - } - } - - - if (!foundDataChunk) { - pWav->dataChunkDataPos = cursor; - } - - chunkSize = header.sizeInBytes; - if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { - if (drwav_fourcc_equal(header.id.fourcc, "data")) { - foundDataChunk = DRWAV_TRUE; - if (pWav->container != drwav_container_rf64) { /* The data chunk size for RF64 will always be set to 0xFFFFFFFF here. It was set to it's true value earlier. */ - dataChunkSize = chunkSize; - } - } - } else { - if (drwav_guid_equal(header.id.guid, drwavGUID_W64_DATA)) { - foundDataChunk = DRWAV_TRUE; - dataChunkSize = chunkSize; - } - } - - /* - If at this point we have found the data chunk and we're running in sequential mode, we need to break out of this loop. The reason for - this is that we would otherwise require a backwards seek which sequential mode forbids. - */ - if (foundDataChunk && sequential) { - break; - } - - /* Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats. */ - if (pWav->container == drwav_container_riff) { - if (drwav_fourcc_equal(header.id.fourcc, "fact")) { - drwav_uint32 sampleCount; - if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) { - return DRWAV_FALSE; - } - chunkSize -= 4; - - if (!foundDataChunk) { - pWav->dataChunkDataPos = cursor; - } - - /* - The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this - for Microsoft ADPCM formats. - */ - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - sampleCountFromFactChunk = sampleCount; - } else { - sampleCountFromFactChunk = 0; - } - } - } else if (pWav->container == drwav_container_w64) { - if (drwav_guid_equal(header.id.guid, drwavGUID_W64_FACT)) { - if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { - return DRWAV_FALSE; - } - chunkSize -= 8; - - if (!foundDataChunk) { - pWav->dataChunkDataPos = cursor; - } - } - } else if (pWav->container == drwav_container_rf64) { - /* We retrieved the sample count from the ds64 chunk earlier so no need to do that here. */ - } - - /* Make sure we seek past the padding. */ - chunkSize += header.paddingSize; - if (!drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData)) { - break; - } - cursor += chunkSize; - - if (!foundDataChunk) { - pWav->dataChunkDataPos = cursor; - } - } - - pWav->pMetadata = metadataParser.pMetadata; - pWav->metadataCount = metadataParser.metadataCount; - - /* If we haven't found a data chunk, return an error. */ - if (!foundDataChunk) { - return DRWAV_FALSE; - } - - /* We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk. */ - if (!sequential) { - if (!drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData)) { - return DRWAV_FALSE; - } - cursor = pWav->dataChunkDataPos; - } - - - /* At this point we should be sitting on the first byte of the raw audio data. */ - - pWav->fmt = fmt; - pWav->sampleRate = fmt.sampleRate; - pWav->channels = fmt.channels; - pWav->bitsPerSample = fmt.bitsPerSample; - pWav->bytesRemaining = dataChunkSize; - pWav->translatedFormatTag = translatedFormatTag; - pWav->dataChunkDataSize = dataChunkSize; - - if (sampleCountFromFactChunk != 0) { - pWav->totalPCMFrameCount = sampleCountFromFactChunk; - } else { - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return DRWAV_FALSE; /* Invalid file. */ - } - - pWav->totalPCMFrameCount = dataChunkSize / bytesPerFrame; - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - drwav_uint64 totalBlockHeaderSizeInBytes; - drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; - - /* Make sure any trailing partial block is accounted for. */ - if ((blockCount * fmt.blockAlign) < dataChunkSize) { - blockCount += 1; - } - - /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ - totalBlockHeaderSizeInBytes = blockCount * (6*fmt.channels); - pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; - } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - drwav_uint64 totalBlockHeaderSizeInBytes; - drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; - - /* Make sure any trailing partial block is accounted for. */ - if ((blockCount * fmt.blockAlign) < dataChunkSize) { - blockCount += 1; - } - - /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ - totalBlockHeaderSizeInBytes = blockCount * (4*fmt.channels); - pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; - - /* The header includes a decoded sample for each channel which acts as the initial predictor sample. */ - pWav->totalPCMFrameCount += blockCount; - } - } - - /* Some formats only support a certain number of channels. */ - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - if (pWav->channels > 2) { - return DRWAV_FALSE; - } - } - - /* The number of bytes per frame must be known. If not, it's an invalid file and not decodable. */ - if (drwav_get_bytes_per_pcm_frame(pWav) == 0) { - return DRWAV_FALSE; - } - -#ifdef DR_WAV_LIBSNDFILE_COMPAT - /* - I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), - it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count - from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct - way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should - always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my - correctness tests against libsndfile, and is disabled by default. - */ - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; - pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; /* x2 because two samples per byte. */ - } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; - pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; - } -#endif - - return DRWAV_TRUE; -} - -DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (!drwav_preinit(pWav, onRead, onSeek, pReadSeekUserData, pAllocationCallbacks)) { - return DRWAV_FALSE; - } - - return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); -} - -DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (!drwav_preinit(pWav, onRead, onSeek, pUserData, pAllocationCallbacks)) { - return DRWAV_FALSE; - } - - pWav->allowedMetadataTypes = drwav_metadata_type_all_including_unknown; /* <-- Needs to be set to tell drwav_init_ex() that we need to process metadata. */ - return drwav_init__internal(pWav, NULL, NULL, flags); -} - -DRWAV_API drwav_metadata* drwav_take_ownership_of_metadata(drwav* pWav) -{ - drwav_metadata *result = pWav->pMetadata; - - pWav->pMetadata = NULL; - pWav->metadataCount = 0; - - return result; -} - - -DRWAV_PRIVATE size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) -{ - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(pWav->onWrite != NULL); - - /* Generic write. Assumes no byte reordering required. */ - return pWav->onWrite(pWav->pUserData, pData, dataSize); -} - -DRWAV_PRIVATE size_t drwav__write_byte(drwav* pWav, drwav_uint8 byte) -{ - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(pWav->onWrite != NULL); - - return pWav->onWrite(pWav->pUserData, &byte, 1); -} - -DRWAV_PRIVATE size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) -{ - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(pWav->onWrite != NULL); - - if (!drwav__is_little_endian()) { - value = drwav__bswap16(value); - } - - return drwav__write(pWav, &value, 2); -} - -DRWAV_PRIVATE size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) -{ - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(pWav->onWrite != NULL); - - if (!drwav__is_little_endian()) { - value = drwav__bswap32(value); - } - - return drwav__write(pWav, &value, 4); -} - -DRWAV_PRIVATE size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) -{ - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(pWav->onWrite != NULL); - - if (!drwav__is_little_endian()) { - value = drwav__bswap64(value); - } - - return drwav__write(pWav, &value, 8); -} - -DRWAV_PRIVATE size_t drwav__write_f32ne_to_le(drwav* pWav, float value) -{ - union { - drwav_uint32 u32; - float f32; - } u; - - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(pWav->onWrite != NULL); - - u.f32 = value; - - if (!drwav__is_little_endian()) { - u.u32 = drwav__bswap32(u.u32); - } - - return drwav__write(pWav, &u.u32, 4); -} - -DRWAV_PRIVATE size_t drwav__write_or_count(drwav* pWav, const void* pData, size_t dataSize) -{ - if (pWav == NULL) { - return dataSize; - } - - return drwav__write(pWav, pData, dataSize); -} - -DRWAV_PRIVATE size_t drwav__write_or_count_byte(drwav* pWav, drwav_uint8 byte) -{ - if (pWav == NULL) { - return 1; - } - - return drwav__write_byte(pWav, byte); -} - -DRWAV_PRIVATE size_t drwav__write_or_count_u16ne_to_le(drwav* pWav, drwav_uint16 value) -{ - if (pWav == NULL) { - return 2; - } - - return drwav__write_u16ne_to_le(pWav, value); -} - -DRWAV_PRIVATE size_t drwav__write_or_count_u32ne_to_le(drwav* pWav, drwav_uint32 value) -{ - if (pWav == NULL) { - return 4; - } - - return drwav__write_u32ne_to_le(pWav, value); -} - -#if 0 /* Unused for now. */ -DRWAV_PRIVATE size_t drwav__write_or_count_u64ne_to_le(drwav* pWav, drwav_uint64 value) -{ - if (pWav == NULL) { - return 8; - } - - return drwav__write_u64ne_to_le(pWav, value); -} -#endif - -DRWAV_PRIVATE size_t drwav__write_or_count_f32ne_to_le(drwav* pWav, float value) -{ - if (pWav == NULL) { - return 4; - } - - return drwav__write_f32ne_to_le(pWav, value); -} - -DRWAV_PRIVATE size_t drwav__write_or_count_string_to_fixed_size_buf(drwav* pWav, char* str, size_t bufFixedSize) -{ - size_t len; - - if (pWav == NULL) { - return bufFixedSize; - } - - len = drwav__strlen_clamped(str, bufFixedSize); - drwav__write_or_count(pWav, str, len); - - if (len < bufFixedSize) { - size_t i; - for (i = 0; i < bufFixedSize - len; ++i) { - drwav__write_byte(pWav, 0); - } - } - - return bufFixedSize; -} - - -/* pWav can be NULL meaning just count the bytes that would be written. */ -DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* pMetadatas, drwav_uint32 metadataCount) -{ - size_t bytesWritten = 0; - drwav_bool32 hasListAdtl = DRWAV_FALSE; - drwav_bool32 hasListInfo = DRWAV_FALSE; - drwav_uint32 iMetadata; - - if (pMetadatas == NULL || metadataCount == 0) { - return 0; - } - - for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { - drwav_metadata* pMetadata = &pMetadatas[iMetadata]; - drwav_uint32 chunkSize = 0; - - if ((pMetadata->type & drwav_metadata_type_list_all_info_strings) || (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list)) { - hasListInfo = DRWAV_TRUE; - } - - if ((pMetadata->type & drwav_metadata_type_list_all_adtl) || (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list)) { - hasListAdtl = DRWAV_TRUE; - } - - switch (pMetadata->type) { - case drwav_metadata_type_smpl: - { - drwav_uint32 iLoop; - - chunkSize = DRWAV_SMPL_BYTES + DRWAV_SMPL_LOOP_BYTES * pMetadata->data.smpl.sampleLoopCount + pMetadata->data.smpl.samplerSpecificDataSizeInBytes; - - bytesWritten += drwav__write_or_count(pWav, "smpl", 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); - - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.manufacturerId); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.productId); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.samplePeriodNanoseconds); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.midiUnityNote); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.midiPitchFraction); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.smpteFormat); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.smpteOffset); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.sampleLoopCount); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.samplerSpecificDataSizeInBytes); - - for (iLoop = 0; iLoop < pMetadata->data.smpl.sampleLoopCount; ++iLoop) { - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].cuePointId); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].type); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].firstSampleByteOffset); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].lastSampleByteOffset); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].sampleFraction); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].playCount); - } - - if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { - bytesWritten += drwav__write(pWav, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes); - } - } break; - - case drwav_metadata_type_inst: - { - chunkSize = DRWAV_INST_BYTES; - - bytesWritten += drwav__write_or_count(pWav, "inst", 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); - bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.midiUnityNote, 1); - bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.fineTuneCents, 1); - bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.gainDecibels, 1); - bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.lowNote, 1); - bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.highNote, 1); - bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.lowVelocity, 1); - bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.highVelocity, 1); - } break; - - case drwav_metadata_type_cue: - { - drwav_uint32 iCuePoint; - - chunkSize = DRWAV_CUE_BYTES + DRWAV_CUE_POINT_BYTES * pMetadata->data.cue.cuePointCount; - - bytesWritten += drwav__write_or_count(pWav, "cue ", 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.cuePointCount); - for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].id); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId, 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].blockStart); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset); - } - } break; - - case drwav_metadata_type_acid: - { - chunkSize = DRWAV_ACID_BYTES; - - bytesWritten += drwav__write_or_count(pWav, "acid", 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.acid.flags); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.midiUnityNote); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.reserved1); - bytesWritten += drwav__write_or_count_f32ne_to_le(pWav, pMetadata->data.acid.reserved2); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.acid.numBeats); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.meterDenominator); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.meterNumerator); - bytesWritten += drwav__write_or_count_f32ne_to_le(pWav, pMetadata->data.acid.tempo); - } break; - - case drwav_metadata_type_bext: - { - char reservedBuf[DRWAV_BEXT_RESERVED_BYTES]; - drwav_uint32 timeReferenceLow; - drwav_uint32 timeReferenceHigh; - - chunkSize = DRWAV_BEXT_BYTES + pMetadata->data.bext.codingHistorySize; - - bytesWritten += drwav__write_or_count(pWav, "bext", 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); - - bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pDescription, DRWAV_BEXT_DESCRIPTION_BYTES); - bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pOriginatorName, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); - bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pOriginatorReference, DRWAV_BEXT_ORIGINATOR_REF_BYTES); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate)); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime)); - - timeReferenceLow = (drwav_uint32)(pMetadata->data.bext.timeReference & 0xFFFFFFFF); - timeReferenceHigh = (drwav_uint32)(pMetadata->data.bext.timeReference >> 32); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, timeReferenceLow); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, timeReferenceHigh); - - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.version); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.loudnessValue); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.loudnessRange); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxTruePeakLevel); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxMomentaryLoudness); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxShortTermLoudness); - - DRWAV_ZERO_MEMORY(reservedBuf, sizeof(reservedBuf)); - bytesWritten += drwav__write_or_count(pWav, reservedBuf, sizeof(reservedBuf)); - - if (pMetadata->data.bext.codingHistorySize > 0) { - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pCodingHistory, pMetadata->data.bext.codingHistorySize); - } - } break; - - case drwav_metadata_type_unknown: - { - if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_top_level) { - chunkSize = pMetadata->data.unknown.dataSizeInBytes; - - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, pMetadata->data.unknown.dataSizeInBytes); - } - } break; - - default: break; - } - if ((chunkSize % 2) != 0) { - bytesWritten += drwav__write_or_count_byte(pWav, 0); - } - } - - if (hasListInfo) { - drwav_uint32 chunkSize = 4; /* Start with 4 bytes for "INFO". */ - for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { - drwav_metadata* pMetadata = &pMetadatas[iMetadata]; - - if ((pMetadata->type & drwav_metadata_type_list_all_info_strings)) { - chunkSize += 8; /* For id and string size. */ - chunkSize += pMetadata->data.infoText.stringLength + 1; /* Include null terminator. */ - } else if (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list) { - chunkSize += 8; /* For id string size. */ - chunkSize += pMetadata->data.unknown.dataSizeInBytes; - } - - if ((chunkSize % 2) != 0) { - chunkSize += 1; - } - } - - bytesWritten += drwav__write_or_count(pWav, "LIST", 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); - bytesWritten += drwav__write_or_count(pWav, "INFO", 4); - - for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { - drwav_metadata* pMetadata = &pMetadatas[iMetadata]; - drwav_uint32 subchunkSize = 0; - - if (pMetadata->type & drwav_metadata_type_list_all_info_strings) { - const char* pID = NULL; - - switch (pMetadata->type) { - case drwav_metadata_type_list_info_software: pID = "ISFT"; break; - case drwav_metadata_type_list_info_copyright: pID = "ICOP"; break; - case drwav_metadata_type_list_info_title: pID = "INAM"; break; - case drwav_metadata_type_list_info_artist: pID = "IART"; break; - case drwav_metadata_type_list_info_comment: pID = "ICMT"; break; - case drwav_metadata_type_list_info_date: pID = "ICRD"; break; - case drwav_metadata_type_list_info_genre: pID = "IGNR"; break; - case drwav_metadata_type_list_info_album: pID = "IPRD"; break; - case drwav_metadata_type_list_info_tracknumber: pID = "ITRK"; break; - default: break; - } - - DRWAV_ASSERT(pID != NULL); - - if (pMetadata->data.infoText.stringLength) { - subchunkSize = pMetadata->data.infoText.stringLength + 1; - bytesWritten += drwav__write_or_count(pWav, pID, 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.infoText.pString, pMetadata->data.infoText.stringLength); - bytesWritten += drwav__write_or_count_byte(pWav, '\0'); - } - } else if (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list) { - if (pMetadata->data.unknown.dataSizeInBytes) { - subchunkSize = pMetadata->data.unknown.dataSizeInBytes; - - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.unknown.dataSizeInBytes); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, subchunkSize); - } - } - - if ((subchunkSize % 2) != 0) { - bytesWritten += drwav__write_or_count_byte(pWav, 0); - } - } - } - - if (hasListAdtl) { - drwav_uint32 chunkSize = 4; /* start with 4 bytes for "adtl" */ - - for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { - drwav_metadata* pMetadata = &pMetadatas[iMetadata]; - - switch (pMetadata->type) - { - case drwav_metadata_type_list_label: - case drwav_metadata_type_list_note: - { - chunkSize += 8; /* for id and chunk size */ - chunkSize += DRWAV_LIST_LABEL_OR_NOTE_BYTES; - - if (pMetadata->data.labelOrNote.stringLength > 0) { - chunkSize += pMetadata->data.labelOrNote.stringLength + 1; - } - } break; - - case drwav_metadata_type_list_labelled_cue_region: - { - chunkSize += 8; /* for id and chunk size */ - chunkSize += DRWAV_LIST_LABELLED_TEXT_BYTES; - - if (pMetadata->data.labelledCueRegion.stringLength > 0) { - chunkSize += pMetadata->data.labelledCueRegion.stringLength + 1; - } - } break; - - case drwav_metadata_type_unknown: - { - if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list) { - chunkSize += 8; /* for id and chunk size */ - chunkSize += pMetadata->data.unknown.dataSizeInBytes; - } - } break; - - default: break; - } - - if ((chunkSize % 2) != 0) { - chunkSize += 1; - } - } - - bytesWritten += drwav__write_or_count(pWav, "LIST", 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); - bytesWritten += drwav__write_or_count(pWav, "adtl", 4); - - for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { - drwav_metadata* pMetadata = &pMetadatas[iMetadata]; - drwav_uint32 subchunkSize = 0; - - switch (pMetadata->type) - { - case drwav_metadata_type_list_label: - case drwav_metadata_type_list_note: - { - if (pMetadata->data.labelOrNote.stringLength > 0) { - const char *pID = NULL; - - if (pMetadata->type == drwav_metadata_type_list_label) { - pID = "labl"; - } - else if (pMetadata->type == drwav_metadata_type_list_note) { - pID = "note"; - } - - DRWAV_ASSERT(pID != NULL); - DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); - - subchunkSize = DRWAV_LIST_LABEL_OR_NOTE_BYTES; - - bytesWritten += drwav__write_or_count(pWav, pID, 4); - subchunkSize += pMetadata->data.labelOrNote.stringLength + 1; - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); - - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelOrNote.cuePointId); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelOrNote.pString, pMetadata->data.labelOrNote.stringLength); - bytesWritten += drwav__write_or_count_byte(pWav, '\0'); - } - } break; - - case drwav_metadata_type_list_labelled_cue_region: - { - subchunkSize = DRWAV_LIST_LABELLED_TEXT_BYTES; - - bytesWritten += drwav__write_or_count(pWav, "ltxt", 4); - if (pMetadata->data.labelledCueRegion.stringLength > 0) { - subchunkSize += pMetadata->data.labelledCueRegion.stringLength + 1; - } - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelledCueRegion.cuePointId); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelledCueRegion.sampleLength); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelledCueRegion.purposeId, 4); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.country); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.language); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.dialect); - bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.codePage); - - if (pMetadata->data.labelledCueRegion.stringLength > 0) { - DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); - - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelledCueRegion.pString, pMetadata->data.labelledCueRegion.stringLength); - bytesWritten += drwav__write_or_count_byte(pWav, '\0'); - } - } break; - - case drwav_metadata_type_unknown: - { - if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list) { - subchunkSize = pMetadata->data.unknown.dataSizeInBytes; - - DRWAV_ASSERT(pMetadata->data.unknown.pData != NULL); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); - bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); - bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, subchunkSize); - } - } break; - - default: break; - } - - if ((subchunkSize % 2) != 0) { - bytesWritten += drwav__write_or_count_byte(pWav, 0); - } - } - } - - DRWAV_ASSERT((bytesWritten % 2) == 0); - - return bytesWritten; -} - -DRWAV_PRIVATE drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize, drwav_metadata* pMetadata, drwav_uint32 metadataCount) -{ - drwav_uint64 chunkSize = 4 + 24 + (drwav_uint64)drwav__write_or_count_metadata(NULL, pMetadata, metadataCount) + 8 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 24 = "fmt " chunk. 8 = "data" + u32 data size. */ - if (chunkSize > 0xFFFFFFFFUL) { - chunkSize = 0xFFFFFFFFUL; - } - - return (drwav_uint32)chunkSize; /* Safe cast due to the clamp above. */ -} - -DRWAV_PRIVATE drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) -{ - if (dataChunkSize <= 0xFFFFFFFFUL) { - return (drwav_uint32)dataChunkSize; - } else { - return 0xFFFFFFFFUL; - } -} - -DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) -{ - drwav_uint64 dataSubchunkPaddingSize = drwav__chunk_padding_size_w64(dataChunkSize); - - return 80 + 24 + dataChunkSize + dataSubchunkPaddingSize; /* +24 because W64 includes the size of the GUID and size fields. */ -} - -DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) -{ - return 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ -} - -DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize, drwav_metadata *metadata, drwav_uint32 numMetadata) -{ - drwav_uint64 chunkSize = 4 + 36 + 24 + (drwav_uint64)drwav__write_or_count_metadata(NULL, metadata, numMetadata) + 8 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 36 = "ds64" chunk. 24 = "fmt " chunk. 8 = "data" + u32 data size. */ - if (chunkSize > 0xFFFFFFFFUL) { - chunkSize = 0xFFFFFFFFUL; - } - - return chunkSize; -} - -DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) -{ - return dataChunkSize; -} - - - -DRWAV_PRIVATE drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pWav == NULL || onWrite == NULL) { - return DRWAV_FALSE; - } - - if (!isSequential && onSeek == NULL) { - return DRWAV_FALSE; /* <-- onSeek is required when in non-sequential mode. */ - } - - /* Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. */ - if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) { - return DRWAV_FALSE; - } - if (pFormat->format == DR_WAVE_FORMAT_ADPCM || pFormat->format == DR_WAVE_FORMAT_DVI_ADPCM) { - return DRWAV_FALSE; - } - - DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); - pWav->onWrite = onWrite; - pWav->onSeek = onSeek; - pWav->pUserData = pUserData; - pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); - - if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { - return DRWAV_FALSE; /* Invalid allocation callbacks. */ - } - - pWav->fmt.formatTag = (drwav_uint16)pFormat->format; - pWav->fmt.channels = (drwav_uint16)pFormat->channels; - pWav->fmt.sampleRate = pFormat->sampleRate; - pWav->fmt.avgBytesPerSec = (drwav_uint32)((pFormat->bitsPerSample * pFormat->sampleRate * pFormat->channels) / 8); - pWav->fmt.blockAlign = (drwav_uint16)((pFormat->channels * pFormat->bitsPerSample) / 8); - pWav->fmt.bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; - pWav->fmt.extendedSize = 0; - pWav->isSequentialWrite = isSequential; - - return DRWAV_TRUE; -} - - -DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) -{ - /* The function assumes drwav_preinit_write() was called beforehand. */ - - size_t runningPos = 0; - drwav_uint64 initialDataChunkSize = 0; - drwav_uint64 chunkSizeFMT; - - /* - The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In - sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- - sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. - */ - if (pWav->isSequentialWrite) { - initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8; - - /* - The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 - so for the sake of simplicity I'm not doing any validation for that. - */ - if (pFormat->container == drwav_container_riff) { - if (initialDataChunkSize > (0xFFFFFFFFUL - 36)) { - return DRWAV_FALSE; /* Not enough room to store every sample. */ - } - } - } - - pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; - - - /* "RIFF" chunk. */ - if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; /* +28 = "WAVE" + [sizeof "fmt " chunk] */ - runningPos += drwav__write(pWav, "RIFF", 4); - runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); - runningPos += drwav__write(pWav, "WAVE", 4); - } else if (pFormat->container == drwav_container_w64) { - drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ - runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); - runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); - runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); - } else if (pFormat->container == drwav_container_rf64) { - runningPos += drwav__write(pWav, "RF64", 4); - runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always 0xFFFFFFFF for RF64. Set to a proper value in the "ds64" chunk. */ - runningPos += drwav__write(pWav, "WAVE", 4); - } - - - /* "ds64" chunk (RF64 only). */ - if (pFormat->container == drwav_container_rf64) { - drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */ - drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; /* +8 for the ds64 header. */ - - runningPos += drwav__write(pWav, "ds64", 4); - runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); /* Size of ds64. */ - runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); /* Size of RIFF. Set to true value at the end. */ - runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); /* Size of DATA. Set to true value at the end. */ - runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); /* Sample count. */ - runningPos += drwav__write_u32ne_to_le(pWav, 0); /* Table length. Always set to zero in our case since we're not doing any other chunks than "DATA". */ - } - - - /* "fmt " chunk. */ - if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { - chunkSizeFMT = 16; - runningPos += drwav__write(pWav, "fmt ", 4); - runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); - } else if (pFormat->container == drwav_container_w64) { - chunkSizeFMT = 40; - runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); - runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); - } - - runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.formatTag); - runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.channels); - runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.sampleRate); - runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec); - runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign); - runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample); - - /* TODO: is a 'fact' chunk required for DR_WAVE_FORMAT_IEEE_FLOAT? */ - - if (!pWav->isSequentialWrite && pWav->pMetadata != NULL && pWav->metadataCount > 0 && (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64)) { - runningPos += drwav__write_or_count_metadata(pWav, pWav->pMetadata, pWav->metadataCount); - } - - pWav->dataChunkDataPos = runningPos; - - /* "data" chunk. */ - if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; - runningPos += drwav__write(pWav, "data", 4); - runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); - } else if (pFormat->container == drwav_container_w64) { - drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ - runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); - runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); - } else if (pFormat->container == drwav_container_rf64) { - runningPos += drwav__write(pWav, "data", 4); - runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always set to 0xFFFFFFFF for RF64. The true size of the data chunk is specified in the ds64 chunk. */ - } - - /* Set some properties for the client's convenience. */ - pWav->container = pFormat->container; - pWav->channels = (drwav_uint16)pFormat->channels; - pWav->sampleRate = pFormat->sampleRate; - pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; - pWav->translatedFormatTag = (drwav_uint16)pFormat->format; - pWav->dataChunkDataPos = runningPos; - - return DRWAV_TRUE; -} - - -DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { - return DRWAV_FALSE; - } - - return drwav_init_write__internal(pWav, pFormat, 0); /* DRWAV_FALSE = Not Sequential */ -} - -DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (!drwav_preinit_write(pWav, pFormat, DRWAV_TRUE, onWrite, NULL, pUserData, pAllocationCallbacks)) { - return DRWAV_FALSE; - } - - return drwav_init_write__internal(pWav, pFormat, totalSampleCount); /* DRWAV_TRUE = Sequential */ -} - -DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pFormat == NULL) { - return DRWAV_FALSE; - } - - return drwav_init_write_sequential(pWav, pFormat, totalPCMFrameCount*pFormat->channels, onWrite, pUserData, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_write_with_metadata(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks, drwav_metadata* pMetadata, drwav_uint32 metadataCount) -{ - if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { - return DRWAV_FALSE; - } - - pWav->pMetadata = pMetadata; - pWav->metadataCount = metadataCount; - - return drwav_init_write__internal(pWav, pFormat, 0); -} - - -DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalFrameCount, drwav_metadata* pMetadata, drwav_uint32 metadataCount) -{ - /* Casting totalFrameCount to drwav_int64 for VC6 compatibility. No issues in practice because nobody is going to exhaust the whole 63 bits. */ - drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalFrameCount * pFormat->channels * pFormat->bitsPerSample/8.0); - drwav_uint64 riffChunkSizeBytes; - drwav_uint64 fileSizeBytes = 0; - - if (pFormat->container == drwav_container_riff) { - riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes, pMetadata, metadataCount); - fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ - } else if (pFormat->container == drwav_container_w64) { - riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); - fileSizeBytes = riffChunkSizeBytes; - } else if (pFormat->container == drwav_container_rf64) { - riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes, pMetadata, metadataCount); - fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ - } - - return fileSizeBytes; -} - - -#ifndef DR_WAV_NO_STDIO - -/* drwav_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ -#include -DRWAV_PRIVATE drwav_result drwav_result_from_errno(int e) -{ - switch (e) - { - case 0: return DRWAV_SUCCESS; - #ifdef EPERM - case EPERM: return DRWAV_INVALID_OPERATION; - #endif - #ifdef ENOENT - case ENOENT: return DRWAV_DOES_NOT_EXIST; - #endif - #ifdef ESRCH - case ESRCH: return DRWAV_DOES_NOT_EXIST; - #endif - #ifdef EINTR - case EINTR: return DRWAV_INTERRUPT; - #endif - #ifdef EIO - case EIO: return DRWAV_IO_ERROR; - #endif - #ifdef ENXIO - case ENXIO: return DRWAV_DOES_NOT_EXIST; - #endif - #ifdef E2BIG - case E2BIG: return DRWAV_INVALID_ARGS; - #endif - #ifdef ENOEXEC - case ENOEXEC: return DRWAV_INVALID_FILE; - #endif - #ifdef EBADF - case EBADF: return DRWAV_INVALID_FILE; - #endif - #ifdef ECHILD - case ECHILD: return DRWAV_ERROR; - #endif - #ifdef EAGAIN - case EAGAIN: return DRWAV_UNAVAILABLE; - #endif - #ifdef ENOMEM - case ENOMEM: return DRWAV_OUT_OF_MEMORY; - #endif - #ifdef EACCES - case EACCES: return DRWAV_ACCESS_DENIED; - #endif - #ifdef EFAULT - case EFAULT: return DRWAV_BAD_ADDRESS; - #endif - #ifdef ENOTBLK - case ENOTBLK: return DRWAV_ERROR; - #endif - #ifdef EBUSY - case EBUSY: return DRWAV_BUSY; - #endif - #ifdef EEXIST - case EEXIST: return DRWAV_ALREADY_EXISTS; - #endif - #ifdef EXDEV - case EXDEV: return DRWAV_ERROR; - #endif - #ifdef ENODEV - case ENODEV: return DRWAV_DOES_NOT_EXIST; - #endif - #ifdef ENOTDIR - case ENOTDIR: return DRWAV_NOT_DIRECTORY; - #endif - #ifdef EISDIR - case EISDIR: return DRWAV_IS_DIRECTORY; - #endif - #ifdef EINVAL - case EINVAL: return DRWAV_INVALID_ARGS; - #endif - #ifdef ENFILE - case ENFILE: return DRWAV_TOO_MANY_OPEN_FILES; - #endif - #ifdef EMFILE - case EMFILE: return DRWAV_TOO_MANY_OPEN_FILES; - #endif - #ifdef ENOTTY - case ENOTTY: return DRWAV_INVALID_OPERATION; - #endif - #ifdef ETXTBSY - case ETXTBSY: return DRWAV_BUSY; - #endif - #ifdef EFBIG - case EFBIG: return DRWAV_TOO_BIG; - #endif - #ifdef ENOSPC - case ENOSPC: return DRWAV_NO_SPACE; - #endif - #ifdef ESPIPE - case ESPIPE: return DRWAV_BAD_SEEK; - #endif - #ifdef EROFS - case EROFS: return DRWAV_ACCESS_DENIED; - #endif - #ifdef EMLINK - case EMLINK: return DRWAV_TOO_MANY_LINKS; - #endif - #ifdef EPIPE - case EPIPE: return DRWAV_BAD_PIPE; - #endif - #ifdef EDOM - case EDOM: return DRWAV_OUT_OF_RANGE; - #endif - #ifdef ERANGE - case ERANGE: return DRWAV_OUT_OF_RANGE; - #endif - #ifdef EDEADLK - case EDEADLK: return DRWAV_DEADLOCK; - #endif - #ifdef ENAMETOOLONG - case ENAMETOOLONG: return DRWAV_PATH_TOO_LONG; - #endif - #ifdef ENOLCK - case ENOLCK: return DRWAV_ERROR; - #endif - #ifdef ENOSYS - case ENOSYS: return DRWAV_NOT_IMPLEMENTED; - #endif - #ifdef ENOTEMPTY - case ENOTEMPTY: return DRWAV_DIRECTORY_NOT_EMPTY; - #endif - #ifdef ELOOP - case ELOOP: return DRWAV_TOO_MANY_LINKS; - #endif - #ifdef ENOMSG - case ENOMSG: return DRWAV_NO_MESSAGE; - #endif - #ifdef EIDRM - case EIDRM: return DRWAV_ERROR; - #endif - #ifdef ECHRNG - case ECHRNG: return DRWAV_ERROR; - #endif - #ifdef EL2NSYNC - case EL2NSYNC: return DRWAV_ERROR; - #endif - #ifdef EL3HLT - case EL3HLT: return DRWAV_ERROR; - #endif - #ifdef EL3RST - case EL3RST: return DRWAV_ERROR; - #endif - #ifdef ELNRNG - case ELNRNG: return DRWAV_OUT_OF_RANGE; - #endif - #ifdef EUNATCH - case EUNATCH: return DRWAV_ERROR; - #endif - #ifdef ENOCSI - case ENOCSI: return DRWAV_ERROR; - #endif - #ifdef EL2HLT - case EL2HLT: return DRWAV_ERROR; - #endif - #ifdef EBADE - case EBADE: return DRWAV_ERROR; - #endif - #ifdef EBADR - case EBADR: return DRWAV_ERROR; - #endif - #ifdef EXFULL - case EXFULL: return DRWAV_ERROR; - #endif - #ifdef ENOANO - case ENOANO: return DRWAV_ERROR; - #endif - #ifdef EBADRQC - case EBADRQC: return DRWAV_ERROR; - #endif - #ifdef EBADSLT - case EBADSLT: return DRWAV_ERROR; - #endif - #ifdef EBFONT - case EBFONT: return DRWAV_INVALID_FILE; - #endif - #ifdef ENOSTR - case ENOSTR: return DRWAV_ERROR; - #endif - #ifdef ENODATA - case ENODATA: return DRWAV_NO_DATA_AVAILABLE; - #endif - #ifdef ETIME - case ETIME: return DRWAV_TIMEOUT; - #endif - #ifdef ENOSR - case ENOSR: return DRWAV_NO_DATA_AVAILABLE; - #endif - #ifdef ENONET - case ENONET: return DRWAV_NO_NETWORK; - #endif - #ifdef ENOPKG - case ENOPKG: return DRWAV_ERROR; - #endif - #ifdef EREMOTE - case EREMOTE: return DRWAV_ERROR; - #endif - #ifdef ENOLINK - case ENOLINK: return DRWAV_ERROR; - #endif - #ifdef EADV - case EADV: return DRWAV_ERROR; - #endif - #ifdef ESRMNT - case ESRMNT: return DRWAV_ERROR; - #endif - #ifdef ECOMM - case ECOMM: return DRWAV_ERROR; - #endif - #ifdef EPROTO - case EPROTO: return DRWAV_ERROR; - #endif - #ifdef EMULTIHOP - case EMULTIHOP: return DRWAV_ERROR; - #endif - #ifdef EDOTDOT - case EDOTDOT: return DRWAV_ERROR; - #endif - #ifdef EBADMSG - case EBADMSG: return DRWAV_BAD_MESSAGE; - #endif - #ifdef EOVERFLOW - case EOVERFLOW: return DRWAV_TOO_BIG; - #endif - #ifdef ENOTUNIQ - case ENOTUNIQ: return DRWAV_NOT_UNIQUE; - #endif - #ifdef EBADFD - case EBADFD: return DRWAV_ERROR; - #endif - #ifdef EREMCHG - case EREMCHG: return DRWAV_ERROR; - #endif - #ifdef ELIBACC - case ELIBACC: return DRWAV_ACCESS_DENIED; - #endif - #ifdef ELIBBAD - case ELIBBAD: return DRWAV_INVALID_FILE; - #endif - #ifdef ELIBSCN - case ELIBSCN: return DRWAV_INVALID_FILE; - #endif - #ifdef ELIBMAX - case ELIBMAX: return DRWAV_ERROR; - #endif - #ifdef ELIBEXEC - case ELIBEXEC: return DRWAV_ERROR; - #endif - #ifdef EILSEQ - case EILSEQ: return DRWAV_INVALID_DATA; - #endif - #ifdef ERESTART - case ERESTART: return DRWAV_ERROR; - #endif - #ifdef ESTRPIPE - case ESTRPIPE: return DRWAV_ERROR; - #endif - #ifdef EUSERS - case EUSERS: return DRWAV_ERROR; - #endif - #ifdef ENOTSOCK - case ENOTSOCK: return DRWAV_NOT_SOCKET; - #endif - #ifdef EDESTADDRREQ - case EDESTADDRREQ: return DRWAV_NO_ADDRESS; - #endif - #ifdef EMSGSIZE - case EMSGSIZE: return DRWAV_TOO_BIG; - #endif - #ifdef EPROTOTYPE - case EPROTOTYPE: return DRWAV_BAD_PROTOCOL; - #endif - #ifdef ENOPROTOOPT - case ENOPROTOOPT: return DRWAV_PROTOCOL_UNAVAILABLE; - #endif - #ifdef EPROTONOSUPPORT - case EPROTONOSUPPORT: return DRWAV_PROTOCOL_NOT_SUPPORTED; - #endif - #ifdef ESOCKTNOSUPPORT - case ESOCKTNOSUPPORT: return DRWAV_SOCKET_NOT_SUPPORTED; - #endif - #ifdef EOPNOTSUPP - case EOPNOTSUPP: return DRWAV_INVALID_OPERATION; - #endif - #ifdef EPFNOSUPPORT - case EPFNOSUPPORT: return DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED; - #endif - #ifdef EAFNOSUPPORT - case EAFNOSUPPORT: return DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED; - #endif - #ifdef EADDRINUSE - case EADDRINUSE: return DRWAV_ALREADY_IN_USE; - #endif - #ifdef EADDRNOTAVAIL - case EADDRNOTAVAIL: return DRWAV_ERROR; - #endif - #ifdef ENETDOWN - case ENETDOWN: return DRWAV_NO_NETWORK; - #endif - #ifdef ENETUNREACH - case ENETUNREACH: return DRWAV_NO_NETWORK; - #endif - #ifdef ENETRESET - case ENETRESET: return DRWAV_NO_NETWORK; - #endif - #ifdef ECONNABORTED - case ECONNABORTED: return DRWAV_NO_NETWORK; - #endif - #ifdef ECONNRESET - case ECONNRESET: return DRWAV_CONNECTION_RESET; - #endif - #ifdef ENOBUFS - case ENOBUFS: return DRWAV_NO_SPACE; - #endif - #ifdef EISCONN - case EISCONN: return DRWAV_ALREADY_CONNECTED; - #endif - #ifdef ENOTCONN - case ENOTCONN: return DRWAV_NOT_CONNECTED; - #endif - #ifdef ESHUTDOWN - case ESHUTDOWN: return DRWAV_ERROR; - #endif - #ifdef ETOOMANYREFS - case ETOOMANYREFS: return DRWAV_ERROR; - #endif - #ifdef ETIMEDOUT - case ETIMEDOUT: return DRWAV_TIMEOUT; - #endif - #ifdef ECONNREFUSED - case ECONNREFUSED: return DRWAV_CONNECTION_REFUSED; - #endif - #ifdef EHOSTDOWN - case EHOSTDOWN: return DRWAV_NO_HOST; - #endif - #ifdef EHOSTUNREACH - case EHOSTUNREACH: return DRWAV_NO_HOST; - #endif - #ifdef EALREADY - case EALREADY: return DRWAV_IN_PROGRESS; - #endif - #ifdef EINPROGRESS - case EINPROGRESS: return DRWAV_IN_PROGRESS; - #endif - #ifdef ESTALE - case ESTALE: return DRWAV_INVALID_FILE; - #endif - #ifdef EUCLEAN - case EUCLEAN: return DRWAV_ERROR; - #endif - #ifdef ENOTNAM - case ENOTNAM: return DRWAV_ERROR; - #endif - #ifdef ENAVAIL - case ENAVAIL: return DRWAV_ERROR; - #endif - #ifdef EISNAM - case EISNAM: return DRWAV_ERROR; - #endif - #ifdef EREMOTEIO - case EREMOTEIO: return DRWAV_IO_ERROR; - #endif - #ifdef EDQUOT - case EDQUOT: return DRWAV_NO_SPACE; - #endif - #ifdef ENOMEDIUM - case ENOMEDIUM: return DRWAV_DOES_NOT_EXIST; - #endif - #ifdef EMEDIUMTYPE - case EMEDIUMTYPE: return DRWAV_ERROR; - #endif - #ifdef ECANCELED - case ECANCELED: return DRWAV_CANCELLED; - #endif - #ifdef ENOKEY - case ENOKEY: return DRWAV_ERROR; - #endif - #ifdef EKEYEXPIRED - case EKEYEXPIRED: return DRWAV_ERROR; - #endif - #ifdef EKEYREVOKED - case EKEYREVOKED: return DRWAV_ERROR; - #endif - #ifdef EKEYREJECTED - case EKEYREJECTED: return DRWAV_ERROR; - #endif - #ifdef EOWNERDEAD - case EOWNERDEAD: return DRWAV_ERROR; - #endif - #ifdef ENOTRECOVERABLE - case ENOTRECOVERABLE: return DRWAV_ERROR; - #endif - #ifdef ERFKILL - case ERFKILL: return DRWAV_ERROR; - #endif - #ifdef EHWPOISON - case EHWPOISON: return DRWAV_ERROR; - #endif - default: return DRWAV_ERROR; - } -} - -DRWAV_PRIVATE drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) -{ -#if defined(_MSC_VER) && _MSC_VER >= 1400 - errno_t err; -#endif - - if (ppFile != NULL) { - *ppFile = NULL; /* Safety. */ - } - - if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { - return DRWAV_INVALID_ARGS; - } - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - err = fopen_s(ppFile, pFilePath, pOpenMode); - if (err != 0) { - return drwav_result_from_errno(err); - } -#else -#if defined(_WIN32) || defined(__APPLE__) - *ppFile = fopen(pFilePath, pOpenMode); -#else - #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) - *ppFile = fopen64(pFilePath, pOpenMode); - #else - *ppFile = fopen(pFilePath, pOpenMode); - #endif -#endif - if (*ppFile == NULL) { - drwav_result result = drwav_result_from_errno(errno); - if (result == DRWAV_SUCCESS) { - result = DRWAV_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ - } - - return result; - } -#endif - - return DRWAV_SUCCESS; -} - -/* -_wfopen() isn't always available in all compilation environments. - - * Windows only. - * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). - * MinGW-64 (both 32- and 64-bit) seems to support it. - * MinGW wraps it in !defined(__STRICT_ANSI__). - * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). - -This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() -fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. -*/ -#if defined(_WIN32) - #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) - #define DRWAV_HAS_WFOPEN - #endif -#endif - -DRWAV_PRIVATE drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (ppFile != NULL) { - *ppFile = NULL; /* Safety. */ - } - - if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { - return DRWAV_INVALID_ARGS; - } - -#if defined(DRWAV_HAS_WFOPEN) - { - /* Use _wfopen() on Windows. */ - #if defined(_MSC_VER) && _MSC_VER >= 1400 - errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); - if (err != 0) { - return drwav_result_from_errno(err); - } - #else - *ppFile = _wfopen(pFilePath, pOpenMode); - if (*ppFile == NULL) { - return drwav_result_from_errno(errno); - } - #endif - (void)pAllocationCallbacks; - } -#else - /* - Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can - think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for - maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. - */ - { - mbstate_t mbs; - size_t lenMB; - const wchar_t* pFilePathTemp = pFilePath; - char* pFilePathMB = NULL; - char pOpenModeMB[32] = {0}; - - /* Get the length first. */ - DRWAV_ZERO_OBJECT(&mbs); - lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); - if (lenMB == (size_t)-1) { - return drwav_result_from_errno(errno); - } - - pFilePathMB = (char*)drwav__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); - if (pFilePathMB == NULL) { - return DRWAV_OUT_OF_MEMORY; - } - - pFilePathTemp = pFilePath; - DRWAV_ZERO_OBJECT(&mbs); - wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); - - /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ - { - size_t i = 0; - for (;;) { - if (pOpenMode[i] == 0) { - pOpenModeMB[i] = '\0'; - break; - } - - pOpenModeMB[i] = (char)pOpenMode[i]; - i += 1; - } - } - - *ppFile = fopen(pFilePathMB, pOpenModeMB); - - drwav__free_from_callbacks(pFilePathMB, pAllocationCallbacks); - } - - if (*ppFile == NULL) { - return DRWAV_ERROR; - } -#endif - - return DRWAV_SUCCESS; -} - - -DRWAV_PRIVATE size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) -{ - return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); -} - -DRWAV_PRIVATE size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) -{ - return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); -} - -DRWAV_PRIVATE drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) -{ - return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; -} - -DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_file_ex(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); -} - - -DRWAV_PRIVATE drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, drwav_metadata_type allowedMetadataTypes, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav_bool32 result; - - result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); - if (result != DRWAV_TRUE) { - fclose(pFile); - return result; - } - - pWav->allowedMetadataTypes = allowedMetadataTypes; - - result = drwav_init__internal(pWav, onChunk, pChunkUserData, flags); - if (result != DRWAV_TRUE) { - fclose(pFile); - return result; - } - - return DRWAV_TRUE; -} - -DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - FILE* pFile; - if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - - /* This takes ownership of the FILE* object. */ - return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, drwav_metadata_type_none, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_file_ex_w(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - FILE* pFile; - if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - - /* This takes ownership of the FILE* object. */ - return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, drwav_metadata_type_none, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_with_metadata(drwav* pWav, const char* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - FILE* pFile; - if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - - /* This takes ownership of the FILE* object. */ - return drwav_init_file__internal_FILE(pWav, pFile, NULL, NULL, flags, drwav_metadata_type_all_including_unknown, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_with_metadata_w(drwav* pWav, const wchar_t* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - FILE* pFile; - if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - - /* This takes ownership of the FILE* object. */ - return drwav_init_file__internal_FILE(pWav, pFile, NULL, NULL, flags, drwav_metadata_type_all_including_unknown, pAllocationCallbacks); -} - - -DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav_bool32 result; - - result = drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); - if (result != DRWAV_TRUE) { - fclose(pFile); - return result; - } - - result = drwav_init_write__internal(pWav, pFormat, totalSampleCount); - if (result != DRWAV_TRUE) { - fclose(pFile); - return result; - } - - return DRWAV_TRUE; -} - -DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - FILE* pFile; - if (drwav_fopen(&pFile, filename, "wb") != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - - /* This takes ownership of the FILE* object. */ - return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); -} - -DRWAV_PRIVATE drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - FILE* pFile; - if (drwav_wfopen(&pFile, filename, L"wb", pAllocationCallbacks) != DRWAV_SUCCESS) { - return DRWAV_FALSE; - } - - /* This takes ownership of the FILE* object. */ - return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_file_write__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_file_write__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pFormat == NULL) { - return DRWAV_FALSE; - } - - return drwav_init_file_write_sequential(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_file_write_w__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_file_write_w__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pFormat == NULL) { - return DRWAV_FALSE; - } - - return drwav_init_file_write_sequential_w(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); -} -#endif /* DR_WAV_NO_STDIO */ - - -DRWAV_PRIVATE size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) -{ - drwav* pWav = (drwav*)pUserData; - size_t bytesRemaining; - - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(pWav->memoryStream.dataSize >= pWav->memoryStream.currentReadPos); - - bytesRemaining = pWav->memoryStream.dataSize - pWav->memoryStream.currentReadPos; - if (bytesToRead > bytesRemaining) { - bytesToRead = bytesRemaining; - } - - if (bytesToRead > 0) { - DRWAV_COPY_MEMORY(pBufferOut, pWav->memoryStream.data + pWav->memoryStream.currentReadPos, bytesToRead); - pWav->memoryStream.currentReadPos += bytesToRead; - } - - return bytesToRead; -} - -DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) -{ - drwav* pWav = (drwav*)pUserData; - DRWAV_ASSERT(pWav != NULL); - - if (origin == drwav_seek_origin_current) { - if (offset > 0) { - if (pWav->memoryStream.currentReadPos + offset > pWav->memoryStream.dataSize) { - return DRWAV_FALSE; /* Trying to seek too far forward. */ - } - } else { - if (pWav->memoryStream.currentReadPos < (size_t)-offset) { - return DRWAV_FALSE; /* Trying to seek too far backwards. */ - } - } - - /* This will never underflow thanks to the clamps above. */ - pWav->memoryStream.currentReadPos += offset; - } else { - if ((drwav_uint32)offset <= pWav->memoryStream.dataSize) { - pWav->memoryStream.currentReadPos = offset; - } else { - return DRWAV_FALSE; /* Trying to seek too far forward. */ - } - } - - return DRWAV_TRUE; -} - -DRWAV_PRIVATE size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) -{ - drwav* pWav = (drwav*)pUserData; - size_t bytesRemaining; - - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(pWav->memoryStreamWrite.dataCapacity >= pWav->memoryStreamWrite.currentWritePos); - - bytesRemaining = pWav->memoryStreamWrite.dataCapacity - pWav->memoryStreamWrite.currentWritePos; - if (bytesRemaining < bytesToWrite) { - /* Need to reallocate. */ - void* pNewData; - size_t newDataCapacity = (pWav->memoryStreamWrite.dataCapacity == 0) ? 256 : pWav->memoryStreamWrite.dataCapacity * 2; - - /* If doubling wasn't enough, just make it the minimum required size to write the data. */ - if ((newDataCapacity - pWav->memoryStreamWrite.currentWritePos) < bytesToWrite) { - newDataCapacity = pWav->memoryStreamWrite.currentWritePos + bytesToWrite; - } - - pNewData = drwav__realloc_from_callbacks(*pWav->memoryStreamWrite.ppData, newDataCapacity, pWav->memoryStreamWrite.dataCapacity, &pWav->allocationCallbacks); - if (pNewData == NULL) { - return 0; - } - - *pWav->memoryStreamWrite.ppData = pNewData; - pWav->memoryStreamWrite.dataCapacity = newDataCapacity; - } - - DRWAV_COPY_MEMORY(((drwav_uint8*)(*pWav->memoryStreamWrite.ppData)) + pWav->memoryStreamWrite.currentWritePos, pDataIn, bytesToWrite); - - pWav->memoryStreamWrite.currentWritePos += bytesToWrite; - if (pWav->memoryStreamWrite.dataSize < pWav->memoryStreamWrite.currentWritePos) { - pWav->memoryStreamWrite.dataSize = pWav->memoryStreamWrite.currentWritePos; - } - - *pWav->memoryStreamWrite.pDataSize = pWav->memoryStreamWrite.dataSize; - - return bytesToWrite; -} - -DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) -{ - drwav* pWav = (drwav*)pUserData; - DRWAV_ASSERT(pWav != NULL); - - if (origin == drwav_seek_origin_current) { - if (offset > 0) { - if (pWav->memoryStreamWrite.currentWritePos + offset > pWav->memoryStreamWrite.dataSize) { - offset = (int)(pWav->memoryStreamWrite.dataSize - pWav->memoryStreamWrite.currentWritePos); /* Trying to seek too far forward. */ - } - } else { - if (pWav->memoryStreamWrite.currentWritePos < (size_t)-offset) { - offset = -(int)pWav->memoryStreamWrite.currentWritePos; /* Trying to seek too far backwards. */ - } - } - - /* This will never underflow thanks to the clamps above. */ - pWav->memoryStreamWrite.currentWritePos += offset; - } else { - if ((drwav_uint32)offset <= pWav->memoryStreamWrite.dataSize) { - pWav->memoryStreamWrite.currentWritePos = offset; - } else { - pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; /* Trying to seek too far forward. */ - } - } - - return DRWAV_TRUE; -} - -DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (data == NULL || dataSize == 0) { - return DRWAV_FALSE; - } - - if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { - return DRWAV_FALSE; - } - - pWav->memoryStream.data = (const drwav_uint8*)data; - pWav->memoryStream.dataSize = dataSize; - pWav->memoryStream.currentReadPos = 0; - - return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); -} - -DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* data, size_t dataSize, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (data == NULL || dataSize == 0) { - return DRWAV_FALSE; - } - - if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { - return DRWAV_FALSE; - } - - pWav->memoryStream.data = (const drwav_uint8*)data; - pWav->memoryStream.dataSize = dataSize; - pWav->memoryStream.currentReadPos = 0; - - pWav->allowedMetadataTypes = drwav_metadata_type_all_including_unknown; - - return drwav_init__internal(pWav, NULL, NULL, flags); -} - - -DRWAV_PRIVATE drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (ppData == NULL || pDataSize == NULL) { - return DRWAV_FALSE; - } - - *ppData = NULL; /* Important because we're using realloc()! */ - *pDataSize = 0; - - if (!drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, pWav, pAllocationCallbacks)) { - return DRWAV_FALSE; - } - - pWav->memoryStreamWrite.ppData = ppData; - pWav->memoryStreamWrite.pDataSize = pDataSize; - pWav->memoryStreamWrite.dataSize = 0; - pWav->memoryStreamWrite.dataCapacity = 0; - pWav->memoryStreamWrite.currentWritePos = 0; - - return drwav_init_write__internal(pWav, pFormat, totalSampleCount); -} - -DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); -} - -DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pFormat == NULL) { - return DRWAV_FALSE; - } - - return drwav_init_memory_write_sequential(pWav, ppData, pDataSize, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); -} - - -#pragma stackfunction 200 -DRWAV_API drwav_result drwav_uninit(drwav* pWav) -{ - drwav_result result = DRWAV_SUCCESS; - - if (pWav == NULL) { - return DRWAV_INVALID_ARGS; - } - - /* - If the drwav object was opened in write mode we'll need to finalize a few things: - - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. - - Set the size of the "data" chunk. - */ - if (pWav->onWrite != NULL) { - drwav_uint32 paddingSize = 0; - - /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */ - if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { - paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); - } else { - paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); - } - - if (paddingSize > 0) { - drwav_uint64 paddingData = 0; - drwav__write(pWav, &paddingData, paddingSize); /* Byte order does not matter for this. */ - } - - /* - Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need - to do this when using non-sequential mode. - */ - if (pWav->onSeek && !pWav->isSequentialWrite) { - if (pWav->container == drwav_container_riff) { - /* The "RIFF" chunk size. */ - if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { - drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); - drwav__write_u32ne_to_le(pWav, riffChunkSize); - } - - /* The "data" chunk size. */ - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) { - drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); - drwav__write_u32ne_to_le(pWav, dataChunkSize); - } - } else if (pWav->container == drwav_container_w64) { - /* The "RIFF" chunk size. */ - if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { - drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); - drwav__write_u64ne_to_le(pWav, riffChunkSize); - } - - /* The "data" chunk size. */ - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) { - drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); - drwav__write_u64ne_to_le(pWav, dataChunkSize); - } - } else if (pWav->container == drwav_container_rf64) { - /* We only need to update the ds64 chunk. The "RIFF" and "data" chunks always have their sizes set to 0xFFFFFFFF for RF64. */ - int ds64BodyPos = 12 + 8; - - /* The "RIFF" chunk size. */ - if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { - drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); - drwav__write_u64ne_to_le(pWav, riffChunkSize); - } - - /* The "data" chunk size. */ - if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { - drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); - drwav__write_u64ne_to_le(pWav, dataChunkSize); - } - } - } - - /* Validation for sequential mode. */ - if (pWav->isSequentialWrite) { - if (pWav->dataChunkDataSize != pWav->dataChunkDataSizeTargetWrite) { - result = DRWAV_INVALID_FILE; - } - } - } else { - if (pWav->pMetadata != NULL) { - pWav->allocationCallbacks.onFree(pWav->pMetadata, pWav->allocationCallbacks.pUserData); - } - } - -#ifndef DR_WAV_NO_STDIO - /* - If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() - was used by looking at the onRead and onSeek callbacks. - */ - if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) { - fclose((FILE*)pWav->pUserData); - } -#endif - - return result; -} - - -#pragma stackfunction 800 -DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) -{ - size_t bytesRead; - drwav_uint32 bytesPerFrame; - - if (pWav == NULL || bytesToRead == 0) { - return 0; /* Invalid args. */ - } - - if (bytesToRead > pWav->bytesRemaining) { - bytesToRead = (size_t)pWav->bytesRemaining; - } - - if (bytesToRead == 0) { - return 0; /* At end. */ - } - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; /* Could not determine the bytes per frame. */ - } - - if (pBufferOut != NULL) { - bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); - } else { - /* We need to seek. If we fail, we need to read-and-discard to make sure we get a good byte count. */ - bytesRead = 0; - while (bytesRead < bytesToRead) { - size_t bytesToSeek = (bytesToRead - bytesRead); - if (bytesToSeek > 0x7FFFFFFF) { - bytesToSeek = 0x7FFFFFFF; - } - - if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, drwav_seek_origin_current) == DRWAV_FALSE) { - break; - } - - bytesRead += bytesToSeek; - } - - /* When we get here we may need to read-and-discard some data. */ - while (bytesRead < bytesToRead) { - drwav_uint8 buffer[4096]; - size_t bytesSeeked; - size_t bytesToSeek = (bytesToRead - bytesRead); - if (bytesToSeek > sizeof(buffer)) { - bytesToSeek = sizeof(buffer); - } - - bytesSeeked = pWav->onRead(pWav->pUserData, buffer, bytesToSeek); - bytesRead += bytesSeeked; - - if (bytesSeeked < bytesToSeek) { - break; /* Reached the end. */ - } - } - } - - pWav->readCursorInPCMFrames += bytesRead / bytesPerFrame; - - pWav->bytesRemaining -= bytesRead; - return bytesRead; -} - - - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) -{ - drwav_uint32 bytesPerFrame; - drwav_uint64 bytesToRead; /* Intentionally uint64 instead of size_t so we can do a check that we're not reading too much on 32-bit builds. */ - - if (pWav == NULL || framesToRead == 0) { - return 0; - } - - /* Cannot use this function for compressed formats. */ - if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { - return 0; - } - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - /* Don't try to read more samples than can potentially fit in the output buffer. */ - bytesToRead = framesToRead * bytesPerFrame; - if (bytesToRead > DRWAV_SIZE_MAX) { - bytesToRead = (DRWAV_SIZE_MAX / bytesPerFrame) * bytesPerFrame; /* Round the number of bytes to read to a clean frame boundary. */ - } - - /* - Doing an explicit check here just to make it clear that we don't want to be attempt to read anything if there's no bytes to read. There - *could* be a time where it evaluates to 0 due to overflowing. - */ - if (bytesToRead == 0) { - return 0; - } - - return drwav_read_raw(pWav, (size_t)bytesToRead, pBufferOut) / bytesPerFrame; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) -{ - drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); - - if (pBufferOut != NULL) { - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; /* Could not get the bytes per frame which means bytes per sample cannot be determined and we don't know how to byte swap. */ - } - - drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, bytesPerFrame/pWav->channels, pWav->translatedFormatTag); - } - - return framesRead; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) -{ - if (drwav__is_little_endian()) { - return drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); - } else { - return drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); - } -} - - - -DRWAV_PRIVATE drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) -{ - if (pWav->onWrite != NULL) { - return DRWAV_FALSE; /* No seeking in write mode. */ - } - - if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { - return DRWAV_FALSE; - } - - if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { - /* Cached data needs to be cleared for compressed formats. */ - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - DRWAV_ZERO_OBJECT(&pWav->msadpcm); - } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - DRWAV_ZERO_OBJECT(&pWav->ima); - } else { - DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ - } - } - - pWav->readCursorInPCMFrames = 0; - pWav->bytesRemaining = pWav->dataChunkDataSize; - - return DRWAV_TRUE; -} - -#pragma stackfunction 1200 -DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex) -{ - /* Seeking should be compatible with wave files > 2GB. */ - - if (pWav == NULL || pWav->onSeek == NULL) { - return DRWAV_FALSE; - } - - /* No seeking in write mode. */ - if (pWav->onWrite != NULL) { - return DRWAV_FALSE; - } - - /* If there are no samples, just return DRWAV_TRUE without doing anything. */ - if (pWav->totalPCMFrameCount == 0) { - return DRWAV_TRUE; - } - - /* Make sure the sample is clamped. */ - if (targetFrameIndex > pWav->totalPCMFrameCount) { - targetFrameIndex = pWav->totalPCMFrameCount; - } - - /* - For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need - to seek back to the start. - */ - if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { - /* TODO: This can be optimized. */ - - /* - If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, - we first need to seek back to the start and then just do the same thing as a forward seek. - */ - if (targetFrameIndex < pWav->readCursorInPCMFrames) { - if (!drwav_seek_to_first_pcm_frame(pWav)) { - return DRWAV_FALSE; - } - } - - if (targetFrameIndex > pWav->readCursorInPCMFrames) { - drwav_uint64 offsetInFrames = targetFrameIndex - pWav->readCursorInPCMFrames; - - drwav_int16 devnull[2048]; - while (offsetInFrames > 0) { - drwav_uint64 framesRead = 0; - drwav_uint64 framesToRead = offsetInFrames; - if (framesToRead > drwav_countof(devnull)/pWav->channels) { - framesToRead = drwav_countof(devnull)/pWav->channels; - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - framesRead = drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, devnull); - } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - framesRead = drwav_read_pcm_frames_s16__ima(pWav, framesToRead, devnull); - } else { - DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ - } - - if (framesRead != framesToRead) { - return DRWAV_FALSE; - } - - offsetInFrames -= framesRead; - } - } - } else { - drwav_uint64 totalSizeInBytes; - drwav_uint64 currentBytePos; - drwav_uint64 targetBytePos; - drwav_uint64 offset; - drwav_uint32 bytesPerFrame; - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return DRWAV_FALSE; /* Not able to calculate offset. */ - } - - totalSizeInBytes = pWav->totalPCMFrameCount * bytesPerFrame; - DRWAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining); - - currentBytePos = totalSizeInBytes - pWav->bytesRemaining; - targetBytePos = targetFrameIndex * bytesPerFrame; - - if (currentBytePos < targetBytePos) { - /* Offset forwards. */ - offset = (targetBytePos - currentBytePos); - } else { - /* Offset backwards. */ - if (!drwav_seek_to_first_pcm_frame(pWav)) { - return DRWAV_FALSE; - } - offset = targetBytePos; - } - - while (offset > 0) { - int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); - if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { - return DRWAV_FALSE; - } - - pWav->readCursorInPCMFrames += offset32 / bytesPerFrame; - pWav->bytesRemaining -= offset32; - offset -= offset32; - } - } - - return DRWAV_TRUE; -} - -DRWAV_API drwav_result drwav_get_cursor_in_pcm_frames(drwav* pWav, drwav_uint64* pCursor) -{ - if (pCursor == NULL) { - return DRWAV_INVALID_ARGS; - } - - *pCursor = 0; /* Safety. */ - - if (pWav == NULL) { - return DRWAV_INVALID_ARGS; - } - - *pCursor = pWav->readCursorInPCMFrames; - - return DRWAV_SUCCESS; -} - -DRWAV_API drwav_result drwav_get_length_in_pcm_frames(drwav* pWav, drwav_uint64* pLength) -{ - if (pLength == NULL) { - return DRWAV_INVALID_ARGS; - } - - *pLength = 0; /* Safety. */ - - if (pWav == NULL) { - return DRWAV_INVALID_ARGS; - } - - *pLength = pWav->totalPCMFrameCount; - - return DRWAV_SUCCESS; -} - - -DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) -{ - size_t bytesWritten; - - if (pWav == NULL || bytesToWrite == 0 || pData == NULL) { - return 0; - } - - bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); - pWav->dataChunkDataSize += bytesWritten; - - return bytesWritten; -} - -DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) -{ - drwav_uint64 bytesToWrite; - drwav_uint64 bytesWritten; - const drwav_uint8* pRunningData; - - if (pWav == NULL || framesToWrite == 0 || pData == NULL) { - return 0; - } - - bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); - if (bytesToWrite > DRWAV_SIZE_MAX) { - return 0; - } - - bytesWritten = 0; - pRunningData = (const drwav_uint8*)pData; - - while (bytesToWrite > 0) { - size_t bytesJustWritten; - drwav_uint64 bytesToWriteThisIteration; - - bytesToWriteThisIteration = bytesToWrite; - DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ - - bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); - if (bytesJustWritten == 0) { - break; - } - - bytesToWrite -= bytesJustWritten; - bytesWritten += bytesJustWritten; - pRunningData += bytesJustWritten; - } - - return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; -} - -DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) -{ - drwav_uint64 bytesToWrite; - drwav_uint64 bytesWritten; - drwav_uint32 bytesPerSample; - const drwav_uint8* pRunningData; - - if (pWav == NULL || framesToWrite == 0 || pData == NULL) { - return 0; - } - - bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); - if (bytesToWrite > DRWAV_SIZE_MAX) { - return 0; - } - - bytesWritten = 0; - pRunningData = (const drwav_uint8*)pData; - - bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels; - if (bytesPerSample == 0) { - return 0; /* Cannot determine bytes per sample, or bytes per sample is less than one byte. */ - } - - while (bytesToWrite > 0) { - drwav_uint8 temp[4096]; - drwav_uint32 sampleCount; - size_t bytesJustWritten; - drwav_uint64 bytesToWriteThisIteration; - - bytesToWriteThisIteration = bytesToWrite; - DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ - - /* - WAV files are always little-endian. We need to byte swap on big-endian architectures. Since our input buffer is read-only we need - to use an intermediary buffer for the conversion. - */ - sampleCount = sizeof(temp)/bytesPerSample; - - if (bytesToWriteThisIteration > ((drwav_uint64)sampleCount)*bytesPerSample) { - bytesToWriteThisIteration = ((drwav_uint64)sampleCount)*bytesPerSample; - } - - DRWAV_COPY_MEMORY(temp, pRunningData, (size_t)bytesToWriteThisIteration); - drwav__bswap_samples(temp, sampleCount, bytesPerSample, pWav->translatedFormatTag); - - bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, temp); - if (bytesJustWritten == 0) { - break; - } - - bytesToWrite -= bytesJustWritten; - bytesWritten += bytesJustWritten; - pRunningData += bytesJustWritten; - } - - return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; -} - -DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) -{ - if (drwav__is_little_endian()) { - return drwav_write_pcm_frames_le(pWav, framesToWrite, pData); - } else { - return drwav_write_pcm_frames_be(pWav, framesToWrite, pData); - } -} - - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - drwav_uint64 totalFramesRead = 0; - - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(framesToRead > 0); - - /* TODO: Lots of room for optimization here. */ - - while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { - DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ - - /* If there are no cached frames we need to load a new block. */ - if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { - if (pWav->channels == 1) { - /* Mono. */ - drwav_uint8 header[7]; - if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { - return totalFramesRead; - } - pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); - - pWav->msadpcm.predictor[0] = header[0]; - pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 1); - pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 3); - pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 5); - pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; - pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; - pWav->msadpcm.cachedFrameCount = 2; - } else { - /* Stereo. */ - drwav_uint8 header[14]; - if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { - return totalFramesRead; - } - pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); - - pWav->msadpcm.predictor[0] = header[0]; - pWav->msadpcm.predictor[1] = header[1]; - pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 2); - pWav->msadpcm.delta[1] = drwav_bytes_to_s16(header + 4); - pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 6); - pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav_bytes_to_s16(header + 8); - pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 10); - pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav_bytes_to_s16(header + 12); - - pWav->msadpcm.cachedFrames[0] = pWav->msadpcm.prevFrames[0][0]; - pWav->msadpcm.cachedFrames[1] = pWav->msadpcm.prevFrames[1][0]; - pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; - pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; - pWav->msadpcm.cachedFrameCount = 2; - } - } - - /* Output anything that's cached. */ - while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { - if (pBufferOut != NULL) { - drwav_uint32 iSample = 0; - for (iSample = 0; iSample < pWav->channels; iSample += 1) { - pBufferOut[iSample] = (drwav_int16)pWav->msadpcm.cachedFrames[(drwav_countof(pWav->msadpcm.cachedFrames) - (pWav->msadpcm.cachedFrameCount*pWav->channels)) + iSample]; - } - - pBufferOut += pWav->channels; - } - - framesToRead -= 1; - totalFramesRead += 1; - pWav->readCursorInPCMFrames += 1; - pWav->msadpcm.cachedFrameCount -= 1; - } - - if (framesToRead == 0) { - break; - } - - - /* - If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next - loop iteration which will trigger the loading of a new block. - */ - if (pWav->msadpcm.cachedFrameCount == 0) { - if (pWav->msadpcm.bytesRemainingInBlock == 0) { - continue; - } else { - static drwav_int32 adaptationTable[] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 - }; - static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; - static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; - - drwav_uint8 nibbles; - drwav_int32 nibble0; - drwav_int32 nibble1; - - if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { - return totalFramesRead; - } - pWav->msadpcm.bytesRemainingInBlock -= 1; - - /* TODO: Optimize away these if statements. */ - nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } - nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } - - if (pWav->channels == 1) { - /* Mono. */ - drwav_int32 newSample0; - drwav_int32 newSample1; - - newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; - newSample0 += nibble0 * pWav->msadpcm.delta[0]; - newSample0 = drwav_clamp(newSample0, -32768, 32767); - - pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; - if (pWav->msadpcm.delta[0] < 16) { - pWav->msadpcm.delta[0] = 16; - } - - pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; - pWav->msadpcm.prevFrames[0][1] = newSample0; - - - newSample1 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; - newSample1 += nibble1 * pWav->msadpcm.delta[0]; - newSample1 = drwav_clamp(newSample1, -32768, 32767); - - pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8; - if (pWav->msadpcm.delta[0] < 16) { - pWav->msadpcm.delta[0] = 16; - } - - pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; - pWav->msadpcm.prevFrames[0][1] = newSample1; - - - pWav->msadpcm.cachedFrames[2] = newSample0; - pWav->msadpcm.cachedFrames[3] = newSample1; - pWav->msadpcm.cachedFrameCount = 2; - } else { - /* Stereo. */ - drwav_int32 newSample0; - drwav_int32 newSample1; - - /* Left. */ - newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; - newSample0 += nibble0 * pWav->msadpcm.delta[0]; - newSample0 = drwav_clamp(newSample0, -32768, 32767); - - pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; - if (pWav->msadpcm.delta[0] < 16) { - pWav->msadpcm.delta[0] = 16; - } - - pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; - pWav->msadpcm.prevFrames[0][1] = newSample0; - - - /* Right. */ - newSample1 = ((pWav->msadpcm.prevFrames[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevFrames[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; - newSample1 += nibble1 * pWav->msadpcm.delta[1]; - newSample1 = drwav_clamp(newSample1, -32768, 32767); - - pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8; - if (pWav->msadpcm.delta[1] < 16) { - pWav->msadpcm.delta[1] = 16; - } - - pWav->msadpcm.prevFrames[1][0] = pWav->msadpcm.prevFrames[1][1]; - pWav->msadpcm.prevFrames[1][1] = newSample1; - - pWav->msadpcm.cachedFrames[2] = newSample0; - pWav->msadpcm.cachedFrames[3] = newSample1; - pWav->msadpcm.cachedFrameCount = 1; - } - } - } - } - - return totalFramesRead; -} - - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - drwav_uint64 totalFramesRead = 0; - drwav_uint32 iChannel; - - static drwav_int32 indexTable[16] = { - -1, -1, -1, -1, 2, 4, 6, 8, - -1, -1, -1, -1, 2, 4, 6, 8 - }; - - static drwav_int32 stepTable[89] = { - 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, - 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, - 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, - 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, - 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, - 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, - 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, - 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, - 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 - }; - - DRWAV_ASSERT(pWav != NULL); - DRWAV_ASSERT(framesToRead > 0); - - /* TODO: Lots of room for optimization here. */ - - while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { - DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ - - /* If there are no cached samples we need to load a new block. */ - if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { - if (pWav->channels == 1) { - /* Mono. */ - drwav_uint8 header[4]; - if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { - return totalFramesRead; - } - pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); - - if (header[2] >= drwav_countof(stepTable)) { - pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); - pWav->ima.bytesRemainingInBlock = 0; - return totalFramesRead; /* Invalid data. */ - } - - pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); - pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ - pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; - pWav->ima.cachedFrameCount = 1; - } else { - /* Stereo. */ - drwav_uint8 header[8]; - if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { - return totalFramesRead; - } - pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); - - if (header[2] >= drwav_countof(stepTable) || header[6] >= drwav_countof(stepTable)) { - pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); - pWav->ima.bytesRemainingInBlock = 0; - return totalFramesRead; /* Invalid data. */ - } - - pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); - pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ - pWav->ima.predictor[1] = drwav_bytes_to_s16(header + 4); - pWav->ima.stepIndex[1] = drwav_clamp(header[6], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ - - pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; - pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1]; - pWav->ima.cachedFrameCount = 1; - } - } - - /* Output anything that's cached. */ - while (framesToRead > 0 && pWav->ima.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { - if (pBufferOut != NULL) { - drwav_uint32 iSample; - for (iSample = 0; iSample < pWav->channels; iSample += 1) { - pBufferOut[iSample] = (drwav_int16)pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + iSample]; - } - pBufferOut += pWav->channels; - } - - framesToRead -= 1; - totalFramesRead += 1; - pWav->readCursorInPCMFrames += 1; - pWav->ima.cachedFrameCount -= 1; - } - - if (framesToRead == 0) { - break; - } - - /* - If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next - loop iteration which will trigger the loading of a new block. - */ - if (pWav->ima.cachedFrameCount == 0) { - if (pWav->ima.bytesRemainingInBlock == 0) { - continue; - } else { - /* - From what I can tell with stereo streams, it looks like every 4 bytes (8 samples) is for one channel. So it goes 4 bytes for the - left channel, 4 bytes for the right channel. - */ - pWav->ima.cachedFrameCount = 8; - for (iChannel = 0; iChannel < pWav->channels; ++iChannel) { - drwav_uint32 iByte; - drwav_uint8 nibbles[4]; - if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) { - pWav->ima.cachedFrameCount = 0; - return totalFramesRead; - } - pWav->ima.bytesRemainingInBlock -= 4; - - for (iByte = 0; iByte < 4; ++iByte) { - drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0); - drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4); - - drwav_int32 step = stepTable[pWav->ima.stepIndex[iChannel]]; - drwav_int32 predictor = pWav->ima.predictor[iChannel]; - - drwav_int32 diff = step >> 3; - if (nibble0 & 1) diff += step >> 2; - if (nibble0 & 2) diff += step >> 1; - if (nibble0 & 4) diff += step; - if (nibble0 & 8) diff = -diff; - - predictor = drwav_clamp(predictor + diff, -32768, 32767); - pWav->ima.predictor[iChannel] = predictor; - pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble0], 0, (drwav_int32)drwav_countof(stepTable)-1); - pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+0)*pWav->channels + iChannel] = predictor; - - - step = stepTable[pWav->ima.stepIndex[iChannel]]; - predictor = pWav->ima.predictor[iChannel]; - - diff = step >> 3; - if (nibble1 & 1) diff += step >> 2; - if (nibble1 & 2) diff += step >> 1; - if (nibble1 & 4) diff += step; - if (nibble1 & 8) diff = -diff; - - predictor = drwav_clamp(predictor + diff, -32768, 32767); - pWav->ima.predictor[iChannel] = predictor; - pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble1], 0, (drwav_int32)drwav_countof(stepTable)-1); - pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+1)*pWav->channels + iChannel] = predictor; - } - } - } - } - } - - return totalFramesRead; -} - - -#ifndef DR_WAV_NO_CONVERSION_API -static unsigned short g_drwavAlawTable[256] = { - 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, - 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, - 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, - 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, - 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, - 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, - 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, - 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, - 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, - 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, - 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, - 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, - 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, - 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, - 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, - 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 -}; - -static unsigned short g_drwavMulawTable[256] = { - 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, - 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, - 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, - 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, - 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, - 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, - 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, - 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, - 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, - 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, - 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, - 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, - 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, - 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, - 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, - 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 -}; - -static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) -{ - return (short)g_drwavAlawTable[sampleIn]; -} - -static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) -{ - return (short)g_drwavMulawTable[sampleIn]; -} - - - -DRWAV_PRIVATE void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) -{ - size_t i; - - /* Special case for 8-bit sample data because it's treated as unsigned. */ - if (bytesPerSample == 1) { - drwav_u8_to_s16(pOut, pIn, totalSampleCount); - return; - } - - - /* Slightly more optimal implementation for common formats. */ - if (bytesPerSample == 2) { - for (i = 0; i < totalSampleCount; ++i) { - *pOut++ = ((const drwav_int16*)pIn)[i]; - } - return; - } - if (bytesPerSample == 3) { - drwav_s24_to_s16(pOut, pIn, totalSampleCount); - return; - } - if (bytesPerSample == 4) { - drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); - return; - } - - - /* Anything more than 64 bits per sample is not supported. */ - if (bytesPerSample > 8) { - DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); - return; - } - - - /* Generic, slow converter. */ - for (i = 0; i < totalSampleCount; ++i) { - drwav_uint64 sample = 0; - unsigned int shift = (8 - bytesPerSample) * 8; - - unsigned int j; - for (j = 0; j < bytesPerSample; j += 1) { - DRWAV_ASSERT(j < 8); - sample |= (drwav_uint64)(pIn[j]) << shift; - shift += 8; - } - - pIn += j; - *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); - } -} - -DRWAV_PRIVATE void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) -{ - if (bytesPerSample == 4) { - drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); - return; - } else if (bytesPerSample == 8) { - drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount); - return; - } else { - /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ - DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); - return; - } -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - /* Fast path. */ - if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { - return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); - } - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - if (pBufferOut == NULL) { - return drwav_read_pcm_frames(pWav, framesToRead, NULL); - } - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); /* Safe cast. */ - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - if (pBufferOut == NULL) { - return drwav_read_pcm_frames(pWav, framesToRead, NULL); - } - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - if (pBufferOut == NULL) { - return drwav_read_pcm_frames(pWav, framesToRead, NULL); - } - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - if (pWav == NULL || framesToRead == 0) { - return 0; - } - - if (pBufferOut == NULL) { - return drwav_read_pcm_frames(pWav, framesToRead, NULL); - } - - /* Don't try to read more samples than can potentially fit in the output buffer. */ - if (framesToRead * pWav->channels * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { - framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16) / pWav->channels; - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { - return drwav_read_pcm_frames_s16__pcm(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { - return drwav_read_pcm_frames_s16__ieee(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { - return drwav_read_pcm_frames_s16__alaw(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { - return drwav_read_pcm_frames_s16__mulaw(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - return drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - return drwav_read_pcm_frames_s16__ima(pWav, framesToRead, pBufferOut); - } - - return 0; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); - if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { - drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); - } - - return framesRead; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) -{ - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); - if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { - drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); - } - - return framesRead; -} - - -DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - int r; - size_t i; - for (i = 0; i < sampleCount; ++i) { - int x = pIn[i]; - r = x << 8; - r = r - 32768; - pOut[i] = (short)r; - } -} - -DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - int r; - size_t i; - for (i = 0; i < sampleCount; ++i) { - int x = ((int)(((unsigned int)(((const drwav_uint8*)pIn)[i*3+0]) << 8) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+1]) << 16) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+2])) << 24)) >> 8; - r = x >> 8; - pOut[i] = (short)r; - } -} - -DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount) -{ - int r; - size_t i; - for (i = 0; i < sampleCount; ++i) { - int x = pIn[i]; - r = x >> 16; - pOut[i] = (short)r; - } -} - -DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) -{ - int r; - size_t i; - for (i = 0; i < sampleCount; ++i) { - float x = pIn[i]; - float c; - c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); - c = c + 1; - r = (int)(c * 32767.5f); - r = r - 32768; - pOut[i] = (short)r; - } -} - -DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) -{ - int r; - size_t i; - for (i = 0; i < sampleCount; ++i) { - double x = pIn[i]; - double c; - c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); - c = c + 1; - r = (int)(c * 32767.5); - r = r - 32768; - pOut[i] = (short)r; - } -} - -DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - for (i = 0; i < sampleCount; ++i) { - pOut[i] = drwav__alaw_to_s16(pIn[i]); - } -} - -DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - for (i = 0; i < sampleCount; ++i) { - pOut[i] = drwav__mulaw_to_s16(pIn[i]); - } -} - - - -DRWAV_PRIVATE void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) -{ - unsigned int i; - - /* Special case for 8-bit sample data because it's treated as unsigned. */ - if (bytesPerSample == 1) { - drwav_u8_to_f32(pOut, pIn, sampleCount); - return; - } - - /* Slightly more optimal implementation for common formats. */ - if (bytesPerSample == 2) { - drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount); - return; - } - if (bytesPerSample == 3) { - drwav_s24_to_f32(pOut, pIn, sampleCount); - return; - } - if (bytesPerSample == 4) { - drwav_s32_to_f32(pOut, (const drwav_int32*)pIn, sampleCount); - return; - } - - - /* Anything more than 64 bits per sample is not supported. */ - if (bytesPerSample > 8) { - DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); - return; - } - - - /* Generic, slow converter. */ - for (i = 0; i < sampleCount; ++i) { - drwav_uint64 sample = 0; - unsigned int shift = (8 - bytesPerSample) * 8; - - unsigned int j; - for (j = 0; j < bytesPerSample; j += 1) { - DRWAV_ASSERT(j < 8); - sample |= (drwav_uint64)(pIn[j]) << shift; - shift += 8; - } - - pIn += j; - *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); - } -} - -DRWAV_PRIVATE void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) -{ - if (bytesPerSample == 4) { - unsigned int i; - for (i = 0; i < sampleCount; ++i) { - *pOut++ = ((const float*)pIn)[i]; - } - return; - } else if (bytesPerSample == 8) { - drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount); - return; - } else { - /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ - DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); - return; - } -} - - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - /* - We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't - want to duplicate that code. - */ - drwav_uint64 totalFramesRead; - drwav_int16 samples16[2048]; - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ - - pBufferOut += framesRead*pWav->channels; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - /* Fast path. */ - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { - return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); - } - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - if (pWav == NULL || framesToRead == 0) { - return 0; - } - - if (pBufferOut == NULL) { - return drwav_read_pcm_frames(pWav, framesToRead, NULL); - } - - /* Don't try to read more samples than can potentially fit in the output buffer. */ - if (framesToRead * pWav->channels * sizeof(float) > DRWAV_SIZE_MAX) { - framesToRead = DRWAV_SIZE_MAX / sizeof(float) / pWav->channels; - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { - return drwav_read_pcm_frames_f32__pcm(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - return drwav_read_pcm_frames_f32__msadpcm_ima(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { - return drwav_read_pcm_frames_f32__ieee(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { - return drwav_read_pcm_frames_f32__alaw(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { - return drwav_read_pcm_frames_f32__mulaw(pWav, framesToRead, pBufferOut); - } - - return 0; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); - if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { - drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); - } - - return framesRead; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); - if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { - drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); - } - - return framesRead; -} - - -DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - -#ifdef DR_WAV_LIBSNDFILE_COMPAT - /* - It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears - libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note - the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated - correctness testing. This is disabled by default. - */ - for (i = 0; i < sampleCount; ++i) { - *pOut++ = (pIn[i] / 256.0f) * 2 - 1; - } -#else - for (i = 0; i < sampleCount; ++i) { - float x = pIn[i]; - x = x * 0.00784313725490196078f; /* 0..255 to 0..2 */ - x = x - 1; /* 0..2 to -1..1 */ - - *pOut++ = x; - } -#endif -} - -DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = pIn[i] * 0.000030517578125f; - } -} - -DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - double x; - drwav_uint32 a = ((drwav_uint32)(pIn[i*3+0]) << 8); - drwav_uint32 b = ((drwav_uint32)(pIn[i*3+1]) << 16); - drwav_uint32 c = ((drwav_uint32)(pIn[i*3+2]) << 24); - - x = (double)((drwav_int32)(a | b | c) >> 8); - *pOut++ = (float)(x * 0.00000011920928955078125); - } -} - -DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount) -{ - size_t i; - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = (float)(pIn[i] / 2147483648.0); - } -} - -DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = (float)pIn[i]; - } -} - -DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f; - } -} - -DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; - } -} - - - -DRWAV_PRIVATE void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) -{ - unsigned int i; - - /* Special case for 8-bit sample data because it's treated as unsigned. */ - if (bytesPerSample == 1) { - drwav_u8_to_s32(pOut, pIn, totalSampleCount); - return; - } - - /* Slightly more optimal implementation for common formats. */ - if (bytesPerSample == 2) { - drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount); - return; - } - if (bytesPerSample == 3) { - drwav_s24_to_s32(pOut, pIn, totalSampleCount); - return; - } - if (bytesPerSample == 4) { - for (i = 0; i < totalSampleCount; ++i) { - *pOut++ = ((const drwav_int32*)pIn)[i]; - } - return; - } - - - /* Anything more than 64 bits per sample is not supported. */ - if (bytesPerSample > 8) { - DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); - return; - } - - - /* Generic, slow converter. */ - for (i = 0; i < totalSampleCount; ++i) { - drwav_uint64 sample = 0; - unsigned int shift = (8 - bytesPerSample) * 8; - - unsigned int j; - for (j = 0; j < bytesPerSample; j += 1) { - DRWAV_ASSERT(j < 8); - sample |= (drwav_uint64)(pIn[j]) << shift; - shift += 8; - } - - pIn += j; - *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); - } -} - -DRWAV_PRIVATE void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) -{ - if (bytesPerSample == 4) { - drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); - return; - } else if (bytesPerSample == 8) { - drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount); - return; - } else { - /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ - DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); - return; - } -} - - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - /* Fast path. */ - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { - return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); - } - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - /* - We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't - want to duplicate that code. - */ - drwav_uint64 totalFramesRead = 0; - drwav_int16 samples16[2048]; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ - - pBufferOut += framesRead*pWav->channels; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096] = {0}; - drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerSample; - drwav_uint64 samplesRead; - - bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); - if (bytesPerFrame == 0) { - return 0; - } - - bytesPerSample = bytesPerFrame / pWav->channels; - if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { - return 0; /* Only byte-aligned formats are supported. */ - } - - totalFramesRead = 0; - - while (framesToRead > 0) { - drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); - if (framesRead == 0) { - break; - } - - DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ - - /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ - samplesRead = framesRead * pWav->channels; - if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { - DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ - break; - } - - drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); - - pBufferOut += samplesRead; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - - return totalFramesRead; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - if (pWav == NULL || framesToRead == 0) { - return 0; - } - - if (pBufferOut == NULL) { - return drwav_read_pcm_frames(pWav, framesToRead, NULL); - } - - /* Don't try to read more samples than can potentially fit in the output buffer. */ - if (framesToRead * pWav->channels * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { - framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32) / pWav->channels; - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { - return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - return drwav_read_pcm_frames_s32__msadpcm_ima(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { - return drwav_read_pcm_frames_s32__ieee(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { - return drwav_read_pcm_frames_s32__alaw(pWav, framesToRead, pBufferOut); - } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { - return drwav_read_pcm_frames_s32__mulaw(pWav, framesToRead, pBufferOut); - } - - return 0; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); - if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { - drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); - } - - return framesRead; -} - -DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); - if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { - drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); - } - - return framesRead; -} - - -DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = ((int)pIn[i] - 128) << 24; - } -} - -DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = pIn[i] << 16; - } -} - -DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - unsigned int s0 = pIn[i*3 + 0]; - unsigned int s1 = pIn[i*3 + 1]; - unsigned int s2 = pIn[i*3 + 2]; - - drwav_int32 sample32 = (drwav_int32)((s0 << 8) | (s1 << 16) | (s2 << 24)); - *pOut++ = sample32; - } -} - -DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); - } -} - -DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); - } -} - -DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i = 0; i < sampleCount; ++i) { - *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16; - } -} - -DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) -{ - size_t i; - - if (pOut == NULL || pIn == NULL) { - return; - } - - for (i= 0; i < sampleCount; ++i) { - *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; - } -} - - - -DRWAV_PRIVATE drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) -{ - drwav_uint64 sampleDataSize; - drwav_int16* pSampleData; - drwav_uint64 framesRead; - - DRWAV_ASSERT(pWav != NULL); - - sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int16); - if (sampleDataSize > DRWAV_SIZE_MAX) { - drwav_uninit(pWav); - return NULL; /* File's too big. */ - } - - pSampleData = (drwav_int16*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ - if (pSampleData == NULL) { - drwav_uninit(pWav); - return NULL; /* Failed to allocate memory. */ - } - - framesRead = drwav_read_pcm_frames_s16(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); - if (framesRead != pWav->totalPCMFrameCount) { - drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); - drwav_uninit(pWav); - return NULL; /* There was an error reading the samples. */ - } - - drwav_uninit(pWav); - - if (sampleRate) { - *sampleRate = pWav->sampleRate; - } - if (channels) { - *channels = pWav->channels; - } - if (totalFrameCount) { - *totalFrameCount = pWav->totalPCMFrameCount; - } - - return pSampleData; -} - -DRWAV_PRIVATE float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) -{ - drwav_uint64 sampleDataSize; - float* pSampleData; - drwav_uint64 framesRead; - - DRWAV_ASSERT(pWav != NULL); - - sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(float); - if (sampleDataSize > DRWAV_SIZE_MAX) { - drwav_uninit(pWav); - return NULL; /* File's too big. */ - } - - pSampleData = (float*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ - if (pSampleData == NULL) { - drwav_uninit(pWav); - return NULL; /* Failed to allocate memory. */ - } - - framesRead = drwav_read_pcm_frames_f32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); - if (framesRead != pWav->totalPCMFrameCount) { - drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); - drwav_uninit(pWav); - return NULL; /* There was an error reading the samples. */ - } - - drwav_uninit(pWav); - - if (sampleRate) { - *sampleRate = pWav->sampleRate; - } - if (channels) { - *channels = pWav->channels; - } - if (totalFrameCount) { - *totalFrameCount = pWav->totalPCMFrameCount; - } - - return pSampleData; -} - -DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) -{ - drwav_uint64 sampleDataSize; - drwav_int32* pSampleData; - drwav_uint64 framesRead; - - DRWAV_ASSERT(pWav != NULL); - - sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int32); - if (sampleDataSize > DRWAV_SIZE_MAX) { - drwav_uninit(pWav); - return NULL; /* File's too big. */ - } - - pSampleData = (drwav_int32*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ - if (pSampleData == NULL) { - drwav_uninit(pWav); - return NULL; /* Failed to allocate memory. */ - } - - framesRead = drwav_read_pcm_frames_s32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); - if (framesRead != pWav->totalPCMFrameCount) { - drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); - drwav_uninit(pWav); - return NULL; /* There was an error reading the samples. */ - } - - drwav_uninit(pWav); - - if (sampleRate) { - *sampleRate = pWav->sampleRate; - } - if (channels) { - *channels = pWav->channels; - } - if (totalFrameCount) { - *totalFrameCount = pWav->totalPCMFrameCount; - } - - return pSampleData; -} - - - -DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -#ifndef DR_WAV_NO_STDIO -DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - - -DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (channelsOut) { - *channelsOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (channelsOut) { - *channelsOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (channelsOut) { - *channelsOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} -#endif - -DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} - -DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - drwav wav; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalFrameCountOut) { - *totalFrameCountOut = 0; - } - - if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { - return NULL; - } - - return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); -} -#endif /* DR_WAV_NO_CONVERSION_API */ - - -DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks != NULL) { - drwav__free_from_callbacks(p, pAllocationCallbacks); - } else { - drwav__free_default(p, NULL); - } -} - -DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data) -{ - return ((drwav_uint16)data[0] << 0) | ((drwav_uint16)data[1] << 8); -} - -DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data) -{ - return (drwav_int16)drwav_bytes_to_u16(data); -} - -DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data) -{ - return ((drwav_uint32)data[0] << 0) | ((drwav_uint32)data[1] << 8) | ((drwav_uint32)data[2] << 16) | ((drwav_uint32)data[3] << 24); -} - -DRWAV_API float drwav_bytes_to_f32(const drwav_uint8* data) -{ - union { - drwav_uint32 u32; - float f32; - } value; - - value.u32 = drwav_bytes_to_u32(data); - return value.f32; -} - -DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data) -{ - return (drwav_int32)drwav_bytes_to_u32(data); -} - -DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data) -{ - return - ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | - ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); -} - -DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data) -{ - return (drwav_int64)drwav_bytes_to_u64(data); -} - - -DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) -{ - int i; - for (i = 0; i < 16; i += 1) { - if (a[i] != b[i]) { - return DRWAV_FALSE; - } - } - - return DRWAV_TRUE; -} - -DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) -{ - return - a[0] == b[0] && - a[1] == b[1] && - a[2] == b[2] && - a[3] == b[3]; -} - -#endif /* dr_wav_c */ -#endif /* DR_WAV_IMPLEMENTATION */ - -/* -REVISION HISTORY -================ -v0.13.6 - 2022-04-10 - - Fix compilation error on older versions of GCC. - - Remove some dependencies on the standard library. - -v0.13.5 - 2022-01-26 - - Fix an error when seeking to the end of the file. - -v0.13.4 - 2021-12-08 - - Fix some static analysis warnings. - -v0.13.3 - 2021-11-24 - - Fix an incorrect assertion when trying to endian swap 1-byte sample formats. This is now a no-op - rather than a failed assertion. - - Fix a bug with parsing of the bext chunk. - - Fix some static analysis warnings. - -v0.13.2 - 2021-10-02 - - Fix a possible buffer overflow when reading from compressed formats. - -v0.13.1 - 2021-07-31 - - Fix platform detection for ARM64. - -v0.13.0 - 2021-07-01 - - Improve support for reading and writing metadata. Use the `_with_metadata()` APIs to initialize - a WAV decoder and store the metadata within the `drwav` object. Use the `pMetadata` and - `metadataCount` members of the `drwav` object to read the data. The old way of handling metadata - via a callback is still usable and valid. - - API CHANGE: drwav_target_write_size_bytes() now takes extra parameters for calculating the - required write size when writing metadata. - - Add drwav_get_cursor_in_pcm_frames() - - Add drwav_get_length_in_pcm_frames() - - Fix a bug where drwav_read_raw() can call the read callback with a byte count of zero. - -v0.12.20 - 2021-06-11 - - Fix some undefined behavior. - -v0.12.19 - 2021-02-21 - - Fix a warning due to referencing _MSC_VER when it is undefined. - - Minor improvements to the management of some internal state concerning the data chunk cursor. - -v0.12.18 - 2021-01-31 - - Clean up some static analysis warnings. - -v0.12.17 - 2021-01-17 - - Minor fix to sample code in documentation. - - Correctly qualify a private API as private rather than public. - - Code cleanup. - -v0.12.16 - 2020-12-02 - - Fix a bug when trying to read more bytes than can fit in a size_t. - -v0.12.15 - 2020-11-21 - - Fix compilation with OpenWatcom. - -v0.12.14 - 2020-11-13 - - Minor code clean up. - -v0.12.13 - 2020-11-01 - - Improve compiler support for older versions of GCC. - -v0.12.12 - 2020-09-28 - - Add support for RF64. - - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section. - -v0.12.11 - 2020-09-08 - - Fix a compilation error on older compilers. - -v0.12.10 - 2020-08-24 - - Fix a bug when seeking with ADPCM formats. - -v0.12.9 - 2020-08-02 - - Simplify sized types. - -v0.12.8 - 2020-07-25 - - Fix a compilation warning. - -v0.12.7 - 2020-07-15 - - Fix some bugs on big-endian architectures. - - Fix an error in s24 to f32 conversion. - -v0.12.6 - 2020-06-23 - - Change drwav_read_*() to allow NULL to be passed in as the output buffer which is equivalent to a forward seek. - - Fix a buffer overflow when trying to decode invalid IMA-ADPCM files. - - Add include guard for the implementation section. - -v0.12.5 - 2020-05-27 - - Minor documentation fix. - -v0.12.4 - 2020-05-16 - - Replace assert() with DRWAV_ASSERT(). - - Add compile-time and run-time version querying. - - DRWAV_VERSION_MINOR - - DRWAV_VERSION_MAJOR - - DRWAV_VERSION_REVISION - - DRWAV_VERSION_STRING - - drwav_version() - - drwav_version_string() - -v0.12.3 - 2020-04-30 - - Fix compilation errors with VC6. - -v0.12.2 - 2020-04-21 - - Fix a bug where drwav_init_file() does not close the file handle after attempting to load an erroneous file. - -v0.12.1 - 2020-04-13 - - Fix some pedantic warnings. - -v0.12.0 - 2020-04-04 - - API CHANGE: Add container and format parameters to the chunk callback. - - Minor documentation updates. - -v0.11.5 - 2020-03-07 - - Fix compilation error with Visual Studio .NET 2003. - -v0.11.4 - 2020-01-29 - - Fix some static analysis warnings. - - Fix a bug when reading f32 samples from an A-law encoded stream. - -v0.11.3 - 2020-01-12 - - Minor changes to some f32 format conversion routines. - - Minor bug fix for ADPCM conversion when end of file is reached. - -v0.11.2 - 2019-12-02 - - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. - - Fix an integer overflow bug. - - Fix a null pointer dereference bug. - - Add limits to sample rate, channels and bits per sample to tighten up some validation. - -v0.11.1 - 2019-10-07 - - Internal code clean up. - -v0.11.0 - 2019-10-06 - - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation - routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: - - drwav_init() - - drwav_init_ex() - - drwav_init_file() - - drwav_init_file_ex() - - drwav_init_file_w() - - drwav_init_file_w_ex() - - drwav_init_memory() - - drwav_init_memory_ex() - - drwav_init_write() - - drwav_init_write_sequential() - - drwav_init_write_sequential_pcm_frames() - - drwav_init_file_write() - - drwav_init_file_write_sequential() - - drwav_init_file_write_sequential_pcm_frames() - - drwav_init_file_write_w() - - drwav_init_file_write_sequential_w() - - drwav_init_file_write_sequential_pcm_frames_w() - - drwav_init_memory_write() - - drwav_init_memory_write_sequential() - - drwav_init_memory_write_sequential_pcm_frames() - - drwav_open_and_read_pcm_frames_s16() - - drwav_open_and_read_pcm_frames_f32() - - drwav_open_and_read_pcm_frames_s32() - - drwav_open_file_and_read_pcm_frames_s16() - - drwav_open_file_and_read_pcm_frames_f32() - - drwav_open_file_and_read_pcm_frames_s32() - - drwav_open_file_and_read_pcm_frames_s16_w() - - drwav_open_file_and_read_pcm_frames_f32_w() - - drwav_open_file_and_read_pcm_frames_s32_w() - - drwav_open_memory_and_read_pcm_frames_s16() - - drwav_open_memory_and_read_pcm_frames_f32() - - drwav_open_memory_and_read_pcm_frames_s32() - Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use - DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE. - - Add support for reading and writing PCM frames in an explicit endianness. New APIs: - - drwav_read_pcm_frames_le() - - drwav_read_pcm_frames_be() - - drwav_read_pcm_frames_s16le() - - drwav_read_pcm_frames_s16be() - - drwav_read_pcm_frames_f32le() - - drwav_read_pcm_frames_f32be() - - drwav_read_pcm_frames_s32le() - - drwav_read_pcm_frames_s32be() - - drwav_write_pcm_frames_le() - - drwav_write_pcm_frames_be() - - Remove deprecated APIs. - - API CHANGE: The following APIs now return native-endian data. Previously they returned little-endian data. - - drwav_read_pcm_frames() - - drwav_read_pcm_frames_s16() - - drwav_read_pcm_frames_s32() - - drwav_read_pcm_frames_f32() - - drwav_open_and_read_pcm_frames_s16() - - drwav_open_and_read_pcm_frames_s32() - - drwav_open_and_read_pcm_frames_f32() - - drwav_open_file_and_read_pcm_frames_s16() - - drwav_open_file_and_read_pcm_frames_s32() - - drwav_open_file_and_read_pcm_frames_f32() - - drwav_open_file_and_read_pcm_frames_s16_w() - - drwav_open_file_and_read_pcm_frames_s32_w() - - drwav_open_file_and_read_pcm_frames_f32_w() - - drwav_open_memory_and_read_pcm_frames_s16() - - drwav_open_memory_and_read_pcm_frames_s32() - - drwav_open_memory_and_read_pcm_frames_f32() - -v0.10.1 - 2019-08-31 - - Correctly handle partial trailing ADPCM blocks. - -v0.10.0 - 2019-08-04 - - Remove deprecated APIs. - - Add wchar_t variants for file loading APIs: - drwav_init_file_w() - drwav_init_file_ex_w() - drwav_init_file_write_w() - drwav_init_file_write_sequential_w() - - Add drwav_target_write_size_bytes() which calculates the total size in bytes of a WAV file given a format and sample count. - - Add APIs for specifying the PCM frame count instead of the sample count when opening in sequential write mode: - drwav_init_write_sequential_pcm_frames() - drwav_init_file_write_sequential_pcm_frames() - drwav_init_file_write_sequential_pcm_frames_w() - drwav_init_memory_write_sequential_pcm_frames() - - Deprecate drwav_open*() and drwav_close(): - drwav_open() - drwav_open_ex() - drwav_open_write() - drwav_open_write_sequential() - drwav_open_file() - drwav_open_file_ex() - drwav_open_file_write() - drwav_open_file_write_sequential() - drwav_open_memory() - drwav_open_memory_ex() - drwav_open_memory_write() - drwav_open_memory_write_sequential() - drwav_close() - - Minor documentation updates. - -v0.9.2 - 2019-05-21 - - Fix warnings. - -v0.9.1 - 2019-05-05 - - Add support for C89. - - Change license to choice of public domain or MIT-0. - -v0.9.0 - 2018-12-16 - - API CHANGE: Add new reading APIs for reading by PCM frames instead of samples. Old APIs have been deprecated and - will be removed in v0.10.0. Deprecated APIs and their replacements: - drwav_read() -> drwav_read_pcm_frames() - drwav_read_s16() -> drwav_read_pcm_frames_s16() - drwav_read_f32() -> drwav_read_pcm_frames_f32() - drwav_read_s32() -> drwav_read_pcm_frames_s32() - drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() - drwav_write() -> drwav_write_pcm_frames() - drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() - drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() - drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() - drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() - drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() - drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() - drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() - drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() - drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() - drwav::totalSampleCount -> drwav::totalPCMFrameCount - - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*(). - - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*(). - - Add built-in support for smpl chunks. - - Add support for firing a callback for each chunk in the file at initialization time. - - This is enabled through the drwav_init_ex(), etc. family of APIs. - - Handle invalid FMT chunks more robustly. - -v0.8.5 - 2018-09-11 - - Const correctness. - - Fix a potential stack overflow. - -v0.8.4 - 2018-08-07 - - Improve 64-bit detection. - -v0.8.3 - 2018-08-05 - - Fix C++ build on older versions of GCC. - -v0.8.2 - 2018-08-02 - - Fix some big-endian bugs. - -v0.8.1 - 2018-06-29 - - Add support for sequential writing APIs. - - Disable seeking in write mode. - - Fix bugs with Wave64. - - Fix typos. - -v0.8 - 2018-04-27 - - Bug fix. - - Start using major.minor.revision versioning. - -v0.7f - 2018-02-05 - - Restrict ADPCM formats to a maximum of 2 channels. - -v0.7e - 2018-02-02 - - Fix a crash. - -v0.7d - 2018-02-01 - - Fix a crash. - -v0.7c - 2018-02-01 - - Set drwav.bytesPerSample to 0 for all compressed formats. - - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for - all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). - - Fix some divide-by-zero errors. - -v0.7b - 2018-01-22 - - Fix errors with seeking of compressed formats. - - Fix compilation error when DR_WAV_NO_CONVERSION_API - -v0.7a - 2017-11-17 - - Fix some GCC warnings. - -v0.7 - 2017-11-04 - - Add writing APIs. - -v0.6 - 2017-08-16 - - API CHANGE: Rename dr_* types to drwav_*. - - Add support for custom implementations of malloc(), realloc(), etc. - - Add support for Microsoft ADPCM. - - Add support for IMA ADPCM (DVI, format code 0x11). - - Optimizations to drwav_read_s16(). - - Bug fixes. - -v0.5g - 2017-07-16 - - Change underlying type for booleans to unsigned. - -v0.5f - 2017-04-04 - - Fix a minor bug with drwav_open_and_read_s16() and family. - -v0.5e - 2016-12-29 - - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. - - Minor fixes to documentation. - -v0.5d - 2016-12-28 - - Use drwav_int* and drwav_uint* sized types to improve compiler support. - -v0.5c - 2016-11-11 - - Properly handle JUNK chunks that come before the FMT chunk. - -v0.5b - 2016-10-23 - - A minor change to drwav_bool8 and drwav_bool32 types. - -v0.5a - 2016-10-11 - - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. - - Improve A-law and mu-law efficiency. - -v0.5 - 2016-09-29 - - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to - keep it consistent with dr_audio and dr_flac. - -v0.4b - 2016-09-18 - - Fixed a typo in documentation. - -v0.4a - 2016-09-18 - - Fixed a typo. - - Change date format to ISO 8601 (YYYY-MM-DD) - -v0.4 - 2016-07-13 - - API CHANGE. Make onSeek consistent with dr_flac. - - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. - - Added support for Sony Wave64. - -v0.3a - 2016-05-28 - - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. - - Fixed a memory leak. - -v0.3 - 2016-05-22 - - Lots of API changes for consistency. - -v0.2a - 2016-05-16 - - Fixed Linux/GCC build. - -v0.2 - 2016-05-11 - - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. - -v0.1a - 2016-05-07 - - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. - -v0.1 - 2016-05-04 - - Initial versioned release. -*/ - -/* -This software is available as a choice of the following licenses. Choose -whichever you prefer. - -=============================================================================== -ALTERNATIVE 1 - Public Domain (www.unlicense.org) -=============================================================================== -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. - -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. - -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 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. - -For more information, please refer to - -=============================================================================== -ALTERNATIVE 2 - MIT No Attribution -=============================================================================== -Copyright 2020 David Reid - -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. - -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/examples/ffva/src/intent_handler/audio_response/dr_wav_freertos_port.h b/examples/ffva/src/intent_handler/audio_response/dr_wav_freertos_port.h deleted file mode 100644 index e530ab7c..00000000 --- a/examples/ffva/src/intent_handler/audio_response/dr_wav_freertos_port.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#ifndef DR_WAV_FREERTOS_PORT_H_ -#define DR_WAV_FREERTOS_PORT_H_ - -/* STD headers */ -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "fs_support.h" -#include "ff.h" - -/* dr_wav Options */ -#undef DR_WAV_NO_CONVERSION_API -#define DR_WAV_NO_STDIO - -/* dr_wav STD lib port */ -// #define DRWAV_ASSERT(expression) xassert(expression) -// #define DRWAV_MALLOC(sz) pvPortMalloc((sz)) -// #define DRWAV_REALLOC(sz) pvPortMalloc((sz)) -// #define DRWAV_FREE(p) vPortFree(p) - -void *dr_wav_malloc_port(size_t sz, void* pUserData); -void *dr_wav_realloc_port(void* p, size_t sz, void* pUserData); -void dr_wav_free_port(void* p, void* pUserData); - -#define DR_WAV_IMPLEMENTATION -#include "dr_wav.h" - -drwav_allocation_callbacks drwav_memory_cbs = { - .pUserData = NULL, - .onMalloc = dr_wav_malloc_port, - .onRealloc = dr_wav_realloc_port, - .onFree = dr_wav_free_port, -}; - -void *dr_wav_malloc_port(size_t sz, void* pUserData) { - (void) pUserData; - rtos_printf("**malloc was called\n"); - return pvPortMalloc(sz); -} - -void *dr_wav_realloc_port(void* p, size_t sz, void* pUserData) { - rtos_printf("**realloc was called\n"); - xassert(0); /* Not implemented in FreeRTOS */ - return NULL; -} - -void dr_wav_free_port(void* p, void* pUserData) { - (void) pUserData; - rtos_printf("**free was called\n"); - vPortFree(p); -} - -size_t drwav_read_proc_port(void* pUserData, void* pBufferOut, size_t bytesToRead) { - FIL *file = (FIL*)pUserData; - FRESULT result; - uint32_t bytes_read = 0; - - result = f_read(file, - (uint8_t*)pBufferOut, - bytesToRead, - (unsigned int*)&bytes_read); - - return (result == FR_OK) ? bytes_read : 0; -} - -size_t drwav_write_proc_port(void* pUserData, const void* pData, size_t bytesToWrite) { - rtos_printf("drwav_write_proc_port not implemented\n"); - return 0; -} - -drwav_bool32 drwav_seek_proc_port(void* pUserData, int offset, drwav_seek_origin origin) { - FIL *file = (FIL*)pUserData; - FRESULT result; - - result = f_lseek(file, offset); - - return (result == FR_OK) ? DRWAV_TRUE : DRWAV_FALSE;; -} - -drwav_uint64 drwav_chunk_proc_port(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader, drwav_container container, const drwav_fmt* pFMT) { - rtos_printf("drwav_chunk_proc_port not implemented\n"); - return (drwav_uint64)-1; -} - -#endif /* DR_WAV_FREERTOS_PORT_H_ */ diff --git a/examples/ffva/src/intent_handler/intent_handler.c b/examples/ffva/src/intent_handler/intent_handler.c deleted file mode 100644 index 8d052c9c..00000000 --- a/examples/ffva/src/intent_handler/intent_handler.c +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -/* STD headers */ -#include -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" -#include "task.h" -#include "queue.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "intent_handler/intent_handler.h" -#include "fs_support.h" -#include "ff.h" -#include "audio_response.h" -#include "intent_engine/intent_engine.h" - -#define WAKEUP_LOW (appconfINTENT_WAKEUP_EDGE_TYPE) -#define WAKEUP_HIGH (appconfINTENT_WAKEUP_EDGE_TYPE == 0) - -#if ON_TILE(ASR_TILE_NO) - -static bool audio_response_playing = 0; - -static void proc_keyword_res(void *args) { - QueueHandle_t q_intent = (QueueHandle_t) args; - int32_t id = 0; - int32_t host_status = 0; - - configASSERT(q_intent != 0); - - const rtos_gpio_port_id_t p_out_wakeup = rtos_gpio_port(GPIO_OUT_HOST_WAKEUP_PORT); - const rtos_gpio_port_id_t p_in_host_status = rtos_gpio_port(GPIO_IN_HOST_STATUS_PORT); - - rtos_gpio_port_enable(gpio_ctx_t0, p_out_wakeup); - rtos_gpio_port_enable(gpio_ctx_t0, p_in_host_status); - - rtos_gpio_port_out(gpio_ctx_t0, p_out_wakeup, WAKEUP_LOW); - -#if appconfAUDIO_PLAYBACK_ENABLED - audio_response_init(); -#endif - while(1) { - xQueueReceive(q_intent, &id, portMAX_DELAY); - - host_status = rtos_gpio_port_in(gpio_ctx_t0, p_in_host_status); - - if (host_status == 0) { /* Host is not awake */ - rtos_gpio_port_out(gpio_ctx_t0, p_out_wakeup, WAKEUP_HIGH); - rtos_printf("Delay for host wake up\n"); - vTaskDelay(pdMS_TO_TICKS(appconfINTENT_TRANSPORT_DELAY_MS)); - rtos_gpio_port_out(gpio_ctx_t0, p_out_wakeup, WAKEUP_LOW); - } -#if appconfINTENT_I2C_OUTPUT_ENABLED - i2c_res_t ret; - uint32_t buf = id; - size_t sent = 0; - - ret = rtos_i2c_master_write( - i2c_master_ctx, - appconfINTENT_I2C_OUTPUT_DEVICE_ADDR, - (uint8_t*)&buf, - sizeof(uint32_t), - &sent, - 1 - ); - - if (ret != I2C_ACK) { - rtos_printf("I2C inference output was not acknowledged\n\tSent %d bytes\n", sent); - } -#endif -#if appconfINTENT_UART_OUTPUT_ENABLED && (UART_TILE_NO == ASR_TILE_NO) - uint32_t buf_uart = id; - rtos_uart_tx_write(uart_tx_ctx, (uint8_t*)&buf_uart, sizeof(uint32_t)); -#endif -#if appconfAUDIO_PLAYBACK_ENABLED - audio_response_playing = true; - audio_response_play(id); - audio_response_playing = false; -#endif - } -} - -bool intent_handler_response_playing() { - return audio_response_playing; -} - -int32_t intent_handler_create(uint32_t priority, void *args) -{ - xTaskCreate((TaskFunction_t)proc_keyword_res, - "proc_keyword_res", - RTOS_THREAD_STACK_SIZE(proc_keyword_res), - args, - priority, - NULL); - - return 0; -} - -#endif /* ON_TILE(ASR_TILE_NO) */ diff --git a/examples/ffva/src/intent_handler/intent_handler.h b/examples/ffva/src/intent_handler/intent_handler.h deleted file mode 100644 index 4d3d8b78..00000000 --- a/examples/ffva/src/intent_handler/intent_handler.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#ifndef INTENT_HANDLER_H_ -#define INTENT_HANDLER_H_ - -#include - -#if XK_VOICE_L71 -#define GPIO_OUT_HOST_WAKEUP_PORT XS1_PORT_1D /* PORT_SPI_MOSI */ -#define GPIO_IN_HOST_STATUS_PORT XS1_PORT_1P /* PORT_SPI_MISO */ - -#elif XCOREAI_EXPLORER -#define GPIO_OUT_HOST_WAKEUP_PORT XS1_PORT_1M /* X0D36 */ -#define GPIO_IN_HOST_STATUS_PORT XS1_PORT_1P /* X0D39 */ - -#else -#define GPIO_OUT_HOST_WAKEUP_PORT 0 -#define GPIO_IN_HOST_STATUS_PORT 0 -#endif - -int32_t intent_handler_create(uint32_t priority, void *args); - -bool intent_handler_response_playing(); - -#endif /* INTENT_HANDLER_H_ */ diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 6005fd1d..8b76e6bd 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -22,11 +22,15 @@ #include "usb_support.h" #include "usb_audio.h" #include "audio_pipeline.h" -#include "ww_model_runner/ww_model_runner.h" +//#include "ww_model_runner/ww_model_runner.h" +#include "intent_engine.h" +#include "intent_handler.h" #include "fs_support.h" #include "print.h" #include "gpio_test/gpio_test.h" +#define ASR_CHANNEL (0) + volatile int mic_from_usb = appconfMIC_SRC_DEFAULT; volatile int aec_ref_source = appconfAEC_REF_DEFAULT; @@ -212,10 +216,19 @@ int audio_pipeline_output(void *output_app_data, output_audio_frames, 6); #endif -#if appconfWW_ENABLED - ww_audio_send(intertile_ctx, - frame_count, - (int32_t(*)[2])output_audio_frames); +#if appconfINTENT_ENABLED + int32_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; + + for (int i = 0; i < frame_count; i++) { + ww_samples[i] = ((int32_t(*)[2])output_audio_frames)[i][ASR_CHANNEL]; + } + intent_engine_sample_push(ww_samples, + frame_count); + + + //ww_audio_send(intertile_ctx, + // frame_count, + // (int32_t(*)[2])output_audio_frames); #endif return AUDIO_PIPELINE_FREE_FRAME; @@ -344,11 +357,11 @@ void startup_task(void *arg) rtos_qspi_flash_fast_read_setup_ll(qspi_flash_ctx); #endif //vTaskDelay(pdMS_TO_TICKS(100)); -#if appconfWW_ENABLED && ON_TILE(WW_TILE_NO) - ww_task_create(appconfWW_TASK_PRIORITY); +#if appconfINTENT_ENABLED && ON_TILE(ASR_TILE_NO) + intent_engine_task_create(appconfWW_TASK_PRIORITY); #endif -#if appconfWW_ENABLED && !ON_TILE(WW_TILE_NO) +#if appconfINTENT_ENABLED && !ON_TILE(ASR_TILE_NO) // Wait until the intent engine is initialized before starting the // audio pipeline. intent_engine_ready_sync(); diff --git a/examples/ffva/src/ww_model_runner/model_runner.c b/examples/ffva/src/ww_model_runner/model_runner.c deleted file mode 100644 index 6c0c0d32..00000000 --- a/examples/ffva/src/ww_model_runner/model_runner.c +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2021-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#include -#include -#include - -#include "FreeRTOS.h" -#include "task.h" -#include "stream_buffer.h" -#include "device_memory_impl.h" -#include "asr.h" - -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "ww_model_runner/ww_model_runner.h" - -#if ASR_SENSORY - #define IS_KEYWORD(id) (id == 17) - #define IS_COMMAND(id) (id > 0 && id != 17) -#elif ASR_CYBERON - #define IS_KEYWORD(id) (id == 1) - #define IS_COMMAND(id) (id >= 2) -#else -#error "Model has to be either Sensory or Cyberon" -#endif - -#define SAMPLES_PER_ASR (appconfINTENT_SAMPLE_BLOCK_LENGTH) - -configSTACK_DEPTH_TYPE model_runner_manager_stack_size = 287; -// SEARCH model file is specified in the CMakeLists SENSORY_COMMAND_SEARCH_SOURCE_FILE variable -#ifdef COMMAND_SEARCH_SOURCE_FILE -extern const unsigned short gs_grammarLabel[]; -void* grammar = (void*)gs_grammarLabel; -#else -void* grammar = NULL; -#endif -#include "print.h" -// Model file is in flash at the offset specified in the CMakeLists -// QSPI_FLASH_MODEL_START_ADDRESS variable. The XS1_SWMEM_BASE value needs -// to be added so the address in in the SwMem range. -uint16_t *model = (uint16_t *) (XS1_SWMEM_BASE + QSPI_FLASH_MODEL_START_ADDRESS); - -static asr_port_t asr_ctx; -static devmem_manager_t devmem_ctx; - -#pragma stackfunction 1000 -void model_runner_manager(void *args) -{ - StreamBufferHandle_t input_queue = (StreamBufferHandle_t)args; - - //int16_t buf[appconfWW_FRAMES_PER_INFERENCE]; - - /* Perform any initialization here */ - //intent_state = STATE_EXPECTING_WAKEWORD; - - /*TimerHandle_t int_eng_tmr = xTimerCreate( - "int_eng_tmr", - pdMS_TO_TICKS(appconfINTENT_RESET_DELAY_MS), - pdFALSE, - NULL, - vIntentTimerCallback); - */ - /* Alert other tile to start the audio pipeline */ - intent_engine_ready_sync(); - devmem_init(&devmem_ctx); - printf("Call asr_init(). model = 0x%x, grammar = 0x%x\n", (unsigned int) model, (unsigned int) grammar); - asr_ctx = asr_init((int32_t *)model, (int32_t *)grammar, &devmem_ctx); - - int16_t buf[appconfINTENT_SAMPLE_BLOCK_LENGTH] = {0}; - //int16_t buf_short[SAMPLES_PER_ASR] = {0}; - - asr_reset(asr_ctx); - - //size_t buf_short_index = 0; - asr_error_t asr_error; - asr_result_t asr_result; - int word_id; - - while (1) - { - /* Receive audio frames */ - uint8_t *buf_ptr = (uint8_t*)buf; - size_t buf_len = appconfWW_FRAMES_PER_INFERENCE * sizeof(int16_t); - do { - size_t bytes_rxed = xStreamBufferReceive(input_queue, - buf_ptr, - buf_len, - portMAX_DELAY); - buf_len -= bytes_rxed; - buf_ptr += bytes_rxed; - } while(buf_len > 0); - - //for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { - // buf_short[buf_short_index++] = buf[i] >> 16; - //} - //if (buf_short_index < SAMPLES_PER_ASR) - // continue; - - //buf_short_index = 0; // reset the offset into the buffer of int16s. - // Note, we do not need to overlap the window of samples. - // This is handled in the ASR ports. - - // this application does not support barge-in - // so, we need to check if an audio response is playing and skip to the next - // audio frame because the playback may trigger the ASR. - //if (intent_handler_response_playing()) continue; - asr_error = asr_process(asr_ctx, buf, SAMPLES_PER_ASR); - - if (asr_error == ASR_EVALUATION_EXPIRED) { - //led_indicate_end_of_eval(); - continue; - } - if (asr_error != ASR_OK) continue; - - asr_error = asr_get_result(asr_ctx, &asr_result); - if (asr_error != ASR_OK) continue; - - word_id = asr_result.id; - printintln(555); - printintln(55555); - - printintln(word_id); - - if (!IS_KEYWORD(word_id) && !IS_COMMAND(word_id)) continue; - -#if 0 - #if appconfINTENT_RAW_OUTPUT - intent_engine_process_asr_result(word_id); - #else - if (intent_state == STATE_EXPECTING_WAKEWORD && IS_KEYWORD(word_id)) { - led_indicate_listening(); - xTimerStart(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - intent_state = STATE_EXPECTING_COMMAND; - } else if (intent_state == STATE_EXPECTING_COMMAND && IS_COMMAND(word_id)) { - xTimerReset(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - intent_state = STATE_PROCESSING_COMMAND; - } else if (intent_state == STATE_EXPECTING_COMMAND && IS_KEYWORD(word_id)) { - xTimerReset(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - // remain in STATE_EXPECTING_COMMAND state - } else if (intent_state == STATE_PROCESSING_COMMAND && IS_KEYWORD(word_id)) { - xTimerReset(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - intent_state = STATE_EXPECTING_COMMAND; - } else if (intent_state == STATE_PROCESSING_COMMAND && IS_COMMAND(word_id)) { - xTimerReset(int_eng_tmr, 0); - intent_engine_process_asr_result(word_id); - // remain in STATE_PROCESSING_COMMAND state - } - #endif -#endif - /* Perform inference here */ - // rtos_printf("inference\n"); - } -} diff --git a/examples/ffva/src/ww_model_runner/ww_model_runner.c b/examples/ffva/src/ww_model_runner/ww_model_runner.c deleted file mode 100644 index 9b11fb2c..00000000 --- a/examples/ffva/src/ww_model_runner/ww_model_runner.c +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2021-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#include -#include -#include - -#include "FreeRTOS.h" -#include "task.h" -#include "stream_buffer.h" - -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "ww_model_runner/ww_model_runner.h" - -#define ASR_CHANNEL (0) -#define COMMS_CHANNEL (1) - -#if appconfWW_ENABLED -extern configSTACK_DEPTH_TYPE model_runner_manager_stack_size; - -static StreamBufferHandle_t audio_stream = NULL; -#include "print.h" -void ww_audio_send(rtos_intertile_t *intertile_ctx, - size_t frame_count, - int32_t (*processed_audio_frame)[2]) -{ - - configASSERT(frame_count == appconfAUDIO_PIPELINE_FRAME_ADVANCE); - - int16_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; - - for (int i = 0; i < frame_count; i++) { - ww_samples[i] = (int16_t)((processed_audio_frame[i][ASR_CHANNEL] >> 16) & 0xFFFF); - } - //printintln(processed_audio_frame[0][ASR_CHANNEL] ); - //printintln(ww_samples[0]); - if(audio_stream != NULL) { - if (xStreamBufferSend(audio_stream, ww_samples, sizeof(ww_samples), 0) != sizeof(ww_samples)) { - rtos_printf("lost output samples for ww\n"); - } - } - - /*for (int i = 0; i < appconfINTENT_SAMPLE_BLOCK_LENGTH; i++) { - buf_short[(*buf_short_index)++] = buf[i] >> 16; - }*/ -} - -void ww_task_create(unsigned priority) -{ - - audio_stream = xStreamBufferCreate(appconfINTENT_FRAME_BUFFER_MULT * appconfAUDIO_PIPELINE_FRAME_ADVANCE, - appconfWW_FRAMES_PER_INFERENCE); - - xTaskCreate((TaskFunction_t)model_runner_manager, - "model_manager", - model_runner_manager_stack_size, - audio_stream, - uxTaskPriorityGet(NULL), - NULL); -} - -void intent_engine_ready_sync(void) -{ - //return; - int sync = 3456; -#if ON_TILE(WW_TILE_NO) - size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); - - xassert(len == sizeof(sync)); - rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); - -#else - - rtos_intertile_tx(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, &sync, sizeof(sync)); - -#endif -} - -#endif diff --git a/examples/ffva/src/ww_model_runner/ww_model_runner.h b/examples/ffva/src/ww_model_runner/ww_model_runner.h deleted file mode 100644 index 756393f0..00000000 --- a/examples/ffva/src/ww_model_runner/ww_model_runner.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2021-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#ifndef WW_MODEL_RUNNER_H_ -#define WW_MODEL_RUNNER_H_ - -#include "FreeRTOS.h" - -void ww_task_create(unsigned priority); - -void ww_audio_send(rtos_intertile_t *intertile_ctx, - size_t frame_count, - int32_t (*processed_audio_frame)[2]); - -void model_runner_manager(void *args); - -void intent_engine_ready_sync(void); - -#endif /* WW_MODEL_RUNNER_H_ */ From 70b75c06f06c25acd0cdfbe54233ab98b16d494c Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Mar 2024 14:48:19 +0000 Subject: [PATCH 022/288] Clean up files --- examples/ffva/ffva.cmake | 1 - examples/ffva/src/main.c | 1 - modules/asr/Cyberon/DSpotter_asr.c | 3 --- 3 files changed, 5 deletions(-) diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index 44347102..d216e4bd 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -9,7 +9,6 @@ file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_ set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/usb - #${CMAKE_CURRENT_LIST_DIR}/src/ww_model_runner ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler ) diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 8b76e6bd..16904495 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -22,7 +22,6 @@ #include "usb_support.h" #include "usb_audio.h" #include "audio_pipeline.h" -//#include "ww_model_runner/ww_model_runner.h" #include "intent_engine.h" #include "intent_handler.h" #include "fs_support.h" diff --git a/modules/asr/Cyberon/DSpotter_asr.c b/modules/asr/Cyberon/DSpotter_asr.c index bfb21cb1..868c9c64 100644 --- a/modules/asr/Cyberon/DSpotter_asr.c +++ b/modules/asr/Cyberon/DSpotter_asr.c @@ -130,9 +130,6 @@ asr_error_t asr_process(asr_port_t *ctx, int16_t *audio_buf, size_t buf_len) DBG_TRACE("."); } #endif - //printintln(buf_len); - printintln(audio_buf[0]); -// printintln(audio_buf[1]); #ifdef SKIP_DSPOTTER_RECOG return ASR_ERROR; From 25f11d304823821c56335577eda9873744b53b65 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Mar 2024 14:49:11 +0000 Subject: [PATCH 023/288] Add missing file --- examples/ffva/ffva_int_cyberon.cmake | 222 +++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 examples/ffva/ffva_int_cyberon.cmake diff --git a/examples/ffva/ffva_int_cyberon.cmake b/examples/ffva/ffva_int_cyberon.cmake new file mode 100644 index 00000000..cf9a8aa5 --- /dev/null +++ b/examples/ffva/ffva_int_cyberon.cmake @@ -0,0 +1,222 @@ +#********************** +# QSPI Flash Layout +#********************** +set(BOOT_PARTITION_SIZE 0x100000) +set(FILESYSTEM_SIZE_KB 1024) +math(EXPR FILESYSTEM_SIZE_BYTES + "1024 * ${FILESYSTEM_SIZE_KB}" + OUTPUT_FORMAT HEXADECIMAL +) + +set(CALIBRATION_PATTERN_START_ADDRESS ${BOOT_PARTITION_SIZE}) + +math(EXPR FILESYSTEM_START_ADDRESS + "${CALIBRATION_PATTERN_START_ADDRESS} + ${LIB_QSPI_FAST_READ_DEFAULT_CAL_SIZE_BYTES}" + OUTPUT_FORMAT HEXADECIMAL +) + +math(EXPR MODEL_START_ADDRESS + "${FILESYSTEM_START_ADDRESS} + ${FILESYSTEM_SIZE_BYTES}" + OUTPUT_FORMAT HEXADECIMAL +) + +set(CALIBRATION_PATTERN_DATA_PARTITION_OFFSET 0) + +math(EXPR FILESYSTEM_DATA_PARTITION_OFFSET + "${CALIBRATION_PATTERN_DATA_PARTITION_OFFSET} + ${LIB_QSPI_FAST_READ_DEFAULT_CAL_SIZE_BYTES}" + OUTPUT_FORMAT DECIMAL +) + +math(EXPR MODEL_DATA_PARTITION_OFFSET + "${FILESYSTEM_DATA_PARTITION_OFFSET} + ${FILESYSTEM_SIZE_BYTES}" + OUTPUT_FORMAT DECIMAL +) + +set(FFVA_INT_CYBERON_COMPILE_DEFINITIONS + ${APP_COMPILE_DEFINITIONS} + appconfEXTERNAL_MCLK=1 + appconfI2S_ENABLED=1 + appconfUSB_ENABLED=0 + appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S + appconfI2S_MODE=appconfI2S_MODE_SLAVE + appconfI2S_AUDIO_SAMPLE_RATE=48000 + configENABLE_DEBUG_PRINTF=1 + QSPI_FLASH_FILESYSTEM_START_ADDRESS=${FILESYSTEM_START_ADDRESS} + QSPI_FLASH_MODEL_START_ADDRESS=${MODEL_START_ADDRESS} + QSPI_FLASH_CALIBRATION_ADDRESS=${CALIBRATION_PATTERN_START_ADDRESS} + ASR_CYBERON=1 + MIC_ARRAY_CONFIG_MCLK_FREQ=12288000 +) + +foreach(FFVA_AP ${FFVA_PIPELINES_INT}) + #********************** + # Tile Targets + #********************** + set(TARGET_NAME tile0_example_ffva_int_cyberon_${FFVA_AP}) + add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL) + target_sources(${TARGET_NAME} PUBLIC ${APP_SOURCES}) + target_include_directories(${TARGET_NAME} PUBLIC ${APP_INCLUDES}) + target_compile_definitions(${TARGET_NAME} + PUBLIC + ${FFVA_INT_CYBERON_COMPILE_DEFINITIONS} + THIS_XCORE_TILE=0 + ) + target_compile_options(${TARGET_NAME} PRIVATE ${APP_COMPILER_FLAGS}) + target_link_libraries(${TARGET_NAME} + PUBLIC + ${APP_COMMON_LINK_LIBRARIES} + sln_voice::app::ffva::xk_voice_l71 + sln_voice::app::ffva::ap::${FFVA_AP} + sln_voice::app::asr::Cyberon + ) + target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) + unset(TARGET_NAME) + + set(TARGET_NAME tile1_example_ffva_int_cyberon_${FFVA_AP}) + add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL) + target_sources(${TARGET_NAME} PUBLIC ${APP_SOURCES}) + target_include_directories(${TARGET_NAME} PUBLIC ${APP_INCLUDES}) + target_compile_definitions(${TARGET_NAME} + PUBLIC + ${FFVA_INT_CYBERON_COMPILE_DEFINITIONS} + THIS_XCORE_TILE=1 + ) + target_compile_options(${TARGET_NAME} PRIVATE ${APP_COMPILER_FLAGS}) + target_link_libraries(${TARGET_NAME} + PUBLIC + ${APP_COMMON_LINK_LIBRARIES} + sln_voice::app::ffva::xk_voice_l71 + sln_voice::app::ffva::ap::${FFVA_AP} + sln_voice::app::asr::Cyberon + ) + target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) + unset(TARGET_NAME) + + #********************** + # Merge binaries + #********************** + merge_binaries(example_ffva_int_cyberon_${FFVA_AP} tile0_example_ffva_int_cyberon_${FFVA_AP} tile1_example_ffva_int_cyberon_${FFVA_AP} 1) + + #********************** + # Create run and debug targets + #********************** + create_run_target(example_ffva_int_cyberon_${FFVA_AP}) + create_debug_target(example_ffva_int_cyberon_${FFVA_AP}) + + #********************** + # Create data partition support targets + #********************** + set(TARGET_NAME example_ffva_int_cyberon_${FFVA_AP}) + set(DATA_PARTITION_FILE ${TARGET_NAME}_data_partition.bin) + set(MODEL_FILE ${TARGET_NAME}_model.bin) + set(FATFS_FILE ${TARGET_NAME}_fat.fs) + set(FLASH_CAL_FILE ${LIB_QSPI_FAST_READ_ROOT_PATH}/lib_qspi_fast_read/calibration_pattern_nibble_swap.bin) + #set(FATFS_CONTENTS_DIR ${TARGET_NAME}_fatmktmp) + + + add_custom_target(${MODEL_FILE} ALL + COMMAND ${CMAKE_COMMAND} -E copy ${CYBERON_COMMAND_NET_FILE} ${MODEL_FILE} + COMMENT + "Copy Cyberon NET file" + VERBATIM + ) + + create_filesystem_target( + #[[ Target ]] ${TARGET_NAME} + #[[ Input Directory ]] ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/${MODEL_LANGUAGE} + #[[ Image Size ]] ${FILESYSTEM_SIZE_BYTES} + ) + + add_custom_command( + OUTPUT ${DATA_PARTITION_FILE} + COMMAND ${CMAKE_COMMAND} -E rm -f ${DATA_PARTITION_FILE} + COMMAND datapartition_mkimage -v -b 1 + -i ${FLASH_CAL_FILE}:${CALIBRATION_PATTERN_DATA_PARTITION_OFFSET} ${FATFS_FILE}:${FILESYSTEM_DATA_PARTITION_OFFSET} ${MODEL_FILE}:${MODEL_DATA_PARTITION_OFFSET} + -o ${DATA_PARTITION_FILE} + DEPENDS + ${MODEL_FILE} + make_fs_${TARGET_NAME} + ${FLASH_CAL_FILE} + COMMENT + "Create data partition" + VERBATIM + ) + + set(DATA_PARTITION_FILE_LIST + ${DATA_PARTITION_FILE} + ${MODEL_FILE} + ${FATFS_FILE} + ${FLASH_CAL_FILE} + ) + + set(DATA_PARTITION_DEPENDS_LIST + ${DATA_PARTITION_FILE} + ${MODEL_FILE} + make_fs_${TARGET_NAME} + ) + + # The list of files to copy and the dependency list for populating + # the data partition folder are identical. + create_data_partition_directory( + #[[ Target ]] ${TARGET_NAME} + #[[ Copy Files ]] "${DATA_PARTITION_FILE_LIST}" + #[[ Dependencies ]] "${DATA_PARTITION_DEPENDS_LIST}" + ) + + create_flash_app_target( + #[[ Target ]] ${TARGET_NAME} + #[[ Boot Partition Size ]] ${BOOT_PARTITION_SIZE} + #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} + #[[ Dependencies ]] ${DATA_PARTITION_FILE} + ) + + unset(DATA_PARTITION_FILE_LIST) + unset(DATA_PARTITION_DEPENDS_LIST) + + #add_custom_target( + # ${FATFS_FILE} ALL + # COMMAND ${CMAKE_COMMAND} -E rm -rf ${FATFS_CONTENTS_DIR}/fs/ + # COMMAND ${CMAKE_COMMAND} -E make_directory ${FATFS_CONTENTS_DIR}/fs/ + # COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/demo.txt ${FATFS_CONTENTS_DIR}/fs/ + # COMMAND fatfs_mkimage --input=${FATFS_CONTENTS_DIR} --output=${FATFS_FILE} + # COMMENT + # "Create filesystem" + # VERBATIM + #) + + #set_target_properties(${FATFS_FILE} PROPERTIES + # ADDITIONAL_CLEAN_FILES ${FATFS_CONTENTS_DIR} + #) + + # The filesystem is the only component in the data partition, copy it to + # the assocated data partition file which is required for CI. + #add_custom_command( + # OUTPUT ${DATA_PARTITION_FILE} + # COMMAND ${CMAKE_COMMAND} -E copy ${FATFS_FILE} ${DATA_PARTITION_FILE} + # DEPENDS + # ${FATFS_FILE} + # COMMENT + # "Create data partition" + # VERBATIM + #) + + #list(APPEND DATA_PARTITION_FILE_LIST + # ${FATFS_FILE} + # ${DATA_PARTITION_FILE} + #) + + #create_data_partition_directory( + # #[[ Target ]] ${TARGET_NAME} + # #[[ Copy Files ]] "${DATA_PARTITION_FILE_LIST}" + # #[[ Dependencies ]] "${DATA_PARTITION_FILE_LIST}" + #) + + #create_flash_app_target( + # #[[ Target ]] ${TARGET_NAME} + # #[[ Boot Partition Size ]] 0x100000 + # #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} + # #[[ Dependencies ]] ${DATA_PARTITION_FILE} + #) + + #unset(DATA_PARTITION_FILE_LIST) +endforeach() From bf04aba20fd21111493c866619af3289343762ad Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Mar 2024 15:04:26 +0000 Subject: [PATCH 024/288] WW working --- examples/ffva/src/main.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 16904495..b823f79d 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -216,18 +216,16 @@ int audio_pipeline_output(void *output_app_data, 6); #endif #if appconfINTENT_ENABLED - int32_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; - for (int i = 0; i < frame_count; i++) { - ww_samples[i] = ((int32_t(*)[2])output_audio_frames)[i][ASR_CHANNEL]; + int32_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; + int32_t *tmpptr_1 = (int32_t *)output_audio_frames; + for (int j=0; j Date: Mon, 4 Mar 2024 15:11:09 +0000 Subject: [PATCH 025/288] Remove warning --- examples/ffva/src/main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index b823f79d..c4a24157 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -218,10 +218,9 @@ int audio_pipeline_output(void *output_app_data, #if appconfINTENT_ENABLED int32_t ww_samples[appconfAUDIO_PIPELINE_FRAME_ADVANCE]; - int32_t *tmpptr_1 = (int32_t *)output_audio_frames; for (int j=0; j Date: Mon, 4 Mar 2024 15:45:01 +0000 Subject: [PATCH 026/288] Compile intent handler --- examples/ffd/src/intent_engine/intent_engine.c | 2 +- .../ffd/src/intent_handler/audio_response/audio_response.c | 4 ++-- examples/ffd/src/intent_handler/intent_handler.c | 4 ++-- examples/ffva/ffva.cmake | 3 ++- examples/ffva/src/main.c | 2 -- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/ffd/src/intent_engine/intent_engine.c b/examples/ffd/src/intent_engine/intent_engine.c index 788d8e55..2cf7dda9 100644 --- a/examples/ffd/src/intent_engine/intent_engine.c +++ b/examples/ffd/src/intent_engine/intent_engine.c @@ -161,7 +161,7 @@ void intent_engine_task(void *args) // so, we need to check if an audio response is playing and skip to the next // audio frame because the playback may trigger the ASR. //TODO: Enable this line - //if (intent_handler_response_playing()) continue; + if (intent_handler_response_playing()) continue; asr_error = asr_process(asr_ctx, buf_short, SAMPLES_PER_ASR); if (asr_error == ASR_EVALUATION_EXPIRED) { diff --git a/examples/ffd/src/intent_handler/audio_response/audio_response.c b/examples/ffd/src/intent_handler/audio_response/audio_response.c index f28024fe..b95f9790 100644 --- a/examples/ffd/src/intent_handler/audio_response/audio_response.c +++ b/examples/ffd/src/intent_handler/audio_response/audio_response.c @@ -13,7 +13,7 @@ /* App headers */ #include "app_conf.h" #include "platform/driver_instances.h" -#include "intent_handler/intent_handler.h" +#include "intent_handler.h" #include "audio_response.h" #include "fs_support.h" #include "ff.h" @@ -95,7 +95,7 @@ void audio_response_play(int32_t id) { i2s_audio[(2*i)+0] = (int32_t) file_audio[i] << 16; i2s_audio[(2*i)+1] = (int32_t) file_audio[i] << 16; } - + rtos_i2s_tx(i2s_ctx, (int32_t*) i2s_audio, appconfAUDIO_PIPELINE_FRAME_ADVANCE, diff --git a/examples/ffd/src/intent_handler/intent_handler.c b/examples/ffd/src/intent_handler/intent_handler.c index 1110668e..7bd96b9d 100644 --- a/examples/ffd/src/intent_handler/intent_handler.c +++ b/examples/ffd/src/intent_handler/intent_handler.c @@ -1,6 +1,6 @@ // Copyright 2022-2023 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. -fddsfsdf + /* STD headers */ #include #include @@ -18,7 +18,7 @@ fddsfsdf #include "fs_support.h" #include "ff.h" #include "audio_response.h" -#include "intent_engine/intent_engine.h" +#include "intent_engine.h" #define WAKEUP_LOW (appconfINTENT_WAKEUP_EDGE_TYPE) #define WAKEUP_HIGH (appconfINTENT_WAKEUP_EDGE_TYPE == 0) diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index d216e4bd..9464d743 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -5,12 +5,13 @@ set(MODEL_LANGUAGE "english_usa") set(CYBERON_COMMAND_NET_FILE "${CMAKE_CURRENT_LIST_DIR}/../ffd/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap") -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine/*.c) +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine/*.c ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler/*.c) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/usb ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler + ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler/audio_response ) include(${CMAKE_CURRENT_LIST_DIR}/bsp_config/bsp_config.cmake) diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index c4a24157..5bc552ce 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -28,8 +28,6 @@ #include "print.h" #include "gpio_test/gpio_test.h" -#define ASR_CHANNEL (0) - volatile int mic_from_usb = appconfMIC_SRC_DEFAULT; volatile int aec_ref_source = appconfAEC_REF_DEFAULT; From b5761a30692883d2207fad4e3dee3a6dd944b88b Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Mar 2024 16:26:08 +0000 Subject: [PATCH 027/288] Fix intent handler code --- examples/ffd/src/gpio_ctrl/gpi_ctrl.c | 2 +- examples/ffd/src/gpio_ctrl/leds.c | 2 +- examples/ffd/src/intent_engine/intent_engine.c | 9 ++++----- examples/ffva/ffva.cmake | 7 ++++++- examples/ffva/src/FreeRTOSConfig.h | 2 +- examples/ffva/src/app_conf.h | 3 ++- examples/ffva/src/main.c | 17 ++++++++++++++--- 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/examples/ffd/src/gpio_ctrl/gpi_ctrl.c b/examples/ffd/src/gpio_ctrl/gpi_ctrl.c index 0cfcc43d..c5d25796 100644 --- a/examples/ffd/src/gpio_ctrl/gpi_ctrl.c +++ b/examples/ffd/src/gpio_ctrl/gpi_ctrl.c @@ -7,7 +7,7 @@ #include "FreeRTOS.h" #include "platform/app_pll_ctrl.h" -#include "gpio_ctrl/gpi_ctrl.h" +#include "gpi_ctrl.h" __attribute__((weak)) void gpio_gpi_toggled_cb(uint32_t gpio_val) diff --git a/examples/ffd/src/gpio_ctrl/leds.c b/examples/ffd/src/gpio_ctrl/leds.c index 3e329c60..1c0e47cf 100644 --- a/examples/ffd/src/gpio_ctrl/leds.c +++ b/examples/ffd/src/gpio_ctrl/leds.c @@ -12,7 +12,7 @@ /* App headers */ #include "app_conf.h" -#include "gpio_ctrl/leds.h" +#include "leds.h" #include "platform/driver_instances.h" diff --git a/examples/ffd/src/intent_engine/intent_engine.c b/examples/ffd/src/intent_engine/intent_engine.c index 2cf7dda9..d186faec 100644 --- a/examples/ffd/src/intent_engine/intent_engine.c +++ b/examples/ffd/src/intent_engine/intent_engine.c @@ -18,7 +18,7 @@ #include "intent_handler.h" #include "asr.h" #include "device_memory_impl.h" -//#include "gpio_ctrl/leds.h" +#include "leds.h" #if ON_TILE(ASR_TILE_NO) @@ -109,7 +109,7 @@ static void timeout_event_handler(TimerHandle_t pxTimer) timeout_event &= ~TIMEOUT_EVENT_INTENT; intent_engine_play_response(STOP_LISTENING_SOUND_WAV_ID); //TODO: Enable this line - //led_indicate_waiting(); + led_indicate_waiting(); intent_state = STATE_EXPECTING_WAKEWORD; } } @@ -160,13 +160,12 @@ void intent_engine_task(void *args) // this application does not support barge-in // so, we need to check if an audio response is playing and skip to the next // audio frame because the playback may trigger the ASR. - //TODO: Enable this line if (intent_handler_response_playing()) continue; asr_error = asr_process(asr_ctx, buf_short, SAMPLES_PER_ASR); if (asr_error == ASR_EVALUATION_EXPIRED) { //TODO: Enable this line - //led_indicate_end_of_eval(); + led_indicate_end_of_eval(); continue; } if (asr_error != ASR_OK) continue; @@ -184,7 +183,7 @@ void intent_engine_task(void *args) #else if (intent_state == STATE_EXPECTING_WAKEWORD && IS_KEYWORD(word_id)) { //TODO: Enable this line - //led_indicate_listening(); + led_indicate_listening(); xTimerStart(int_eng_tmr, 0); intent_engine_process_asr_result(word_id); intent_state = STATE_EXPECTING_COMMAND; diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index 9464d743..90f53d1a 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -5,10 +5,15 @@ set(MODEL_LANGUAGE "english_usa") set(CYBERON_COMMAND_NET_FILE "${CMAKE_CURRENT_LIST_DIR}/../ffd/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap") -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine/*.c ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler/*.c) +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c + ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine/*.c + ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler/*.c + ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/gpio_ctrl/*.c) + set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/usb + ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/gpio_ctrl ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_engine ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/intent_handler/audio_response diff --git a/examples/ffva/src/FreeRTOSConfig.h b/examples/ffva/src/FreeRTOSConfig.h index 97312deb..505389da 100644 --- a/examples/ffva/src/FreeRTOSConfig.h +++ b/examples/ffva/src/FreeRTOSConfig.h @@ -46,7 +46,7 @@ your application. */ #define configSUPPORT_STATIC_ALLOCATION 0 #define configSUPPORT_DYNAMIC_ALLOCATION 1 #if ON_TILE(0) -#define configTOTAL_HEAP_SIZE 150*1024 +#define configTOTAL_HEAP_SIZE 160*1024 #endif #if ON_TILE(1) #define configTOTAL_HEAP_SIZE 150*1024 diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 63d7590d..2c20f8eb 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -221,6 +221,7 @@ #define appconfUSB_AUDIO_TASK_PRIORITY (configMAX_PRIORITIES/2 + 1) #define appconfSPI_TASK_PRIORITY (configMAX_PRIORITIES/2 + 1) #define appconfQSPI_FLASH_TASK_PRIORITY (configMAX_PRIORITIES/2 + 0) -#define appconfWW_TASK_PRIORITY (configMAX_PRIORITIES-2) +#define appconfINTENT_MODEL_RUNNER_TASK_PRIORITY (configMAX_PRIORITIES - 2) +#define appconfLED_TASK_PRIORITY (configMAX_PRIORITIES / 2 - 1) #endif /* APP_CONF_H_ */ diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 5bc552ce..91341067 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -26,6 +26,8 @@ #include "intent_handler.h" #include "fs_support.h" #include "print.h" +#include "gpi_ctrl.h" +#include "leds.h" #include "gpio_test/gpio_test.h" volatile int mic_from_usb = appconfMIC_SRC_DEFAULT; @@ -343,16 +345,25 @@ void startup_task(void *arg) gpio_test(gpio_ctx_t0); #endif +#if ON_TILE(0) + led_task_create(appconfLED_TASK_PRIORITY, NULL); +#endif + +#if ON_TILE(1) + gpio_gpi_init(gpio_ctx_t0); +#endif + #if ON_TILE(FS_TILE_NO) rtos_fatfs_init(qspi_flash_ctx); - //rtos_dfu_image_print_debug(dfu_image_ctx); // Setup flash low-level mode // NOTE: must call rtos_qspi_flash_fast_read_shutdown_ll to use non low-level mode calls rtos_qspi_flash_fast_read_setup_ll(qspi_flash_ctx); #endif -//vTaskDelay(pdMS_TO_TICKS(100)); + #if appconfINTENT_ENABLED && ON_TILE(ASR_TILE_NO) - intent_engine_task_create(appconfWW_TASK_PRIORITY); + QueueHandle_t q_intent = xQueueCreate(appconfINTENT_QUEUE_LEN, sizeof(int32_t)); + intent_handler_create(appconfINTENT_MODEL_RUNNER_TASK_PRIORITY, q_intent); + intent_engine_create(appconfINTENT_MODEL_RUNNER_TASK_PRIORITY, q_intent); #endif #if appconfINTENT_ENABLED && !ON_TILE(ASR_TILE_NO) From 1b8a9cf4f173f448f7cf149191425fa0c4b6a9d3 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 4 Mar 2024 20:52:10 +0000 Subject: [PATCH 028/288] Move intent files to asr module --- examples/ffd/ffd_cyberon.cmake | 4 +- examples/ffd/ffd_sensory.cmake | 4 +- ...Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap | Bin 0 -> 163116 bytes examples/ffva/ffva.cmake | 8 --- examples/ffva/ffva_int_cyberon.cmake | 5 ++ examples/ffva/ffva_int_dev.cmake | 3 + modules/asr/CMakeLists.txt | 68 ++++++++++++++++++ .../asr}/intent_engine/intent_engine.c | 0 .../asr}/intent_engine/intent_engine.h | 0 .../asr}/intent_engine/intent_engine_io.c | 0 .../intent_engine/intent_engine_support.c | 0 .../audio_response/audio_response.c | 0 .../audio_response/audio_response.h | 0 .../intent_handler/audio_response/dr_wav.h | 0 .../audio_response/dr_wav_freertos_port.h | 0 .../asr}/intent_handler/intent_handler.c | 0 .../asr}/intent_handler/intent_handler.h | 0 17 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 examples/ffva/asr/model/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap rename {examples/ffd/src => modules/asr}/intent_engine/intent_engine.c (100%) rename {examples/ffd/src => modules/asr}/intent_engine/intent_engine.h (100%) rename {examples/ffd/src => modules/asr}/intent_engine/intent_engine_io.c (100%) rename {examples/ffd/src => modules/asr}/intent_engine/intent_engine_support.c (100%) rename {examples/ffd/src => modules/asr}/intent_handler/audio_response/audio_response.c (100%) rename {examples/ffd/src => modules/asr}/intent_handler/audio_response/audio_response.h (100%) rename {examples/ffd/src => modules/asr}/intent_handler/audio_response/dr_wav.h (100%) rename {examples/ffd/src => modules/asr}/intent_handler/audio_response/dr_wav_freertos_port.h (100%) rename {examples/ffd/src => modules/asr}/intent_handler/intent_handler.c (100%) rename {examples/ffd/src => modules/asr}/intent_handler/intent_handler.h (100%) diff --git a/examples/ffd/ffd_cyberon.cmake b/examples/ffd/ffd_cyberon.cmake index 1cf7db3b..a099d034 100644 --- a/examples/ffd/ffd_cyberon.cmake +++ b/examples/ffd/ffd_cyberon.cmake @@ -13,8 +13,6 @@ set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/gpio_ctrl ${CMAKE_CURRENT_LIST_DIR}/src/intent_engine - ${CMAKE_CURRENT_LIST_DIR}/src/intent_handler - ${CMAKE_CURRENT_LIST_DIR}/src/intent_handler/audio_response ${CMAKE_CURRENT_LIST_DIR}/src/power ) set(RTOS_CONF_INCLUDES @@ -88,6 +86,8 @@ set(APP_LINK_OPTIONS set(APP_COMMON_LINK_LIBRARIES sln_voice::app::ffd::ap sln_voice::app::asr::Cyberon + sln_voice::app::asr::intent_engine + sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 ) diff --git a/examples/ffd/ffd_sensory.cmake b/examples/ffd/ffd_sensory.cmake index 4bc76690..f77f5b85 100644 --- a/examples/ffd/ffd_sensory.cmake +++ b/examples/ffd/ffd_sensory.cmake @@ -25,8 +25,6 @@ set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/gpio_ctrl ${CMAKE_CURRENT_LIST_DIR}/src/intent_engine - ${CMAKE_CURRENT_LIST_DIR}/src/intent_handler - ${CMAKE_CURRENT_LIST_DIR}/src/intent_handler/audio_response ${CMAKE_CURRENT_LIST_DIR}/src/power ) set(RTOS_CONF_INCLUDES @@ -100,6 +98,8 @@ set(APP_LINK_OPTIONS set(APP_COMMON_LINK_LIBRARIES sln_voice::app::ffd::ap sln_voice::app::asr::sensory + sln_voice::app::asr::intent_engine + sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 ) diff --git a/examples/ffva/asr/model/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap b/examples/ffva/asr/model/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap new file mode 100644 index 0000000000000000000000000000000000000000..5cc86af726679e6ca051b9360b415a84167f2771 GIT binary patch literal 163116 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 Date: Mon, 4 Mar 2024 21:04:41 +0000 Subject: [PATCH 029/288] Move gpio ctrl files to asr module --- examples/ffd/ffd_cyberon.cmake | 1 + examples/ffd/ffd_sensory.cmake | 1 + examples/ffva/ffva.cmake | 4 +-- examples/ffva/ffva_int_cyberon.cmake | 2 ++ modules/asr/CMakeLists.txt | 34 ++++++++++++++++++- .../src => modules/asr}/gpio_ctrl/gpi_ctrl.c | 0 .../src => modules/asr}/gpio_ctrl/gpi_ctrl.h | 0 .../ffd/src => modules/asr}/gpio_ctrl/leds.c | 0 .../ffd/src => modules/asr}/gpio_ctrl/leds.h | 0 9 files changed, 38 insertions(+), 4 deletions(-) rename {examples/ffd/src => modules/asr}/gpio_ctrl/gpi_ctrl.c (100%) rename {examples/ffd/src => modules/asr}/gpio_ctrl/gpi_ctrl.h (100%) rename {examples/ffd/src => modules/asr}/gpio_ctrl/leds.c (100%) rename {examples/ffd/src => modules/asr}/gpio_ctrl/leds.h (100%) diff --git a/examples/ffd/ffd_cyberon.cmake b/examples/ffd/ffd_cyberon.cmake index a099d034..c61fd713 100644 --- a/examples/ffd/ffd_cyberon.cmake +++ b/examples/ffd/ffd_cyberon.cmake @@ -86,6 +86,7 @@ set(APP_LINK_OPTIONS set(APP_COMMON_LINK_LIBRARIES sln_voice::app::ffd::ap sln_voice::app::asr::Cyberon + sln_voice::app::asr::gpio_ctrl sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 diff --git a/examples/ffd/ffd_sensory.cmake b/examples/ffd/ffd_sensory.cmake index f77f5b85..4935bc19 100644 --- a/examples/ffd/ffd_sensory.cmake +++ b/examples/ffd/ffd_sensory.cmake @@ -98,6 +98,7 @@ set(APP_LINK_OPTIONS set(APP_COMMON_LINK_LIBRARIES sln_voice::app::ffd::ap sln_voice::app::asr::sensory + sln_voice::app::asr::gpio_ctrl sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler sln_voice::app::ffd::xk_voice_l71 diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index 8037a3a8..e4e5f762 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -2,13 +2,11 @@ # Gather Sources #********************** -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c - ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/gpio_ctrl/*.c) +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/usb - ${CMAKE_CURRENT_LIST_DIR}/../ffd/src/gpio_ctrl ) include(${CMAKE_CURRENT_LIST_DIR}/bsp_config/bsp_config.cmake) diff --git a/examples/ffva/ffva_int_cyberon.cmake b/examples/ffva/ffva_int_cyberon.cmake index 58a62a25..fe81671a 100644 --- a/examples/ffva/ffva_int_cyberon.cmake +++ b/examples/ffva/ffva_int_cyberon.cmake @@ -68,6 +68,7 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) sln_voice::app::ffva::xk_voice_l71 sln_voice::app::ffva::ap::${FFVA_AP} sln_voice::app::asr::Cyberon + sln_voice::app::asr::gpio_ctrl sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler @@ -91,6 +92,7 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) sln_voice::app::ffva::xk_voice_l71 sln_voice::app::ffva::ap::${FFVA_AP} sln_voice::app::asr::Cyberon + sln_voice::app::asr::gpio_ctrl sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler ) diff --git a/modules/asr/CMakeLists.txt b/modules/asr/CMakeLists.txt index bdecc07f..c9a9c619 100644 --- a/modules/asr/CMakeLists.txt +++ b/modules/asr/CMakeLists.txt @@ -78,7 +78,39 @@ target_compile_definitions(asr_Cyberon add_library(sln_voice::app::asr::Cyberon ALIAS asr_Cyberon) ##***************************** -## Create Intent Handler target +## Create GPIO Control target +##***************************** + +add_library(asr_gpio_ctrl INTERFACE) + +target_sources(asr_gpio_ctrl + INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/gpio_ctrl/gpi_ctrl.c + ${CMAKE_CURRENT_LIST_DIR}/gpio_ctrl/leds.c +) +target_include_directories(asr_gpio_ctrl + INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/gpio_ctrl +) +## suppress all linker warnings +target_link_options(asr_gpio_ctrl + INTERFACE + -Wl,-w +) + +target_compile_definitions(asr_gpio_ctrl + INTERFACE +) + +##********************************************* +## Create aliases for sln_voice example designs +##********************************************* + +add_library(sln_voice::app::asr::gpio_ctrl ALIAS asr_gpio_ctrl) + +##***************************** +## Create Intent Engine target ##***************************** add_library(asr_intent_engine INTERFACE) diff --git a/examples/ffd/src/gpio_ctrl/gpi_ctrl.c b/modules/asr/gpio_ctrl/gpi_ctrl.c similarity index 100% rename from examples/ffd/src/gpio_ctrl/gpi_ctrl.c rename to modules/asr/gpio_ctrl/gpi_ctrl.c diff --git a/examples/ffd/src/gpio_ctrl/gpi_ctrl.h b/modules/asr/gpio_ctrl/gpi_ctrl.h similarity index 100% rename from examples/ffd/src/gpio_ctrl/gpi_ctrl.h rename to modules/asr/gpio_ctrl/gpi_ctrl.h diff --git a/examples/ffd/src/gpio_ctrl/leds.c b/modules/asr/gpio_ctrl/leds.c similarity index 100% rename from examples/ffd/src/gpio_ctrl/leds.c rename to modules/asr/gpio_ctrl/leds.c diff --git a/examples/ffd/src/gpio_ctrl/leds.h b/modules/asr/gpio_ctrl/leds.h similarity index 100% rename from examples/ffd/src/gpio_ctrl/leds.h rename to modules/asr/gpio_ctrl/leds.h From 466ee30a3afcd7c9118081698cbf287e653943ce Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 12:03:52 +0000 Subject: [PATCH 030/288] Quality checks --- CHANGELOG.rst | 7 +++++++ .../XCORE-AI-EXPLORER/platform/platform_conf.h | 2 +- .../bsp_config/XK_VOICE_L71/platform/driver_instances.c | 2 +- .../bsp_config/XK_VOICE_L71/platform/driver_instances.h | 2 +- .../bsp_config/XK_VOICE_L71/platform/platform_init.c | 9 ++++++--- .../bsp_config/XK_VOICE_L71/platform/platform_start.c | 2 +- examples/ffva/src/FreeRTOSConfig.h | 4 ++-- examples/ffva/src/app_conf.h | 2 +- examples/ffva/src/device_memory_impl.c | 2 +- examples/ffva/src/device_memory_impl.h | 2 +- examples/ffva/src/main.c | 2 +- modules/asr/gpio_ctrl/gpi_ctrl.c | 2 +- modules/asr/gpio_ctrl/gpi_ctrl.h | 2 +- modules/asr/gpio_ctrl/leds.c | 2 +- modules/asr/gpio_ctrl/leds.h | 2 +- modules/asr/intent_engine/intent_engine.c | 5 +---- modules/asr/intent_engine/intent_engine.h | 2 +- modules/asr/intent_engine/intent_engine_io.c | 2 +- modules/asr/intent_engine/intent_engine_support.c | 2 +- .../asr/intent_handler/audio_response/audio_response.c | 2 +- .../asr/intent_handler/audio_response/audio_response.h | 2 +- modules/asr/intent_handler/audio_response/dr_wav.h | 2 +- .../intent_handler/audio_response/dr_wav_freertos_port.h | 2 +- modules/asr/intent_handler/intent_handler.c | 2 +- modules/asr/intent_handler/intent_handler.h | 2 +- .../reference/fixed_delay/audio_pipeline_t0.c | 2 +- .../reference/fixed_delay/audio_pipeline_t1.c | 2 +- settings.yml | 2 +- 28 files changed, 40 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 374ffb12..75743296 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,13 @@ XCORE-VOICE change log ====================== +2.3.0 +----- + + * ADDED: FFVA INT example with Cyberon speech recognition engine and model + (DSpotter v2.2.18.0). + + 2.2.0 ----- diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h index fd593d18..45f9ad25 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef PLATFORM_CONF_H_ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c index 32fab8a0..7eb25cc1 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "platform/driver_instances.h" diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h index 5807bfd7..83da5c08 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef DRIVER_INSTANCES_H_ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index b1ffc658..ec5ef91e 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* System headers */ @@ -25,6 +25,10 @@ static void mclk_init(chanend_t other_tile_c) static void flash_init(void) { #if ON_TILE(FLASH_TILE_NO) +// Flash fast read is used for reading the WW model in the INT device, +// normal read is used for the DFU in the UA device. +// The two read mechanisms are not compatible, so we must choose them at initialization. +#if !appconfUSB_ENABLED: rtos_qspi_flash_fast_read_init( qspi_flash_ctx, FLASH_CLKBLK, @@ -35,8 +39,7 @@ static void flash_init(void) qspi_fast_flash_read_transfer_nibble_swap, 3, QSPI_FLASH_CALIBRATION_ADDRESS); -#endif -#if 0 //ON_TILE(FLASH_TILE_NO) +#else fl_QuadDeviceSpec qspi_spec = BOARD_QSPI_SPEC; fl_QSPIPorts qspi_ports = { .qspiCS = PORT_SQI_CS, diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index 8e51798d..dc51981a 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* System headers */ diff --git a/examples/ffva/src/FreeRTOSConfig.h b/examples/ffva/src/FreeRTOSConfig.h index 505389da..4e081461 100644 --- a/examples/ffva/src/FreeRTOSConfig.h +++ b/examples/ffva/src/FreeRTOSConfig.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef FREERTOS_CONFIG_H #define FREERTOS_CONFIG_H @@ -49,7 +49,7 @@ your application. */ #define configTOTAL_HEAP_SIZE 160*1024 #endif #if ON_TILE(1) -#define configTOTAL_HEAP_SIZE 150*1024 +#define configTOTAL_HEAP_SIZE 128*1024 #endif #define configAPPLICATION_ALLOCATED_HEAP 0 diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 2c20f8eb..6e133fd0 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef APP_CONF_H_ diff --git a/examples/ffva/src/device_memory_impl.c b/examples/ffva/src/device_memory_impl.c index 71fd30ca..4fc09c20 100644 --- a/examples/ffva/src/device_memory_impl.c +++ b/examples/ffva/src/device_memory_impl.c @@ -1,4 +1,4 @@ -// Copyright 2023 XMOS LIMITED. +// Copyright 2023-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/examples/ffva/src/device_memory_impl.h b/examples/ffva/src/device_memory_impl.h index 0822a303..ea8735cb 100644 --- a/examples/ffva/src/device_memory_impl.h +++ b/examples/ffva/src/device_memory_impl.h @@ -1,4 +1,4 @@ -// Copyright 2023 XMOS LIMITED. +// Copyright 2023-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef DEVICE_MEMORY_IMPL_H #define DEVICE_MEMORY_IMPL_H diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 91341067..02814d68 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -1,4 +1,4 @@ -// Copyright 2020-2023 XMOS LIMITED. +// Copyright 2020-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/modules/asr/gpio_ctrl/gpi_ctrl.c b/modules/asr/gpio_ctrl/gpi_ctrl.c index c5d25796..847fcaa8 100644 --- a/modules/asr/gpio_ctrl/gpi_ctrl.c +++ b/modules/asr/gpio_ctrl/gpi_ctrl.c @@ -1,4 +1,4 @@ -// Copyright 2021-2022 XMOS LIMITED. +// Copyright 2021-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/modules/asr/gpio_ctrl/gpi_ctrl.h b/modules/asr/gpio_ctrl/gpi_ctrl.h index c278588f..7165edc5 100644 --- a/modules/asr/gpio_ctrl/gpi_ctrl.h +++ b/modules/asr/gpio_ctrl/gpi_ctrl.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef GPI_CTRL_H_ diff --git a/modules/asr/gpio_ctrl/leds.c b/modules/asr/gpio_ctrl/leds.c index 1c0e47cf..66e2b9e2 100644 --- a/modules/asr/gpio_ctrl/leds.c +++ b/modules/asr/gpio_ctrl/leds.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/modules/asr/gpio_ctrl/leds.h b/modules/asr/gpio_ctrl/leds.h index 97685ca9..9f739b64 100644 --- a/modules/asr/gpio_ctrl/leds.h +++ b/modules/asr/gpio_ctrl/leds.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef LEDS_H_ diff --git a/modules/asr/intent_engine/intent_engine.c b/modules/asr/intent_engine/intent_engine.c index d186faec..5459803a 100644 --- a/modules/asr/intent_engine/intent_engine.c +++ b/modules/asr/intent_engine/intent_engine.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ @@ -108,7 +108,6 @@ static void timeout_event_handler(TimerHandle_t pxTimer) if (timeout_event & TIMEOUT_EVENT_INTENT) { timeout_event &= ~TIMEOUT_EVENT_INTENT; intent_engine_play_response(STOP_LISTENING_SOUND_WAV_ID); - //TODO: Enable this line led_indicate_waiting(); intent_state = STATE_EXPECTING_WAKEWORD; } @@ -164,7 +163,6 @@ void intent_engine_task(void *args) asr_error = asr_process(asr_ctx, buf_short, SAMPLES_PER_ASR); if (asr_error == ASR_EVALUATION_EXPIRED) { - //TODO: Enable this line led_indicate_end_of_eval(); continue; } @@ -182,7 +180,6 @@ void intent_engine_task(void *args) intent_engine_process_asr_result(word_id); #else if (intent_state == STATE_EXPECTING_WAKEWORD && IS_KEYWORD(word_id)) { - //TODO: Enable this line led_indicate_listening(); xTimerStart(int_eng_tmr, 0); intent_engine_process_asr_result(word_id); diff --git a/modules/asr/intent_engine/intent_engine.h b/modules/asr/intent_engine/intent_engine.h index 1cf46d39..81b03044 100644 --- a/modules/asr/intent_engine/intent_engine.h +++ b/modules/asr/intent_engine/intent_engine.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef INTENT_ENGINE_H_ diff --git a/modules/asr/intent_engine/intent_engine_io.c b/modules/asr/intent_engine/intent_engine_io.c index 9077af51..f10212ee 100644 --- a/modules/asr/intent_engine/intent_engine_io.c +++ b/modules/asr/intent_engine/intent_engine_io.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/modules/asr/intent_engine/intent_engine_support.c b/modules/asr/intent_engine/intent_engine_support.c index a21497a8..3fc1a2af 100644 --- a/modules/asr/intent_engine/intent_engine_support.c +++ b/modules/asr/intent_engine/intent_engine_support.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/modules/asr/intent_handler/audio_response/audio_response.c b/modules/asr/intent_handler/audio_response/audio_response.c index b95f9790..1989059f 100644 --- a/modules/asr/intent_handler/audio_response/audio_response.c +++ b/modules/asr/intent_handler/audio_response/audio_response.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/modules/asr/intent_handler/audio_response/audio_response.h b/modules/asr/intent_handler/audio_response/audio_response.h index 233bf0a9..97ed1e82 100644 --- a/modules/asr/intent_handler/audio_response/audio_response.h +++ b/modules/asr/intent_handler/audio_response/audio_response.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef AUDIO_RESPONSE_H_ diff --git a/modules/asr/intent_handler/audio_response/dr_wav.h b/modules/asr/intent_handler/audio_response/dr_wav.h index 94edf4ae..d8da0c09 100644 --- a/modules/asr/intent_handler/audio_response/dr_wav.h +++ b/modules/asr/intent_handler/audio_response/dr_wav.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. diff --git a/modules/asr/intent_handler/audio_response/dr_wav_freertos_port.h b/modules/asr/intent_handler/audio_response/dr_wav_freertos_port.h index e530ab7c..26c51e72 100644 --- a/modules/asr/intent_handler/audio_response/dr_wav_freertos_port.h +++ b/modules/asr/intent_handler/audio_response/dr_wav_freertos_port.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef DR_WAV_FREERTOS_PORT_H_ diff --git a/modules/asr/intent_handler/intent_handler.c b/modules/asr/intent_handler/intent_handler.c index 7bd96b9d..cfa4cee6 100644 --- a/modules/asr/intent_handler/intent_handler.c +++ b/modules/asr/intent_handler/intent_handler.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/modules/asr/intent_handler/intent_handler.h b/modules/asr/intent_handler/intent_handler.h index 4d3d8b78..5fd3f210 100644 --- a/modules/asr/intent_handler/intent_handler.h +++ b/modules/asr/intent_handler/intent_handler.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef INTENT_HANDLER_H_ diff --git a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c index 2ec19776..60d01f6f 100644 --- a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c +++ b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t0.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c index 44f262c2..670168dd 100644 --- a/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c +++ b/modules/audio_pipelines/reference/fixed_delay/audio_pipeline_t1.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/settings.yml b/settings.yml index 49014ccb..3f19710d 100644 --- a/settings.yml +++ b/settings.yml @@ -1,7 +1,7 @@ --- project: sln_voice title: XCORE-VOICE SOLUTION -version: 2.2.0 +version: 2.3.0 documentation: exclude_patterns_path: doc/exclude_patterns.inc From 1411f2a177a77f3fb0db32120ebdab1332a6d2c6 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 13:21:35 +0000 Subject: [PATCH 031/288] Fix builds --- examples/ffd/ffd_cyberon.cmake | 1 + examples/ffd/ffd_sensory.cmake | 1 + .../XK_VOICE_L71/platform/platform_init.c | 3 +- examples/ffva/ffva_int_cyberon.cmake | 4 +- examples/ffva/src/app_conf.h | 4 -- examples/ffva/src/device_memory_impl.c | 68 ------------------- examples/ffva/src/device_memory_impl.h | 10 --- examples/ffva/src/main.c | 11 +-- .../asr_example/asr_example.cmake | 2 +- .../speech_recognition.cmake | 4 +- modules/asr/CMakeLists.txt | 36 +++++++++- .../asr/{ => device_memory}/device_memory.c | 0 .../asr/{ => device_memory}/device_memory.h | 0 .../asr/device_memory}/device_memory_impl.c | 0 .../asr/device_memory}/device_memory_impl.h | 0 15 files changed, 51 insertions(+), 93 deletions(-) delete mode 100644 examples/ffva/src/device_memory_impl.c delete mode 100644 examples/ffva/src/device_memory_impl.h rename modules/asr/{ => device_memory}/device_memory.c (100%) rename modules/asr/{ => device_memory}/device_memory.h (100%) rename {examples/ffd/src => modules/asr/device_memory}/device_memory_impl.c (100%) rename {examples/ffd/src => modules/asr/device_memory}/device_memory_impl.h (100%) diff --git a/examples/ffd/ffd_cyberon.cmake b/examples/ffd/ffd_cyberon.cmake index c61fd713..70d69f3c 100644 --- a/examples/ffd/ffd_cyberon.cmake +++ b/examples/ffd/ffd_cyberon.cmake @@ -86,6 +86,7 @@ set(APP_LINK_OPTIONS set(APP_COMMON_LINK_LIBRARIES sln_voice::app::ffd::ap sln_voice::app::asr::Cyberon + sln_voice::app::asr::device_memory sln_voice::app::asr::gpio_ctrl sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler diff --git a/examples/ffd/ffd_sensory.cmake b/examples/ffd/ffd_sensory.cmake index 4935bc19..1e7cb56b 100644 --- a/examples/ffd/ffd_sensory.cmake +++ b/examples/ffd/ffd_sensory.cmake @@ -98,6 +98,7 @@ set(APP_LINK_OPTIONS set(APP_COMMON_LINK_LIBRARIES sln_voice::app::ffd::ap sln_voice::app::asr::sensory + sln_voice::app::asr::device_memory sln_voice::app::asr::gpio_ctrl sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index ec5ef91e..7c6bcf90 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -28,7 +28,7 @@ static void flash_init(void) // Flash fast read is used for reading the WW model in the INT device, // normal read is used for the DFU in the UA device. // The two read mechanisms are not compatible, so we must choose them at initialization. -#if !appconfUSB_ENABLED: +#if !appconfUSB_ENABLED rtos_qspi_flash_fast_read_init( qspi_flash_ctx, FLASH_CLKBLK, @@ -62,6 +62,7 @@ static void flash_init(void) PORT_SQI_SIO, NULL); #endif +#endif } static void gpio_init(void) diff --git a/examples/ffva/ffva_int_cyberon.cmake b/examples/ffva/ffva_int_cyberon.cmake index fe81671a..262a4817 100644 --- a/examples/ffva/ffva_int_cyberon.cmake +++ b/examples/ffva/ffva_int_cyberon.cmake @@ -37,6 +37,7 @@ set(FFVA_INT_CYBERON_COMPILE_DEFINITIONS appconfEXTERNAL_MCLK=1 appconfI2S_ENABLED=1 appconfUSB_ENABLED=0 + appconfINTENT_ENABLED=1 appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 @@ -68,10 +69,10 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) sln_voice::app::ffva::xk_voice_l71 sln_voice::app::ffva::ap::${FFVA_AP} sln_voice::app::asr::Cyberon + sln_voice::app::asr::device_memory sln_voice::app::asr::gpio_ctrl sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler - ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) @@ -92,6 +93,7 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) sln_voice::app::ffva::xk_voice_l71 sln_voice::app::ffva::ap::${FFVA_AP} sln_voice::app::asr::Cyberon + sln_voice::app::asr::device_memory sln_voice::app::asr::gpio_ctrl sln_voice::app::asr::intent_engine sln_voice::app::asr::intent_handler diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 6e133fd0..8652a838 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -130,10 +130,6 @@ #define appconfUSB_ENABLED 0 #endif -#ifndef appconfINTENT_ENABLED -#define appconfINTENT_ENABLED 1 -#endif - #ifndef appconfUSB_AUDIO_SAMPLE_RATE #define appconfUSB_AUDIO_SAMPLE_RATE appconfAUDIO_PIPELINE_SAMPLE_RATE #endif diff --git a/examples/ffva/src/device_memory_impl.c b/examples/ffva/src/device_memory_impl.c deleted file mode 100644 index 4fc09c20..00000000 --- a/examples/ffva/src/device_memory_impl.c +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2023-2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#include -#include -#include - -/* System headers */ -#include -#include -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" - -/* Library headers */ -#include "rtos_printf.h" -#include "rtos_qspi_flash.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "device_memory.h" -#include "device_memory_impl.h" - -void asr_printf(const char * format, ...) { - va_list args; - va_start(args, format); - xcore_utils_vprintf(format, args); - va_end(args); -} - -__attribute__((fptrgroup("devmem_malloc_fptr_grp"))) -void * devmem_malloc_local(size_t size) { - //rtos_printf("devmem_malloc_local size=%d\n", size); - return pvPortMalloc(size); -} - -__attribute__((fptrgroup("devmem_free_fptr_grp"))) -void devmem_free_local(void *ptr) { - vPortFree(ptr); -} - -__attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) -void devmem_read_ext_local(void *dest, const void *src, size_t n) { - //rtos_printf("devmem_read_ext_local dest=0x%x src=0x%x size=%d\n", dest, src, n); - if (IS_FLASH(src)) { - //uint32_t s = get_reference_time(); - int retval = -1; - while (retval == -1) { - // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset - retval = rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); - } - //uint32_t d = get_reference_time() - s; - //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); - } else { - memcpy(dest, src, n); - } -} - -void devmem_init(devmem_manager_t *devmem_ctx) { - xassert(devmem_ctx); - devmem_ctx->malloc = devmem_malloc_local; - devmem_ctx->free = devmem_free_local; - devmem_ctx->read_ext = devmem_read_ext_local; - devmem_ctx->read_ext_async = NULL; // not supported in this application - devmem_ctx->read_ext_wait = NULL; // not supported in this application -} diff --git a/examples/ffva/src/device_memory_impl.h b/examples/ffva/src/device_memory_impl.h deleted file mode 100644 index ea8735cb..00000000 --- a/examples/ffva/src/device_memory_impl.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2023-2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#ifndef DEVICE_MEMORY_IMPL_H -#define DEVICE_MEMORY_IMPL_H - -#include "device_memory.h" - -void devmem_init(devmem_manager_t *devmem_ctx); - -#endif // DEVICE_MEMORY_IMPL_H \ No newline at end of file diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 02814d68..1c1da733 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -22,12 +22,15 @@ #include "usb_support.h" #include "usb_audio.h" #include "audio_pipeline.h" + +/* Headers used for the WW intent engine */ +#if appconfINTENT_ENABLED #include "intent_engine.h" #include "intent_handler.h" #include "fs_support.h" -#include "print.h" #include "gpi_ctrl.h" #include "leds.h" +#endif #include "gpio_test/gpio_test.h" volatile int mic_from_usb = appconfMIC_SRC_DEFAULT; @@ -345,15 +348,15 @@ void startup_task(void *arg) gpio_test(gpio_ctx_t0); #endif -#if ON_TILE(0) +#if appconfINTENT_ENABLED && ON_TILE(0) led_task_create(appconfLED_TASK_PRIORITY, NULL); #endif -#if ON_TILE(1) +#if appconfINTENT_ENABLED && ON_TILE(1) gpio_gpi_init(gpio_ctx_t0); #endif -#if ON_TILE(FS_TILE_NO) +#if appconfINTENT_ENABLED && ON_TILE(FS_TILE_NO) rtos_fatfs_init(qspi_flash_ctx); // Setup flash low-level mode // NOTE: must call rtos_qspi_flash_fast_read_shutdown_ll to use non low-level mode calls diff --git a/examples/speech_recognition/asr_example/asr_example.cmake b/examples/speech_recognition/asr_example/asr_example.cmake index 716995b8..f65e6505 100644 --- a/examples/speech_recognition/asr_example/asr_example.cmake +++ b/examples/speech_recognition/asr_example/asr_example.cmake @@ -2,7 +2,7 @@ add_library(asr_example STATIC) target_sources(asr_example PRIVATE - ${SOLUTION_VOICE_ROOT_PATH}/modules/asr/device_memory.c + ${SOLUTION_VOICE_ROOT_PATH}/modules/asr/device_memory/device_memory.c ${CMAKE_CURRENT_LIST_DIR}/asr_example_impl.c ) target_include_directories(asr_example diff --git a/examples/speech_recognition/speech_recognition.cmake b/examples/speech_recognition/speech_recognition.cmake index 45e7bdf9..137ce818 100644 --- a/examples/speech_recognition/speech_recognition.cmake +++ b/examples/speech_recognition/speech_recognition.cmake @@ -6,8 +6,8 @@ include(${CMAKE_CURRENT_LIST_DIR}/asr_example/asr_example.cmake) #********************** # Gather Sources #********************** -file(GLOB_RECURSE APP_SOURCES - ${CMAKE_CURRENT_LIST_DIR}/src/*.xc +file(GLOB_RECURSE APP_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/*.xc ${CMAKE_CURRENT_LIST_DIR}/src/*.c ) set(APP_INCLUDES diff --git a/modules/asr/CMakeLists.txt b/modules/asr/CMakeLists.txt index c9a9c619..4a059bf3 100644 --- a/modules/asr/CMakeLists.txt +++ b/modules/asr/CMakeLists.txt @@ -6,7 +6,7 @@ add_library(asr_sensory INTERFACE) target_sources(asr_sensory INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/device_memory.c + ${SOLUTION_VOICE_ROOT_PATH}/modules/asr/device_memory/device_memory.c ${CMAKE_CURRENT_LIST_DIR}/sensory/appAudio.c ${CMAKE_CURRENT_LIST_DIR}/sensory/sensory_asr.c ) @@ -44,7 +44,6 @@ add_library(asr_Cyberon INTERFACE) target_sources(asr_Cyberon INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/device_memory.c ${CMAKE_CURRENT_LIST_DIR}/Cyberon/DSpotter_asr.c ${CMAKE_CURRENT_LIST_DIR}/Cyberon/FlashReadData.c ${CMAKE_CURRENT_LIST_DIR}/Cyberon/Convert2TransferBuffer.c @@ -77,6 +76,39 @@ target_compile_definitions(asr_Cyberon add_library(sln_voice::app::asr::Cyberon ALIAS asr_Cyberon) +##***************************** +## Create Device Memory target +##***************************** + +add_library(asr_device_memory INTERFACE) + +target_sources(asr_device_memory + INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/device_memory/device_memory.c + ${CMAKE_CURRENT_LIST_DIR}/device_memory/device_memory_impl.c + +) +target_include_directories(asr_device_memory + INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/device_memory +) +## suppress all linker warnings +target_link_options(asr_device_memory + INTERFACE + -Wl,-w +) + +target_compile_definitions(asr_device_memory + INTERFACE +) + +##********************************************* +## Create aliases for sln_voice example designs +##********************************************* + +add_library(sln_voice::app::asr::device_memory ALIAS asr_device_memory) + ##***************************** ## Create GPIO Control target ##***************************** diff --git a/modules/asr/device_memory.c b/modules/asr/device_memory/device_memory.c similarity index 100% rename from modules/asr/device_memory.c rename to modules/asr/device_memory/device_memory.c diff --git a/modules/asr/device_memory.h b/modules/asr/device_memory/device_memory.h similarity index 100% rename from modules/asr/device_memory.h rename to modules/asr/device_memory/device_memory.h diff --git a/examples/ffd/src/device_memory_impl.c b/modules/asr/device_memory/device_memory_impl.c similarity index 100% rename from examples/ffd/src/device_memory_impl.c rename to modules/asr/device_memory/device_memory_impl.c diff --git a/examples/ffd/src/device_memory_impl.h b/modules/asr/device_memory/device_memory_impl.h similarity index 100% rename from examples/ffd/src/device_memory_impl.h rename to modules/asr/device_memory/device_memory_impl.h From b18f7a4685f626208deba379757c775ec8b44551 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 13:26:08 +0000 Subject: [PATCH 032/288] Add new main example --- tools/ci/main_examples.txt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tools/ci/main_examples.txt b/tools/ci/main_examples.txt index 4df9c6c0..2c13fea4 100644 --- a/tools/ci/main_examples.txt +++ b/tools/ci/main_examples.txt @@ -1,8 +1,9 @@ -ffva_ua_adec_altarch example_ffva_ua_adec_altarch Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake -ffva_int_fixed_delay example_ffva_int_fixed_delay Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake -low_lower_ffd_sensory example_low_power_ffd_sensory Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake -ffd_sensory example_ffd_sensory Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake -ffd_cyberon example_ffd_cyberon Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake -mic_aggregator_TDM example_mic_aggregator_tdm No XCORE-AI-EXPLORER xmos_cmake_toolchain/xs3a.cmake -mic_aggregator_USB example_mic_aggregator_usb No XCORE-AI-EXPLORER xmos_cmake_toolchain/xs3a.cmake -asrc example_asrc_demo No XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +ffva_ua_adec_altarch example_ffva_ua_adec_altarch Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +ffva_int_fixed_delay example_ffva_int_fixed_delay Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +ffva_int_cyberon_fixed_delay example_ffva_int_cyberon_fixed_delay Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +low_lower_ffd_sensory example_low_power_ffd_sensory Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +ffd_sensory example_ffd_sensory Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +ffd_cyberon example_ffd_cyberon Yes XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake +mic_aggregator_TDM example_mic_aggregator_tdm No XCORE-AI-EXPLORER xmos_cmake_toolchain/xs3a.cmake +mic_aggregator_USB example_mic_aggregator_usb No XCORE-AI-EXPLORER xmos_cmake_toolchain/xs3a.cmake +asrc example_asrc_demo No XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake From 31e40727e85b175f328ffc5b2ce5da29cd47851c Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 14:55:56 +0000 Subject: [PATCH 033/288] Fix ASR example --- .../asr_example/asr_example.cmake | 3 +- modules/asr/asr.h | 36 +++++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/examples/speech_recognition/asr_example/asr_example.cmake b/examples/speech_recognition/asr_example/asr_example.cmake index f65e6505..b3df3593 100644 --- a/examples/speech_recognition/asr_example/asr_example.cmake +++ b/examples/speech_recognition/asr_example/asr_example.cmake @@ -2,12 +2,13 @@ add_library(asr_example STATIC) target_sources(asr_example PRIVATE - ${SOLUTION_VOICE_ROOT_PATH}/modules/asr/device_memory/device_memory.c ${CMAKE_CURRENT_LIST_DIR}/asr_example_impl.c + ${SOLUTION_VOICE_ROOT_PATH}/modules/asr/device_memory/device_memory.c ) target_include_directories(asr_example PUBLIC ${SOLUTION_VOICE_ROOT_PATH}/modules/asr + ${SOLUTION_VOICE_ROOT_PATH}/modules/asr/device_memory PRIVATE ${CMAKE_CURRENT_LIST_DIR} ) diff --git a/modules/asr/asr.h b/modules/asr/asr.h index 5dcb2bc3..4036c3f7 100644 --- a/modules/asr/asr.h +++ b/modules/asr/asr.h @@ -8,7 +8,7 @@ #include #include -#include "device_memory.h" +#include "device_memory/device_memory.h" /** * \addtogroup asr_api asr_api @@ -18,9 +18,9 @@ */ /** - * String output function that allows the application + * String output function that allows the application * to provide an alternative implementation. - * + * * ASR ports should call asr_printf instead of printf */ __attribute__((weak)) @@ -36,7 +36,7 @@ void asr_printf(const char * format, ...) { * Typedef to the ASR port context struct. * * An ASR port can store any data needed in the context. - * The context pointer is passed to all API methods and + * The context pointer is passed to all API methods and * can be cast to any struct defined by the ASR port. */ typedef void* asr_port_t; @@ -77,7 +77,7 @@ typedef struct asr_result_struct */ typedef enum asr_error_enum { ASR_OK = 0, ///< Ok - ASR_ERROR, ///< General error + ASR_ERROR, ///< General error ASR_INSUFFICIENT_MEMORY, ///< Insufficient memory for given model ASR_NOT_SUPPORTED, ///< Function not supported for given model ASR_INVALID_PARAMETER, ///< Invalid Parameter @@ -92,7 +92,7 @@ typedef enum asr_error_enum { * * \param model A pointer to the model data. * \param grammar A pointer to the grammar data (Optional). - * \param devmem_ctx A pointer to the device manager (Optional). + * \param devmem_ctx A pointer to the device manager (Optional). * Save this pointer if calling any device manager API functions. * * \returns the ASR port context. @@ -104,8 +104,8 @@ asr_port_t asr_init(int32_t *model, int32_t *grammar, devmem_manager_t *devmem_c * * \param ctx A pointer to the ASR port context. * \param attributes The attributes result. - * - * \returns Success or error code. + * + * \returns Success or error code. */ asr_error_t asr_get_attributes(asr_port_t *ctx, asr_attributes_t *attributes); @@ -115,8 +115,8 @@ asr_error_t asr_get_attributes(asr_port_t *ctx, asr_attributes_t *attributes); * \param ctx A pointer to the ASR port context. * \param audio_buf A pointer to the 16-bit PCM samples. * \param buf_len The number of PCM samples. - * - * \returns Success or error code. + * + * \returns Success or error code. */ asr_error_t asr_process(asr_port_t *ctx, int16_t *audio_buf, size_t buf_len); @@ -125,30 +125,30 @@ asr_error_t asr_process(asr_port_t *ctx, int16_t *audio_buf, size_t buf_len); * * \param ctx A pointer to the ASR port context. * \param result The processed result. - * - * \returns Success or error code. + * + * \returns Success or error code. */ asr_error_t asr_get_result(asr_port_t *ctx, asr_result_t *result); /** * Reset ASR port (if necessary). - * + * * Called before the next call to asr_process. * * \param ctx A pointer to the ASR port context. - * - * \returns Success or error code. + * + * \returns Success or error code. */ asr_error_t asr_reset(asr_port_t *ctx); /** * Release ASR port (if necessary). - * + * * The ASR port must deallocate any memory. * * \param ctx A pointer to the ASR port context. - * - * \returns Success or error code. + * + * \returns Success or error code. */ asr_error_t asr_release(asr_port_t *ctx); From fac8713eb073153ccffa14e0fa4549a3f471fa7b Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 15:52:26 +0000 Subject: [PATCH 034/288] Undo changes --- .../XK_VOICE_L71/platform/platform_init.c | 2 +- examples/ffva/ffva_int.cmake | 147 +++--------------- examples/ffva/ffva_int_dev.cmake | 5 +- 3 files changed, 25 insertions(+), 129 deletions(-) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index 7c6bcf90..ebccdffb 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -28,7 +28,7 @@ static void flash_init(void) // Flash fast read is used for reading the WW model in the INT device, // normal read is used for the DFU in the UA device. // The two read mechanisms are not compatible, so we must choose them at initialization. -#if !appconfUSB_ENABLED +#if !appconfUSB_ENABLED && appconfINTENT_ENABLED rtos_qspi_flash_fast_read_init( qspi_flash_ctx, FLASH_CLKBLK, diff --git a/examples/ffva/ffva_int.cmake b/examples/ffva/ffva_int.cmake index 268bd15d..d610f92c 100644 --- a/examples/ffva/ffva_int.cmake +++ b/examples/ffva/ffva_int.cmake @@ -1,36 +1,3 @@ -#********************** -# QSPI Flash Layout -#********************** -set(BOOT_PARTITION_SIZE 0x100000) -set(FILESYSTEM_SIZE_KB 1024) -math(EXPR FILESYSTEM_SIZE_BYTES - "1024 * ${FILESYSTEM_SIZE_KB}" - OUTPUT_FORMAT HEXADECIMAL -) - -set(CALIBRATION_PATTERN_START_ADDRESS ${BOOT_PARTITION_SIZE}) - -math(EXPR FILESYSTEM_START_ADDRESS - "${CALIBRATION_PATTERN_START_ADDRESS} + ${LIB_QSPI_FAST_READ_DEFAULT_CAL_SIZE_BYTES}" - OUTPUT_FORMAT HEXADECIMAL -) - -math(EXPR MODEL_START_ADDRESS - "${FILESYSTEM_START_ADDRESS} + ${FILESYSTEM_SIZE_BYTES}" - OUTPUT_FORMAT HEXADECIMAL -) - -set(CALIBRATION_PATTERN_DATA_PARTITION_OFFSET 0) - -math(EXPR FILESYSTEM_DATA_PARTITION_OFFSET - "${CALIBRATION_PATTERN_DATA_PARTITION_OFFSET} + ${LIB_QSPI_FAST_READ_DEFAULT_CAL_SIZE_BYTES}" - OUTPUT_FORMAT DECIMAL -) - -math(EXPR MODEL_DATA_PARTITION_OFFSET - "${FILESYSTEM_DATA_PARTITION_OFFSET} + ${FILESYSTEM_SIZE_BYTES}" - OUTPUT_FORMAT DECIMAL -) set(FFVA_INT_COMPILE_DEFINITIONS ${APP_COMPILE_DEFINITIONS} @@ -40,11 +7,7 @@ set(FFVA_INT_COMPILE_DEFINITIONS appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 - configENABLE_DEBUG_PRINTF=1 - QSPI_FLASH_FILESYSTEM_START_ADDRESS=${FILESYSTEM_START_ADDRESS} - QSPI_FLASH_MODEL_START_ADDRESS=${MODEL_START_ADDRESS} - QSPI_FLASH_CALIBRATION_ADDRESS=${CALIBRATION_PATTERN_START_ADDRESS} - ASR_CYBERON=1 + MIC_ARRAY_CONFIG_MCLK_FREQ=12288000 ) @@ -67,7 +30,6 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) ${APP_COMMON_LINK_LIBRARIES} sln_voice::app::ffva::xk_voice_l71 sln_voice::app::ffva::ap::${FFVA_AP} - sln_voice::app::asr::Cyberon ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) @@ -87,7 +49,6 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) ${APP_COMMON_LINK_LIBRARIES} sln_voice::app::ffva::xk_voice_l71 sln_voice::app::ffva::ap::${FFVA_AP} - sln_voice::app::asr::Cyberon ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) @@ -108,115 +69,53 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) #********************** set(TARGET_NAME example_ffva_int_${FFVA_AP}) set(DATA_PARTITION_FILE ${TARGET_NAME}_data_partition.bin) - set(MODEL_FILE ${TARGET_NAME}_model.bin) set(FATFS_FILE ${TARGET_NAME}_fat.fs) - set(FLASH_CAL_FILE ${LIB_QSPI_FAST_READ_ROOT_PATH}/lib_qspi_fast_read/calibration_pattern_nibble_swap.bin) - #set(FATFS_CONTENTS_DIR ${TARGET_NAME}_fatmktmp) - - - add_custom_target(${MODEL_FILE} ALL - COMMAND ${CMAKE_COMMAND} -E copy ${CYBERON_COMMAND_NET_FILE} ${MODEL_FILE} + set(FATFS_CONTENTS_DIR ${TARGET_NAME}_fatmktmp) + + add_custom_target( + ${FATFS_FILE} ALL + COMMAND ${CMAKE_COMMAND} -E rm -rf ${FATFS_CONTENTS_DIR}/fs/ + COMMAND ${CMAKE_COMMAND} -E make_directory ${FATFS_CONTENTS_DIR}/fs/ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/demo.txt ${FATFS_CONTENTS_DIR}/fs/ + COMMAND fatfs_mkimage --input=${FATFS_CONTENTS_DIR} --output=${FATFS_FILE} COMMENT - "Copy Cyberon NET file" + "Create filesystem" VERBATIM ) - create_filesystem_target( - #[[ Target ]] ${TARGET_NAME} - #[[ Input Directory ]] ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/${MODEL_LANGUAGE} - #[[ Image Size ]] ${FILESYSTEM_SIZE_BYTES} - ) + set_target_properties(${FATFS_FILE} PROPERTIES + ADDITIONAL_CLEAN_FILES ${FATFS_CONTENTS_DIR} + ) + # The filesystem is the only component in the data partition, copy it to + # the assocated data partition file which is required for CI. add_custom_command( OUTPUT ${DATA_PARTITION_FILE} - COMMAND ${CMAKE_COMMAND} -E rm -f ${DATA_PARTITION_FILE} - COMMAND datapartition_mkimage -v -b 1 - -i ${FLASH_CAL_FILE}:${CALIBRATION_PATTERN_DATA_PARTITION_OFFSET} ${FATFS_FILE}:${FILESYSTEM_DATA_PARTITION_OFFSET} ${MODEL_FILE}:${MODEL_DATA_PARTITION_OFFSET} - -o ${DATA_PARTITION_FILE} + COMMAND ${CMAKE_COMMAND} -E copy ${FATFS_FILE} ${DATA_PARTITION_FILE} DEPENDS - ${MODEL_FILE} - make_fs_${TARGET_NAME} - ${FLASH_CAL_FILE} + ${FATFS_FILE} COMMENT "Create data partition" VERBATIM ) - set(DATA_PARTITION_FILE_LIST - ${DATA_PARTITION_FILE} - ${MODEL_FILE} + list(APPEND DATA_PARTITION_FILE_LIST ${FATFS_FILE} - ${FLASH_CAL_FILE} - ) - - set(DATA_PARTITION_DEPENDS_LIST ${DATA_PARTITION_FILE} - ${MODEL_FILE} - make_fs_${TARGET_NAME} ) - # The list of files to copy and the dependency list for populating - # the data partition folder are identical. create_data_partition_directory( #[[ Target ]] ${TARGET_NAME} #[[ Copy Files ]] "${DATA_PARTITION_FILE_LIST}" - #[[ Dependencies ]] "${DATA_PARTITION_DEPENDS_LIST}" + #[[ Dependencies ]] "${DATA_PARTITION_FILE_LIST}" ) create_flash_app_target( - #[[ Target ]] ${TARGET_NAME} - #[[ Boot Partition Size ]] ${BOOT_PARTITION_SIZE} - #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} - #[[ Dependencies ]] ${DATA_PARTITION_FILE} + #[[ Target ]] ${TARGET_NAME} + #[[ Boot Partition Size ]] 0x100000 + #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} + #[[ Dependencies ]] ${DATA_PARTITION_FILE} ) unset(DATA_PARTITION_FILE_LIST) - unset(DATA_PARTITION_DEPENDS_LIST) - - #add_custom_target( - # ${FATFS_FILE} ALL - # COMMAND ${CMAKE_COMMAND} -E rm -rf ${FATFS_CONTENTS_DIR}/fs/ - # COMMAND ${CMAKE_COMMAND} -E make_directory ${FATFS_CONTENTS_DIR}/fs/ - # COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/demo.txt ${FATFS_CONTENTS_DIR}/fs/ - # COMMAND fatfs_mkimage --input=${FATFS_CONTENTS_DIR} --output=${FATFS_FILE} - # COMMENT - # "Create filesystem" - # VERBATIM - #) - - #set_target_properties(${FATFS_FILE} PROPERTIES - # ADDITIONAL_CLEAN_FILES ${FATFS_CONTENTS_DIR} - #) - - # The filesystem is the only component in the data partition, copy it to - # the assocated data partition file which is required for CI. - #add_custom_command( - # OUTPUT ${DATA_PARTITION_FILE} - # COMMAND ${CMAKE_COMMAND} -E copy ${FATFS_FILE} ${DATA_PARTITION_FILE} - # DEPENDS - # ${FATFS_FILE} - # COMMENT - # "Create data partition" - # VERBATIM - #) - - #list(APPEND DATA_PARTITION_FILE_LIST - # ${FATFS_FILE} - # ${DATA_PARTITION_FILE} - #) - - #create_data_partition_directory( - # #[[ Target ]] ${TARGET_NAME} - # #[[ Copy Files ]] "${DATA_PARTITION_FILE_LIST}" - # #[[ Dependencies ]] "${DATA_PARTITION_FILE_LIST}" - #) - - #create_flash_app_target( - # #[[ Target ]] ${TARGET_NAME} - # #[[ Boot Partition Size ]] 0x100000 - # #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} - # #[[ Dependencies ]] ${DATA_PARTITION_FILE} - #) - - #unset(DATA_PARTITION_FILE_LIST) endforeach() diff --git a/examples/ffva/ffva_int_dev.cmake b/examples/ffva/ffva_int_dev.cmake index 60531e58..88cbc5ea 100644 --- a/examples/ffva/ffva_int_dev.cmake +++ b/examples/ffva/ffva_int_dev.cmake @@ -1,12 +1,9 @@ -set(MODEL_LANGUAGE "english_usa") -set(CYBERON_COMMAND_NET_FILE "${CMAKE_CURRENT_LIST_DIR}/asr/model/$(MODEL_LANGUAGE)/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap") - set(FFVA_INT_COMPILE_DEFINITIONS ${APP_COMPILE_DEFINITIONS} appconfEXTERNAL_MCLK=1 appconfI2S_ENABLED=1 - =0 + appconfUSB_ENABLED=0 appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=480000 From 2e263cc885455f2ab71f149d21043d0ec5ed4f1a Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 16:12:53 +0000 Subject: [PATCH 035/288] Fix model --- .../Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap | Bin examples/ffva/ffva_int_cyberon.cmake | 7 +++++++ 2 files changed, 7 insertions(+) rename examples/ffva/asr/model/{ => english_usa}/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap (100%) diff --git a/examples/ffva/asr/model/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap b/examples/ffva/asr/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap similarity index 100% rename from examples/ffva/asr/model/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap rename to examples/ffva/asr/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap diff --git a/examples/ffva/ffva_int_cyberon.cmake b/examples/ffva/ffva_int_cyberon.cmake index 262a4817..b7072dda 100644 --- a/examples/ffva/ffva_int_cyberon.cmake +++ b/examples/ffva/ffva_int_cyberon.cmake @@ -1,3 +1,10 @@ +#********************** +# ASR Language info +#********************** + +set(MODEL_LANGUAGE "english_usa") +set(CYBERON_COMMAND_NET_FILE "${CMAKE_CURRENT_LIST_DIR}/asr/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap") + #********************** # QSPI Flash Layout #********************** From 3eeb228b4500ca81ba7d007b70f38bb79af0efdb Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 16:32:13 +0000 Subject: [PATCH 036/288] Fix build --- .../low_power_ffd/low_power_ffd_sensory.cmake | 1 + .../low_power_ffd/src/device_memory_impl.c | 64 ------------------- .../low_power_ffd/src/device_memory_impl.h | 10 --- 3 files changed, 1 insertion(+), 74 deletions(-) delete mode 100644 examples/low_power_ffd/src/device_memory_impl.c delete mode 100644 examples/low_power_ffd/src/device_memory_impl.h diff --git a/examples/low_power_ffd/low_power_ffd_sensory.cmake b/examples/low_power_ffd/low_power_ffd_sensory.cmake index 98570e1b..f9a6beaf 100644 --- a/examples/low_power_ffd/low_power_ffd_sensory.cmake +++ b/examples/low_power_ffd/low_power_ffd_sensory.cmake @@ -103,6 +103,7 @@ set(APP_COMMON_LINK_LIBRARIES sln_voice::app::ffd::ap sln_voice::app::asr::sensory rtos::drivers::clock_control + sln_voice::app::asr::device_memory ) #********************** diff --git a/examples/low_power_ffd/src/device_memory_impl.c b/examples/low_power_ffd/src/device_memory_impl.c deleted file mode 100644 index 680129a4..00000000 --- a/examples/low_power_ffd/src/device_memory_impl.c +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#include -#include -#include - -/* System headers */ -#include -#include -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" - -/* Library headers */ -#include "rtos_printf.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "device_memory.h" -#include "device_memory_impl.h" - -void asr_printf(const char * format, ...) { - va_list args; - va_start(args, format); - xcore_utils_vprintf(format, args); - va_end(args); -} - -__attribute__((fptrgroup("devmem_malloc_fptr_grp"))) -void * devmem_malloc_local(size_t size) { - //rtos_printf("devmem_malloc_local size=%d\n", size); - return pvPortMalloc(size); -} - -__attribute__((fptrgroup("devmem_free_fptr_grp"))) -void devmem_free_local(void *ptr) { - vPortFree(ptr); -} - -__attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) -void devmem_read_ext_local(void *dest, const void *src, size_t n) { - //rtos_printf("devmem_read_ext_local dest=0x%x src=0x%x size=%d\n", dest, src, n); - if (IS_FLASH(src)) { - // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset - //uint32_t s = get_reference_time(); - rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); - //uint32_t d = get_reference_time() - s; - //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); - } else { - memcpy(dest, src, n); - } -} - -void devmem_init(devmem_manager_t *devmem_ctx) { - xassert(devmem_ctx); - devmem_ctx->malloc = devmem_malloc_local; - devmem_ctx->free = devmem_free_local; - devmem_ctx->read_ext = devmem_read_ext_local; - devmem_ctx->read_ext_async = NULL; // not supported in this application - devmem_ctx->read_ext_wait = NULL; // not supported in this application -} diff --git a/examples/low_power_ffd/src/device_memory_impl.h b/examples/low_power_ffd/src/device_memory_impl.h deleted file mode 100644 index 0822a303..00000000 --- a/examples/low_power_ffd/src/device_memory_impl.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#ifndef DEVICE_MEMORY_IMPL_H -#define DEVICE_MEMORY_IMPL_H - -#include "device_memory.h" - -void devmem_init(devmem_manager_t *devmem_ctx); - -#endif // DEVICE_MEMORY_IMPL_H \ No newline at end of file From 849230d8fa4a1f50042802cf918401cfa804e5b5 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 16:40:20 +0000 Subject: [PATCH 037/288] Update tests --- test/ffd_gpio/gpio.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/ffd_gpio/gpio.cmake b/test/ffd_gpio/gpio.cmake index 29541820..241a00d9 100644 --- a/test/ffd_gpio/gpio.cmake +++ b/test/ffd_gpio/gpio.cmake @@ -2,7 +2,6 @@ # Gather Sources #********************** file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c) -list(APPEND APP_SOURCES ${SOLUTION_VOICE_ROOT_PATH}/examples/ffd/src/intent_handler/intent_handler.c) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/dummy_headers ${SOLUTION_VOICE_ROOT_PATH}/modules/asr @@ -64,7 +63,7 @@ target_sources(${TARGET_NAME} PUBLIC ${APP_SOURCES}) target_include_directories(${TARGET_NAME} PUBLIC ${APP_INCLUDES}) target_compile_definitions(${TARGET_NAME} PUBLIC ${APP_COMPILE_DEFINITIONS} THIS_XCORE_TILE=0) target_compile_options(${TARGET_NAME} PRIVATE ${APP_COMPILER_FLAGS}) -target_link_libraries(${TARGET_NAME} PUBLIC core::general rtos::freertos rtos::drivers::audio) +target_link_libraries(${TARGET_NAME} PUBLIC core::general rtos::freertos rtos::drivers::audio sln_voice::app::asr::intent_handler) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) From ae8fa97c9d1c67c92c55c681fcbd706de46dc9da Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 17:08:09 +0000 Subject: [PATCH 038/288] Fix test --- test/asr/asr.cmake | 5 ++- test/asr/src/device_memory_impl.c | 64 ------------------------------- test/asr/src/device_memory_impl.h | 10 ----- 3 files changed, 3 insertions(+), 76 deletions(-) delete mode 100644 test/asr/src/device_memory_impl.c delete mode 100644 test/asr/src/device_memory_impl.h diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index 217ec8c7..533ce1ca 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -44,8 +44,8 @@ if(${TEST_ASR} STREQUAL "SENSORY") set(APP_SOURCES ${APP_SOURCES} ${FFD_SRC_ROOT}/model/english_usa/command-pc62w-6.4.0-op10-prod-search.c - ) - set(MODEL_FILE ${FFD_SRC_ROOT}/model/english_usa/command-pc62w-6.4.0-op10-prod-net.bin.nibble_swapped) + ) + set(MODEL_FILE ${FFD_SRC_ROOT}/model/english_usa/command-pc62w-6.4.0-op10-prod-net.bin.nibble_swapped) set(TEST_ASR_LIBRARY_ID 0) set(TEST_ASR_NAME test_asr_sensory) else() @@ -130,6 +130,7 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES} ${ASR_LIBRARY} + sln_voice::app::asr::device_memory ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) diff --git a/test/asr/src/device_memory_impl.c b/test/asr/src/device_memory_impl.c deleted file mode 100644 index 680129a4..00000000 --- a/test/asr/src/device_memory_impl.c +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#include -#include -#include - -/* System headers */ -#include -#include -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" - -/* Library headers */ -#include "rtos_printf.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "device_memory.h" -#include "device_memory_impl.h" - -void asr_printf(const char * format, ...) { - va_list args; - va_start(args, format); - xcore_utils_vprintf(format, args); - va_end(args); -} - -__attribute__((fptrgroup("devmem_malloc_fptr_grp"))) -void * devmem_malloc_local(size_t size) { - //rtos_printf("devmem_malloc_local size=%d\n", size); - return pvPortMalloc(size); -} - -__attribute__((fptrgroup("devmem_free_fptr_grp"))) -void devmem_free_local(void *ptr) { - vPortFree(ptr); -} - -__attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) -void devmem_read_ext_local(void *dest, const void *src, size_t n) { - //rtos_printf("devmem_read_ext_local dest=0x%x src=0x%x size=%d\n", dest, src, n); - if (IS_FLASH(src)) { - // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset - //uint32_t s = get_reference_time(); - rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); - //uint32_t d = get_reference_time() - s; - //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); - } else { - memcpy(dest, src, n); - } -} - -void devmem_init(devmem_manager_t *devmem_ctx) { - xassert(devmem_ctx); - devmem_ctx->malloc = devmem_malloc_local; - devmem_ctx->free = devmem_free_local; - devmem_ctx->read_ext = devmem_read_ext_local; - devmem_ctx->read_ext_async = NULL; // not supported in this application - devmem_ctx->read_ext_wait = NULL; // not supported in this application -} diff --git a/test/asr/src/device_memory_impl.h b/test/asr/src/device_memory_impl.h deleted file mode 100644 index 0822a303..00000000 --- a/test/asr/src/device_memory_impl.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#ifndef DEVICE_MEMORY_IMPL_H -#define DEVICE_MEMORY_IMPL_H - -#include "device_memory.h" - -void devmem_init(devmem_manager_t *devmem_ctx); - -#endif // DEVICE_MEMORY_IMPL_H \ No newline at end of file From a4d9185e8401d95eb9bf99af2735f430058a6ef3 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 17:20:07 +0000 Subject: [PATCH 039/288] Fix more builds --- test/asr/asr.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index 533ce1ca..496cef34 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -112,6 +112,7 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES} ${ASR_LIBRARY} + sln_voice::app::asr::device_memory ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) From ec0d92c76db9466652a0f2000d69241b5688790a Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 17:30:48 +0000 Subject: [PATCH 040/288] Fix more builds --- test/ffd_gpio/src/app_conf.h | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ffd_gpio/src/app_conf.h b/test/ffd_gpio/src/app_conf.h index 52bc31d5..a0758cf0 100644 --- a/test/ffd_gpio/src/app_conf.h +++ b/test/ffd_gpio/src/app_conf.h @@ -24,6 +24,7 @@ #define ASR_TILE_NO 0 #define UART_TILE_NO 0 #define appconfINTENT_WAKEUP_EDGE_TYPE 0 +#define appconfAUDIO_PIPELINE_FRAME_ADVANCE 240 #define UART_RX_CORE_MASK (1 << 2) #define UART_RX_ISR_CORE 2 From 0a89882600c0dcf68bec46813a6a7f9cfd5398e9 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 17:34:48 +0000 Subject: [PATCH 041/288] Update copyright dates --- modules/asr/asr.h | 2 +- modules/asr/device_memory/device_memory.c | 2 +- modules/asr/device_memory/device_memory.h | 2 +- modules/asr/device_memory/device_memory_impl.c | 2 +- modules/asr/device_memory/device_memory_impl.h | 2 +- test/ffd_gpio/src/app_conf.h | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/asr/asr.h b/modules/asr/asr.h index 4036c3f7..92ca22aa 100644 --- a/modules/asr/asr.h +++ b/modules/asr/asr.h @@ -1,4 +1,4 @@ -// Copyright 2023 XMOS LIMITED. +// Copyright 2023-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef XCORE_VOICE_ASR_H #define XCORE_VOICE_ASR_H diff --git a/modules/asr/device_memory/device_memory.c b/modules/asr/device_memory/device_memory.c index 0e42d45e..d0958b15 100644 --- a/modules/asr/device_memory/device_memory.c +++ b/modules/asr/device_memory/device_memory.c @@ -1,4 +1,4 @@ -// Copyright 2023 XMOS LIMITED. +// Copyright 2023-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/modules/asr/device_memory/device_memory.h b/modules/asr/device_memory/device_memory.h index 96b520a3..04c53fa5 100644 --- a/modules/asr/device_memory/device_memory.h +++ b/modules/asr/device_memory/device_memory.h @@ -1,4 +1,4 @@ -// Copyright 2023 XMOS LIMITED. +// Copyright 2023-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef XCORE_DEVICE_MEMORY_H #define XCORE_DEVICE_MEMORY_H diff --git a/modules/asr/device_memory/device_memory_impl.c b/modules/asr/device_memory/device_memory_impl.c index 11976438..ff35c630 100644 --- a/modules/asr/device_memory/device_memory_impl.c +++ b/modules/asr/device_memory/device_memory_impl.c @@ -1,4 +1,4 @@ -// Copyright 2023 XMOS LIMITED. +// Copyright 2023-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/modules/asr/device_memory/device_memory_impl.h b/modules/asr/device_memory/device_memory_impl.h index 0822a303..ea8735cb 100644 --- a/modules/asr/device_memory/device_memory_impl.h +++ b/modules/asr/device_memory/device_memory_impl.h @@ -1,4 +1,4 @@ -// Copyright 2023 XMOS LIMITED. +// Copyright 2023-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef DEVICE_MEMORY_IMPL_H #define DEVICE_MEMORY_IMPL_H diff --git a/test/ffd_gpio/src/app_conf.h b/test/ffd_gpio/src/app_conf.h index a0758cf0..99f22c27 100644 --- a/test/ffd_gpio/src/app_conf.h +++ b/test/ffd_gpio/src/app_conf.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 XMOS LIMITED. +// Copyright 2021-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef APP_CONF_H_ From 6f827b9a3ce49f93ebfe3d094bee51c4c4eeefd6 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 5 Mar 2024 18:01:00 +0000 Subject: [PATCH 042/288] Fix test build --- test/ffd_gpio/gpio.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/ffd_gpio/gpio.cmake b/test/ffd_gpio/gpio.cmake index 241a00d9..79ebc16c 100644 --- a/test/ffd_gpio/gpio.cmake +++ b/test/ffd_gpio/gpio.cmake @@ -2,6 +2,7 @@ # Gather Sources #********************** file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c) +list(APPEND APP_SOURCES ${SOLUTION_VOICE_ROOT_PATH}/modules/asr/intent_handler/intent_handler.c) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/dummy_headers ${SOLUTION_VOICE_ROOT_PATH}/modules/asr @@ -63,7 +64,8 @@ target_sources(${TARGET_NAME} PUBLIC ${APP_SOURCES}) target_include_directories(${TARGET_NAME} PUBLIC ${APP_INCLUDES}) target_compile_definitions(${TARGET_NAME} PUBLIC ${APP_COMPILE_DEFINITIONS} THIS_XCORE_TILE=0) target_compile_options(${TARGET_NAME} PRIVATE ${APP_COMPILER_FLAGS}) -target_link_libraries(${TARGET_NAME} PUBLIC core::general rtos::freertos rtos::drivers::audio sln_voice::app::asr::intent_handler) + +target_link_libraries(${TARGET_NAME} PUBLIC core::general rtos::freertos rtos::drivers::audio) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) From 1e0cf1a3cd6609743ddfcb70bca50dcd17f16f41 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Wed, 6 Mar 2024 08:21:43 +0000 Subject: [PATCH 043/288] Undo change --- test/ffd_gpio/src/app_conf.h | 1 - 1 file changed, 1 deletion(-) diff --git a/test/ffd_gpio/src/app_conf.h b/test/ffd_gpio/src/app_conf.h index 99f22c27..f5747b96 100644 --- a/test/ffd_gpio/src/app_conf.h +++ b/test/ffd_gpio/src/app_conf.h @@ -24,7 +24,6 @@ #define ASR_TILE_NO 0 #define UART_TILE_NO 0 #define appconfINTENT_WAKEUP_EDGE_TYPE 0 -#define appconfAUDIO_PIPELINE_FRAME_ADVANCE 240 #define UART_RX_CORE_MASK (1 << 2) #define UART_RX_ISR_CORE 2 From 02e675c9afc0c4fec25e39d9aa1467915db7dc0c Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 10:29:25 +0000 Subject: [PATCH 044/288] Added changes to remove external clock --- .../bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn | 4 + .../XK_VOICE_L71/platform/platform_init.c | 1 + examples/ffva/ffva.cmake | 1 + examples/ffva/src/app_conf.h | 15 ++++ examples/ffva/src/main.c | 78 ++++++++++++++++++- modules/CMakeLists.txt | 1 + 6 files changed, 99 insertions(+), 1 deletion(-) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn b/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn index b927e064..3b666f18 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn +++ b/examples/ffva/bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn @@ -56,6 +56,10 @@ + + + + diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index ebccdffb..3bae514e 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -176,6 +176,7 @@ static void mics_init(void) static void i2s_init(void) { + #if appconfI2S_ENABLED #if appconfI2S_MODE == appconfI2S_MODE_MASTER static rtos_driver_rpc_t i2s_rpc_config; diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index e4e5f762..477a197b 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -45,6 +45,7 @@ set(APP_COMMON_LINK_LIBRARIES inferencing_tflite_micro rtos::freertos_usb lib_src + lib_sw_pll ) #********************** diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 8652a838..14444901 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -146,6 +146,8 @@ #define appconfEXTERNAL_MCLK 0 #endif +#define appconfRECOVER_MCLK_I2S_APP_PLL 1 + /* * This option sends all 6 16 KHz channels (two channels of processed audio, * stereo reference audio, and stereo microphone audio) out over a single @@ -220,4 +222,17 @@ #define appconfINTENT_MODEL_RUNNER_TASK_PRIORITY (configMAX_PRIORITIES - 2) #define appconfLED_TASK_PRIORITY (configMAX_PRIORITIES / 2 - 1) +/* Software PLL settings for mclk recovery configurations */ +/* see fractions.h and register_setup.h for other pll settings */ +#define appconfLRCLK_NOMINAL_HZ appconfI2S_AUDIO_SAMPLE_RATE +#define appconfBCLK_NOMINAL_HZ (appconfLRCLK_NOMINAL_HZ * 64) +#define PLL_RATIO (MIC_ARRAY_CONFIG_MCLK_FREQ / appconfLRCLK_NOMINAL_HZ) +#define PLL_CONTROL_LOOP_COUNT_UA 80 // How many SoF periods per control loop iteration. Aim for ~100Hz +#define PLL_CONTROL_LOOP_COUNT_INT 512 // How many refclk ticks (LRCLK) per control loop iteration. Aim for ~100Hz +#define PLL_PPM_RANGE 1000 // Max allowable diff in clk count. For the PID constants we + // have chosen, this number should be larger than the number + // of elements in the look up table as the clk count diff is + // added to the LUT index with a multiplier of 1. Only used for INT mclkless + + #endif /* APP_CONF_H_ */ diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 1c1da733..f9c74485 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -33,9 +33,21 @@ #endif #include "gpio_test/gpio_test.h" +/* Config headers for sw_pll */ +#include "sw_pll.h" +#include "fractions_1000ppm.h" +#include "register_setup_1000ppm.h" + volatile int mic_from_usb = appconfMIC_SRC_DEFAULT; volatile int aec_ref_source = appconfAEC_REF_DEFAULT; +typedef struct i2s_callback_args_t { + port_t p_mclk_count; // Used for keeping track of MCLK output for sw_pll + port_t p_bclk_count; // Used for keeping track of BCLK input for sw_pll + sw_pll_state_t *sw_pll; // Pointer to sw_pll state (if used) + +} i2s_callback_args_t; + #if appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) void i2s_slave_intertile(void *args) { (void) args; @@ -61,6 +73,18 @@ void i2s_slave_intertile(void *args) { (int32_t*) tmp, appconfAUDIO_PIPELINE_FRAME_ADVANCE, portMAX_DELAY); + + +#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL + i2s_callback_args_t* i2s_callback_args = (i2s_callback_args_t*) args; + port_clear_buffer(i2s_callback_args->p_bclk_count); + port_in(i2s_callback_args->p_bclk_count); // Block until BCLK transition to synchronise. Will consume up to 1/64 of a LRCLK cycle + uint16_t mclk_pt = port_get_trigger_time(i2s_callback_args->p_mclk_count); // Immediately sample mclk_count + uint16_t bclk_pt = port_get_trigger_time(i2s_callback_args->p_bclk_count); // Now grab bclk_count (which won't have changed) + + sw_pll_do_control(i2s_callback_args->sw_pll, mclk_pt, bclk_pt); +#endif + } } #endif @@ -329,17 +353,69 @@ static void mem_analysis(void) } } +static int *p_lock_status = NULL; +/// @brief Save the pointer to the pll lock_status variable +static void set_pll_lock_status_ptr(int* p) +{ + p_lock_status = p; +} + void startup_task(void *arg) { rtos_printf("Startup task running from tile %d on core %d\n", THIS_XCORE_TILE, portGET_CORE_ID()); platform_start(); +#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL + + sw_pll_state_t sw_pll = {0}; + port_t p_bclk = PORT_I2S_BCLK; + port_t p_lrclk = PORT_I2S_LRCLK; + port_t p_mclk = PORT_MCLK; + port_t p_mclk_count = PORT_MCLK_COUNT; // Used internally by sw_pll + port_t p_bclk_count = PORT_BCLK_COUNT; // Used internally by sw_pll + xclock_t ck_bclk = I2S_CLKBLK; + set_pll_lock_status_ptr(&sw_pll.lock_status); + // Create clock from mclk port and use it to clock the p_mclk_count port which will count MCLKs. + port_enable(p_mclk_count); + port_enable(p_bclk_count); + + // Allow p_mclk_count to count mclks + xclock_t clk_mclk = MCLK_CLKBLK; + clock_enable(clk_mclk); + clock_set_source_port(clk_mclk, p_mclk); + port_set_clock(p_mclk_count, clk_mclk); + clock_start(clk_mclk); + + // Allow p_bclk_count to count bclks + port_set_clock(p_bclk_count, ck_bclk); + printintln(111); + sw_pll_init(&sw_pll, + SW_PLL_15Q16(0.0), + SW_PLL_15Q16(1.0), + PLL_CONTROL_LOOP_COUNT_INT, + PLL_RATIO, + (appconfBCLK_NOMINAL_HZ / appconfLRCLK_NOMINAL_HZ), + frac_values_90, + SW_PLL_NUM_LUT_ENTRIES(frac_values_90), + APP_PLL_CTL_REG, + APP_PLL_DIV_REG, + SW_PLL_NUM_LUT_ENTRIES(frac_values_90) / 2, + PLL_PPM_RANGE); + printintln(112); + + debug_printf("Using SW PLL to track I2S input\n"); + i2s_callback_args_t i2s_callback_args = { + .sw_pll = &sw_pll, + .p_mclk_count = p_mclk_count, + .p_bclk_count = p_bclk_count + }; +#endif #if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) xTaskCreate((TaskFunction_t) i2s_slave_intertile, "i2s_slave_intertile", RTOS_THREAD_STACK_SIZE(i2s_slave_intertile), - NULL, + &i2s_callback_args, appconfAUDIO_PIPELINE_TASK_PRIORITY, NULL); #endif diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 7b365c43..133742b9 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -23,3 +23,4 @@ add_subdirectory(asr) add_subdirectory(audio_pipelines) add_subdirectory(sample_rate_conversion) add_subdirectory(xscope_fileio) +add_subdirectory(sw_pll/lib_sw_pll) From 30f5d733e9f4849c83335b57ad041bf8cc8cb9b6 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 10:31:49 +0000 Subject: [PATCH 045/288] Add submodule --- .gitmodules | 3 +++ modules/sw_pll | 1 + 2 files changed, 4 insertions(+) create mode 160000 modules/sw_pll diff --git a/.gitmodules b/.gitmodules index c11b15c6..25473ae0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ [submodule "modules/xua"] path = modules/xua url = git@github.com:xmos/lib_xua.git +[submodule "modules/sw_pll"] + path = modules/sw_pll + url = git@github.com:xmos/lib_sw_pll diff --git a/modules/sw_pll b/modules/sw_pll new file mode 160000 index 00000000..6c5db866 --- /dev/null +++ b/modules/sw_pll @@ -0,0 +1 @@ +Subproject commit 6c5db866a13ff20d5d23845e542f39951bca5592 From e73fa397179011f6575dad6fe7abfe0abc3ff2ba Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 10:47:44 +0000 Subject: [PATCH 046/288] Shuchita's comments --- .../ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c | 2 +- examples/ffva/src/app_conf_check.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index ebccdffb..a2ff296a 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -245,7 +245,7 @@ static void usb_init(void) static void uart_init(void) { -#if ON_TILE(UART_TILE_NO) +#if appconfINTENT_ENABLED && ON_TILE(UART_TILE_NO) hwtimer_t tmr_tx = hwtimer_alloc(); rtos_uart_tx_init( diff --git a/examples/ffva/src/app_conf_check.h b/examples/ffva/src/app_conf_check.h index 9ca85d76..06b9bd26 100644 --- a/examples/ffva/src/app_conf_check.h +++ b/examples/ffva/src/app_conf_check.h @@ -12,6 +12,10 @@ #error Cannot use USB with an external mclk source #endif +#if appconfUSB_ENABLED && appconfINTENT_ENABLED +#error Cannot use wakeword engine in USB configurations +#endif + #if appconfI2S_TDM_ENABLED && appconfI2S_AUDIO_SAMPLE_RATE != 3*appconfAUDIO_PIPELINE_SAMPLE_RATE #error appconfI2S_AUDIO_SAMPLE_RATE must be 48000 to use I2S TDM #endif From 81f80f96cb503c9c5f40f40b39f85eb7e6857aa2 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 11:15:55 +0000 Subject: [PATCH 047/288] Add missing files --- examples/ffva/src/fractions_1000ppm.h | 796 +++++++++++++++++++++ examples/ffva/src/register_setup_1000ppm.h | 17 + 2 files changed, 813 insertions(+) create mode 100644 examples/ffva/src/fractions_1000ppm.h create mode 100644 examples/ffva/src/register_setup_1000ppm.h diff --git a/examples/ffva/src/fractions_1000ppm.h b/examples/ffva/src/fractions_1000ppm.h new file mode 100644 index 00000000..b0470f7d --- /dev/null +++ b/examples/ffva/src/fractions_1000ppm.h @@ -0,0 +1,796 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +// Header file listing fraction options searched +// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. +static short frac_values_90[790] = { +0x1832, // Index: 0 Fraction: 25/51 = 0.4902 +0x1934, // Index: 1 Fraction: 26/53 = 0.4906 +0x1A36, // Index: 2 Fraction: 27/55 = 0.4909 +0x1B38, // Index: 3 Fraction: 28/57 = 0.4912 +0x1C3A, // Index: 4 Fraction: 29/59 = 0.4915 +0x1D3C, // Index: 5 Fraction: 30/61 = 0.4918 +0x1E3E, // Index: 6 Fraction: 31/63 = 0.4921 +0x1F40, // Index: 7 Fraction: 32/65 = 0.4923 +0x2042, // Index: 8 Fraction: 33/67 = 0.4925 +0x2144, // Index: 9 Fraction: 34/69 = 0.4928 +0x2246, // Index: 10 Fraction: 35/71 = 0.4930 +0x2348, // Index: 11 Fraction: 36/73 = 0.4932 +0x244A, // Index: 12 Fraction: 37/75 = 0.4933 +0x254C, // Index: 13 Fraction: 38/77 = 0.4935 +0x264E, // Index: 14 Fraction: 39/79 = 0.4937 +0x2750, // Index: 15 Fraction: 40/81 = 0.4938 +0x2852, // Index: 16 Fraction: 41/83 = 0.4940 +0x2954, // Index: 17 Fraction: 42/85 = 0.4941 +0x2A56, // Index: 18 Fraction: 43/87 = 0.4943 +0x2B58, // Index: 19 Fraction: 44/89 = 0.4944 +0x0001, // Index: 20 Fraction: 1/2 = 0.5000 +0x2C58, // Index: 21 Fraction: 45/89 = 0.5056 +0x2B56, // Index: 22 Fraction: 44/87 = 0.5057 +0x2A54, // Index: 23 Fraction: 43/85 = 0.5059 +0x2952, // Index: 24 Fraction: 42/83 = 0.5060 +0x2850, // Index: 25 Fraction: 41/81 = 0.5062 +0x274E, // Index: 26 Fraction: 40/79 = 0.5063 +0x264C, // Index: 27 Fraction: 39/77 = 0.5065 +0x254A, // Index: 28 Fraction: 38/75 = 0.5067 +0x2448, // Index: 29 Fraction: 37/73 = 0.5068 +0x2346, // Index: 30 Fraction: 36/71 = 0.5070 +0x2244, // Index: 31 Fraction: 35/69 = 0.5072 +0x2142, // Index: 32 Fraction: 34/67 = 0.5075 +0x2040, // Index: 33 Fraction: 33/65 = 0.5077 +0x1F3E, // Index: 34 Fraction: 32/63 = 0.5079 +0x1E3C, // Index: 35 Fraction: 31/61 = 0.5082 +0x1D3A, // Index: 36 Fraction: 30/59 = 0.5085 +0x1C38, // Index: 37 Fraction: 29/57 = 0.5088 +0x1B36, // Index: 38 Fraction: 28/55 = 0.5091 +0x1A34, // Index: 39 Fraction: 27/53 = 0.5094 +0x1932, // Index: 40 Fraction: 26/51 = 0.5098 +0x1830, // Index: 41 Fraction: 25/49 = 0.5102 +0x172E, // Index: 42 Fraction: 24/47 = 0.5106 +0x162C, // Index: 43 Fraction: 23/45 = 0.5111 +0x2C57, // Index: 44 Fraction: 45/88 = 0.5114 +0x152A, // Index: 45 Fraction: 22/43 = 0.5116 +0x2A53, // Index: 46 Fraction: 43/84 = 0.5119 +0x1428, // Index: 47 Fraction: 21/41 = 0.5122 +0x284F, // Index: 48 Fraction: 41/80 = 0.5125 +0x1326, // Index: 49 Fraction: 20/39 = 0.5128 +0x264B, // Index: 50 Fraction: 39/76 = 0.5132 +0x1224, // Index: 51 Fraction: 19/37 = 0.5135 +0x2447, // Index: 52 Fraction: 37/72 = 0.5139 +0x1122, // Index: 53 Fraction: 18/35 = 0.5143 +0x2243, // Index: 54 Fraction: 35/68 = 0.5147 +0x1020, // Index: 55 Fraction: 17/33 = 0.5152 +0x203F, // Index: 56 Fraction: 33/64 = 0.5156 +0x0F1E, // Index: 57 Fraction: 16/31 = 0.5161 +0x1E3B, // Index: 58 Fraction: 31/60 = 0.5167 +0x2D58, // Index: 59 Fraction: 46/89 = 0.5169 +0x0E1C, // Index: 60 Fraction: 15/29 = 0.5172 +0x2B54, // Index: 61 Fraction: 44/85 = 0.5176 +0x1C37, // Index: 62 Fraction: 29/56 = 0.5179 +0x2A52, // Index: 63 Fraction: 43/83 = 0.5181 +0x0D1A, // Index: 64 Fraction: 14/27 = 0.5185 +0x284E, // Index: 65 Fraction: 41/79 = 0.5190 +0x1A33, // Index: 66 Fraction: 27/52 = 0.5192 +0x274C, // Index: 67 Fraction: 40/77 = 0.5195 +0x0C18, // Index: 68 Fraction: 13/25 = 0.5200 +0x2548, // Index: 69 Fraction: 38/73 = 0.5205 +0x182F, // Index: 70 Fraction: 25/48 = 0.5208 +0x2446, // Index: 71 Fraction: 37/71 = 0.5211 +0x0B16, // Index: 72 Fraction: 12/23 = 0.5217 +0x2E59, // Index: 73 Fraction: 47/90 = 0.5222 +0x2242, // Index: 74 Fraction: 35/67 = 0.5224 +0x162B, // Index: 75 Fraction: 23/44 = 0.5227 +0x2140, // Index: 76 Fraction: 34/65 = 0.5231 +0x2C55, // Index: 77 Fraction: 45/86 = 0.5233 +0x0A14, // Index: 78 Fraction: 11/21 = 0.5238 +0x2A51, // Index: 79 Fraction: 43/82 = 0.5244 +0x1F3C, // Index: 80 Fraction: 32/61 = 0.5246 +0x1427, // Index: 81 Fraction: 21/40 = 0.5250 +0x1E3A, // Index: 82 Fraction: 31/59 = 0.5254 +0x284D, // Index: 83 Fraction: 41/78 = 0.5256 +0x0912, // Index: 84 Fraction: 10/19 = 0.5263 +0x2649, // Index: 85 Fraction: 39/74 = 0.5270 +0x1C36, // Index: 86 Fraction: 29/55 = 0.5273 +0x1223, // Index: 87 Fraction: 19/36 = 0.5278 +0x2E58, // Index: 88 Fraction: 47/89 = 0.5281 +0x1B34, // Index: 89 Fraction: 28/53 = 0.5283 +0x2445, // Index: 90 Fraction: 37/70 = 0.5286 +0x2D56, // Index: 91 Fraction: 46/87 = 0.5287 +0x0810, // Index: 92 Fraction: 9/17 = 0.5294 +0x2B52, // Index: 93 Fraction: 44/83 = 0.5301 +0x2241, // Index: 94 Fraction: 35/66 = 0.5303 +0x1930, // Index: 95 Fraction: 26/49 = 0.5306 +0x2A50, // Index: 96 Fraction: 43/81 = 0.5309 +0x101F, // Index: 97 Fraction: 17/32 = 0.5312 +0x294E, // Index: 98 Fraction: 42/79 = 0.5316 +0x182E, // Index: 99 Fraction: 25/47 = 0.5319 +0x203D, // Index: 100 Fraction: 33/62 = 0.5323 +0x284C, // Index: 101 Fraction: 41/77 = 0.5325 +0x070E, // Index: 102 Fraction: 8/15 = 0.5333 +0x2E57, // Index: 103 Fraction: 47/88 = 0.5341 +0x2648, // Index: 104 Fraction: 39/73 = 0.5342 +0x1E39, // Index: 105 Fraction: 31/58 = 0.5345 +0x162A, // Index: 106 Fraction: 23/43 = 0.5349 +0x2546, // Index: 107 Fraction: 38/71 = 0.5352 +0x0E1B, // Index: 108 Fraction: 15/28 = 0.5357 +0x2444, // Index: 109 Fraction: 37/69 = 0.5362 +0x1528, // Index: 110 Fraction: 22/41 = 0.5366 +0x1C35, // Index: 111 Fraction: 29/54 = 0.5370 +0x2342, // Index: 112 Fraction: 36/67 = 0.5373 +0x2A4F, // Index: 113 Fraction: 43/80 = 0.5375 +0x060C, // Index: 114 Fraction: 7/13 = 0.5385 +0x2F58, // Index: 115 Fraction: 48/89 = 0.5393 +0x284B, // Index: 116 Fraction: 41/76 = 0.5395 +0x213E, // Index: 117 Fraction: 34/63 = 0.5397 +0x1A31, // Index: 118 Fraction: 27/50 = 0.5400 +0x2E56, // Index: 119 Fraction: 47/87 = 0.5402 +0x1324, // Index: 120 Fraction: 20/37 = 0.5405 +0x203C, // Index: 121 Fraction: 33/61 = 0.5410 +0x2D54, // Index: 122 Fraction: 46/85 = 0.5412 +0x0C17, // Index: 123 Fraction: 13/24 = 0.5417 +0x2C52, // Index: 124 Fraction: 45/83 = 0.5422 +0x1F3A, // Index: 125 Fraction: 32/59 = 0.5424 +0x1222, // Index: 126 Fraction: 19/35 = 0.5429 +0x2B50, // Index: 127 Fraction: 44/81 = 0.5432 +0x182D, // Index: 128 Fraction: 25/46 = 0.5435 +0x1E38, // Index: 129 Fraction: 31/57 = 0.5439 +0x2443, // Index: 130 Fraction: 37/68 = 0.5441 +0x2A4E, // Index: 131 Fraction: 43/79 = 0.5443 +0x3059, // Index: 132 Fraction: 49/90 = 0.5444 +0x050A, // Index: 133 Fraction: 6/11 = 0.5455 +0x2E55, // Index: 134 Fraction: 47/86 = 0.5465 +0x284A, // Index: 135 Fraction: 41/75 = 0.5467 +0x223F, // Index: 136 Fraction: 35/64 = 0.5469 +0x1C34, // Index: 137 Fraction: 29/53 = 0.5472 +0x1629, // Index: 138 Fraction: 23/42 = 0.5476 +0x2748, // Index: 139 Fraction: 40/73 = 0.5479 +0x101E, // Index: 140 Fraction: 17/31 = 0.5484 +0x2C51, // Index: 141 Fraction: 45/82 = 0.5488 +0x1B32, // Index: 142 Fraction: 28/51 = 0.5490 +0x2646, // Index: 143 Fraction: 39/71 = 0.5493 +0x0A13, // Index: 144 Fraction: 11/20 = 0.5500 +0x3058, // Index: 145 Fraction: 49/89 = 0.5506 +0x2544, // Index: 146 Fraction: 38/69 = 0.5507 +0x1A30, // Index: 147 Fraction: 27/49 = 0.5510 +0x2A4D, // Index: 148 Fraction: 43/78 = 0.5513 +0x0F1C, // Index: 149 Fraction: 16/29 = 0.5517 +0x2442, // Index: 150 Fraction: 37/67 = 0.5522 +0x1425, // Index: 151 Fraction: 21/38 = 0.5526 +0x2E54, // Index: 152 Fraction: 47/85 = 0.5529 +0x192E, // Index: 153 Fraction: 26/47 = 0.5532 +0x1E37, // Index: 154 Fraction: 31/56 = 0.5536 +0x2340, // Index: 155 Fraction: 36/65 = 0.5538 +0x2849, // Index: 156 Fraction: 41/74 = 0.5541 +0x2D52, // Index: 157 Fraction: 46/83 = 0.5542 +0x0408, // Index: 158 Fraction: 5/9 = 0.5556 +0x3057, // Index: 159 Fraction: 49/88 = 0.5568 +0x2B4E, // Index: 160 Fraction: 44/79 = 0.5570 +0x2645, // Index: 161 Fraction: 39/70 = 0.5571 +0x213C, // Index: 162 Fraction: 34/61 = 0.5574 +0x1C33, // Index: 163 Fraction: 29/52 = 0.5577 +0x172A, // Index: 164 Fraction: 24/43 = 0.5581 +0x2A4C, // Index: 165 Fraction: 43/77 = 0.5584 +0x1221, // Index: 166 Fraction: 19/34 = 0.5588 +0x203A, // Index: 167 Fraction: 33/59 = 0.5593 +0x2E53, // Index: 168 Fraction: 47/84 = 0.5595 +0x0D18, // Index: 169 Fraction: 14/25 = 0.5600 +0x2441, // Index: 170 Fraction: 37/66 = 0.5606 +0x1628, // Index: 171 Fraction: 23/41 = 0.5610 +0x1F38, // Index: 172 Fraction: 32/57 = 0.5614 +0x2848, // Index: 173 Fraction: 41/73 = 0.5616 +0x3158, // Index: 174 Fraction: 50/89 = 0.5618 +0x080F, // Index: 175 Fraction: 9/16 = 0.5625 +0x3056, // Index: 176 Fraction: 49/87 = 0.5632 +0x2746, // Index: 177 Fraction: 40/71 = 0.5634 +0x1E36, // Index: 178 Fraction: 31/55 = 0.5636 +0x1526, // Index: 179 Fraction: 22/39 = 0.5641 +0x223D, // Index: 180 Fraction: 35/62 = 0.5645 +0x2F54, // Index: 181 Fraction: 48/85 = 0.5647 +0x0C16, // Index: 182 Fraction: 13/23 = 0.5652 +0x2A4B, // Index: 183 Fraction: 43/76 = 0.5658 +0x1D34, // Index: 184 Fraction: 30/53 = 0.5660 +0x2E52, // Index: 185 Fraction: 47/83 = 0.5663 +0x101D, // Index: 186 Fraction: 17/30 = 0.5667 +0x2542, // Index: 187 Fraction: 38/67 = 0.5672 +0x1424, // Index: 188 Fraction: 21/37 = 0.5676 +0x2D50, // Index: 189 Fraction: 46/81 = 0.5679 +0x182B, // Index: 190 Fraction: 25/44 = 0.5682 +0x1C32, // Index: 191 Fraction: 29/51 = 0.5686 +0x2039, // Index: 192 Fraction: 33/58 = 0.5690 +0x2440, // Index: 193 Fraction: 37/65 = 0.5692 +0x2847, // Index: 194 Fraction: 41/72 = 0.5694 +0x2C4E, // Index: 195 Fraction: 45/79 = 0.5696 +0x3055, // Index: 196 Fraction: 49/86 = 0.5698 +0x0306, // Index: 197 Fraction: 4/7 = 0.5714 +0x3258, // Index: 198 Fraction: 51/89 = 0.5730 +0x2E51, // Index: 199 Fraction: 47/82 = 0.5732 +0x2A4A, // Index: 200 Fraction: 43/75 = 0.5733 +0x2643, // Index: 201 Fraction: 39/68 = 0.5735 +0x223C, // Index: 202 Fraction: 35/61 = 0.5738 +0x1E35, // Index: 203 Fraction: 31/54 = 0.5741 +0x1A2E, // Index: 204 Fraction: 27/47 = 0.5745 +0x3156, // Index: 205 Fraction: 50/87 = 0.5747 +0x1627, // Index: 206 Fraction: 23/40 = 0.5750 +0x2948, // Index: 207 Fraction: 42/73 = 0.5753 +0x1220, // Index: 208 Fraction: 19/33 = 0.5758 +0x213A, // Index: 209 Fraction: 34/59 = 0.5763 +0x3054, // Index: 210 Fraction: 49/85 = 0.5765 +0x0E19, // Index: 211 Fraction: 15/26 = 0.5769 +0x2846, // Index: 212 Fraction: 41/71 = 0.5775 +0x192C, // Index: 213 Fraction: 26/45 = 0.5778 +0x243F, // Index: 214 Fraction: 37/64 = 0.5781 +0x2F52, // Index: 215 Fraction: 48/83 = 0.5783 +0x0A12, // Index: 216 Fraction: 11/19 = 0.5789 +0x3257, // Index: 217 Fraction: 51/88 = 0.5795 +0x2744, // Index: 218 Fraction: 40/69 = 0.5797 +0x1C31, // Index: 219 Fraction: 29/50 = 0.5800 +0x2E50, // Index: 220 Fraction: 47/81 = 0.5802 +0x111E, // Index: 221 Fraction: 18/31 = 0.5806 +0x2A49, // Index: 222 Fraction: 43/74 = 0.5811 +0x182A, // Index: 223 Fraction: 25/43 = 0.5814 +0x1F36, // Index: 224 Fraction: 32/55 = 0.5818 +0x2642, // Index: 225 Fraction: 39/67 = 0.5821 +0x2D4E, // Index: 226 Fraction: 46/79 = 0.5823 +0x060B, // Index: 227 Fraction: 7/12 = 0.5833 +0x3358, // Index: 228 Fraction: 52/89 = 0.5843 +0x2C4C, // Index: 229 Fraction: 45/77 = 0.5844 +0x2540, // Index: 230 Fraction: 38/65 = 0.5846 +0x1E34, // Index: 231 Fraction: 31/53 = 0.5849 +0x1728, // Index: 232 Fraction: 24/41 = 0.5854 +0x2845, // Index: 233 Fraction: 41/70 = 0.5857 +0x101C, // Index: 234 Fraction: 17/29 = 0.5862 +0x2B4A, // Index: 235 Fraction: 44/75 = 0.5867 +0x1A2D, // Index: 236 Fraction: 27/46 = 0.5870 +0x243E, // Index: 237 Fraction: 37/63 = 0.5873 +0x2E4F, // Index: 238 Fraction: 47/80 = 0.5875 +0x0910, // Index: 239 Fraction: 10/17 = 0.5882 +0x3459, // Index: 240 Fraction: 53/90 = 0.5889 +0x2A48, // Index: 241 Fraction: 43/73 = 0.5890 +0x2037, // Index: 242 Fraction: 33/56 = 0.5893 +0x1626, // Index: 243 Fraction: 23/39 = 0.5897 +0x233C, // Index: 244 Fraction: 36/61 = 0.5902 +0x3052, // Index: 245 Fraction: 49/83 = 0.5904 +0x0C15, // Index: 246 Fraction: 13/22 = 0.5909 +0x2946, // Index: 247 Fraction: 42/71 = 0.5915 +0x1C30, // Index: 248 Fraction: 29/49 = 0.5918 +0x2C4B, // Index: 249 Fraction: 45/76 = 0.5921 +0x0F1A, // Index: 250 Fraction: 16/27 = 0.5926 +0x3255, // Index: 251 Fraction: 51/86 = 0.5930 +0x223A, // Index: 252 Fraction: 35/59 = 0.5932 +0x121F, // Index: 253 Fraction: 19/32 = 0.5938 +0x2844, // Index: 254 Fraction: 41/69 = 0.5942 +0x1524, // Index: 255 Fraction: 22/37 = 0.5946 +0x2E4E, // Index: 256 Fraction: 47/79 = 0.5949 +0x1829, // Index: 257 Fraction: 25/42 = 0.5952 +0x3458, // Index: 258 Fraction: 53/89 = 0.5955 +0x1B2E, // Index: 259 Fraction: 28/47 = 0.5957 +0x1E33, // Index: 260 Fraction: 31/52 = 0.5962 +0x2138, // Index: 261 Fraction: 34/57 = 0.5965 +0x243D, // Index: 262 Fraction: 37/62 = 0.5968 +0x2742, // Index: 263 Fraction: 40/67 = 0.5970 +0x2A47, // Index: 264 Fraction: 43/72 = 0.5972 +0x2D4C, // Index: 265 Fraction: 46/77 = 0.5974 +0x3051, // Index: 266 Fraction: 49/82 = 0.5976 +0x3356, // Index: 267 Fraction: 52/87 = 0.5977 +0x0204, // Index: 268 Fraction: 3/5 = 0.6000 +0x3457, // Index: 269 Fraction: 53/88 = 0.6023 +0x3152, // Index: 270 Fraction: 50/83 = 0.6024 +0x2E4D, // Index: 271 Fraction: 47/78 = 0.6026 +0x2B48, // Index: 272 Fraction: 44/73 = 0.6027 +0x2843, // Index: 273 Fraction: 41/68 = 0.6029 +0x253E, // Index: 274 Fraction: 38/63 = 0.6032 +0x2239, // Index: 275 Fraction: 35/58 = 0.6034 +0x1F34, // Index: 276 Fraction: 32/53 = 0.6038 +0x1C2F, // Index: 277 Fraction: 29/48 = 0.6042 +0x192A, // Index: 278 Fraction: 26/43 = 0.6047 +0x3050, // Index: 279 Fraction: 49/81 = 0.6049 +0x1625, // Index: 280 Fraction: 23/38 = 0.6053 +0x2A46, // Index: 281 Fraction: 43/71 = 0.6056 +0x1320, // Index: 282 Fraction: 20/33 = 0.6061 +0x243C, // Index: 283 Fraction: 37/61 = 0.6066 +0x3558, // Index: 284 Fraction: 54/89 = 0.6067 +0x101B, // Index: 285 Fraction: 17/28 = 0.6071 +0x2F4E, // Index: 286 Fraction: 48/79 = 0.6076 +0x1E32, // Index: 287 Fraction: 31/51 = 0.6078 +0x2C49, // Index: 288 Fraction: 45/74 = 0.6081 +0x0D16, // Index: 289 Fraction: 14/23 = 0.6087 +0x3456, // Index: 290 Fraction: 53/87 = 0.6092 +0x263F, // Index: 291 Fraction: 39/64 = 0.6094 +0x1828, // Index: 292 Fraction: 25/41 = 0.6098 +0x233A, // Index: 293 Fraction: 36/59 = 0.6102 +0x2E4C, // Index: 294 Fraction: 47/77 = 0.6104 +0x0A11, // Index: 295 Fraction: 11/18 = 0.6111 +0x3354, // Index: 296 Fraction: 52/85 = 0.6118 +0x2842, // Index: 297 Fraction: 41/67 = 0.6119 +0x1D30, // Index: 298 Fraction: 30/49 = 0.6122 +0x304F, // Index: 299 Fraction: 49/80 = 0.6125 +0x121E, // Index: 300 Fraction: 19/31 = 0.6129 +0x2D4A, // Index: 301 Fraction: 46/75 = 0.6133 +0x1A2B, // Index: 302 Fraction: 27/44 = 0.6136 +0x2238, // Index: 303 Fraction: 35/57 = 0.6140 +0x2A45, // Index: 304 Fraction: 43/70 = 0.6143 +0x3252, // Index: 305 Fraction: 51/83 = 0.6145 +0x070C, // Index: 306 Fraction: 8/13 = 0.6154 +0x3455, // Index: 307 Fraction: 53/86 = 0.6163 +0x2C48, // Index: 308 Fraction: 45/73 = 0.6164 +0x243B, // Index: 309 Fraction: 37/60 = 0.6167 +0x1C2E, // Index: 310 Fraction: 29/47 = 0.6170 +0x3150, // Index: 311 Fraction: 50/81 = 0.6173 +0x1421, // Index: 312 Fraction: 21/34 = 0.6176 +0x3658, // Index: 313 Fraction: 55/89 = 0.6180 +0x2136, // Index: 314 Fraction: 34/55 = 0.6182 +0x2E4B, // Index: 315 Fraction: 47/76 = 0.6184 +0x0C14, // Index: 316 Fraction: 13/21 = 0.6190 +0x2B46, // Index: 317 Fraction: 44/71 = 0.6197 +0x1E31, // Index: 318 Fraction: 31/50 = 0.6200 +0x304E, // Index: 319 Fraction: 49/79 = 0.6203 +0x111C, // Index: 320 Fraction: 18/29 = 0.6207 +0x2841, // Index: 321 Fraction: 41/66 = 0.6212 +0x1624, // Index: 322 Fraction: 23/37 = 0.6216 +0x3251, // Index: 323 Fraction: 51/82 = 0.6220 +0x1B2C, // Index: 324 Fraction: 28/45 = 0.6222 +0x2034, // Index: 325 Fraction: 33/53 = 0.6226 +0x253C, // Index: 326 Fraction: 38/61 = 0.6230 +0x2A44, // Index: 327 Fraction: 43/69 = 0.6232 +0x2F4C, // Index: 328 Fraction: 48/77 = 0.6234 +0x3454, // Index: 329 Fraction: 53/85 = 0.6235 +0x0407, // Index: 330 Fraction: 5/8 = 0.6250 +0x3352, // Index: 331 Fraction: 52/83 = 0.6265 +0x2E4A, // Index: 332 Fraction: 47/75 = 0.6267 +0x2942, // Index: 333 Fraction: 42/67 = 0.6269 +0x243A, // Index: 334 Fraction: 37/59 = 0.6271 +0x1F32, // Index: 335 Fraction: 32/51 = 0.6275 +0x1A2A, // Index: 336 Fraction: 27/43 = 0.6279 +0x304D, // Index: 337 Fraction: 49/78 = 0.6282 +0x1522, // Index: 338 Fraction: 22/35 = 0.6286 +0x263D, // Index: 339 Fraction: 39/62 = 0.6290 +0x3758, // Index: 340 Fraction: 56/89 = 0.6292 +0x101A, // Index: 341 Fraction: 17/27 = 0.6296 +0x2D48, // Index: 342 Fraction: 46/73 = 0.6301 +0x1C2D, // Index: 343 Fraction: 29/46 = 0.6304 +0x2840, // Index: 344 Fraction: 41/65 = 0.6308 +0x3453, // Index: 345 Fraction: 53/84 = 0.6310 +0x0B12, // Index: 346 Fraction: 12/19 = 0.6316 +0x3656, // Index: 347 Fraction: 55/87 = 0.6322 +0x2A43, // Index: 348 Fraction: 43/68 = 0.6324 +0x1E30, // Index: 349 Fraction: 31/49 = 0.6327 +0x314E, // Index: 350 Fraction: 50/79 = 0.6329 +0x121D, // Index: 351 Fraction: 19/30 = 0.6333 +0x2C46, // Index: 352 Fraction: 45/71 = 0.6338 +0x1928, // Index: 353 Fraction: 26/41 = 0.6341 +0x2033, // Index: 354 Fraction: 33/52 = 0.6346 +0x273E, // Index: 355 Fraction: 40/63 = 0.6349 +0x2E49, // Index: 356 Fraction: 47/74 = 0.6351 +0x3554, // Index: 357 Fraction: 54/85 = 0.6353 +0x060A, // Index: 358 Fraction: 7/11 = 0.6364 +0x324F, // Index: 359 Fraction: 51/80 = 0.6375 +0x2B44, // Index: 360 Fraction: 44/69 = 0.6377 +0x2439, // Index: 361 Fraction: 37/58 = 0.6379 +0x1D2E, // Index: 362 Fraction: 30/47 = 0.6383 +0x3452, // Index: 363 Fraction: 53/83 = 0.6386 +0x1623, // Index: 364 Fraction: 23/36 = 0.6389 +0x263C, // Index: 365 Fraction: 39/61 = 0.6393 +0x3655, // Index: 366 Fraction: 55/86 = 0.6395 +0x0F18, // Index: 367 Fraction: 16/25 = 0.6400 +0x3858, // Index: 368 Fraction: 57/89 = 0.6404 +0x283F, // Index: 369 Fraction: 41/64 = 0.6406 +0x1826, // Index: 370 Fraction: 25/39 = 0.6410 +0x2134, // Index: 371 Fraction: 34/53 = 0.6415 +0x2A42, // Index: 372 Fraction: 43/67 = 0.6418 +0x3350, // Index: 373 Fraction: 52/81 = 0.6420 +0x080D, // Index: 374 Fraction: 9/14 = 0.6429 +0x3756, // Index: 375 Fraction: 56/87 = 0.6437 +0x2E48, // Index: 376 Fraction: 47/73 = 0.6438 +0x253A, // Index: 377 Fraction: 38/59 = 0.6441 +0x1C2C, // Index: 378 Fraction: 29/45 = 0.6444 +0x304B, // Index: 379 Fraction: 49/76 = 0.6447 +0x131E, // Index: 380 Fraction: 20/31 = 0.6452 +0x324E, // Index: 381 Fraction: 51/79 = 0.6456 +0x1E2F, // Index: 382 Fraction: 31/48 = 0.6458 +0x2940, // Index: 383 Fraction: 42/65 = 0.6462 +0x3451, // Index: 384 Fraction: 53/82 = 0.6463 +0x0A10, // Index: 385 Fraction: 11/17 = 0.6471 +0x3857, // Index: 386 Fraction: 57/88 = 0.6477 +0x2D46, // Index: 387 Fraction: 46/71 = 0.6479 +0x2235, // Index: 388 Fraction: 35/54 = 0.6481 +0x1724, // Index: 389 Fraction: 24/37 = 0.6486 +0x2438, // Index: 390 Fraction: 37/57 = 0.6491 +0x314C, // Index: 391 Fraction: 50/77 = 0.6494 +0x0C13, // Index: 392 Fraction: 13/20 = 0.6500 +0x3552, // Index: 393 Fraction: 54/83 = 0.6506 +0x283E, // Index: 394 Fraction: 41/63 = 0.6508 +0x1B2A, // Index: 395 Fraction: 28/43 = 0.6512 +0x2A41, // Index: 396 Fraction: 43/66 = 0.6515 +0x3958, // Index: 397 Fraction: 58/89 = 0.6517 +0x0E16, // Index: 398 Fraction: 15/23 = 0.6522 +0x2E47, // Index: 399 Fraction: 47/72 = 0.6528 +0x1F30, // Index: 400 Fraction: 32/49 = 0.6531 +0x304A, // Index: 401 Fraction: 49/75 = 0.6533 +0x1019, // Index: 402 Fraction: 17/26 = 0.6538 +0x3450, // Index: 403 Fraction: 53/81 = 0.6543 +0x2336, // Index: 404 Fraction: 36/55 = 0.6545 +0x3653, // Index: 405 Fraction: 55/84 = 0.6548 +0x121C, // Index: 406 Fraction: 19/29 = 0.6552 +0x3A59, // Index: 407 Fraction: 59/90 = 0.6556 +0x273C, // Index: 408 Fraction: 40/61 = 0.6557 +0x141F, // Index: 409 Fraction: 21/32 = 0.6562 +0x2B42, // Index: 410 Fraction: 44/67 = 0.6567 +0x1622, // Index: 411 Fraction: 23/35 = 0.6571 +0x2F48, // Index: 412 Fraction: 48/73 = 0.6575 +0x1825, // Index: 413 Fraction: 25/38 = 0.6579 +0x334E, // Index: 414 Fraction: 52/79 = 0.6582 +0x1A28, // Index: 415 Fraction: 27/41 = 0.6585 +0x3754, // Index: 416 Fraction: 56/85 = 0.6588 +0x1C2B, // Index: 417 Fraction: 29/44 = 0.6591 +0x1E2E, // Index: 418 Fraction: 31/47 = 0.6596 +0x2031, // Index: 419 Fraction: 33/50 = 0.6600 +0x2234, // Index: 420 Fraction: 35/53 = 0.6604 +0x2437, // Index: 421 Fraction: 37/56 = 0.6607 +0x263A, // Index: 422 Fraction: 39/59 = 0.6610 +0x283D, // Index: 423 Fraction: 41/62 = 0.6613 +0x2A40, // Index: 424 Fraction: 43/65 = 0.6615 +0x2C43, // Index: 425 Fraction: 45/68 = 0.6618 +0x2E46, // Index: 426 Fraction: 47/71 = 0.6620 +0x3049, // Index: 427 Fraction: 49/74 = 0.6622 +0x324C, // Index: 428 Fraction: 51/77 = 0.6623 +0x344F, // Index: 429 Fraction: 53/80 = 0.6625 +0x3652, // Index: 430 Fraction: 55/83 = 0.6627 +0x3855, // Index: 431 Fraction: 57/86 = 0.6628 +0x3A58, // Index: 432 Fraction: 59/89 = 0.6629 +0x0102, // Index: 433 Fraction: 2/3 = 0.6667 +0x3A57, // Index: 434 Fraction: 59/88 = 0.6705 +0x3854, // Index: 435 Fraction: 57/85 = 0.6706 +0x3651, // Index: 436 Fraction: 55/82 = 0.6707 +0x344E, // Index: 437 Fraction: 53/79 = 0.6709 +0x324B, // Index: 438 Fraction: 51/76 = 0.6711 +0x3048, // Index: 439 Fraction: 49/73 = 0.6712 +0x2E45, // Index: 440 Fraction: 47/70 = 0.6714 +0x2C42, // Index: 441 Fraction: 45/67 = 0.6716 +0x2A3F, // Index: 442 Fraction: 43/64 = 0.6719 +0x283C, // Index: 443 Fraction: 41/61 = 0.6721 +0x2639, // Index: 444 Fraction: 39/58 = 0.6724 +0x2436, // Index: 445 Fraction: 37/55 = 0.6727 +0x2233, // Index: 446 Fraction: 35/52 = 0.6731 +0x2030, // Index: 447 Fraction: 33/49 = 0.6735 +0x1E2D, // Index: 448 Fraction: 31/46 = 0.6739 +0x3B58, // Index: 449 Fraction: 60/89 = 0.6742 +0x1C2A, // Index: 450 Fraction: 29/43 = 0.6744 +0x3752, // Index: 451 Fraction: 56/83 = 0.6747 +0x1A27, // Index: 452 Fraction: 27/40 = 0.6750 +0x334C, // Index: 453 Fraction: 52/77 = 0.6753 +0x1824, // Index: 454 Fraction: 25/37 = 0.6757 +0x2F46, // Index: 455 Fraction: 48/71 = 0.6761 +0x1621, // Index: 456 Fraction: 23/34 = 0.6765 +0x2B40, // Index: 457 Fraction: 44/65 = 0.6769 +0x141E, // Index: 458 Fraction: 21/31 = 0.6774 +0x3C59, // Index: 459 Fraction: 61/90 = 0.6778 +0x273A, // Index: 460 Fraction: 40/59 = 0.6780 +0x3A56, // Index: 461 Fraction: 59/87 = 0.6782 +0x121B, // Index: 462 Fraction: 19/28 = 0.6786 +0x3650, // Index: 463 Fraction: 55/81 = 0.6790 +0x2334, // Index: 464 Fraction: 36/53 = 0.6792 +0x344D, // Index: 465 Fraction: 53/78 = 0.6795 +0x1018, // Index: 466 Fraction: 17/25 = 0.6800 +0x3047, // Index: 467 Fraction: 49/72 = 0.6806 +0x1F2E, // Index: 468 Fraction: 32/47 = 0.6809 +0x2E44, // Index: 469 Fraction: 47/69 = 0.6812 +0x0E15, // Index: 470 Fraction: 15/22 = 0.6818 +0x3954, // Index: 471 Fraction: 58/85 = 0.6824 +0x2A3E, // Index: 472 Fraction: 43/63 = 0.6825 +0x1B28, // Index: 473 Fraction: 28/41 = 0.6829 +0x283B, // Index: 474 Fraction: 41/60 = 0.6833 +0x354E, // Index: 475 Fraction: 54/79 = 0.6835 +0x0C12, // Index: 476 Fraction: 13/19 = 0.6842 +0x3148, // Index: 477 Fraction: 50/73 = 0.6849 +0x2435, // Index: 478 Fraction: 37/54 = 0.6852 +0x3C58, // Index: 479 Fraction: 61/89 = 0.6854 +0x1722, // Index: 480 Fraction: 24/35 = 0.6857 +0x3A55, // Index: 481 Fraction: 59/86 = 0.6860 +0x2232, // Index: 482 Fraction: 35/51 = 0.6863 +0x2D42, // Index: 483 Fraction: 46/67 = 0.6866 +0x3852, // Index: 484 Fraction: 57/83 = 0.6867 +0x0A0F, // Index: 485 Fraction: 11/16 = 0.6875 +0x344C, // Index: 486 Fraction: 53/77 = 0.6883 +0x293C, // Index: 487 Fraction: 42/61 = 0.6885 +0x1E2C, // Index: 488 Fraction: 31/45 = 0.6889 +0x3249, // Index: 489 Fraction: 51/74 = 0.6892 +0x131C, // Index: 490 Fraction: 20/29 = 0.6897 +0x3046, // Index: 491 Fraction: 49/71 = 0.6901 +0x1C29, // Index: 492 Fraction: 29/42 = 0.6905 +0x2536, // Index: 493 Fraction: 38/55 = 0.6909 +0x2E43, // Index: 494 Fraction: 47/68 = 0.6912 +0x3750, // Index: 495 Fraction: 56/81 = 0.6914 +0x080C, // Index: 496 Fraction: 9/13 = 0.6923 +0x3C57, // Index: 497 Fraction: 61/88 = 0.6932 +0x334A, // Index: 498 Fraction: 52/75 = 0.6933 +0x2A3D, // Index: 499 Fraction: 43/62 = 0.6935 +0x2130, // Index: 500 Fraction: 34/49 = 0.6939 +0x3A54, // Index: 501 Fraction: 59/85 = 0.6941 +0x1823, // Index: 502 Fraction: 25/36 = 0.6944 +0x283A, // Index: 503 Fraction: 41/59 = 0.6949 +0x3851, // Index: 504 Fraction: 57/82 = 0.6951 +0x0F16, // Index: 505 Fraction: 16/23 = 0.6957 +0x364E, // Index: 506 Fraction: 55/79 = 0.6962 +0x2637, // Index: 507 Fraction: 39/56 = 0.6964 +0x3D58, // Index: 508 Fraction: 62/89 = 0.6966 +0x1620, // Index: 509 Fraction: 23/33 = 0.6970 +0x344B, // Index: 510 Fraction: 53/76 = 0.6974 +0x1D2A, // Index: 511 Fraction: 30/43 = 0.6977 +0x2434, // Index: 512 Fraction: 37/53 = 0.6981 +0x2B3E, // Index: 513 Fraction: 44/63 = 0.6984 +0x3248, // Index: 514 Fraction: 51/73 = 0.6986 +0x3952, // Index: 515 Fraction: 58/83 = 0.6988 +0x0609, // Index: 516 Fraction: 7/10 = 0.7000 +0x3C56, // Index: 517 Fraction: 61/87 = 0.7011 +0x354C, // Index: 518 Fraction: 54/77 = 0.7013 +0x2E42, // Index: 519 Fraction: 47/67 = 0.7015 +0x2738, // Index: 520 Fraction: 40/57 = 0.7018 +0x202E, // Index: 521 Fraction: 33/47 = 0.7021 +0x3A53, // Index: 522 Fraction: 59/84 = 0.7024 +0x1924, // Index: 523 Fraction: 26/37 = 0.7027 +0x2C3F, // Index: 524 Fraction: 45/64 = 0.7031 +0x121A, // Index: 525 Fraction: 19/27 = 0.7037 +0x3146, // Index: 526 Fraction: 50/71 = 0.7042 +0x1E2B, // Index: 527 Fraction: 31/44 = 0.7045 +0x2A3C, // Index: 528 Fraction: 43/61 = 0.7049 +0x364D, // Index: 529 Fraction: 55/78 = 0.7051 +0x0B10, // Index: 530 Fraction: 12/17 = 0.7059 +0x344A, // Index: 531 Fraction: 53/75 = 0.7067 +0x2839, // Index: 532 Fraction: 41/58 = 0.7069 +0x1C28, // Index: 533 Fraction: 29/41 = 0.7073 +0x2D40, // Index: 534 Fraction: 46/65 = 0.7077 +0x3E58, // Index: 535 Fraction: 63/89 = 0.7079 +0x1017, // Index: 536 Fraction: 17/24 = 0.7083 +0x374E, // Index: 537 Fraction: 56/79 = 0.7089 +0x2636, // Index: 538 Fraction: 39/55 = 0.7091 +0x3C55, // Index: 539 Fraction: 61/86 = 0.7093 +0x151E, // Index: 540 Fraction: 22/31 = 0.7097 +0x3044, // Index: 541 Fraction: 49/69 = 0.7101 +0x1A25, // Index: 542 Fraction: 27/38 = 0.7105 +0x3A52, // Index: 543 Fraction: 59/83 = 0.7108 +0x1F2C, // Index: 544 Fraction: 32/45 = 0.7111 +0x2433, // Index: 545 Fraction: 37/52 = 0.7115 +0x293A, // Index: 546 Fraction: 42/59 = 0.7119 +0x2E41, // Index: 547 Fraction: 47/66 = 0.7121 +0x3348, // Index: 548 Fraction: 52/73 = 0.7123 +0x384F, // Index: 549 Fraction: 57/80 = 0.7125 +0x3D56, // Index: 550 Fraction: 62/87 = 0.7126 +0x0406, // Index: 551 Fraction: 5/7 = 0.7143 +0x3E57, // Index: 552 Fraction: 63/88 = 0.7159 +0x3950, // Index: 553 Fraction: 58/81 = 0.7160 +0x3449, // Index: 554 Fraction: 53/74 = 0.7162 +0x2F42, // Index: 555 Fraction: 48/67 = 0.7164 +0x2A3B, // Index: 556 Fraction: 43/60 = 0.7167 +0x2534, // Index: 557 Fraction: 38/53 = 0.7170 +0x202D, // Index: 558 Fraction: 33/46 = 0.7174 +0x3C54, // Index: 559 Fraction: 61/85 = 0.7176 +0x1B26, // Index: 560 Fraction: 28/39 = 0.7179 +0x3246, // Index: 561 Fraction: 51/71 = 0.7183 +0x161F, // Index: 562 Fraction: 23/32 = 0.7188 +0x3F58, // Index: 563 Fraction: 64/89 = 0.7191 +0x2838, // Index: 564 Fraction: 41/57 = 0.7193 +0x3A51, // Index: 565 Fraction: 59/82 = 0.7195 +0x1118, // Index: 566 Fraction: 18/25 = 0.7200 +0x3043, // Index: 567 Fraction: 49/68 = 0.7206 +0x1E2A, // Index: 568 Fraction: 31/43 = 0.7209 +0x2B3C, // Index: 569 Fraction: 44/61 = 0.7213 +0x384E, // Index: 570 Fraction: 57/79 = 0.7215 +0x0C11, // Index: 571 Fraction: 13/18 = 0.7222 +0x3B52, // Index: 572 Fraction: 60/83 = 0.7229 +0x2E40, // Index: 573 Fraction: 47/65 = 0.7231 +0x212E, // Index: 574 Fraction: 34/47 = 0.7234 +0x364B, // Index: 575 Fraction: 55/76 = 0.7237 +0x141C, // Index: 576 Fraction: 21/29 = 0.7241 +0x3144, // Index: 577 Fraction: 50/69 = 0.7246 +0x1C27, // Index: 578 Fraction: 29/40 = 0.7250 +0x2432, // Index: 579 Fraction: 37/51 = 0.7255 +0x2C3D, // Index: 580 Fraction: 45/62 = 0.7258 +0x3448, // Index: 581 Fraction: 53/73 = 0.7260 +0x3C53, // Index: 582 Fraction: 61/84 = 0.7262 +0x070A, // Index: 583 Fraction: 8/11 = 0.7273 +0x3A50, // Index: 584 Fraction: 59/81 = 0.7284 +0x3245, // Index: 585 Fraction: 51/70 = 0.7286 +0x2A3A, // Index: 586 Fraction: 43/59 = 0.7288 +0x222F, // Index: 587 Fraction: 35/48 = 0.7292 +0x3D54, // Index: 588 Fraction: 62/85 = 0.7294 +0x1A24, // Index: 589 Fraction: 27/37 = 0.7297 +0x2D3E, // Index: 590 Fraction: 46/63 = 0.7302 +0x4058, // Index: 591 Fraction: 65/89 = 0.7303 +0x1219, // Index: 592 Fraction: 19/26 = 0.7308 +0x3042, // Index: 593 Fraction: 49/67 = 0.7313 +0x1D28, // Index: 594 Fraction: 30/41 = 0.7317 +0x2837, // Index: 595 Fraction: 41/56 = 0.7321 +0x3346, // Index: 596 Fraction: 52/71 = 0.7324 +0x3E55, // Index: 597 Fraction: 63/86 = 0.7326 +0x0A0E, // Index: 598 Fraction: 11/15 = 0.7333 +0x394E, // Index: 599 Fraction: 58/79 = 0.7342 +0x2E3F, // Index: 600 Fraction: 47/64 = 0.7344 +0x2330, // Index: 601 Fraction: 36/49 = 0.7347 +0x3C52, // Index: 602 Fraction: 61/83 = 0.7349 +0x1821, // Index: 603 Fraction: 25/34 = 0.7353 +0x3F56, // Index: 604 Fraction: 64/87 = 0.7356 +0x2634, // Index: 605 Fraction: 39/53 = 0.7358 +0x3447, // Index: 606 Fraction: 53/72 = 0.7361 +0x0D12, // Index: 607 Fraction: 14/19 = 0.7368 +0x3A4F, // Index: 608 Fraction: 59/80 = 0.7375 +0x2C3C, // Index: 609 Fraction: 45/61 = 0.7377 +0x1E29, // Index: 610 Fraction: 31/42 = 0.7381 +0x2F40, // Index: 611 Fraction: 48/65 = 0.7385 +0x4057, // Index: 612 Fraction: 65/88 = 0.7386 +0x1016, // Index: 613 Fraction: 17/23 = 0.7391 +0x3548, // Index: 614 Fraction: 54/73 = 0.7397 +0x2431, // Index: 615 Fraction: 37/50 = 0.7400 +0x384C, // Index: 616 Fraction: 57/77 = 0.7403 +0x131A, // Index: 617 Fraction: 20/27 = 0.7407 +0x3E54, // Index: 618 Fraction: 63/85 = 0.7412 +0x2A39, // Index: 619 Fraction: 43/58 = 0.7414 +0x4158, // Index: 620 Fraction: 66/89 = 0.7416 +0x161E, // Index: 621 Fraction: 23/31 = 0.7419 +0x3041, // Index: 622 Fraction: 49/66 = 0.7424 +0x1922, // Index: 623 Fraction: 26/35 = 0.7429 +0x3649, // Index: 624 Fraction: 55/74 = 0.7432 +0x1C26, // Index: 625 Fraction: 29/39 = 0.7436 +0x3C51, // Index: 626 Fraction: 61/82 = 0.7439 +0x1F2A, // Index: 627 Fraction: 32/43 = 0.7442 +0x4259, // Index: 628 Fraction: 67/90 = 0.7444 +0x222E, // Index: 629 Fraction: 35/47 = 0.7447 +0x2532, // Index: 630 Fraction: 38/51 = 0.7451 +0x2836, // Index: 631 Fraction: 41/55 = 0.7455 +0x2B3A, // Index: 632 Fraction: 44/59 = 0.7458 +0x2E3E, // Index: 633 Fraction: 47/63 = 0.7460 +0x3142, // Index: 634 Fraction: 50/67 = 0.7463 +0x3446, // Index: 635 Fraction: 53/71 = 0.7465 +0x374A, // Index: 636 Fraction: 56/75 = 0.7467 +0x3A4E, // Index: 637 Fraction: 59/79 = 0.7468 +0x3D52, // Index: 638 Fraction: 62/83 = 0.7470 +0x4056, // Index: 639 Fraction: 65/87 = 0.7471 +0x0203, // Index: 640 Fraction: 3/4 = 0.7500 +0x4258, // Index: 641 Fraction: 67/89 = 0.7528 +0x3F54, // Index: 642 Fraction: 64/85 = 0.7529 +0x3C50, // Index: 643 Fraction: 61/81 = 0.7531 +0x394C, // Index: 644 Fraction: 58/77 = 0.7532 +0x3648, // Index: 645 Fraction: 55/73 = 0.7534 +0x3344, // Index: 646 Fraction: 52/69 = 0.7536 +0x3040, // Index: 647 Fraction: 49/65 = 0.7538 +0x2D3C, // Index: 648 Fraction: 46/61 = 0.7541 +0x2A38, // Index: 649 Fraction: 43/57 = 0.7544 +0x2734, // Index: 650 Fraction: 40/53 = 0.7547 +0x2430, // Index: 651 Fraction: 37/49 = 0.7551 +0x212C, // Index: 652 Fraction: 34/45 = 0.7556 +0x4055, // Index: 653 Fraction: 65/86 = 0.7558 +0x1E28, // Index: 654 Fraction: 31/41 = 0.7561 +0x3A4D, // Index: 655 Fraction: 59/78 = 0.7564 +0x1B24, // Index: 656 Fraction: 28/37 = 0.7568 +0x3445, // Index: 657 Fraction: 53/70 = 0.7571 +0x1820, // Index: 658 Fraction: 25/33 = 0.7576 +0x2E3D, // Index: 659 Fraction: 47/62 = 0.7581 +0x151C, // Index: 660 Fraction: 22/29 = 0.7586 +0x3E52, // Index: 661 Fraction: 63/83 = 0.7590 +0x2835, // Index: 662 Fraction: 41/54 = 0.7593 +0x3B4E, // Index: 663 Fraction: 60/79 = 0.7595 +0x1218, // Index: 664 Fraction: 19/25 = 0.7600 +0x3546, // Index: 665 Fraction: 54/71 = 0.7606 +0x222D, // Index: 666 Fraction: 35/46 = 0.7609 +0x3242, // Index: 667 Fraction: 51/67 = 0.7612 +0x4257, // Index: 668 Fraction: 67/88 = 0.7614 +0x0F14, // Index: 669 Fraction: 16/21 = 0.7619 +0x3C4F, // Index: 670 Fraction: 61/80 = 0.7625 +0x2C3A, // Index: 671 Fraction: 45/59 = 0.7627 +0x1C25, // Index: 672 Fraction: 29/38 = 0.7632 +0x2936, // Index: 673 Fraction: 42/55 = 0.7636 +0x3647, // Index: 674 Fraction: 55/72 = 0.7639 +0x4358, // Index: 675 Fraction: 68/89 = 0.7640 +0x0C10, // Index: 676 Fraction: 13/17 = 0.7647 +0x3D50, // Index: 677 Fraction: 62/81 = 0.7654 +0x303F, // Index: 678 Fraction: 49/64 = 0.7656 +0x232E, // Index: 679 Fraction: 36/47 = 0.7660 +0x3A4C, // Index: 680 Fraction: 59/77 = 0.7662 +0x161D, // Index: 681 Fraction: 23/30 = 0.7667 +0x3748, // Index: 682 Fraction: 56/73 = 0.7671 +0x202A, // Index: 683 Fraction: 33/43 = 0.7674 +0x2A37, // Index: 684 Fraction: 43/56 = 0.7679 +0x3444, // Index: 685 Fraction: 53/69 = 0.7681 +0x3E51, // Index: 686 Fraction: 63/82 = 0.7683 +0x090C, // Index: 687 Fraction: 10/13 = 0.7692 +0x4256, // Index: 688 Fraction: 67/87 = 0.7701 +0x3849, // Index: 689 Fraction: 57/74 = 0.7703 +0x2E3C, // Index: 690 Fraction: 47/61 = 0.7705 +0x242F, // Index: 691 Fraction: 37/48 = 0.7708 +0x3F52, // Index: 692 Fraction: 64/83 = 0.7711 +0x1A22, // Index: 693 Fraction: 27/35 = 0.7714 +0x2B38, // Index: 694 Fraction: 44/57 = 0.7719 +0x3C4E, // Index: 695 Fraction: 61/79 = 0.7722 +0x1015, // Index: 696 Fraction: 17/22 = 0.7727 +0x394A, // Index: 697 Fraction: 58/75 = 0.7733 +0x2834, // Index: 698 Fraction: 41/53 = 0.7736 +0x4053, // Index: 699 Fraction: 65/84 = 0.7738 +0x171E, // Index: 700 Fraction: 24/31 = 0.7742 +0x3646, // Index: 701 Fraction: 55/71 = 0.7746 +0x1E27, // Index: 702 Fraction: 31/40 = 0.7750 +0x4458, // Index: 703 Fraction: 69/89 = 0.7753 +0x2530, // Index: 704 Fraction: 38/49 = 0.7755 +0x2C39, // Index: 705 Fraction: 45/58 = 0.7759 +0x3342, // Index: 706 Fraction: 52/67 = 0.7761 +0x3A4B, // Index: 707 Fraction: 59/76 = 0.7763 +0x4154, // Index: 708 Fraction: 66/85 = 0.7765 +0x0608, // Index: 709 Fraction: 7/9 = 0.7778 +0x4255, // Index: 710 Fraction: 67/86 = 0.7791 +0x3B4C, // Index: 711 Fraction: 60/77 = 0.7792 +0x3443, // Index: 712 Fraction: 53/68 = 0.7794 +0x2D3A, // Index: 713 Fraction: 46/59 = 0.7797 +0x2631, // Index: 714 Fraction: 39/50 = 0.7800 +0x1F28, // Index: 715 Fraction: 32/41 = 0.7805 +0x3848, // Index: 716 Fraction: 57/73 = 0.7808 +0x181F, // Index: 717 Fraction: 25/32 = 0.7812 +0x4356, // Index: 718 Fraction: 68/87 = 0.7816 +0x2A36, // Index: 719 Fraction: 43/55 = 0.7818 +0x3C4D, // Index: 720 Fraction: 61/78 = 0.7821 +0x1116, // Index: 721 Fraction: 18/23 = 0.7826 +0x4052, // Index: 722 Fraction: 65/83 = 0.7831 +0x2E3B, // Index: 723 Fraction: 47/60 = 0.7833 +0x1C24, // Index: 724 Fraction: 29/37 = 0.7838 +0x4457, // Index: 725 Fraction: 69/88 = 0.7841 +0x2732, // Index: 726 Fraction: 40/51 = 0.7843 +0x3240, // Index: 727 Fraction: 51/65 = 0.7846 +0x3D4E, // Index: 728 Fraction: 62/79 = 0.7848 +0x0A0D, // Index: 729 Fraction: 11/14 = 0.7857 +0x4558, // Index: 730 Fraction: 70/89 = 0.7865 +0x3A4A, // Index: 731 Fraction: 59/75 = 0.7867 +0x2F3C, // Index: 732 Fraction: 48/61 = 0.7869 +0x242E, // Index: 733 Fraction: 37/47 = 0.7872 +0x3E4F, // Index: 734 Fraction: 63/80 = 0.7875 +0x1920, // Index: 735 Fraction: 26/33 = 0.7879 +0x4254, // Index: 736 Fraction: 67/85 = 0.7882 +0x2833, // Index: 737 Fraction: 41/52 = 0.7885 +0x3746, // Index: 738 Fraction: 56/71 = 0.7887 +0x4659, // Index: 739 Fraction: 71/90 = 0.7889 +0x0E12, // Index: 740 Fraction: 15/19 = 0.7895 +0x3F50, // Index: 741 Fraction: 64/81 = 0.7901 +0x303D, // Index: 742 Fraction: 49/62 = 0.7903 +0x212A, // Index: 743 Fraction: 34/43 = 0.7907 +0x3442, // Index: 744 Fraction: 53/67 = 0.7910 +0x1217, // Index: 745 Fraction: 19/24 = 0.7917 +0x3C4C, // Index: 746 Fraction: 61/77 = 0.7922 +0x2934, // Index: 747 Fraction: 42/53 = 0.7925 +0x4051, // Index: 748 Fraction: 65/82 = 0.7927 +0x161C, // Index: 749 Fraction: 23/29 = 0.7931 +0x313E, // Index: 750 Fraction: 50/63 = 0.7937 +0x1A21, // Index: 751 Fraction: 27/34 = 0.7941 +0x3948, // Index: 752 Fraction: 58/73 = 0.7945 +0x1E26, // Index: 753 Fraction: 31/39 = 0.7949 +0x4152, // Index: 754 Fraction: 66/83 = 0.7952 +0x222B, // Index: 755 Fraction: 35/44 = 0.7955 +0x2630, // Index: 756 Fraction: 39/49 = 0.7959 +0x2A35, // Index: 757 Fraction: 43/54 = 0.7963 +0x2E3A, // Index: 758 Fraction: 47/59 = 0.7966 +0x323F, // Index: 759 Fraction: 51/64 = 0.7969 +0x3644, // Index: 760 Fraction: 55/69 = 0.7971 +0x3A49, // Index: 761 Fraction: 59/74 = 0.7973 +0x3E4E, // Index: 762 Fraction: 63/79 = 0.7975 +0x4253, // Index: 763 Fraction: 67/84 = 0.7976 +0x4658, // Index: 764 Fraction: 71/89 = 0.7978 +0x0304, // Index: 765 Fraction: 4/5 = 0.8000 +0x4455, // Index: 766 Fraction: 69/86 = 0.8023 +0x4050, // Index: 767 Fraction: 65/81 = 0.8025 +0x3C4B, // Index: 768 Fraction: 61/76 = 0.8026 +0x3846, // Index: 769 Fraction: 57/71 = 0.8028 +0x3441, // Index: 770 Fraction: 53/66 = 0.8030 +0x303C, // Index: 771 Fraction: 49/61 = 0.8033 +0x2C37, // Index: 772 Fraction: 45/56 = 0.8036 +0x2832, // Index: 773 Fraction: 41/51 = 0.8039 +0x242D, // Index: 774 Fraction: 37/46 = 0.8043 +0x4556, // Index: 775 Fraction: 70/87 = 0.8046 +0x2028, // Index: 776 Fraction: 33/41 = 0.8049 +0x3D4C, // Index: 777 Fraction: 62/77 = 0.8052 +0x1C23, // Index: 778 Fraction: 29/36 = 0.8056 +0x3542, // Index: 779 Fraction: 54/67 = 0.8060 +0x181E, // Index: 780 Fraction: 25/31 = 0.8065 +0x4657, // Index: 781 Fraction: 71/88 = 0.8068 +0x2D38, // Index: 782 Fraction: 46/57 = 0.8070 +0x4252, // Index: 783 Fraction: 67/83 = 0.8072 +0x1419, // Index: 784 Fraction: 21/26 = 0.8077 +0x3A48, // Index: 785 Fraction: 59/73 = 0.8082 +0x252E, // Index: 786 Fraction: 38/47 = 0.8085 +0x3643, // Index: 787 Fraction: 55/68 = 0.8088 +0x4758, // Index: 788 Fraction: 72/89 = 0.8090 +0x1014, // Index: 789 Fraction: 17/21 = 0.8095 +}; diff --git a/examples/ffva/src/register_setup_1000ppm.h b/examples/ffva/src/register_setup_1000ppm.h new file mode 100644 index 00000000..68f3ed52 --- /dev/null +++ b/examples/ffva/src/register_setup_1000ppm.h @@ -0,0 +1,17 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +/* Autogenerated by sw_pll_sim.py using command: + /Users/ed/sandboxes/sw_xvf3800/lib_sw_pll/python/sw_pll/pll_calc.py -i 24.0 -a -m 90 -t 12.288 -p 6.0 -e 5 -r --fracmin 0.49 --fracmax 0.81 --header + Picked output solution #83 + F: 154 + R: 1 + f: 56 + p: 87 + OD: 1 + ACD: 18 + Output freq: 12287978.0 + VCO freq: 1867770000.0 */ + +#define APP_PLL_CTL_REG 0x08809A01 +#define APP_PLL_DIV_REG 0x80000012 +#define APP_PLL_FRAC_REG 0x80003857 From 943eec599d6d2f9b50afa9b4a0bb150a65b4fb30 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 11:31:30 +0000 Subject: [PATCH 048/288] Enable ports --- examples/ffva/src/main.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index f9c74485..daf009bf 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -363,17 +363,20 @@ static void set_pll_lock_status_ptr(int* p) void startup_task(void *arg) { rtos_printf("Startup task running from tile %d on core %d\n", THIS_XCORE_TILE, portGET_CORE_ID()); - platform_start(); #if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL sw_pll_state_t sw_pll = {0}; port_t p_bclk = PORT_I2S_BCLK; - port_t p_lrclk = PORT_I2S_LRCLK; port_t p_mclk = PORT_MCLK; port_t p_mclk_count = PORT_MCLK_COUNT; // Used internally by sw_pll port_t p_bclk_count = PORT_BCLK_COUNT; // Used internally by sw_pll xclock_t ck_bclk = I2S_CLKBLK; + + port_enable(p_mclk); + port_enable(p_bclk); + // NOTE: p_lrclk does not need to be enabled by the caller + set_pll_lock_status_ptr(&sw_pll.lock_status); // Create clock from mclk port and use it to clock the p_mclk_count port which will count MCLKs. port_enable(p_mclk_count); From abf7540de45332750341a8901965d6ed736029de Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 12:26:00 +0000 Subject: [PATCH 049/288] SW PPL is working --- .../XK_VOICE_L71/platform/platform_init.c | 1 + .../XK_VOICE_L71/platform/driver_instances.c | 3 + .../XK_VOICE_L71/platform/driver_instances.h | 16 +++++ .../XK_VOICE_L71/platform/platform_init.c | 61 +++++++++++++++- examples/ffva/src/main.c | 70 +------------------ 5 files changed, 83 insertions(+), 68 deletions(-) diff --git a/examples/asrc_demo/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/asrc_demo/bsp_config/XK_VOICE_L71/platform/platform_init.c index fb330f22..97fd2d7d 100644 --- a/examples/asrc_demo/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/asrc_demo/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -114,6 +114,7 @@ static void usb_init(void) void platform_init(chanend_t other_tile_c) { rtos_intertile_init(intertile_ctx, other_tile_c); + rtos_intertile_init(intertile_usb_audio_ctx, other_tile_c); rtos_intertile_init(intertile_i2s_audio_ctx, other_tile_c); diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c index 7eb25cc1..106899db 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.c @@ -38,3 +38,6 @@ rtos_dfu_image_t *dfu_image_ctx = &dfu_image_ctx_s; static rtos_uart_tx_t uart_tx_ctx_s; rtos_uart_tx_t *uart_tx_ctx = &uart_tx_ctx_s; + +static sw_pll_ctx_t sw_pll_ctx_s; +sw_pll_ctx_t *sw_pll_ctx = &sw_pll_ctx_s; diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h index 83da5c08..9e02ae72 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/driver_instances.h @@ -15,6 +15,11 @@ #include "rtos_spi_slave.h" #include "rtos_uart_tx.h" +/* Config headers for sw_pll */ +#include "sw_pll.h" +#include "fractions_1000ppm.h" +#include "register_setup_1000ppm.h" + /* Tile specifiers */ #define FLASH_TILE_NO 0 #define I2C_TILE_NO 0 @@ -63,4 +68,15 @@ extern rtos_i2s_t *i2s_ctx; extern rtos_dfu_image_t *dfu_image_ctx; extern rtos_uart_tx_t *uart_tx_ctx; +typedef struct { + port_t p_mclk_count; // Used for keeping track of MCLK output for sw_pll + port_t p_bclk_count; // Used for keeping track of BCLK input for sw_pll + sw_pll_state_t *sw_pll; // Pointer to sw_pll state (if used) + +}sw_pll_ctx_t; + +static sw_pll_state_t sw_pll = {0}; + +extern sw_pll_ctx_t *sw_pll_ctx; + #endif /* DRIVER_INSTANCES_H_ */ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index 3bae514e..c4429903 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -152,6 +152,64 @@ static void spi_init(void) #endif } +#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL +static int *p_lock_status = NULL; +/// @brief Save the pointer to the pll lock_status variable +static void set_pll_lock_status_ptr(int* p) +{ + p_lock_status = p; +} +#endif + +static void platform_sw_pll_init(void) +{ +#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL + + port_t p_bclk = PORT_I2S_BCLK; + port_t p_mclk = PORT_MCLK; + port_t p_mclk_count = PORT_MCLK_COUNT; // Used internally by sw_pll + port_t p_bclk_count = PORT_BCLK_COUNT; // Used internally by sw_pll + xclock_t ck_bclk = I2S_CLKBLK; + + port_enable(p_mclk); + port_enable(p_bclk); + // NOTE: p_lrclk does not need to be enabled by the caller + + set_pll_lock_status_ptr(&sw_pll.lock_status); + // Create clock from mclk port and use it to clock the p_mclk_count port which will count MCLKs. + port_enable(p_mclk_count); + port_enable(p_bclk_count); + + // Allow p_mclk_count to count mclks + xclock_t clk_mclk = MCLK_CLKBLK; + clock_enable(clk_mclk); + clock_set_source_port(clk_mclk, p_mclk); + port_set_clock(p_mclk_count, clk_mclk); + clock_start(clk_mclk); + + // Allow p_bclk_count to count bclks + port_set_clock(p_bclk_count, ck_bclk); + sw_pll_init(&sw_pll, + SW_PLL_15Q16(0.0), + SW_PLL_15Q16(1.0), + PLL_CONTROL_LOOP_COUNT_INT, + PLL_RATIO, + (appconfBCLK_NOMINAL_HZ / appconfLRCLK_NOMINAL_HZ), + frac_values_90, + SW_PLL_NUM_LUT_ENTRIES(frac_values_90), + APP_PLL_CTL_REG, + APP_PLL_DIV_REG, + SW_PLL_NUM_LUT_ENTRIES(frac_values_90) / 2, + PLL_PPM_RANGE); + + debug_printf("Using SW PLL to track I2S input\n"); + sw_pll_ctx->sw_pll = &sw_pll; + sw_pll_ctx->p_mclk_count = p_mclk_count; + sw_pll_ctx->p_bclk_count = p_bclk_count; + +#endif +} + static void mics_init(void) { static rtos_driver_rpc_t mic_array_rpc_config; @@ -264,7 +322,7 @@ void platform_init(chanend_t other_tile_c) { rtos_intertile_init(intertile_ctx, other_tile_c); rtos_intertile_init(intertile_usb_audio_ctx, other_tile_c); - + platform_sw_pll_init(); mclk_init(other_tile_c); gpio_init(); flash_init(); @@ -274,4 +332,5 @@ void platform_init(chanend_t other_tile_c) i2s_init(); usb_init(); uart_init(); + } diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index daf009bf..11133b24 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -35,19 +35,11 @@ /* Config headers for sw_pll */ #include "sw_pll.h" -#include "fractions_1000ppm.h" -#include "register_setup_1000ppm.h" +//#include "register_setup_1000ppm.h" volatile int mic_from_usb = appconfMIC_SRC_DEFAULT; volatile int aec_ref_source = appconfAEC_REF_DEFAULT; -typedef struct i2s_callback_args_t { - port_t p_mclk_count; // Used for keeping track of MCLK output for sw_pll - port_t p_bclk_count; // Used for keeping track of BCLK input for sw_pll - sw_pll_state_t *sw_pll; // Pointer to sw_pll state (if used) - -} i2s_callback_args_t; - #if appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) void i2s_slave_intertile(void *args) { (void) args; @@ -76,7 +68,7 @@ void i2s_slave_intertile(void *args) { #if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL - i2s_callback_args_t* i2s_callback_args = (i2s_callback_args_t*) args; + sw_pll_ctx_t* i2s_callback_args = (sw_pll_ctx_t*) args; port_clear_buffer(i2s_callback_args->p_bclk_count); port_in(i2s_callback_args->p_bclk_count); // Block until BCLK transition to synchronise. Will consume up to 1/64 of a LRCLK cycle uint16_t mclk_pt = port_get_trigger_time(i2s_callback_args->p_mclk_count); // Immediately sample mclk_count @@ -353,72 +345,16 @@ static void mem_analysis(void) } } -static int *p_lock_status = NULL; -/// @brief Save the pointer to the pll lock_status variable -static void set_pll_lock_status_ptr(int* p) -{ - p_lock_status = p; -} - void startup_task(void *arg) { rtos_printf("Startup task running from tile %d on core %d\n", THIS_XCORE_TILE, portGET_CORE_ID()); platform_start(); -#if ON_TILE(1) && appconfRECOVER_MCLK_I2S_APP_PLL - - sw_pll_state_t sw_pll = {0}; - port_t p_bclk = PORT_I2S_BCLK; - port_t p_mclk = PORT_MCLK; - port_t p_mclk_count = PORT_MCLK_COUNT; // Used internally by sw_pll - port_t p_bclk_count = PORT_BCLK_COUNT; // Used internally by sw_pll - xclock_t ck_bclk = I2S_CLKBLK; - - port_enable(p_mclk); - port_enable(p_bclk); - // NOTE: p_lrclk does not need to be enabled by the caller - - set_pll_lock_status_ptr(&sw_pll.lock_status); - // Create clock from mclk port and use it to clock the p_mclk_count port which will count MCLKs. - port_enable(p_mclk_count); - port_enable(p_bclk_count); - - // Allow p_mclk_count to count mclks - xclock_t clk_mclk = MCLK_CLKBLK; - clock_enable(clk_mclk); - clock_set_source_port(clk_mclk, p_mclk); - port_set_clock(p_mclk_count, clk_mclk); - clock_start(clk_mclk); - - // Allow p_bclk_count to count bclks - port_set_clock(p_bclk_count, ck_bclk); - printintln(111); - sw_pll_init(&sw_pll, - SW_PLL_15Q16(0.0), - SW_PLL_15Q16(1.0), - PLL_CONTROL_LOOP_COUNT_INT, - PLL_RATIO, - (appconfBCLK_NOMINAL_HZ / appconfLRCLK_NOMINAL_HZ), - frac_values_90, - SW_PLL_NUM_LUT_ENTRIES(frac_values_90), - APP_PLL_CTL_REG, - APP_PLL_DIV_REG, - SW_PLL_NUM_LUT_ENTRIES(frac_values_90) / 2, - PLL_PPM_RANGE); - printintln(112); - - debug_printf("Using SW PLL to track I2S input\n"); - i2s_callback_args_t i2s_callback_args = { - .sw_pll = &sw_pll, - .p_mclk_count = p_mclk_count, - .p_bclk_count = p_bclk_count - }; -#endif #if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) xTaskCreate((TaskFunction_t) i2s_slave_intertile, "i2s_slave_intertile", RTOS_THREAD_STACK_SIZE(i2s_slave_intertile), - &i2s_callback_args, + sw_pll_ctx, appconfAUDIO_PIPELINE_TASK_PRIORITY, NULL); #endif From 25505fdf00a675a7ac5965acebce0d4e80774361 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 16:08:29 +0000 Subject: [PATCH 050/288] More review comments --- .../XK_VOICE_L71/platform/platform_start.c | 2 + examples/ffva/ffva_int_cyberon.cmake | 48 ------------------- 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index dc51981a..6d1631dd 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -40,6 +40,8 @@ static void flash_start(void) { #if ON_TILE(FLASH_TILE_NO) rtos_qspi_flash_start(qspi_flash_ctx, appconfQSPI_FLASH_TASK_PRIORITY); + rtos_qspi_flash_op_core_affinity_set(qspi_flash_ctx, flash_core_map); + #endif } diff --git a/examples/ffva/ffva_int_cyberon.cmake b/examples/ffva/ffva_int_cyberon.cmake index b7072dda..573908d5 100644 --- a/examples/ffva/ffva_int_cyberon.cmake +++ b/examples/ffva/ffva_int_cyberon.cmake @@ -127,8 +127,6 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) set(MODEL_FILE ${TARGET_NAME}_model.bin) set(FATFS_FILE ${TARGET_NAME}_fat.fs) set(FLASH_CAL_FILE ${LIB_QSPI_FAST_READ_ROOT_PATH}/lib_qspi_fast_read/calibration_pattern_nibble_swap.bin) - #set(FATFS_CONTENTS_DIR ${TARGET_NAME}_fatmktmp) - add_custom_target(${MODEL_FILE} ALL COMMAND ${CMAKE_COMMAND} -E copy ${CYBERON_COMMAND_NET_FILE} ${MODEL_FILE} @@ -189,50 +187,4 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) unset(DATA_PARTITION_FILE_LIST) unset(DATA_PARTITION_DEPENDS_LIST) - #add_custom_target( - # ${FATFS_FILE} ALL - # COMMAND ${CMAKE_COMMAND} -E rm -rf ${FATFS_CONTENTS_DIR}/fs/ - # COMMAND ${CMAKE_COMMAND} -E make_directory ${FATFS_CONTENTS_DIR}/fs/ - # COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/filesystem_support/demo.txt ${FATFS_CONTENTS_DIR}/fs/ - # COMMAND fatfs_mkimage --input=${FATFS_CONTENTS_DIR} --output=${FATFS_FILE} - # COMMENT - # "Create filesystem" - # VERBATIM - #) - - #set_target_properties(${FATFS_FILE} PROPERTIES - # ADDITIONAL_CLEAN_FILES ${FATFS_CONTENTS_DIR} - #) - - # The filesystem is the only component in the data partition, copy it to - # the assocated data partition file which is required for CI. - #add_custom_command( - # OUTPUT ${DATA_PARTITION_FILE} - # COMMAND ${CMAKE_COMMAND} -E copy ${FATFS_FILE} ${DATA_PARTITION_FILE} - # DEPENDS - # ${FATFS_FILE} - # COMMENT - # "Create data partition" - # VERBATIM - #) - - #list(APPEND DATA_PARTITION_FILE_LIST - # ${FATFS_FILE} - # ${DATA_PARTITION_FILE} - #) - - #create_data_partition_directory( - # #[[ Target ]] ${TARGET_NAME} - # #[[ Copy Files ]] "${DATA_PARTITION_FILE_LIST}" - # #[[ Dependencies ]] "${DATA_PARTITION_FILE_LIST}" - #) - - #create_flash_app_target( - # #[[ Target ]] ${TARGET_NAME} - # #[[ Boot Partition Size ]] 0x100000 - # #[[ Data Partition Contents ]] ${DATA_PARTITION_FILE} - # #[[ Dependencies ]] ${DATA_PARTITION_FILE} - #) - - #unset(DATA_PARTITION_FILE_LIST) endforeach() From e9448d58144473187c9df44ced5d545f561f3cbc Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 16:13:06 +0000 Subject: [PATCH 051/288] Rename define --- .../ffd/software_desc/intent_engine.rst | 8 ++++---- .../low_power_ffd/software_desc/intent_engine.rst | 2 +- examples/ffd/src/app_conf.h | 6 +++--- examples/ffd/src/main.c | 6 +++--- examples/ffva/src/app_conf.h | 6 +++--- examples/ffva/src/app_conf_check.h | 2 +- examples/low_power_ffd/src/app_conf.h | 4 ++-- examples/low_power_ffd/src/app_conf_check.h | 2 +- .../src/intent_engine/intent_engine.c | 2 +- .../src/intent_engine/intent_engine_support.c | 6 +++--- examples/low_power_ffd/src/main.c | 6 +++--- .../src/power/low_power_audio_buffer.h | 2 +- examples/low_power_ffd/src/power/power_control.h | 2 +- modules/asr/intent_engine/intent_engine.c | 2 +- modules/asr/intent_engine/intent_engine_io.c | 6 +++--- modules/asr/intent_engine/intent_engine_support.c | 14 +++++++------- test/ffd_low_power_audio_buffer/src/app_conf.h | 2 +- 17 files changed, 39 insertions(+), 39 deletions(-) diff --git a/doc/programming_guide/ffd/software_desc/intent_engine.rst b/doc/programming_guide/ffd/software_desc/intent_engine.rst index 3331173a..fd252d18 100644 --- a/doc/programming_guide/ffd/software_desc/intent_engine.rst +++ b/doc/programming_guide/ffd/software_desc/intent_engine.rst @@ -48,7 +48,7 @@ In FFD, the audio pipeline output is on tile 1 and the ASR engine on tile 0. .. code-block:: c :caption: intent_engine_create snippet (intent_engine_io.c) - #if ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO + #if ASR_TILE_NO == AUDIO_PIPELINE_OUTPUT_TILE_NO intent_engine_task_create(priority); #else intent_engine_intertile_task_create(priority); @@ -67,7 +67,7 @@ samples at startup. :caption: intent_engine_create snippet (intent_engine_io.c) int sync = 0; - #if ON_TILE(AUDIO_PIPELINE_TILE_NO) + #if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); xassert(len == sizeof(sync)); rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); @@ -85,8 +85,8 @@ In FFD, the audio pipeline output is on tile 1 and the ASR engine on tile 0. .. code-block:: c :caption: intent_engine_create snippet (intent_engine_io.c) - #if appconfINTENT_ENABLED && ON_TILE(AUDIO_PIPELINE_TILE_NO) - #if ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO + #if appconfINTENT_ENABLED && ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) + #if ASR_TILE_NO == AUDIO_PIPELINE_OUTPUT_TILE_NO intent_engine_samples_send_local( frames, buf); diff --git a/doc/programming_guide/low_power_ffd/software_desc/intent_engine.rst b/doc/programming_guide/low_power_ffd/software_desc/intent_engine.rst index d94be582..f2225310 100644 --- a/doc/programming_guide/low_power_ffd/software_desc/intent_engine.rst +++ b/doc/programming_guide/low_power_ffd/software_desc/intent_engine.rst @@ -68,7 +68,7 @@ samples at startup. :caption: intent_engine_create snippet (intent_engine_io.c) int sync = 0; - #if ON_TILE(AUDIO_PIPELINE_TILE_NO) + #if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); xassert(len == sizeof(sync)); rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index d5a4ce41..c3bf7111 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -14,9 +14,9 @@ /* Application tile specifiers */ #include "platform/driver_instances.h" -#define AUDIO_PIPELINE_TILE_NO MICARRAY_TILE_NO -#define ASR_TILE_NO FLASH_TILE_NO -#define FS_TILE_NO FLASH_TILE_NO +#define AUDIO_PIPELINE_OUTPUT_TILE_NO MICARRAY_TILE_NO +#define ASR_TILE_NO FLASH_TILE_NO +#define FS_TILE_NO FLASH_TILE_NO /* Audio Pipeline Configuration */ #define appconfAUDIO_CLOCK_FREQUENCY MIC_ARRAY_CONFIG_MCLK_FREQ diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index 043ce624..e73db1b7 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -66,9 +66,9 @@ int audio_pipeline_output(void *output_app_data, size_t ch_count, size_t frame_count) { -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) && appconfINTENT_ENABLED +#if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) && appconfINTENT_ENABLED intent_engine_sample_push((int32_t *)output_audio_frames, frame_count); -#endif // ON_TILE(AUDIO_PIPELINE_TILE_NO) && appconfINTENT_ENABLED +#endif // ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) && appconfINTENT_ENABLED return AUDIO_PIPELINE_FREE_FRAME; } @@ -117,7 +117,7 @@ void startup_task(void *arg) intent_engine_create(appconfINTENT_MODEL_RUNNER_TASK_PRIORITY, q_intent); #endif -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) +#if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) #if appconfINTENT_ENABLED // Wait until the intent engine is initialized before starting the // audio pipeline. diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 8652a838..64e57475 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -21,9 +21,9 @@ /* Application tile specifiers */ #include "platform/driver_instances.h" -#define ASR_TILE_NO FLASH_TILE_NO -#define FS_TILE_NO FLASH_TILE_NO -#define AUDIO_PIPELINE_TILE_NO FLASH_TILE_NO +#define ASR_TILE_NO FLASH_TILE_NO +#define FS_TILE_NO FLASH_TILE_NO +#define AUDIO_PIPELINE_OUTPUT_TILE_NO FLASH_TILE_NO /* Audio Pipeline Configuration */ #define appconfAUDIO_CLOCK_FREQUENCY MIC_ARRAY_CONFIG_MCLK_FREQ diff --git a/examples/ffva/src/app_conf_check.h b/examples/ffva/src/app_conf_check.h index 06b9bd26..c0e04d7d 100644 --- a/examples/ffva/src/app_conf_check.h +++ b/examples/ffva/src/app_conf_check.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 XMOS LIMITED. +// Copyright 2021-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef APP_CONF_CHECK_H_ diff --git a/examples/low_power_ffd/src/app_conf.h b/examples/low_power_ffd/src/app_conf.h index 734c0931..b436fb8b 100644 --- a/examples/low_power_ffd/src/app_conf.h +++ b/examples/low_power_ffd/src/app_conf.h @@ -18,10 +18,10 @@ /* Application tile specifiers */ #include "platform/driver_instances.h" -#define AUDIO_PIPELINE_TILE_NO MICARRAY_TILE_NO +#define AUDIO_PIPELINE_OUTPUT_TILE_NO MICARRAY_TILE_NO #define ASR_TILE_NO FLASH_TILE_NO #define FS_TILE_NO FLASH_TILE_NO -#define WAKEWORD_TILE_NO AUDIO_PIPELINE_TILE_NO +#define WAKEWORD_TILE_NO AUDIO_PIPELINE_OUTPUT_TILE_NO /* Sensory specific settings */ #if ON_TILE(ASR_TILE_NO) diff --git a/examples/low_power_ffd/src/app_conf_check.h b/examples/low_power_ffd/src/app_conf_check.h index 8663c43a..b38c1472 100644 --- a/examples/low_power_ffd/src/app_conf_check.h +++ b/examples/low_power_ffd/src/app_conf_check.h @@ -4,7 +4,7 @@ #ifndef APP_CONF_CHECK_H_ #define APP_CONF_CHECK_H_ -#if ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO +#if ASR_TILE_NO == AUDIO_PIPELINE_OUTPUT_TILE_NO #error "This application currently expects the ASR and audio pipeline to be on separate tiles." #endif diff --git a/examples/low_power_ffd/src/intent_engine/intent_engine.c b/examples/low_power_ffd/src/intent_engine/intent_engine.c index e2af1b42..5af00a4b 100644 --- a/examples/low_power_ffd/src/intent_engine/intent_engine.c +++ b/examples/low_power_ffd/src/intent_engine/intent_engine.c @@ -274,7 +274,7 @@ void intent_engine_task(void *args) void intent_engine_ready_sync(void) { int sync = 0; -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) +#if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); xassert(len == sizeof(sync)); rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); diff --git a/examples/low_power_ffd/src/intent_engine/intent_engine_support.c b/examples/low_power_ffd/src/intent_engine/intent_engine_support.c index 995cb389..df7ba9ae 100644 --- a/examples/low_power_ffd/src/intent_engine/intent_engine_support.c +++ b/examples/low_power_ffd/src/intent_engine/intent_engine_support.c @@ -22,7 +22,7 @@ static StreamBufferHandle_t samples_to_engine_stream_buf = 0; #endif /* ON_TILE(ASR_TILE_NO) */ -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) +#if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) void intent_engine_samples_send_remote( rtos_intertile_t *intertile, @@ -37,7 +37,7 @@ void intent_engine_samples_send_remote( sizeof(asr_sample_t) * frame_count); } -#else /* ON_TILE(AUDIO_PIPELINE_TILE_NO) */ +#else /* ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) */ static void intent_engine_intertile_samples_in_task(void *arg) { @@ -92,4 +92,4 @@ void intent_engine_intertile_task_create(uint32_t priority) NULL); } -#endif /* ON_TILE(AUDIO_PIPELINE_TILE_NO) */ +#endif /* ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) */ diff --git a/examples/low_power_ffd/src/main.c b/examples/low_power_ffd/src/main.c index 7b17cc03..4e1991ba 100644 --- a/examples/low_power_ffd/src/main.c +++ b/examples/low_power_ffd/src/main.c @@ -102,7 +102,7 @@ int audio_pipeline_output(void *output_app_data, size_t ch_count, size_t frame_count) { -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) +#if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) asr_sample_t asr_buf[appconfAUDIO_PIPELINE_FRAME_ADVANCE] = {0}; @@ -145,7 +145,7 @@ int audio_pipeline_output(void *output_app_data, intent_engine_sample_push(asr_buf, frame_count); } #endif // LOW_POWER_AUDIO_BUFFER_ENABLED -#endif // ON_TILE(AUDIO_PIPELINE_TILE_NO) +#endif // ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) return AUDIO_PIPELINE_DONT_FREE_FRAME; } @@ -211,7 +211,7 @@ void startup_task(void *arg) intent_engine_create(appconfINTENT_MODEL_RUNNER_TASK_PRIORITY, q_intent); #endif -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) +#if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) // Wait until the intent engine is initialized before starting the // audio pipeline. intent_engine_ready_sync(); diff --git a/examples/low_power_ffd/src/power/low_power_audio_buffer.h b/examples/low_power_ffd/src/power/low_power_audio_buffer.h index be45e460..fceb2b02 100644 --- a/examples/low_power_ffd/src/power/low_power_audio_buffer.h +++ b/examples/low_power_ffd/src/power/low_power_audio_buffer.h @@ -17,7 +17,7 @@ #define LOW_POWER_AUDIO_BUFFER_ENABLED ( \ appconfAUDIO_PIPELINE_BUFFER_ENABLED && \ - ON_TILE(AUDIO_PIPELINE_TILE_NO) ) + ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) ) /** * Enqueue audio samples into a ring buffer. Oldest data will be overwritten. diff --git a/examples/low_power_ffd/src/power/power_control.h b/examples/low_power_ffd/src/power/power_control.h index a23cfc53..bda18ebf 100644 --- a/examples/low_power_ffd/src/power/power_control.h +++ b/examples/low_power_ffd/src/power/power_control.h @@ -8,7 +8,7 @@ #include "power_state.h" // Specifies the tile that is controlling the low power mode. -#define POWER_CONTROL_TILE_NO AUDIO_PIPELINE_TILE_NO +#define POWER_CONTROL_TILE_NO AUDIO_PIPELINE_OUTPUT_TILE_NO /** * @brief Initialize the power control task. diff --git a/modules/asr/intent_engine/intent_engine.c b/modules/asr/intent_engine/intent_engine.c index 5459803a..46a951ea 100644 --- a/modules/asr/intent_engine/intent_engine.c +++ b/modules/asr/intent_engine/intent_engine.c @@ -210,7 +210,7 @@ void intent_engine_task(void *args) void intent_engine_ready_sync(void) { int sync = 0; -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) +#if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) size_t len = rtos_intertile_rx_len(intertile_ctx, appconfINTENT_ENGINE_READY_SYNC_PORT, RTOS_OSAL_WAIT_FOREVER); xassert(len == sizeof(sync)); rtos_intertile_rx_data(intertile_ctx, &sync, sizeof(sync)); diff --git a/modules/asr/intent_engine/intent_engine_io.c b/modules/asr/intent_engine/intent_engine_io.c index f10212ee..63069968 100644 --- a/modules/asr/intent_engine/intent_engine_io.c +++ b/modules/asr/intent_engine/intent_engine_io.c @@ -104,7 +104,7 @@ int32_t intent_engine_create(uint32_t priority, void *args) { q_intent = (QueueHandle_t) args; -#if ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO +#if ASR_TILE_NO == AUDIO_PIPELINE_OUTPUT_TILE_NO intent_engine_task_create(priority); #else intent_engine_intertile_task_create(priority); @@ -115,8 +115,8 @@ int32_t intent_engine_create(uint32_t priority, void *args) int32_t intent_engine_sample_push(int32_t *buf, size_t frames) { -#if appconfINTENT_ENABLED && ON_TILE(AUDIO_PIPELINE_TILE_NO) -#if ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO +#if appconfINTENT_ENABLED && ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) +#if ASR_TILE_NO == AUDIO_PIPELINE_OUTPUT_TILE_NO intent_engine_samples_send_local( frames, buf); diff --git a/modules/asr/intent_engine/intent_engine_support.c b/modules/asr/intent_engine/intent_engine_support.c index 3fc1a2af..b8c319a7 100644 --- a/modules/asr/intent_engine/intent_engine_support.c +++ b/modules/asr/intent_engine/intent_engine_support.c @@ -29,8 +29,8 @@ void intent_engine_stream_buf_reset(void) #endif /* ON_TILE(ASR_TILE_NO) */ -#if ASR_TILE_NO != AUDIO_PIPELINE_TILE_NO -#if ON_TILE(AUDIO_PIPELINE_TILE_NO) +#if ASR_TILE_NO != AUDIO_PIPELINE_OUTPUT_TILE_NO +#if ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) void intent_engine_samples_send_remote( rtos_intertile_t *intertile, @@ -45,7 +45,7 @@ void intent_engine_samples_send_remote( sizeof(int32_t) * frame_count); } -#else /* ON_TILE(AUDIO_PIPELINE_TILE_NO) */ +#else /* ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) */ static void intent_engine_intertile_samples_in_task(void *arg) { @@ -93,10 +93,10 @@ void intent_engine_intertile_task_create(uint32_t priority) NULL); } -#endif /* ON_TILE(AUDIO_PIPELINE_TILE_NO) */ -#endif /* ASR_TILE_NO != AUDIO_PIPELINE_TILE_NO */ +#endif /* ON_TILE(AUDIO_PIPELINE_OUTPUT_TILE_NO) */ +#endif /* ASR_TILE_NO != AUDIO_PIPELINE_OUTPUT_TILE_NO */ -#if ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO +#if ASR_TILE_NO == AUDIO_PIPELINE_OUTPUT_TILE_NO #if ON_TILE(ASR_TILE_NO) void intent_engine_samples_send_local( @@ -131,4 +131,4 @@ void intent_engine_task_create(unsigned priority) } #endif /* ON_TILE(ASR_TILE_NO) */ -#endif /* ASR_TILE_NO == AUDIO_PIPELINE_TILE_NO */ +#endif /* ASR_TILE_NO == AUDIO_PIPELINE_OUTPUT_TILE_NO */ diff --git a/test/ffd_low_power_audio_buffer/src/app_conf.h b/test/ffd_low_power_audio_buffer/src/app_conf.h index 4bac02c1..70953fbe 100644 --- a/test/ffd_low_power_audio_buffer/src/app_conf.h +++ b/test/ffd_low_power_audio_buffer/src/app_conf.h @@ -14,7 +14,7 @@ #define ON_TILE(t) (!defined(THIS_XCORE_TILE) || THIS_XCORE_TILE == (t)) #endif /* __XC__ */ -#define AUDIO_PIPELINE_TILE_NO 1 +#define AUDIO_PIPELINE_OUTPUT_TILE_NO 1 #define appconfAUDIO_PIPELINE_BUFFER_ENABLED 1 #define appconfAUDIO_PIPELINE_BUFFER_NUM_FRAMES 32 #define appconfAUDIO_PIPELINE_FRAME_ADVANCE 240 From 2b6fdc4d38e8650e8f060154d6ccf1762327773f Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Thu, 7 Mar 2024 16:24:53 +0000 Subject: [PATCH 052/288] Fix code --- examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index 6d1631dd..67f61e7e 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -39,9 +39,9 @@ static void gpio_start(void) static void flash_start(void) { #if ON_TILE(FLASH_TILE_NO) + uint32_t flash_core_map = ~((1 << appconfUSB_INTERRUPT_CORE) | (1 << appconfUSB_SOF_INTERRUPT_CORE)); rtos_qspi_flash_start(qspi_flash_ctx, appconfQSPI_FLASH_TASK_PRIORITY); rtos_qspi_flash_op_core_affinity_set(qspi_flash_ctx, flash_core_map); - #endif } From ef3d85aa9fbbdb6fbb84cf45f5e209da0070e26e Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 10:10:44 +0000 Subject: [PATCH 053/288] Update info --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 75743296..f966fbd6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ XCORE-VOICE change log * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). + * CHANGED: Remove need to use external MCLK in FFVA INT examples + * ADDED: lib_sw_pll submodule v1.1.0. 2.2.0 From 57d5cd9576046f4eb6c11aedf83e9648554fb5a4 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 10:15:49 +0000 Subject: [PATCH 054/288] Undo changes to avoid crash in ASR test --- test/asr/asr.cmake | 2 - test/asr/src/device_memory_impl.c | 65 +++++++++++++++++++++++++++++++ test/asr/src/device_memory_impl.h | 10 +++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 test/asr/src/device_memory_impl.c create mode 100644 test/asr/src/device_memory_impl.h diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index 496cef34..8c31eaa7 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -112,7 +112,6 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES} ${ASR_LIBRARY} - sln_voice::app::asr::device_memory ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) @@ -131,7 +130,6 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES} ${ASR_LIBRARY} - sln_voice::app::asr::device_memory ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) diff --git a/test/asr/src/device_memory_impl.c b/test/asr/src/device_memory_impl.c new file mode 100644 index 00000000..36756601 --- /dev/null +++ b/test/asr/src/device_memory_impl.c @@ -0,0 +1,65 @@ +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include + +/* System headers */ +#include +#include +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" + +/* Library headers */ +#include "rtos_printf.h" +#include "rtos_qspi_flash.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "device_memory.h" +#include "device_memory_impl.h" + +void asr_printf(const char * format, ...) { + va_list args; + va_start(args, format); + xcore_utils_vprintf(format, args); + va_end(args); +} + +__attribute__((fptrgroup("devmem_malloc_fptr_grp"))) +void * devmem_malloc_local(size_t size) { + //rtos_printf("devmem_malloc_local size=%d\n", size); + return pvPortMalloc(size); +} + +__attribute__((fptrgroup("devmem_free_fptr_grp"))) +void devmem_free_local(void *ptr) { + vPortFree(ptr); +} + +__attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) +void devmem_read_ext_local(void *dest, const void *src, size_t n) { + //rtos_printf("devmem_read_ext_local dest=0x%x src=0x%x size=%d\n", dest, src, n); + if (IS_FLASH(src)) { + // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset + //uint32_t s = get_reference_time(); + rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); + //uint32_t d = get_reference_time() - s; + //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); + } else { + memcpy(dest, src, n); + } +} + +void devmem_init(devmem_manager_t *devmem_ctx) { + xassert(devmem_ctx); + devmem_ctx->malloc = devmem_malloc_local; + devmem_ctx->free = devmem_free_local; + devmem_ctx->read_ext = devmem_read_ext_local; + devmem_ctx->read_ext_async = NULL; // not supported in this application + devmem_ctx->read_ext_wait = NULL; // not supported in this application +} diff --git a/test/asr/src/device_memory_impl.h b/test/asr/src/device_memory_impl.h new file mode 100644 index 00000000..ea8735cb --- /dev/null +++ b/test/asr/src/device_memory_impl.h @@ -0,0 +1,10 @@ +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef DEVICE_MEMORY_IMPL_H +#define DEVICE_MEMORY_IMPL_H + +#include "device_memory.h" + +void devmem_init(devmem_manager_t *devmem_ctx); + +#endif // DEVICE_MEMORY_IMPL_H \ No newline at end of file From 5440eba4d5ff1e01cedc23f627b1fc08e91c734e Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 10:10:44 +0000 Subject: [PATCH 055/288] Update info --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 75743296..f966fbd6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ XCORE-VOICE change log * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). + * CHANGED: Remove need to use external MCLK in FFVA INT examples + * ADDED: lib_sw_pll submodule v1.1.0. 2.2.0 From db8ec77e7fb9b4e1d256e410a6ca8a58dcdc8eca Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 10:15:49 +0000 Subject: [PATCH 056/288] Undo changes to avoid crash in ASR test --- test/asr/asr.cmake | 2 - test/asr/src/device_memory_impl.c | 65 +++++++++++++++++++++++++++++++ test/asr/src/device_memory_impl.h | 10 +++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 test/asr/src/device_memory_impl.c create mode 100644 test/asr/src/device_memory_impl.h diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index 496cef34..8c31eaa7 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -112,7 +112,6 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES} ${ASR_LIBRARY} - sln_voice::app::asr::device_memory ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) @@ -131,7 +130,6 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES} ${ASR_LIBRARY} - sln_voice::app::asr::device_memory ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) diff --git a/test/asr/src/device_memory_impl.c b/test/asr/src/device_memory_impl.c new file mode 100644 index 00000000..36756601 --- /dev/null +++ b/test/asr/src/device_memory_impl.c @@ -0,0 +1,65 @@ +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include + +/* System headers */ +#include +#include +#include +#include + +/* FreeRTOS headers */ +#include "FreeRTOS.h" + +/* Library headers */ +#include "rtos_printf.h" +#include "rtos_qspi_flash.h" + +/* App headers */ +#include "app_conf.h" +#include "platform/driver_instances.h" +#include "device_memory.h" +#include "device_memory_impl.h" + +void asr_printf(const char * format, ...) { + va_list args; + va_start(args, format); + xcore_utils_vprintf(format, args); + va_end(args); +} + +__attribute__((fptrgroup("devmem_malloc_fptr_grp"))) +void * devmem_malloc_local(size_t size) { + //rtos_printf("devmem_malloc_local size=%d\n", size); + return pvPortMalloc(size); +} + +__attribute__((fptrgroup("devmem_free_fptr_grp"))) +void devmem_free_local(void *ptr) { + vPortFree(ptr); +} + +__attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) +void devmem_read_ext_local(void *dest, const void *src, size_t n) { + //rtos_printf("devmem_read_ext_local dest=0x%x src=0x%x size=%d\n", dest, src, n); + if (IS_FLASH(src)) { + // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset + //uint32_t s = get_reference_time(); + rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); + //uint32_t d = get_reference_time() - s; + //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); + } else { + memcpy(dest, src, n); + } +} + +void devmem_init(devmem_manager_t *devmem_ctx) { + xassert(devmem_ctx); + devmem_ctx->malloc = devmem_malloc_local; + devmem_ctx->free = devmem_free_local; + devmem_ctx->read_ext = devmem_read_ext_local; + devmem_ctx->read_ext_async = NULL; // not supported in this application + devmem_ctx->read_ext_wait = NULL; // not supported in this application +} diff --git a/test/asr/src/device_memory_impl.h b/test/asr/src/device_memory_impl.h new file mode 100644 index 00000000..ea8735cb --- /dev/null +++ b/test/asr/src/device_memory_impl.h @@ -0,0 +1,10 @@ +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef DEVICE_MEMORY_IMPL_H +#define DEVICE_MEMORY_IMPL_H + +#include "device_memory.h" + +void devmem_init(devmem_manager_t *devmem_ctx); + +#endif // DEVICE_MEMORY_IMPL_H \ No newline at end of file From 1f38981def9a7a5fd964ea864f7706b88a9bd558 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 10:49:07 +0000 Subject: [PATCH 057/288] Add missing file --- test/asr/src/device_memory.h | 116 +++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 test/asr/src/device_memory.h diff --git a/test/asr/src/device_memory.h b/test/asr/src/device_memory.h new file mode 100644 index 00000000..04c53fa5 --- /dev/null +++ b/test/asr/src/device_memory.h @@ -0,0 +1,116 @@ +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef XCORE_DEVICE_MEMORY_H +#define XCORE_DEVICE_MEMORY_H + +#include +#include +#include +#include + +#include + +/** + * Typedef to the device memory manager context. + * Allows an application to define how memory allocation and + * data loading is implemented. + */ +typedef struct devmem_manager_struct +{ + __attribute__((fptrgroup("devmem_malloc_fptr_grp"))) + void * (*malloc)(size_t size); + + __attribute__((fptrgroup("devmem_free_fptr_grp"))) + void (*free)(void *ptr); + + __attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) + void (*read_ext)(void *dest, const void *src, size_t n); + + __attribute__((fptrgroup("devmem_read_ext_async_fptr_grp"))) + int (*read_ext_async)(void *dest, const void * src, size_t n); + + __attribute__((fptrgroup("devmem_read_ext_wait_fptr_grp"))) + void (*read_ext_wait)(int handle); +} devmem_manager_t; + + +/** + * \addtogroup devmem_api devmem_api + * + * The public API for XCORE device memory management. + * @{ + */ + +#define IS_SRAM(a) ((uintptr_t)a < XS1_SWMEM_BASE) + +#define IS_SWMEM(a) (((uintptr_t)a >= XS1_SWMEM_BASE) && (((uintptr_t)a <= (XS1_SWMEM_BASE - 1 + XS1_SWMEM_SIZE)))) + +#define IS_FLASH(a) IS_SWMEM(a) + +/** + * Memory allocation function that allows the application + * to provide an alternative implementation. + * + * Call devmem_malloc instead of malloc + * + * \param ctx A pointer to the device memory context. + * \param size Number of bytes to allocate. + * + * \returns A pointer to the beginning of newly allocated memory, or NULL on failure. + */ +void *devmem_malloc(devmem_manager_t *ctx, size_t size); + +/** + * Memory deallocation function that allows the application + * to provide an alternative implementation. + * + * Call devmem_free instead of free + * + * \param ctx A pointer to the device memory context. + * \param ptr A pointer to the memory to deallocate. + */ +void devmem_free(devmem_manager_t *ctx, void *ptr); + +/** + * Synchronous extended memory read function that allows the application + * to provide an alternative implementation. Blocks the callers thread + * until the read is completed. + * + * Call devmem_read_ext instead of any other functions to read memory from + * flash, LPDDR or SDRAM. Modules are free to use memcpy if the dest and src + * are both SRAM addresses. + * + * \param ctx A pointer to the device memory context. + * \param dest A pointer to the destination array where the content is to be read. + * \param src A pointer to the word-aligned address of data to be read. + * \param n Number of bytes to read. + */ +void devmem_read_ext(devmem_manager_t *ctx, void *dest, const void * src, size_t n); + +/** + * Asynchronous extended memory read function that allows the application + * to provide an alternative implementation. + * + * Call asr_read_ext_async instead of any other functions to read memory + * from flash, LPDDR or SDRAM. + * + * \param ctx A pointer to the device memory context. + * \param dest A pointer to the destination array where the content is to be read. + * \param src A pointer to the word-aligned address of data to be read. + * \param n Number of bytes to read. + * + * \returns A handle that can be used in a call to devmem_read_ext_wait. + */ +int devmem_read_ext_async(devmem_manager_t *ctx, void *dest, const void * src, size_t n); + +/** + * Wait in the caller's thread for an asynchronous extended memory read to finish. + * + * \param ctx A pointer to the device memory context. + * \param handle The devmem_read_ext_asyc handle to wait on. + */ +void devmem_read_ext_wait(devmem_manager_t *ctx, int handle); + +/**@}*/ + +#endif // XCORE_DEVICE_MEMORY_H From ad1d3a8c2e2c3a41c607f72a6565e582b632190f Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 11:23:03 +0000 Subject: [PATCH 058/288] Do not build sw_pll for host apps --- modules/CMakeLists.txt | 4 ++-- tools/ci/build_tests.sh | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 133742b9..4acffb6f 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -12,6 +12,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) ## Need to guard so host targets will not be built add_subdirectory(voice) add_subdirectory(inferencing) + add_subdirectory(sw_pll/lib_sw_pll) ## The following alias is added to support in intermediate version of fwk_voick ## This can be removed once fwk_voice is updated to use the new, core::lib_tflite_micro alias @@ -22,5 +23,4 @@ endif() add_subdirectory(asr) add_subdirectory(audio_pipelines) add_subdirectory(sample_rate_conversion) -add_subdirectory(xscope_fileio) -add_subdirectory(sw_pll/lib_sw_pll) +add_subdirectory(xscope_fileio) \ No newline at end of file diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index eaac2618..2ac95749 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -21,15 +21,15 @@ fi # setup configurations # row format is: "name app_target run_data_partition_target flag BOARD toolchain" tests=( - "test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" - "test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" + #"test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_sensory test_asr_sensory test_asr_sensory TEST_ASR=SENSORY XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" - "test_ffd_low_power_audio_buffer test_ffd_low_power_audio_buffer NONE NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" + #"test_ffd_low_power_audio_buffer test_ffd_low_power_audio_buffer NONE NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" ) # perform builds From 411ca626ba94e56d034c74d30333c84f053a7234 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 11:23:38 +0000 Subject: [PATCH 059/288] Undo changes --- tools/ci/build_tests.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index 2ac95749..eaac2618 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -21,15 +21,15 @@ fi # setup configurations # row format is: "name app_target run_data_partition_target flag BOARD toolchain" tests=( - #"test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" - #"test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" + "test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_sensory test_asr_sensory test_asr_sensory TEST_ASR=SENSORY XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" - #"test_ffd_low_power_audio_buffer test_ffd_low_power_audio_buffer NONE NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" + "test_ffd_low_power_audio_buffer test_ffd_low_power_audio_buffer NONE NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" ) # perform builds From b0a524b9095a9498e0ba0391819180faf09fe91d Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 12:02:19 +0000 Subject: [PATCH 060/288] Allow to build with appconfRECOVER_MCLK_I2S_APP_PLL=0 --- examples/ffva/ffva_int.cmake | 2 +- examples/ffva/ffva_int_cyberon.cmake | 1 + examples/ffva/src/app_conf.h | 2 -- examples/ffva/src/main.c | 4 ++++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/ffva/ffva_int.cmake b/examples/ffva/ffva_int.cmake index d610f92c..9fb4a84d 100644 --- a/examples/ffva/ffva_int.cmake +++ b/examples/ffva/ffva_int.cmake @@ -7,7 +7,7 @@ set(FFVA_INT_COMPILE_DEFINITIONS appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 - + appconfRECOVER_MCLK_I2S_APP_PLL=1 MIC_ARRAY_CONFIG_MCLK_FREQ=12288000 ) diff --git a/examples/ffva/ffva_int_cyberon.cmake b/examples/ffva/ffva_int_cyberon.cmake index 573908d5..6883f164 100644 --- a/examples/ffva/ffva_int_cyberon.cmake +++ b/examples/ffva/ffva_int_cyberon.cmake @@ -49,6 +49,7 @@ set(FFVA_INT_CYBERON_COMPILE_DEFINITIONS appconfI2S_MODE=appconfI2S_MODE_SLAVE appconfI2S_AUDIO_SAMPLE_RATE=48000 configENABLE_DEBUG_PRINTF=1 + appconfRECOVER_MCLK_I2S_APP_PLL=1 QSPI_FLASH_FILESYSTEM_START_ADDRESS=${FILESYSTEM_START_ADDRESS} QSPI_FLASH_MODEL_START_ADDRESS=${MODEL_START_ADDRESS} QSPI_FLASH_CALIBRATION_ADDRESS=${CALIBRATION_PATTERN_START_ADDRESS} diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 34029a4f..d8889766 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -146,8 +146,6 @@ #define appconfEXTERNAL_MCLK 0 #endif -#define appconfRECOVER_MCLK_I2S_APP_PLL 1 - /* * This option sends all 6 16 KHz channels (two channels of processed audio, * stereo reference audio, and stereo microphone audio) out over a single diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 11133b24..af3bce86 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -350,6 +350,10 @@ void startup_task(void *arg) rtos_printf("Startup task running from tile %d on core %d\n", THIS_XCORE_TILE, portGET_CORE_ID()); platform_start(); +#if !appconfRECOVER_MCLK_I2S_APP_PLL + int32_t* sw_pll_ctx = NULL; +#endif + #if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) xTaskCreate((TaskFunction_t) i2s_slave_intertile, "i2s_slave_intertile", From 3e7607749977798e601f748dea9961d122d44110 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 12:09:38 +0000 Subject: [PATCH 061/288] Update copyright notices --- .../XK_VOICE_L71/platform/platform_init.c | 2 +- examples/ffd/src/app_conf.h | 2 +- examples/ffd/src/main.c | 2 +- examples/ffva/src/fractions_1000ppm.h | 4 +- examples/ffva/src/main.c | 1 - examples/ffva/src/register_setup_1000ppm.h | 4 +- examples/low_power_ffd/src/app_conf.h | 2 +- examples/low_power_ffd/src/app_conf_check.h | 2 +- .../src/intent_engine/intent_engine.c | 2 +- .../src/intent_engine/intent_engine_support.c | 2 +- examples/low_power_ffd/src/main.c | 2 +- .../src/power/low_power_audio_buffer.h | 2 +- .../low_power_ffd/src/power/power_control.h | 118 +++++++++--------- .../ffd_low_power_audio_buffer/src/app_conf.h | 2 +- 14 files changed, 73 insertions(+), 74 deletions(-) diff --git a/examples/asrc_demo/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/asrc_demo/bsp_config/XK_VOICE_L71/platform/platform_init.c index 97fd2d7d..d701bd54 100644 --- a/examples/asrc_demo/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/asrc_demo/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* System headers */ diff --git a/examples/ffd/src/app_conf.h b/examples/ffd/src/app_conf.h index c3bf7111..9696d08e 100644 --- a/examples/ffd/src/app_conf.h +++ b/examples/ffd/src/app_conf.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef APP_CONF_H_ diff --git a/examples/ffd/src/main.c b/examples/ffd/src/main.c index e73db1b7..23c435cc 100644 --- a/examples/ffd/src/main.c +++ b/examples/ffd/src/main.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* System headers */ diff --git a/examples/ffva/src/fractions_1000ppm.h b/examples/ffva/src/fractions_1000ppm.h index b0470f7d..9c9ed1b8 100644 --- a/examples/ffva/src/fractions_1000ppm.h +++ b/examples/ffva/src/fractions_1000ppm.h @@ -1,5 +1,5 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. // Header file listing fraction options searched // These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. static short frac_values_90[790] = { diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index af3bce86..61f96158 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -35,7 +35,6 @@ /* Config headers for sw_pll */ #include "sw_pll.h" -//#include "register_setup_1000ppm.h" volatile int mic_from_usb = appconfMIC_SRC_DEFAULT; volatile int aec_ref_source = appconfAEC_REF_DEFAULT; diff --git a/examples/ffva/src/register_setup_1000ppm.h b/examples/ffva/src/register_setup_1000ppm.h index 68f3ed52..25646bcd 100644 --- a/examples/ffva/src/register_setup_1000ppm.h +++ b/examples/ffva/src/register_setup_1000ppm.h @@ -1,5 +1,5 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. /* Autogenerated by sw_pll_sim.py using command: /Users/ed/sandboxes/sw_xvf3800/lib_sw_pll/python/sw_pll/pll_calc.py -i 24.0 -a -m 90 -t 12.288 -p 6.0 -e 5 -r --fracmin 0.49 --fracmax 0.81 --header Picked output solution #83 diff --git a/examples/low_power_ffd/src/app_conf.h b/examples/low_power_ffd/src/app_conf.h index b436fb8b..64061cfd 100644 --- a/examples/low_power_ffd/src/app_conf.h +++ b/examples/low_power_ffd/src/app_conf.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef APP_CONF_H_ diff --git a/examples/low_power_ffd/src/app_conf_check.h b/examples/low_power_ffd/src/app_conf_check.h index b38c1472..f7e43b71 100644 --- a/examples/low_power_ffd/src/app_conf_check.h +++ b/examples/low_power_ffd/src/app_conf_check.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef APP_CONF_CHECK_H_ diff --git a/examples/low_power_ffd/src/intent_engine/intent_engine.c b/examples/low_power_ffd/src/intent_engine/intent_engine.c index 5af00a4b..2ac70139 100644 --- a/examples/low_power_ffd/src/intent_engine/intent_engine.c +++ b/examples/low_power_ffd/src/intent_engine/intent_engine.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/examples/low_power_ffd/src/intent_engine/intent_engine_support.c b/examples/low_power_ffd/src/intent_engine/intent_engine_support.c index df7ba9ae..d11b844f 100644 --- a/examples/low_power_ffd/src/intent_engine/intent_engine_support.c +++ b/examples/low_power_ffd/src/intent_engine/intent_engine_support.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* STD headers */ diff --git a/examples/low_power_ffd/src/main.c b/examples/low_power_ffd/src/main.c index 4e1991ba..45335da5 100644 --- a/examples/low_power_ffd/src/main.c +++ b/examples/low_power_ffd/src/main.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* System headers */ diff --git a/examples/low_power_ffd/src/power/low_power_audio_buffer.h b/examples/low_power_ffd/src/power/low_power_audio_buffer.h index fceb2b02..7279c26c 100644 --- a/examples/low_power_ffd/src/power/low_power_audio_buffer.h +++ b/examples/low_power_ffd/src/power/low_power_audio_buffer.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef LOW_POWER_AUDIO_BUFFER_H_ diff --git a/examples/low_power_ffd/src/power/power_control.h b/examples/low_power_ffd/src/power/power_control.h index bda18ebf..3c093750 100644 --- a/examples/low_power_ffd/src/power/power_control.h +++ b/examples/low_power_ffd/src/power/power_control.h @@ -1,59 +1,59 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#ifndef POWER_CONTROL_H_ -#define POWER_CONTROL_H_ - -#include "app_conf.h" -#include "power_state.h" - -// Specifies the tile that is controlling the low power mode. -#define POWER_CONTROL_TILE_NO AUDIO_PIPELINE_OUTPUT_TILE_NO - -/** - * @brief Initialize the power control task. - * - * @param priority The priority of the task. - * @param args The arguments to send to the task. - */ -void power_control_task_create(unsigned priority, void *args); - -#if ON_TILE(POWER_CONTROL_TILE_NO) - -/** - * @brief Notify that the power control task should exit the low power state. - */ -void power_control_exit_low_power(void); - -/** - * @brief Get the power control state. - * - * @returns The applied power state. - */ -power_state_t power_control_state_get(void); - -/** - * @brief Signal to the power control task that it should halt. This request - * is only acted on when operating in full power mode and the other tile - * requests low power. In this case, the application locks the device to - * full power operation. - */ -void power_control_halt(void); - -#else - -/** - * @brief Notify the power control task that the low power state has been - * requested. The power control task may accept or reject the request. - */ -void power_control_req_low_power(void); - -/** - * @brief Notify the power control task that indication of the power state - * has completed, and it is safe to proceed with the requested operation. - */ -void power_control_ind_complete(void); - -#endif - -#endif /* POWER_CONTROL_H_ */ +// Copyright 2022-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef POWER_CONTROL_H_ +#define POWER_CONTROL_H_ + +#include "app_conf.h" +#include "power_state.h" + +// Specifies the tile that is controlling the low power mode. +#define POWER_CONTROL_TILE_NO AUDIO_PIPELINE_OUTPUT_TILE_NO + +/** + * @brief Initialize the power control task. + * + * @param priority The priority of the task. + * @param args The arguments to send to the task. + */ +void power_control_task_create(unsigned priority, void *args); + +#if ON_TILE(POWER_CONTROL_TILE_NO) + +/** + * @brief Notify that the power control task should exit the low power state. + */ +void power_control_exit_low_power(void); + +/** + * @brief Get the power control state. + * + * @returns The applied power state. + */ +power_state_t power_control_state_get(void); + +/** + * @brief Signal to the power control task that it should halt. This request + * is only acted on when operating in full power mode and the other tile + * requests low power. In this case, the application locks the device to + * full power operation. + */ +void power_control_halt(void); + +#else + +/** + * @brief Notify the power control task that the low power state has been + * requested. The power control task may accept or reject the request. + */ +void power_control_req_low_power(void); + +/** + * @brief Notify the power control task that indication of the power state + * has completed, and it is safe to proceed with the requested operation. + */ +void power_control_ind_complete(void); + +#endif + +#endif /* POWER_CONTROL_H_ */ diff --git a/test/ffd_low_power_audio_buffer/src/app_conf.h b/test/ffd_low_power_audio_buffer/src/app_conf.h index 70953fbe..388b5a65 100644 --- a/test/ffd_low_power_audio_buffer/src/app_conf.h +++ b/test/ffd_low_power_audio_buffer/src/app_conf.h @@ -1,4 +1,4 @@ -// Copyright 2023 XMOS LIMITED. +// Copyright 2023-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef APP_CONF_H From 5009e19e863abac04cd87e7c4ae1e05508b906fc Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 12:22:35 +0000 Subject: [PATCH 062/288] Improve readability --- examples/ffva/src/main.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 61f96158..5589b445 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -349,16 +349,22 @@ void startup_task(void *arg) rtos_printf("Startup task running from tile %d on core %d\n", THIS_XCORE_TILE, portGET_CORE_ID()); platform_start(); -#if !appconfRECOVER_MCLK_I2S_APP_PLL - int32_t* sw_pll_ctx = NULL; -#endif - #if ON_TILE(1) && appconfI2S_ENABLED && (appconfI2S_MODE == appconfI2S_MODE_SLAVE) + +// Use sw_pll_ctx only if the MCLK recovery is enabled +#if appconfRECOVER_MCLK_I2S_APP_PLL xTaskCreate((TaskFunction_t) i2s_slave_intertile, "i2s_slave_intertile", RTOS_THREAD_STACK_SIZE(i2s_slave_intertile), sw_pll_ctx, appconfAUDIO_PIPELINE_TASK_PRIORITY, + NULL);#endif +#else + xTaskCreate((TaskFunction_t) i2s_slave_intertile, + "i2s_slave_intertile", + RTOS_THREAD_STACK_SIZE(i2s_slave_intertile), + NULL, + appconfAUDIO_PIPELINE_TASK_PRIORITY, NULL); #endif From 3c174df54a5194d224fd3550c5810c1907b75b14 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 12:26:32 +0000 Subject: [PATCH 063/288] Fix endif --- examples/ffva/src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 5589b445..d38aeba8 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -358,7 +358,7 @@ void startup_task(void *arg) RTOS_THREAD_STACK_SIZE(i2s_slave_intertile), sw_pll_ctx, appconfAUDIO_PIPELINE_TASK_PRIORITY, - NULL);#endif + NULL); #else xTaskCreate((TaskFunction_t) i2s_slave_intertile, "i2s_slave_intertile", @@ -367,7 +367,7 @@ void startup_task(void *arg) appconfAUDIO_PIPELINE_TASK_PRIORITY, NULL); #endif - +#endif #if ON_TILE(1) gpio_test(gpio_ctx_t0); #endif From 15142be34df14d5e97593603138e6c44d65ea8b1 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Fri, 8 Mar 2024 15:56:03 +0000 Subject: [PATCH 064/288] Remove unused define --- examples/ffva/src/app_conf.h | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index d8889766..afecdf8f 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -225,7 +225,6 @@ #define appconfLRCLK_NOMINAL_HZ appconfI2S_AUDIO_SAMPLE_RATE #define appconfBCLK_NOMINAL_HZ (appconfLRCLK_NOMINAL_HZ * 64) #define PLL_RATIO (MIC_ARRAY_CONFIG_MCLK_FREQ / appconfLRCLK_NOMINAL_HZ) -#define PLL_CONTROL_LOOP_COUNT_UA 80 // How many SoF periods per control loop iteration. Aim for ~100Hz #define PLL_CONTROL_LOOP_COUNT_INT 512 // How many refclk ticks (LRCLK) per control loop iteration. Aim for ~100Hz #define PLL_PPM_RANGE 1000 // Max allowable diff in clk count. For the PID constants we // have chosen, this number should be larger than the number From 36ce92e66b2fd09a466a8719392cc878fa990dab Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 11 Mar 2024 07:16:56 +0000 Subject: [PATCH 065/288] Build Cyberon test --- Jenkinsfile | 3 +++ modules/asr/CMakeLists.txt | 1 + test/asr/asr.cmake | 12 ++++++++++++ test/asr/check_asr.sh | 20 +++++++++++++------- test/asr/src/app_conf.h | 13 +++++++++++-- 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index dfbd5bcd..54a29792 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -256,8 +256,11 @@ pipeline { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> sh "test/asr/check_asr.sh Sensory $ASR_TEST_VECTORS test/asr/ffd_quick.txt test/asr/sensory_output " + adapterIDs[0] + sh "test/asr/check_asr.sh Cyberon $ASR_TEST_VECTORS test/asr/ffd_quick.txt test/asr/cyberon_output " + adapterIDs[0] } sh "pytest test/asr/test_asr.py --log test/asr/sensory_output/results.csv" + sh "pytest test/asr/test_asr.py --log test/asr/cyberon_output/results.csv" + } } } diff --git a/modules/asr/CMakeLists.txt b/modules/asr/CMakeLists.txt index 4a059bf3..60c86ae6 100644 --- a/modules/asr/CMakeLists.txt +++ b/modules/asr/CMakeLists.txt @@ -44,6 +44,7 @@ add_library(asr_Cyberon INTERFACE) target_sources(asr_Cyberon INTERFACE + ${SOLUTION_VOICE_ROOT_PATH}/modules/asr/device_memory/device_memory.c ${CMAKE_CURRENT_LIST_DIR}/Cyberon/DSpotter_asr.c ${CMAKE_CURRENT_LIST_DIR}/Cyberon/FlashReadData.c ${CMAKE_CURRENT_LIST_DIR}/Cyberon/Convert2TransferBuffer.c diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index 8c31eaa7..f2aadaed 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -48,6 +48,17 @@ if(${TEST_ASR} STREQUAL "SENSORY") set(MODEL_FILE ${FFD_SRC_ROOT}/model/english_usa/command-pc62w-6.4.0-op10-prod-net.bin.nibble_swapped) set(TEST_ASR_LIBRARY_ID 0) set(TEST_ASR_NAME test_asr_sensory) +elseif(${TEST_ASR} STREQUAL "CYBERON") + message(STATUS "Building Cyberon ASR test") + set(ASR_LIBRARY sln_voice::app::asr::Cyberon) + set(ASR_BRICK_SIZE_SAMPLES 240) + set(APP_SOURCES + ${APP_SOURCES} + ${FFD_SRC_ROOT}/model/english_usa/command-pc62w-6.4.0-op10-prod-search.c + ) + set(MODEL_FILE ${FFD_SRC_ROOT}/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap) + set(TEST_ASR_LIBRARY_ID 0) + set(TEST_ASR_NAME test_asr_cyberon) else() message(FATAL_ERROR "Unable to build ${TEST_ASR} test") endif() @@ -87,6 +98,7 @@ endif() set(APP_LINK_OPTIONS -report ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope + -lotp3 ) set(APP_COMMON_LINK_LIBRARIES diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index ab00d71c..ba0c3691 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -55,11 +55,17 @@ then DATA_PARTITION="dist/test_asr_sensory_data_partition.bin" TRIM_COMMAND="" # trim is not needed TRUTH_TRACK="${INPUT_DIR}/truth_labels.txt" +elif [[ ${ASR_LIBRARY} == "Cyberon" ]] +then + ASR_FIRMWARE="dist/test_asr_cyberon.xe" + DATA_PARTITION="dist/test_asr_cyberon_data_partition.bin" + TRIM_COMMAND="" # trim is not needed + TRUTH_TRACK="${INPUT_DIR}/truth_labels.txt" # elif [[ ${ASR_LIBRARY} == "Other" ]] # then # ASR_FIRMWARE="dist/test_asr_other.xe" # DATA_PARTITION="dist/test_asr_other_data_partition.bin" -# TRIM_COMMAND="trim 0 01:45" # need to trim input to account for 50 command limit +# TRIM_COMMAND="trim 0 01:45" # need to trim input to account for 50 command limit # TRUTH_TRACK="${INPUT_DIR}/truth_labels_1_45.txt" fi @@ -96,7 +102,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do PIPELINE_OUTPUT_CSV="${OUTPUT_DIR}/${FILE_NAME}_pipeline.csv" ASR_OUTPUT_LOG="${OUTPUT_DIR}/${FILE_NAME}_asr.log" SCORING_OUTPUT_LOG="${OUTPUT_DIR}/${FILE_NAME}_scoring.log" - + TEMP_XSCOPE_FILEIO_INPUT_WAV="${OUTPUT_DIR}/input.wav" TEMP_XSCOPE_FILEIO_OUTPUT_WAV="${OUTPUT_DIR}/output.wav" TEMP_XSCOPE_FILEIO_OUTPUT_LOG="${OUTPUT_DIR}/output.log" @@ -128,7 +134,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do sleep 15 # run xscope host in directory where the TEMP_XSCOPE_FILEIO_INPUT_WAV resides - # xscope_host_endpoint is run in a subshell (inside parentheses) so when + # xscope_host_endpoint is run in a subshell (inside parentheses) so when # it exits, the xrun command above will also exit (cd ${OUTPUT_DIR} ; ${DIST_HOST}/xscope_host_endpoint 12345) @@ -155,9 +161,9 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # wait for app to load sleep 15 - + # run xscope host in directory where the TEMP_XSCOPE_FILEIO_INPUT_WAV resides - # xscope_host_endpoint is run in a subshell (inside parentheses) so when + # xscope_host_endpoint is run in a subshell (inside parentheses) so when # it exits, the xrun command above will also exit (cd ${OUTPUT_DIR} ; ${DIST_HOST}/xscope_host_endpoint 12345) @@ -181,11 +187,11 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do # log results echo "${INPUT_WAV}, ${MAX_ALLOWABLE_WER}, ${WER}" >> ${RESULTS} - # clean up temp + # clean up temp rm ${TEMP_XSCOPE_FILEIO_INPUT_WAV} rm ${TEMP_XSCOPE_FILEIO_OUTPUT_WAV} rm ${TEMP_XSCOPE_FILEIO_OUTPUT_LOG} -done +done # print results cat ${RESULTS} diff --git a/test/asr/src/app_conf.h b/test/asr/src/app_conf.h index ee3cda85..c88bd4b8 100644 --- a/test/asr/src/app_conf.h +++ b/test/asr/src/app_conf.h @@ -13,9 +13,9 @@ #ifndef appconfASR_BRICK_SIZE_SAMPLES #define appconfASR_BRICK_SIZE_SAMPLES 240 // typically set in asr.cmake -#endif +#endif -#ifndef appconfASR_MISSING_METADATA_CORRECTION +#ifndef appconfASR_MISSING_METADATA_CORRECTION #define appconfASR_MISSING_METADATA_CORRECTION (40 * appconfASR_BRICK_SIZE_SAMPLES) #endif @@ -31,4 +31,13 @@ #define appconfXSCOPE_IO_TASK_PRIORITY (configMAX_PRIORITIES - 1) #define appconfQSPI_FLASH_TASK_PRIORITY (configMAX_PRIORITIES - 1) +/* Maximum delay between a wake up phrase and command phrase */ +#ifndef appconfINTENT_RESET_DELAY_MS +#if appconfAUDIO_PLAYBACK_ENABLED +#define appconfINTENT_RESET_DELAY_MS 5000 +#else +#define appconfINTENT_RESET_DELAY_MS 4000 +#endif +#endif + #endif /* APP_CONF_H_ */ From dc1f722d7e55e6e9ee0fcd584cdca849f4f8329a Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 11 Mar 2024 08:23:06 +0000 Subject: [PATCH 066/288] Updates for new test --- test/asr/asr.cmake | 3 +- test/asr/src/main.c | 4 ++- test/asr/src/xscope_fileio_task.c | 47 ++++++++++++++++--------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index f2aadaed..8cf59e1c 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -54,10 +54,9 @@ elseif(${TEST_ASR} STREQUAL "CYBERON") set(ASR_BRICK_SIZE_SAMPLES 240) set(APP_SOURCES ${APP_SOURCES} - ${FFD_SRC_ROOT}/model/english_usa/command-pc62w-6.4.0-op10-prod-search.c ) set(MODEL_FILE ${FFD_SRC_ROOT}/model/english_usa/Hello_XMOS_pack_WithTxt.bin.Enc.NibbleSwap) - set(TEST_ASR_LIBRARY_ID 0) + set(TEST_ASR_LIBRARY_ID 1) set(TEST_ASR_NAME test_asr_cyberon) else() message(FATAL_ERROR "Unable to build ${TEST_ASR} test") diff --git a/test/asr/src/main.c b/test/asr/src/main.c index 5de75eb3..c0411aa0 100644 --- a/test/asr/src/main.c +++ b/test/asr/src/main.c @@ -19,6 +19,7 @@ #include "xscope_fileio_task.h" #include "platform/platform_init.h" #include "platform/driver_instances.h" +//#include "fs_support.h" #ifndef MEM_ANALYSIS_ENABLED #define MEM_ANALYSIS_ENABLED 0 @@ -51,7 +52,8 @@ void startup_task(void *arg) vTaskDelay(pdMS_TO_TICKS(1000)); #if ON_TILE(FLASH_TILE) -#if (appconfASR_LIBRARY_ID == 0) +#if (appconfASR_LIBRARY_ID == 0) || (appconfASR_LIBRARY_ID == 1) + //rtos_fatfs_init(qspi_flash_ctx); // Setup flash low-level mode // NOTE: must call rtos_qspi_flash_fast_read_shutdown_ll to use non low-level mode calls rtos_qspi_flash_fast_read_setup_ll(qspi_flash_ctx); diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index e96d368d..3c57eb85 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -25,24 +25,27 @@ #endif #if (appconfASR_LIBRARY_ID == 0) - // Sensory - + // Sensory only + // SEARCH model file is specified in the CMakeLists SENSORY_SEARCH_FILE variable extern const unsigned short gs_grammarLabel[]; void* grammar = (void*)gs_grammarLabel; - +#elif (appconfASR_LIBRARY_ID == 1) + void* grammar = NULL; +#else + #error "Unsupported appconfASR_LIBRARY_ID" +#endif +#if (appconfASR_LIBRARY_ID == 0) || (appconfASR_LIBRARY_ID == 1) // Model file is in flash at the offset specified in the CMakeLists // QSPI_FLASH_MODEL_START_ADDRESS variable. The XS1_SWMEM_BASE value needs - // to be added so the address in in the SwMem range. + // to be added so the address in in the SwMem range. uint16_t *model = (uint16_t *) (XS1_SWMEM_BASE + QSPI_FLASH_MODEL_START_ADDRESS); -#else - #error "Unsupported appconfASR_LIBRARY_ID" #endif static TaskHandle_t xscope_fileio_task_handle; static xscope_file_t infile; static xscope_file_t outfile; -static asr_port_t asr_ctx; +static asr_port_t asr_ctx; static devmem_manager_t devmem_ctx; static char log_buffer[1024]; @@ -85,7 +88,7 @@ void xscope_fileio_task(void *arg) { wav_header input_header_struct; unsigned input_header_size; unsigned frame_count; - unsigned brick_count; + unsigned brick_count; uint32_t DWORD_ALIGNED in_buf_raw_32[appconfASR_BRICK_SIZE_SAMPLES * appconfINPUT_CHANNELS]; int16_t DWORD_ALIGNED in_buf_int_16[appconfINPUT_CHANNELS * appconfASR_BRICK_SIZE_SAMPLES]; size_t bytes_read = 0; @@ -116,15 +119,15 @@ void xscope_fileio_task(void *arg) { rtos_printf("Error: unsupported wav bit depth (%d) for %s file. Only 32 supported\n", input_header_struct.bit_depth, appconfINPUT_FILENAME); _Exit(1); } - // Ensure input wav file contains correct number of channels + // Ensure input wav file contains correct number of channels if(input_header_struct.num_channels != appconfINPUT_CHANNELS){ rtos_printf("Error: wav num channels(%d) does not match (%u)\n", input_header_struct.num_channels, appconfINPUT_CHANNELS); _Exit(1); } - + // Calculate number of frames in the wav file frame_count = wav_get_num_frames(&input_header_struct); - brick_count = frame_count / appconfASR_BRICK_SIZE_SAMPLES; + brick_count = frame_count / appconfASR_BRICK_SIZE_SAMPLES; // Init ASR library devmem_init(&devmem_ctx); @@ -165,40 +168,40 @@ void xscope_fileio_task(void *arg) { // Send audio to ASR asr_error = asr_process(asr_ctx, in_buf_int_16, appconfASR_BRICK_SIZE_SAMPLES); - if (asr_error != ASR_OK) continue; + if (asr_error != ASR_OK) continue; asr_error = asr_get_result(asr_ctx, &asr_result); - if (asr_error != ASR_OK) continue; + if (asr_error != ASR_OK) continue; // Query or compute recognition event metadata size_t start_index; size_t end_index; size_t duration; - + if (asr_result.id > 0) { if (asr_result.start_index > 0) { start_index = asr_result.start_index; } else { // No metadata so assume this brick - appconfASR_MISSING_START_METADATA_CORRECTION - start_index = (b * appconfASR_BRICK_SIZE_SAMPLES) - appconfASR_MISSING_METADATA_CORRECTION; + start_index = (b * appconfASR_BRICK_SIZE_SAMPLES) - appconfASR_MISSING_METADATA_CORRECTION; } if (asr_result.end_index > 0) { - end_index = asr_result.end_index; + end_index = asr_result.end_index; } else { // No metadata so assume start_index - end_index = start_index; + end_index = start_index; } if (asr_result.duration > 0) { - duration = asr_result.duration; + duration = asr_result.duration; } else { // No metadata so assume no duration - duration = 0; + duration = 0; } // Log result - sprintf(log_buffer, "RECOGNIZED: id=%d, start=%d, end=%d, duration=%d\n", + sprintf(log_buffer, "RECOGNIZED: id=%d, start=%d, end=%d, duration=%d\n", asr_result.id, start_index, end_index, @@ -235,10 +238,10 @@ void xscope_fileio_tasks_create(unsigned priority, void* app_data) { app_data, priority, &xscope_fileio_task_handle); - + // Define the core affinity mask such that this task can only run on a specific core UBaseType_t uxCoreAffinityMask = 0x10; /* Set the core affinity mask for the task. */ - vTaskCoreAffinitySet( xscope_fileio_task_handle, uxCoreAffinityMask ); + vTaskCoreAffinitySet( xscope_fileio_task_handle, uxCoreAffinityMask ); } From 0130d241bc5997d1e00cf1b05968b8c36fb11208 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Mon, 11 Mar 2024 14:11:53 +0000 Subject: [PATCH 067/288] More updates --- test/asr/make_label_track.py | 24 +++++++++++++++++++++++- test/asr/src/xscope_fileio_task.c | 10 ++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/test/asr/make_label_track.py b/test/asr/make_label_track.py index 2c4f3949..b6ebdffa 100755 --- a/test/asr/make_label_track.py +++ b/test/asr/make_label_track.py @@ -30,6 +30,26 @@ 17: "Hello XMOS" } +CYBERON_LUT = { + 1: "Hello XMOS", + 2: "Switch on the TV", + 3: "Switch off the TV", + 4: "Channel up", + 5: "Channel down", + 6: "Volume up", + 7: "Volume down", + 8: "Switch on the lights", + 9: "Switch off the lights", + 10: "Brightness up", + 11: "Brightness down", + 12: "Switch on the lights", + 13: "Switch off the fan", + 14: "Speed up the fan", + 15: "Slow down the fan", + 16: "Set higher temperature", + 17: "Set lower temperature", +} + def convert(val): constructors = [int, float, str] for c in constructors: @@ -59,6 +79,8 @@ def process(log, label_track, lut): for recognition_event in recognition_events: if lut == "Sensory": event_str = SENSORY_LUT[recognition_event["id"]] + elif lut == "Cyberon": + event_str = CYBERON_LUT[recognition_event["id"]] else: event_str = str(recognition_event["id"]) @@ -70,7 +92,7 @@ def process(log, label_track, lut): parser = argparse.ArgumentParser('Label Track Maker') parser.add_argument('--log_file', help='Log file to parse') parser.add_argument('--label_track', help='Label track file') - parser.add_argument('--lut', choices={"Sensory"}, help='Lookup') + parser.add_argument('--lut', choices={"Sensory", "Cyberon"}, help='Lookup') args = parser.parse_args() process(args.log_file, args.label_track, args.lut) \ No newline at end of file diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index 3c57eb85..411103e2 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -66,6 +66,16 @@ void xscope_fileio_lock_release(void) { } void init_xscope_host_data_user_cb(chanend_t c_host) { + // TODO: Use a better way to add a delay here. + // If vTaskDelay() is used, the error is generated: + + // xrun: Program received signal ET_ECALL, Application exception. + // [Switching to tile[0] core[1]] + // vTaskDelay (xTicksToDelay=100) at /home/lucianom/sln_voice/modules/rtos/modules/FreeRTOS/FreeRTOS-SMP-Kernel/tasks.c:1864 I + + // 1864 configASSERT( uxSchedulerSuspended == 1 ); + // Current language: auto; currently minimal + printintln(111); xscope_io_init(c_host); } #endif From e23ed5843be41c685a205ebbeff5b8673062e55a Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 12 Mar 2024 08:05:23 +0000 Subject: [PATCH 068/288] Updates for test --- test/asr/src/xscope_fileio_task.c | 17 +++++++++-------- tools/ci/build_tests.sh | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index 411103e2..b673bbc7 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -189,25 +189,26 @@ void xscope_fileio_task(void *arg) { size_t duration; if (asr_result.id > 0) { - if (asr_result.start_index > 0) { - start_index = asr_result.start_index; + + if (asr_result.end_index > 0) { + end_index = asr_result.end_index; } else { // No metadata so assume this brick - appconfASR_MISSING_START_METADATA_CORRECTION - start_index = (b * appconfASR_BRICK_SIZE_SAMPLES) - appconfASR_MISSING_METADATA_CORRECTION; + end_index = (b * appconfASR_BRICK_SIZE_SAMPLES) - appconfASR_MISSING_METADATA_CORRECTION;; } - if (asr_result.end_index > 0) { - end_index = asr_result.end_index; + if (asr_result.start_index > 0) { + start_index = asr_result.start_index; } else { - // No metadata so assume start_index - end_index = start_index; + // No metadata so assume the end_index - 2*appconfASR_MISSING_START_METADATA_CORRECTION + start_index = end_index - 2 * appconfASR_MISSING_METADATA_CORRECTION; } if (asr_result.duration > 0) { duration = asr_result.duration; } else { // No metadata so assume no duration - duration = 0; + duration = -1; } // Log result diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index eaac2618..11751adb 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -26,6 +26,7 @@ tests=( "test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_sensory test_asr_sensory test_asr_sensory TEST_ASR=SENSORY XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_asr_cyberon test_asr_cyberon test_asr_cyberon TEST_ASR=CYBERON XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" From 39798f85b19797c1b6d42c02a2599e5b9fe08d45 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 12 Mar 2024 08:05:43 +0000 Subject: [PATCH 069/288] Revert "Add missing file" This reverts commit 1f38981def9a7a5fd964ea864f7706b88a9bd558. --- test/asr/src/device_memory.h | 116 ----------------------------------- 1 file changed, 116 deletions(-) delete mode 100644 test/asr/src/device_memory.h diff --git a/test/asr/src/device_memory.h b/test/asr/src/device_memory.h deleted file mode 100644 index 04c53fa5..00000000 --- a/test/asr/src/device_memory.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2023-2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#ifndef XCORE_DEVICE_MEMORY_H -#define XCORE_DEVICE_MEMORY_H - -#include -#include -#include -#include - -#include - -/** - * Typedef to the device memory manager context. - * Allows an application to define how memory allocation and - * data loading is implemented. - */ -typedef struct devmem_manager_struct -{ - __attribute__((fptrgroup("devmem_malloc_fptr_grp"))) - void * (*malloc)(size_t size); - - __attribute__((fptrgroup("devmem_free_fptr_grp"))) - void (*free)(void *ptr); - - __attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) - void (*read_ext)(void *dest, const void *src, size_t n); - - __attribute__((fptrgroup("devmem_read_ext_async_fptr_grp"))) - int (*read_ext_async)(void *dest, const void * src, size_t n); - - __attribute__((fptrgroup("devmem_read_ext_wait_fptr_grp"))) - void (*read_ext_wait)(int handle); -} devmem_manager_t; - - -/** - * \addtogroup devmem_api devmem_api - * - * The public API for XCORE device memory management. - * @{ - */ - -#define IS_SRAM(a) ((uintptr_t)a < XS1_SWMEM_BASE) - -#define IS_SWMEM(a) (((uintptr_t)a >= XS1_SWMEM_BASE) && (((uintptr_t)a <= (XS1_SWMEM_BASE - 1 + XS1_SWMEM_SIZE)))) - -#define IS_FLASH(a) IS_SWMEM(a) - -/** - * Memory allocation function that allows the application - * to provide an alternative implementation. - * - * Call devmem_malloc instead of malloc - * - * \param ctx A pointer to the device memory context. - * \param size Number of bytes to allocate. - * - * \returns A pointer to the beginning of newly allocated memory, or NULL on failure. - */ -void *devmem_malloc(devmem_manager_t *ctx, size_t size); - -/** - * Memory deallocation function that allows the application - * to provide an alternative implementation. - * - * Call devmem_free instead of free - * - * \param ctx A pointer to the device memory context. - * \param ptr A pointer to the memory to deallocate. - */ -void devmem_free(devmem_manager_t *ctx, void *ptr); - -/** - * Synchronous extended memory read function that allows the application - * to provide an alternative implementation. Blocks the callers thread - * until the read is completed. - * - * Call devmem_read_ext instead of any other functions to read memory from - * flash, LPDDR or SDRAM. Modules are free to use memcpy if the dest and src - * are both SRAM addresses. - * - * \param ctx A pointer to the device memory context. - * \param dest A pointer to the destination array where the content is to be read. - * \param src A pointer to the word-aligned address of data to be read. - * \param n Number of bytes to read. - */ -void devmem_read_ext(devmem_manager_t *ctx, void *dest, const void * src, size_t n); - -/** - * Asynchronous extended memory read function that allows the application - * to provide an alternative implementation. - * - * Call asr_read_ext_async instead of any other functions to read memory - * from flash, LPDDR or SDRAM. - * - * \param ctx A pointer to the device memory context. - * \param dest A pointer to the destination array where the content is to be read. - * \param src A pointer to the word-aligned address of data to be read. - * \param n Number of bytes to read. - * - * \returns A handle that can be used in a call to devmem_read_ext_wait. - */ -int devmem_read_ext_async(devmem_manager_t *ctx, void *dest, const void * src, size_t n); - -/** - * Wait in the caller's thread for an asynchronous extended memory read to finish. - * - * \param ctx A pointer to the device memory context. - * \param handle The devmem_read_ext_asyc handle to wait on. - */ -void devmem_read_ext_wait(devmem_manager_t *ctx, int handle); - -/**@}*/ - -#endif // XCORE_DEVICE_MEMORY_H From fb1ecb4043e0877dba00d56f949438a0fd858962 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 12 Mar 2024 08:05:56 +0000 Subject: [PATCH 070/288] Revert "Undo changes to avoid crash in ASR test" This reverts commit db8ec77e7fb9b4e1d256e410a6ca8a58dcdc8eca. --- test/asr/asr.cmake | 2 + test/asr/src/device_memory_impl.c | 65 ------------------------------- test/asr/src/device_memory_impl.h | 10 ----- 3 files changed, 2 insertions(+), 75 deletions(-) delete mode 100644 test/asr/src/device_memory_impl.c delete mode 100644 test/asr/src/device_memory_impl.h diff --git a/test/asr/asr.cmake b/test/asr/asr.cmake index 8cf59e1c..f3567622 100644 --- a/test/asr/asr.cmake +++ b/test/asr/asr.cmake @@ -123,6 +123,7 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES} ${ASR_LIBRARY} + sln_voice::app::asr::device_memory ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) @@ -141,6 +142,7 @@ target_link_libraries(${TARGET_NAME} PUBLIC ${APP_COMMON_LINK_LIBRARIES} ${ASR_LIBRARY} + sln_voice::app::asr::device_memory ) target_link_options(${TARGET_NAME} PRIVATE ${APP_LINK_OPTIONS}) unset(TARGET_NAME) diff --git a/test/asr/src/device_memory_impl.c b/test/asr/src/device_memory_impl.c deleted file mode 100644 index 36756601..00000000 --- a/test/asr/src/device_memory_impl.c +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2023-2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#include -#include -#include - -/* System headers */ -#include -#include -#include -#include - -/* FreeRTOS headers */ -#include "FreeRTOS.h" - -/* Library headers */ -#include "rtos_printf.h" -#include "rtos_qspi_flash.h" - -/* App headers */ -#include "app_conf.h" -#include "platform/driver_instances.h" -#include "device_memory.h" -#include "device_memory_impl.h" - -void asr_printf(const char * format, ...) { - va_list args; - va_start(args, format); - xcore_utils_vprintf(format, args); - va_end(args); -} - -__attribute__((fptrgroup("devmem_malloc_fptr_grp"))) -void * devmem_malloc_local(size_t size) { - //rtos_printf("devmem_malloc_local size=%d\n", size); - return pvPortMalloc(size); -} - -__attribute__((fptrgroup("devmem_free_fptr_grp"))) -void devmem_free_local(void *ptr) { - vPortFree(ptr); -} - -__attribute__((fptrgroup("devmem_read_ext_fptr_grp"))) -void devmem_read_ext_local(void *dest, const void *src, size_t n) { - //rtos_printf("devmem_read_ext_local dest=0x%x src=0x%x size=%d\n", dest, src, n); - if (IS_FLASH(src)) { - // Need to subtract off XS1_SWMEM_BASE because qspi flash driver accounts for the offset - //uint32_t s = get_reference_time(); - rtos_qspi_flash_fast_read_mode_ll(qspi_flash_ctx, (uint8_t *)dest, (unsigned)(src - XS1_SWMEM_BASE), n, qspi_fast_flash_read_transfer_raw); - //uint32_t d = get_reference_time() - s; - //printf("%d, %0.01f (us), %0.04f (M/s)\n", n, d / 100.0f, (n / 1000000.0f ) / (d / 100000000.0f)); - } else { - memcpy(dest, src, n); - } -} - -void devmem_init(devmem_manager_t *devmem_ctx) { - xassert(devmem_ctx); - devmem_ctx->malloc = devmem_malloc_local; - devmem_ctx->free = devmem_free_local; - devmem_ctx->read_ext = devmem_read_ext_local; - devmem_ctx->read_ext_async = NULL; // not supported in this application - devmem_ctx->read_ext_wait = NULL; // not supported in this application -} diff --git a/test/asr/src/device_memory_impl.h b/test/asr/src/device_memory_impl.h deleted file mode 100644 index ea8735cb..00000000 --- a/test/asr/src/device_memory_impl.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2023-2024 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -#ifndef DEVICE_MEMORY_IMPL_H -#define DEVICE_MEMORY_IMPL_H - -#include "device_memory.h" - -void devmem_init(devmem_manager_t *devmem_ctx); - -#endif // DEVICE_MEMORY_IMPL_H \ No newline at end of file From 32fbe03b1b44d32fe04a3399cdbe23ff69a6cbd4 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 12 Mar 2024 10:24:12 +0000 Subject: [PATCH 071/288] Update scoring of the ASR tests --- test/asr/ffd_quick_cyberon.txt | 5 +++++ test/asr/{ffd_quick.txt => ffd_quick_sensory.txt} | 0 test/asr/src/xscope_fileio_task.c | 7 ++++--- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 test/asr/ffd_quick_cyberon.txt rename test/asr/{ffd_quick.txt => ffd_quick_sensory.txt} (100%) diff --git a/test/asr/ffd_quick_cyberon.txt b/test/asr/ffd_quick_cyberon.txt new file mode 100644 index 00000000..1d4a3f01 --- /dev/null +++ b/test/asr/ffd_quick_cyberon.txt @@ -0,0 +1,5 @@ +# filename, Max allowable WER +ETSIRock_Speech54dB_Noise45dB 0.40 +Pink_Speech54dB_Noise45dB 0.37 +Pub_Speech54dB_Noise45dB 0.35 +Silence_Speech59dB 0.21 diff --git a/test/asr/ffd_quick.txt b/test/asr/ffd_quick_sensory.txt similarity index 100% rename from test/asr/ffd_quick.txt rename to test/asr/ffd_quick_sensory.txt diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index b673bbc7..de33c1d0 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -193,14 +193,15 @@ void xscope_fileio_task(void *arg) { if (asr_result.end_index > 0) { end_index = asr_result.end_index; } else { - // No metadata so assume this brick - appconfASR_MISSING_START_METADATA_CORRECTION - end_index = (b * appconfASR_BRICK_SIZE_SAMPLES) - appconfASR_MISSING_METADATA_CORRECTION;; + // No metadata so assume this brick - appconfASR_MISSING_START_METADATA_CORRECTION / 2 + end_index = (b * appconfASR_BRICK_SIZE_SAMPLES) - appconfASR_MISSING_METADATA_CORRECTION / 2; } if (asr_result.start_index > 0) { start_index = asr_result.start_index; } else { - // No metadata so assume the end_index - 2*appconfASR_MISSING_START_METADATA_CORRECTION + // No metadata so assume the end_index - (2 * appconfASR_MISSING_START_METADATA_CORRECTION) + // The average duration of the detection is 1.16 ms, and (2 * appconfASR_MISSING_START_METADATA_CORRECTION) is 1.2 ms start_index = end_index - 2 * appconfASR_MISSING_METADATA_CORRECTION; } From 6e6b5e53e47756f8e2669ec1982bb19ab3478578 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 12 Mar 2024 10:24:42 +0000 Subject: [PATCH 072/288] Disable tests to speedup Jenkins run - DO NOT MERGE --- Jenkinsfile | 187 ++-------------------------------------- test/asr/check_asr.sh | 1 - tools/ci/build_tests.sh | 16 ++-- 3 files changed, 13 insertions(+), 191 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 54a29792..fad41833 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,194 +69,17 @@ pipeline { } } - stage('ASRC Unit tests') { - steps { - withTools(params.TOOLS_VERSION) { - // tools/ci/build_tests.sh does not build for x86 - sh "mkdir -p build_x86" - sh "cmake -B build_x86 -DXCORE_VOICE_TESTS=ON" - sh "cmake --build build_x86 --target test_asrc_div -j8" - // x86 build - sh "./build_x86/test_asrc_div" - // xcore build - sh "xsim dist/test_asrc_div.xe" - } - } - } - - - stage('ASRC Simulator') { - steps { - withTools(params.TOOLS_VERSION) { - dir("test/asrc_sim") { - createVenv('requirements.txt') - withVenv { - sh "pip install -r ./requirements.txt" - sh './run.sh' - } - } - } - } - } - stage('Create virtual environment') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - // Create venv - sh "pyenv install -s $PYTHON_VERSION" - sh "~/.pyenv/versions/$PYTHON_VERSION/bin/python -m venv $VENV_DIRNAME" - // Install dependencies - withVenv() { - sh "pip install git+https://github0.xmos.com/xmos-int/xtagctl.git" - sh "pip install -r test/requirements.txt" - } - } - } - stage('Cleanup xtagctl') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - // Cleanup any xtagctl cruft from previous failed runs - withTools(params.TOOLS_VERSION) { - withVenv { - sh "xtagctl reset_all $VRD_TEST_RIG_TARGET" - } - } - sh "rm -f ~/.xtag/status.lock ~/.xtag/acquired" - } - } - stage('Run Sample Rate Conversion test') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - withTools(params.TOOLS_VERSION) { - withVenv { - script { - withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "test/sample_rate_conversion/check_sample_rate_conversion.sh " + adapterIDs[0] - } - sh "pytest test/sample_rate_conversion/test_sample_rate_conversion.py --wav_file test/sample_rate_conversion/test_output/sample_rate_conversion_output.wav --wav_duration 10" - } - } - } - } - } - stage('Run GPIO test') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - withTools(params.TOOLS_VERSION) { - withVenv { - script { - sh "test/ffd_gpio/run_tests.sh" - sh 'python tools/ci/python/parse_test_output.py testing/test.rpt -outfile="testing/test_results" --print_test_results --verbose' - } - } - } - } - } - stage('Run FFD Low Power Audio Buffer test') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - withTools(params.TOOLS_VERSION) { - withVenv { - script { - sh "test/ffd_low_power_audio_buffer/run_tests.sh" - sh "pytest test/ffd_low_power_audio_buffer/test_verify_low_power_audio_buffer.py" - } - } - } - } - } - stage('Run Device Firmware Update test') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - withTools(params.TOOLS_VERSION) { - withVenv { - script { - uid = sh(returnStdout: true, script: 'id -u').trim() - gid = sh(returnStdout: true, script: 'id -g').trim() - withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "docker run --rm -u $uid:$gid --privileged -v /dev/bus/usb:/dev/bus/usb -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_voice_tester:develop bash -l test/device_firmware_update/check_dfu.sh " + adapterIDs[0] - } - sh "pytest test/device_firmware_update/test_dfu.py --readback_image test/device_firmware_update/test_output/readback_upgrade.bin --upgrade_image test/device_firmware_update/test_output/test_ffva_dfu_upgrade.bin" - } - } - } - } - } - stage('Checkout Amazon WWE') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - sh 'git clone git@github.com:xmos/amazon_wwe.git' - } - } - stage('Setup test vectors') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - sh "cp -r /projects/hydra_audio/xcore-voice_xvf3510_no_processing_xmos_test_suite_subset $PIPELINE_TEST_VECTORS" - sh "ls -la $PIPELINE_TEST_VECTORS" - sh "cp -r /projects/hydra_audio/xcore-voice_no_processing_ffd_test_suite $ASR_TEST_VECTORS" - sh "ls -la $ASR_TEST_VECTORS" - } - } - stage('Run FFVA Pipeline test') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - withTools(params.TOOLS_VERSION) { - withVenv { - script { - withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] - } - sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffva_test_output/results.csv" - } - } - } - } - } - stage('Run FFD Pipeline test') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } - steps { - withTools(params.TOOLS_VERSION) { - withVenv { - script { - withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffd.xe $PIPELINE_TEST_VECTORS test/pipeline/ffd_quick.txt test/pipeline/ffd_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] - } - sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffd_test_output/results.csv" - } - } - } - } - } stage('Run ASR test') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } + //when { + // expression { params.NIGHTLY_TEST_ONLY == true } + //} steps { withTools(params.TOOLS_VERSION) { withVenv { script { withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> - sh "test/asr/check_asr.sh Sensory $ASR_TEST_VECTORS test/asr/ffd_quick.txt test/asr/sensory_output " + adapterIDs[0] - sh "test/asr/check_asr.sh Cyberon $ASR_TEST_VECTORS test/asr/ffd_quick.txt test/asr/cyberon_output " + adapterIDs[0] + sh "test/asr/check_asr.sh Sensory $ASR_TEST_VECTORS test/asr/ffd_quick_sensory.txt test/asr/sensory_output " + adapterIDs[0] + sh "test/asr/check_asr.sh Cyberon $ASR_TEST_VECTORS test/asr/ffd_quick_cyberon.txt test/asr/cyberon_output " + adapterIDs[0] } sh "pytest test/asr/test_asr.py --log test/asr/sensory_output/results.csv" sh "pytest test/asr/test_asr.py --log test/asr/cyberon_output/results.csv" diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index ba0c3691..a309ebba 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -94,7 +94,6 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do read -ra FIELDS <<< ${INPUT_ARRAY[j]} FILE_NAME=${FIELDS[0]} MAX_ALLOWABLE_WER=${FIELDS[1]} - INPUT_WAV="${INPUT_DIR}/${FILE_NAME}.wav" PROCESSED_WAV="${OUTPUT_DIR}/${FILE_NAME}_processed.wav" LABEL_TRACK="${OUTPUT_DIR}/${FILE_NAME}_labels.txt" diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index 11751adb..f22da4a6 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -21,16 +21,16 @@ fi # setup configurations # row format is: "name app_target run_data_partition_target flag BOARD toolchain" tests=( - "test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" - "test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" + #"test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_sensory test_asr_sensory test_asr_sensory TEST_ASR=SENSORY XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_cyberon test_asr_cyberon test_asr_cyberon TEST_ASR=CYBERON XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - "test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" - "test_ffd_low_power_audio_buffer test_ffd_low_power_audio_buffer NONE NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + #"test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" + #"test_ffd_low_power_audio_buffer test_ffd_low_power_audio_buffer NONE NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" ) # perform builds From 79ed9a445e4afa4cbedd2412886215cb40a6f904 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 12 Mar 2024 10:38:58 +0000 Subject: [PATCH 073/288] Re-add steps --- Jenkinsfile | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index fad41833..af683d59 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -68,7 +68,35 @@ pipeline { sh "ls -la dist/" } } - + stage('Create virtual environment') { + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } + steps { + // Create venv + sh "pyenv install -s $PYTHON_VERSION" + sh "~/.pyenv/versions/$PYTHON_VERSION/bin/python -m venv $VENV_DIRNAME" + // Install dependencies + withVenv() { + sh "pip install git+https://github0.xmos.com/xmos-int/xtagctl.git" + sh "pip install -r test/requirements.txt" + } + } + } + stage('Cleanup xtagctl') { + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } + steps { + // Cleanup any xtagctl cruft from previous failed runs + withTools(params.TOOLS_VERSION) { + withVenv { + sh "xtagctl reset_all $VRD_TEST_RIG_TARGET" + } + } + sh "rm -f ~/.xtag/status.lock ~/.xtag/acquired" + } + } stage('Run ASR test') { //when { // expression { params.NIGHTLY_TEST_ONLY == true } From 554436ef89b91756f4c2f97a20949f24a305c250 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 12 Mar 2024 10:44:48 +0000 Subject: [PATCH 074/288] Fix test --- Jenkinsfile | 12 ++++++------ test/asr/check_asr.sh | 1 + test/asr/src/app_conf.h | 6 ------ test/asr/src/main.c | 2 -- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index af683d59..c5652333 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,9 +69,9 @@ pipeline { } } stage('Create virtual environment') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } + //when { + // expression { params.NIGHTLY_TEST_ONLY == true } + //} steps { // Create venv sh "pyenv install -s $PYTHON_VERSION" @@ -84,9 +84,9 @@ pipeline { } } stage('Cleanup xtagctl') { - when { - expression { params.NIGHTLY_TEST_ONLY == true } - } + //when { + // expression { params.NIGHTLY_TEST_ONLY == true } + //} steps { // Cleanup any xtagctl cruft from previous failed runs withTools(params.TOOLS_VERSION) { diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index a309ebba..ba0c3691 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -94,6 +94,7 @@ for ((j = 0; j < ${#INPUT_ARRAY[@]}; j += 1)); do read -ra FIELDS <<< ${INPUT_ARRAY[j]} FILE_NAME=${FIELDS[0]} MAX_ALLOWABLE_WER=${FIELDS[1]} + INPUT_WAV="${INPUT_DIR}/${FILE_NAME}.wav" PROCESSED_WAV="${OUTPUT_DIR}/${FILE_NAME}_processed.wav" LABEL_TRACK="${OUTPUT_DIR}/${FILE_NAME}_labels.txt" diff --git a/test/asr/src/app_conf.h b/test/asr/src/app_conf.h index c88bd4b8..9989c89b 100644 --- a/test/asr/src/app_conf.h +++ b/test/asr/src/app_conf.h @@ -32,12 +32,6 @@ #define appconfQSPI_FLASH_TASK_PRIORITY (configMAX_PRIORITIES - 1) /* Maximum delay between a wake up phrase and command phrase */ -#ifndef appconfINTENT_RESET_DELAY_MS -#if appconfAUDIO_PLAYBACK_ENABLED -#define appconfINTENT_RESET_DELAY_MS 5000 -#else #define appconfINTENT_RESET_DELAY_MS 4000 -#endif -#endif #endif /* APP_CONF_H_ */ diff --git a/test/asr/src/main.c b/test/asr/src/main.c index c0411aa0..8dda77dc 100644 --- a/test/asr/src/main.c +++ b/test/asr/src/main.c @@ -19,7 +19,6 @@ #include "xscope_fileio_task.h" #include "platform/platform_init.h" #include "platform/driver_instances.h" -//#include "fs_support.h" #ifndef MEM_ANALYSIS_ENABLED #define MEM_ANALYSIS_ENABLED 0 @@ -53,7 +52,6 @@ void startup_task(void *arg) #if ON_TILE(FLASH_TILE) #if (appconfASR_LIBRARY_ID == 0) || (appconfASR_LIBRARY_ID == 1) - //rtos_fatfs_init(qspi_flash_ctx); // Setup flash low-level mode // NOTE: must call rtos_qspi_flash_fast_read_shutdown_ll to use non low-level mode calls rtos_qspi_flash_fast_read_setup_ll(qspi_flash_ctx); From ed9fb53ac6a206e6db02a1eeb8b85d366beeff38 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 12 Mar 2024 10:58:11 +0000 Subject: [PATCH 075/288] Put back steps --- test/asr/check_asr.sh | 2 +- test/asr/make_label_track.py | 2 +- test/asr/src/app_conf.h | 2 +- test/asr/src/main.c | 2 +- test/asr/src/xscope_fileio_task.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/asr/check_asr.sh b/test/asr/check_asr.sh index ba0c3691..f4168d43 100755 --- a/test/asr/check_asr.sh +++ b/test/asr/check_asr.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2022, XMOS Ltd, All rights reserved +# Copyright (c) 2022-2024, XMOS Ltd, All rights reserved set -e # exit on first error set -x # echo on diff --git a/test/asr/make_label_track.py b/test/asr/make_label_track.py index b6ebdffa..7a745a94 100755 --- a/test/asr/make_label_track.py +++ b/test/asr/make_label_track.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright 2022-2023 XMOS LIMITED. +# Copyright 2022-2024 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. # XMOS Public License: Version 1 diff --git a/test/asr/src/app_conf.h b/test/asr/src/app_conf.h index 9989c89b..b886e568 100644 --- a/test/asr/src/app_conf.h +++ b/test/asr/src/app_conf.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef APP_CONF_H_ diff --git a/test/asr/src/main.c b/test/asr/src/main.c index 8dda77dc..2459ed05 100644 --- a/test/asr/src/main.c +++ b/test/asr/src/main.c @@ -1,4 +1,4 @@ -// Copyright 2020-2023 XMOS LIMITED. +// Copyright 2020-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index de33c1d0..2ac80aee 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include From a5f87bd64c9ba2ec35944871354cfbf9ca224a75 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 12 Mar 2024 11:05:06 +0000 Subject: [PATCH 076/288] Put back steps --- Jenkinsfile | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index c5652333..56be8cff 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -97,6 +97,42 @@ pipeline { sh "rm -f ~/.xtag/status.lock ~/.xtag/acquired" } } + stage('Checkout Amazon WWE') { + //when { + // expression { params.NIGHTLY_TEST_ONLY == true } + //} + steps { + sh 'git clone git@github.com:xmos/amazon_wwe.git' + } + } + stage('Setup test vectors') { + //when { + // expression { params.NIGHTLY_TEST_ONLY == true } + //} + steps { + sh "cp -r /projects/hydra_audio/xcore-voice_xvf3510_no_processing_xmos_test_suite_subset $PIPELINE_TEST_VECTORS" + sh "ls -la $PIPELINE_TEST_VECTORS" + sh "cp -r /projects/hydra_audio/xcore-voice_no_processing_ffd_test_suite $ASR_TEST_VECTORS" + sh "ls -la $ASR_TEST_VECTORS" + } + } + stage('Run FFVA Pipeline test') { + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } + steps { + withTools(params.TOOLS_VERSION) { + withVenv { + script { + withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> + sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffva_adec_altarch.xe $PIPELINE_TEST_VECTORS test/pipeline/ffva_quick.txt test/pipeline/ffva_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] + } + sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffva_test_output/results.csv" + } + } + } + } + } stage('Run ASR test') { //when { // expression { params.NIGHTLY_TEST_ONLY == true } From d1a563f924fc8838c7fafd615d8dd432afd9113c Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 12 Mar 2024 11:18:51 +0000 Subject: [PATCH 077/288] Re-enable builds --- tools/ci/build_tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index f22da4a6..95aa4a5f 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -23,8 +23,8 @@ fi tests=( #"test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" #"test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_sensory test_asr_sensory test_asr_sensory TEST_ASR=SENSORY XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_cyberon test_asr_cyberon test_asr_cyberon TEST_ASR=CYBERON XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" #"test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" From 9fdd778bd1f8e92809292614dab9ce78909acde7 Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 12 Mar 2024 12:26:02 +0000 Subject: [PATCH 078/288] Use delay instead of printintln() --- test/asr/src/xscope_fileio_task.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/asr/src/xscope_fileio_task.c b/test/asr/src/xscope_fileio_task.c index de33c1d0..c9efda8b 100644 --- a/test/asr/src/xscope_fileio_task.c +++ b/test/asr/src/xscope_fileio_task.c @@ -9,7 +9,7 @@ #include "FreeRTOS.h" #include "task.h" #include "semphr.h" - +#include #include "soc_xscope_host.h" #include "app_conf.h" @@ -66,16 +66,16 @@ void xscope_fileio_lock_release(void) { } void init_xscope_host_data_user_cb(chanend_t c_host) { - // TODO: Use a better way to add a delay here. - // If vTaskDelay() is used, the error is generated: - // xrun: Program received signal ET_ECALL, Application exception. - // [Switching to tile[0] core[1]] - // vTaskDelay (xTicksToDelay=100) at /home/lucianom/sln_voice/modules/rtos/modules/FreeRTOS/FreeRTOS-SMP-Kernel/tasks.c:1864 I + // Delay the xscope initialization by 1 ms, + // to avoid the problem described in https://github.com/xmos/sln_voice/issues/349 + hwtimer_t tmr = hwtimer_alloc(); + { + xassert(tmr != 0); + hwtimer_delay(tmr, 100000); // 1ms with 100 MHz timer tick + } + hwtimer_free(tmr); - // 1864 configASSERT( uxSchedulerSuspended == 1 ); - // Current language: auto; currently minimal - printintln(111); xscope_io_init(c_host); } #endif From a49879ecf85ddd3c218c2e87a7b3db741c95076a Mon Sep 17 00:00:00 2001 From: lucianomartin Date: Tue, 12 Mar 2024 12:33:12 +0000 Subject: [PATCH 079/288] Undo changes --- Jenkinsfile | 142 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 127 insertions(+), 15 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 56be8cff..598ca498 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -68,10 +68,39 @@ pipeline { sh "ls -la dist/" } } + stage('ASRC Unit tests') { + steps { + withTools(params.TOOLS_VERSION) { + // tools/ci/build_tests.sh does not build for x86 + sh "mkdir -p build_x86" + sh "cmake -B build_x86 -DXCORE_VOICE_TESTS=ON" + sh "cmake --build build_x86 --target test_asrc_div -j8" + // x86 build + sh "./build_x86/test_asrc_div" + // xcore build + sh "xsim dist/test_asrc_div.xe" + } + } + } + + + stage('ASRC Simulator') { + steps { + withTools(params.TOOLS_VERSION) { + dir("test/asrc_sim") { + createVenv('requirements.txt') + withVenv { + sh "pip install -r ./requirements.txt" + sh './run.sh' + } + } + } + } + } stage('Create virtual environment') { - //when { - // expression { params.NIGHTLY_TEST_ONLY == true } - //} + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } steps { // Create venv sh "pyenv install -s $PYTHON_VERSION" @@ -84,9 +113,9 @@ pipeline { } } stage('Cleanup xtagctl') { - //when { - // expression { params.NIGHTLY_TEST_ONLY == true } - //} + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } steps { // Cleanup any xtagctl cruft from previous failed runs withTools(params.TOOLS_VERSION) { @@ -97,18 +126,84 @@ pipeline { sh "rm -f ~/.xtag/status.lock ~/.xtag/acquired" } } + stage('Run Sample Rate Conversion test') { + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } + steps { + withTools(params.TOOLS_VERSION) { + withVenv { + script { + withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> + sh "test/sample_rate_conversion/check_sample_rate_conversion.sh " + adapterIDs[0] + } + sh "pytest test/sample_rate_conversion/test_sample_rate_conversion.py --wav_file test/sample_rate_conversion/test_output/sample_rate_conversion_output.wav --wav_duration 10" + } + } + } + } + } + stage('Run GPIO test') { + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } + steps { + withTools(params.TOOLS_VERSION) { + withVenv { + script { + sh "test/ffd_gpio/run_tests.sh" + sh 'python tools/ci/python/parse_test_output.py testing/test.rpt -outfile="testing/test_results" --print_test_results --verbose' + } + } + } + } + } + stage('Run FFD Low Power Audio Buffer test') { + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } + steps { + withTools(params.TOOLS_VERSION) { + withVenv { + script { + sh "test/ffd_low_power_audio_buffer/run_tests.sh" + sh "pytest test/ffd_low_power_audio_buffer/test_verify_low_power_audio_buffer.py" + } + } + } + } + } + stage('Run Device Firmware Update test') { + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } + steps { + withTools(params.TOOLS_VERSION) { + withVenv { + script { + uid = sh(returnStdout: true, script: 'id -u').trim() + gid = sh(returnStdout: true, script: 'id -g').trim() + withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> + sh "docker run --rm -u $uid:$gid --privileged -v /dev/bus/usb:/dev/bus/usb -w /sln_voice -v $WORKSPACE:/sln_voice ghcr.io/xmos/xcore_voice_tester:develop bash -l test/device_firmware_update/check_dfu.sh " + adapterIDs[0] + } + sh "pytest test/device_firmware_update/test_dfu.py --readback_image test/device_firmware_update/test_output/readback_upgrade.bin --upgrade_image test/device_firmware_update/test_output/test_ffva_dfu_upgrade.bin" + } + } + } + } + } stage('Checkout Amazon WWE') { - //when { - // expression { params.NIGHTLY_TEST_ONLY == true } - //} + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } steps { sh 'git clone git@github.com:xmos/amazon_wwe.git' } } stage('Setup test vectors') { - //when { - // expression { params.NIGHTLY_TEST_ONLY == true } - //} + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } steps { sh "cp -r /projects/hydra_audio/xcore-voice_xvf3510_no_processing_xmos_test_suite_subset $PIPELINE_TEST_VECTORS" sh "ls -la $PIPELINE_TEST_VECTORS" @@ -133,10 +228,27 @@ pipeline { } } } + stage('Run FFD Pipeline test') { + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } + steps { + withTools(params.TOOLS_VERSION) { + withVenv { + script { + withXTAG(["$VRD_TEST_RIG_TARGET"]) { adapterIDs -> + sh "test/pipeline/check_pipeline.sh $BUILD_DIRNAME/test_pipeline_ffd.xe $PIPELINE_TEST_VECTORS test/pipeline/ffd_quick.txt test/pipeline/ffd_test_output $WORKSPACE/amazon_wwe " + adapterIDs[0] + } + sh "pytest test/pipeline/test_pipeline.py --log test/pipeline/ffd_test_output/results.csv" + } + } + } + } + } stage('Run ASR test') { - //when { - // expression { params.NIGHTLY_TEST_ONLY == true } - //} + when { + expression { params.NIGHTLY_TEST_ONLY == true } + } steps { withTools(params.TOOLS_VERSION) { withVenv { From 1d4899c77095a27eaca8695a542eea3790a6a9af Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 12 Mar 2024 12:37:43 +0000 Subject: [PATCH 080/288] Re-enable builds --- tools/ci/build_tests.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/ci/build_tests.sh b/tools/ci/build_tests.sh index 95aa4a5f..11751adb 100755 --- a/tools/ci/build_tests.sh +++ b/tools/ci/build_tests.sh @@ -21,16 +21,16 @@ fi # setup configurations # row format is: "name app_target run_data_partition_target flag BOARD toolchain" tests=( - #"test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" - #"test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_asrc_div test_asrc_div NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" + "test_ffva_dfu example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_pipeline_ffd test_pipeline_ffd NONE TEST_PIPELINE=FFD XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_pipeline_ffva_adec_altarch test_pipeline_ffva_adec_altarch NONE TEST_PIPELINE=FFVA_ALT_ARCH XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_sensory test_asr_sensory test_asr_sensory TEST_ASR=SENSORY XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" "test_asr_cyberon test_asr_cyberon test_asr_cyberon TEST_ASR=CYBERON XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" - #"test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" - #"test_ffd_low_power_audio_buffer test_ffd_low_power_audio_buffer NONE NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_ffva_sample_rate_conv example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_MIC_INPUT_PIPELINE_BYPASS=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_ffva_verbose_output example_ffva_ua_adec_altarch example_ffva_ua_adec_altarch DEBUG_FFVA_USB_VERBOSE_OUTPUT=1 XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" + "test_ffd_gpio test_ffd_gpio NONE NONE XCORE_AI_EXPLORER xmos_cmake_toolchain/xs3a.cmake" + "test_ffd_low_power_audio_buffer test_ffd_low_power_audio_buffer NONE NONE XK_VOICE_L71 xmos_cmake_toolchain/xs3a.cmake" ) # perform builds From 5dc8dfd91be1d7b5a6d0e8bbec20ec73b50afbdf Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 14 Mar 2024 12:18:05 +0000 Subject: [PATCH 081/288] Update docs --- CHANGELOG.rst | 1 - .../ffd/software_desc/bsp_config.rst | 6 +-- .../ffd/software_desc/ext.rst | 23 ----------- .../ffd/software_desc/filesystem_support.rst | 6 +-- .../ffd/software_desc/intent_engine.rst | 14 +++---- .../ffd/software_desc/intent_handler.rst | 12 +++--- .../ffd/software_desc/overview.rst | 10 ++--- .../ffd/software_desc/src.rst | 6 +-- .../ffd/speech_recognition_cyberon.rst | 1 - .../ffva/deploying/linux_macos.rst | 10 +++++ .../ffva/software_desc/bsp_config.rst | 6 +-- .../ffva/software_desc/filesystem_support.rst | 6 +-- .../ffva/software_desc/overview.rst | 39 ++++++++++++------- .../ffva/software_desc/src.rst | 6 +-- 14 files changed, 72 insertions(+), 74 deletions(-) delete mode 100644 doc/programming_guide/ffd/software_desc/ext.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f966fbd6..444d224a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,6 @@ XCORE-VOICE change log * CHANGED: Remove need to use external MCLK in FFVA INT examples * ADDED: lib_sw_pll submodule v1.1.0. - 2.2.0 ----- diff --git a/doc/programming_guide/ffd/software_desc/bsp_config.rst b/doc/programming_guide/ffd/software_desc/bsp_config.rst index 4b9df673..ae051811 100644 --- a/doc/programming_guide/ffd/software_desc/bsp_config.rst +++ b/doc/programming_guide/ffd/software_desc/bsp_config.rst @@ -1,8 +1,8 @@ .. _sln_voice_ffd_bsp_config: -########## -bsp_config -########## +####################### +examples/ffd/bsp_config +####################### This folder contains bsp_configs for the FFD application. More information on bsp_configs can be found in the RTOS Framework documentation. diff --git a/doc/programming_guide/ffd/software_desc/ext.rst b/doc/programming_guide/ffd/software_desc/ext.rst deleted file mode 100644 index adb0a494..00000000 --- a/doc/programming_guide/ffd/software_desc/ext.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. _sln_voice_ffd_ext: - -### -ext -### - -This folder contains FFD application debug and experimental extensions. - -.. list-table:: FFD ext - :widths: 30 50 - :header-rows: 1 - :align: left - - * - Filename/Directory - - Description - * - src directory - - custom code for USB output and debug - * - ffd_dev.cmake - - cmake for declaring FFD experimental configs - * - ffd_ext.cmake - - cmake for declaring FFD extensions - * - ffd_usb_audio_testing.cmake - - cmake for declaring FFD usb debug extension diff --git a/doc/programming_guide/ffd/software_desc/filesystem_support.rst b/doc/programming_guide/ffd/software_desc/filesystem_support.rst index f4e842d9..1633c19f 100644 --- a/doc/programming_guide/ffd/software_desc/filesystem_support.rst +++ b/doc/programming_guide/ffd/software_desc/filesystem_support.rst @@ -1,8 +1,8 @@ .. _sln_voice_ffd_filesystem_support: -################## -filesystem_support -################## +############################### +examples/ffd/filesystem_support +############################### This folder contains filesystem contents for the FFD application. diff --git a/doc/programming_guide/ffd/software_desc/intent_engine.rst b/doc/programming_guide/ffd/software_desc/intent_engine.rst index fd252d18..61d88e59 100644 --- a/doc/programming_guide/ffd/software_desc/intent_engine.rst +++ b/doc/programming_guide/ffd/software_desc/intent_engine.rst @@ -1,12 +1,12 @@ -.. _sln_voice_ffd_intent_engine: +.. _sln_voice_intent_engine: -################# -src/intent_engine -################# +######################### +modules/asr/intent_engine +######################### This folder contains the intent engine module for the FFD application. -.. list-table:: FFD Intent Engine +.. list-table:: ASR Intent Engine :widths: 30 50 :header-rows: 1 :align: left @@ -43,7 +43,7 @@ intent_engine_create This function has the role of creating the model running task and providing a pointer, which can be used by the application to handle the output intent result. In the case of the default configuration, the application provides a FreeRTOS Queue object. -In FFD, the audio pipeline output is on tile 1 and the ASR engine on tile 0. +The ASR engine is on tile 0 in both FFD and FFVA, but the audio pipeline output is on tile 1 for FFD and on tile 0 for FFVA. .. code-block:: c :caption: intent_engine_create snippet (intent_engine_io.c) @@ -80,7 +80,7 @@ intent_engine_sample_push This function has the role of sending the ASR output channel from the audio pipeline to the intent engine. -In FFD, the audio pipeline output is on tile 1 and the ASR engine on tile 0. +The ASR engine is on tile 0 in both FFD and FFVA, but the audio pipeline output is on tile 1 for FFD and on tile 0 for FFVA. .. code-block:: c :caption: intent_engine_create snippet (intent_engine_io.c) diff --git a/doc/programming_guide/ffd/software_desc/intent_handler.rst b/doc/programming_guide/ffd/software_desc/intent_handler.rst index a5a8704d..4a2d5e02 100644 --- a/doc/programming_guide/ffd/software_desc/intent_handler.rst +++ b/doc/programming_guide/ffd/software_desc/intent_handler.rst @@ -1,12 +1,12 @@ -.. _sln_voice_ffd_intent_handler: +.. _sln_voice_intent_handler: -################## -src/intent_handler -################## +########################## +modules/asr/intent_handler +########################## -This folder contains ASR output handling modules for the FFD application. +This folder contains ASR output handling modules for the FFD and FFVA applications. -.. list-table:: FFD Intent handler +.. list-table:: ASR Intent handler :widths: 30 50 :header-rows: 1 :align: left diff --git a/doc/programming_guide/ffd/software_desc/overview.rst b/doc/programming_guide/ffd/software_desc/overview.rst index 1eb153e9..69fe3421 100644 --- a/doc/programming_guide/ffd/software_desc/overview.rst +++ b/doc/programming_guide/ffd/software_desc/overview.rst @@ -83,7 +83,7 @@ The estimated power usage of the example application varies from 100-141 mW. Thi - 37.260 Note that these are typical usage statistics for a representative run of the application on hardware. Core allocations may shift run-to-run in a scheduled RTOS. -These statistics are generated by slicing the representative run into 10 ms chunks and calculating % time per chunk not spent in the FreeRTOS IDLE tasks. +These statistics are generated by slicing the representative run into 10 ms chunks and calculating % time per chunk not spent in the FreeRTOS IDLE tasks. Therefore, the underlying distribution of these 10 ms bins should not be assumed to be Normal; this has implications on e.g. the interpretation of the Standard Deviation given here. .. list-table:: FFD Power Usage @@ -98,6 +98,8 @@ Therefore, the underlying distribution of these 10 ms bins should not be assumed The description of the software is split up by folder: +.. _sln_voice_ffd_software_descr_table: + .. list-table:: FFD Software Description :widths: 40 120 :header-rows: 1 @@ -107,13 +109,11 @@ The description of the software is split up by folder: - Description * - :ref:`sln_voice_ffd_bsp_config` - Board support configuration setting up software based IO peripherals - * - :ref:`sln_voice_ffd_ext` - - Application extensions * - :ref:`sln_voice_ffd_filesystem_support` - Filesystem contents for application * - :ref:`sln_voice_ffd_src` - Main application - * - :ref:`sln_voice_ffd_intent_engine` + * - :ref:`sln_voice_intent_engine` - Intent engine integration - * - :ref:`sln_voice_ffd_intent_handler` + * - :ref:`sln_voice_intent_handler` - Intent engine output integration diff --git a/doc/programming_guide/ffd/software_desc/src.rst b/doc/programming_guide/ffd/software_desc/src.rst index 2d887ba3..08dbd9d6 100644 --- a/doc/programming_guide/ffd/software_desc/src.rst +++ b/doc/programming_guide/ffd/software_desc/src.rst @@ -1,8 +1,8 @@ .. _sln_voice_ffd_src: -### -src -### +################ +examples/ffd/src +################ This folder contains the core application source. diff --git a/doc/programming_guide/ffd/speech_recognition_cyberon.rst b/doc/programming_guide/ffd/speech_recognition_cyberon.rst index c58c06f4..191f1361 100644 --- a/doc/programming_guide/ffd/speech_recognition_cyberon.rst +++ b/doc/programming_guide/ffd/speech_recognition_cyberon.rst @@ -1,5 +1,4 @@ .. include:: -.. include:: ../../substitutions.rst .. _sln_voice_ffd_speech_recognition_cyberon: diff --git a/doc/programming_guide/ffva/deploying/linux_macos.rst b/doc/programming_guide/ffva/deploying/linux_macos.rst index 117121bf..c919be42 100644 --- a/doc/programming_guide/ffva/deploying/linux_macos.rst +++ b/doc/programming_guide/ffva/deploying/linux_macos.rst @@ -35,6 +35,14 @@ Run the following commands in the root folder to build the |I2S| firmware: cd build make example_ffva_int_fixed_delay +Run the following commands in the root folder to build the |I2S| firmware with the Cyberon ASR engine: + +.. code-block:: console + + cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + cd build + make example_ffva_int_cyberon_fixed_delay + Run the following commands in the root folder to build the USB firmware: .. code-block:: console @@ -53,6 +61,7 @@ Inside of the build folder root, after building the firmware, run one of: .. code-block:: console make flash_app_example_ffva_int_fixed_delay + make flash_app_example_ffva_int_cyberon_fixed_delay make flash_app_example_ffva_ua_adec_altarch Once flashed, the application will run. @@ -64,6 +73,7 @@ From the build folder run: .. code-block:: console xrun --xscope example_ffva_int_fixed_delay.xe + xrun --xscope example_ffva_int_cyberon_fixed_delay.xe xrun --xscope example_ffva_ua_adec_altarch.xe Upgrading the Firmware diff --git a/doc/programming_guide/ffva/software_desc/bsp_config.rst b/doc/programming_guide/ffva/software_desc/bsp_config.rst index ff5da13d..ee179b7d 100644 --- a/doc/programming_guide/ffva/software_desc/bsp_config.rst +++ b/doc/programming_guide/ffva/software_desc/bsp_config.rst @@ -1,8 +1,8 @@ .. _sln_voice_ffva_bsp_config: -########## -bsp_config -########## +######################## +examples/ffva/bsp_config +######################## This folder contains bsp_configs for the FFVA application. More information on bsp_configs can be found in the RTOS Framework documentation. diff --git a/doc/programming_guide/ffva/software_desc/filesystem_support.rst b/doc/programming_guide/ffva/software_desc/filesystem_support.rst index 029e1920..a08838e0 100644 --- a/doc/programming_guide/ffva/software_desc/filesystem_support.rst +++ b/doc/programming_guide/ffva/software_desc/filesystem_support.rst @@ -1,8 +1,8 @@ .. _sln_voice_ffva_filesystem_support: -################## -filesystem_support -################## +################################ +examples/ffva/filesystem_support +################################ This folder contains filesystem contents for the FFVA application. diff --git a/doc/programming_guide/ffva/software_desc/overview.rst b/doc/programming_guide/ffva/software_desc/overview.rst index 802b4b96..e0796815 100644 --- a/doc/programming_guide/ffva/software_desc/overview.rst +++ b/doc/programming_guide/ffva/software_desc/overview.rst @@ -4,7 +4,7 @@ Overview ******** -There are two main build configurations for this application. +There are three main build configurations for this application. .. list-table:: FFVA INT Fixed Delay Resources :widths: 30 10 30 @@ -14,15 +14,27 @@ There are two main build configurations for this application. * - Resource - Tile 0 - Tile 1 - * - Unused CPU Time (600 MHz) - - 98% - - 75% * - Total Memory Free - - 166k - - 82k + - 141k + - 80k * - Runtime Heap Memory Free - 75k - - 82k + - 76k + +.. list-table:: FFVA INT Cyberon Fixed Delay Resources + :widths: 30 10 30 + :header-rows: 1 + :align: left + + * - Resource + - Tile 0 + - Tile 1 + * - Total Memory Free + - 21k + - 79k + * - Runtime Heap Memory Free + - 19k + - 81k .. list-table:: FFVA UA ADEC Resources :widths: 30 10 30 @@ -32,12 +44,9 @@ There are two main build configurations for this application. * - Resource - Tile 0 - Tile 1 - * - Unused CPU Time (600 MHz) - - 83% - - 45% * - Total Memory Free - - 123k - - 58k + - 94k + - 59k * - Runtime Heap Memory Free - 54k - 83k @@ -58,4 +67,8 @@ The description of the software is split up by folder: * - :ref:`sln_voice_ffva_filesystem_support` - Filesystem contents for application * - :ref:`sln_voice_ffva_src` - - Main application \ No newline at end of file + - Main application + * - :ref:`sln_voice_intent_engine` + - Intent engine integration (FFVA INT Cyberon only) + * - :ref:`sln_voice_intent_handler` + - Intent engine output integration (FFVA INT Cyberon only) \ No newline at end of file diff --git a/doc/programming_guide/ffva/software_desc/src.rst b/doc/programming_guide/ffva/software_desc/src.rst index f8a99e69..f23728e2 100644 --- a/doc/programming_guide/ffva/software_desc/src.rst +++ b/doc/programming_guide/ffva/software_desc/src.rst @@ -1,9 +1,9 @@ .. _sln_voice_ffva_src: -### -src -### +################# +examples/ffva/src +################# This folder contains the core application source. From c462851328cb26b954c0dc426cd5bedda24d4557 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 10:05:30 +0000 Subject: [PATCH 082/288] Update docs --- .../ffd/software_desc/intent_engine.rst | 2 +- .../ffd/software_desc/overview.rst | 2 - doc/programming_guide/ffva/design.rst | 44 ++++--------------- .../ffva/software_modifications.rst | 41 ++--------------- examples/ffva/src/app_conf.h | 3 -- 5 files changed, 12 insertions(+), 80 deletions(-) diff --git a/doc/programming_guide/ffd/software_desc/intent_engine.rst b/doc/programming_guide/ffd/software_desc/intent_engine.rst index 61d88e59..0c29389e 100644 --- a/doc/programming_guide/ffd/software_desc/intent_engine.rst +++ b/doc/programming_guide/ffd/software_desc/intent_engine.rst @@ -4,7 +4,7 @@ modules/asr/intent_engine ######################### -This folder contains the intent engine module for the FFD application. +This folder contains the intent engine module for the and FFVA application. .. list-table:: ASR Intent Engine :widths: 30 50 diff --git a/doc/programming_guide/ffd/software_desc/overview.rst b/doc/programming_guide/ffd/software_desc/overview.rst index 69fe3421..65ae4af4 100644 --- a/doc/programming_guide/ffd/software_desc/overview.rst +++ b/doc/programming_guide/ffd/software_desc/overview.rst @@ -98,8 +98,6 @@ Therefore, the underlying distribution of these 10 ms bins should not be assumed The description of the software is split up by folder: -.. _sln_voice_ffd_software_descr_table: - .. list-table:: FFD Software Description :widths: 40 120 :header-rows: 1 diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index bab10acd..97e2fb40 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -16,46 +16,18 @@ INT requires |I2S| connections to the host. Refer to the schematic, connecting UA requires a USB connection to the host. -Single Controller Solution -========================== - -In a single controller solution, a user can populate the model runner manager task with the application specific code. - -This dummy thread receives only the ASR channel output, which has been downshifted to 16 bits. - -The user must ensure the streambuffer is emptied at the rate of the audio pipeline at minimum, otherwise samples will be lost. - -Populate: - -.. code-block:: c - :caption: Model Runner Dummy (model_runner.c) - - void model_runner_manager(void *args) - { - StreamBufferHandle_t input_queue = (StreamBufferHandle_t)args; +Support for ASR engine +====================== - int16_t buf[appconfWW_FRAMES_PER_INFERENCE]; +The ``example_ffva_int_cyberon_fixed_delay`` provides an example about how to include an ASR engine, the Cyberon DSPotter™. - /* Perform any initialization here */ +All the considerations made in the section about FFD are still valid for the FFVA example. The only notable difference is that the pipeline output in the FFVA example +is on the same tile as the ASR engine, i.e. tile 0. +.. note:: - while (1) - { - /* Receive audio frames */ - uint8_t *buf_ptr = (uint8_t*)buf; - size_t buf_len = appconfWW_FRAMES_PER_INFERENCE * sizeof(int16_t); - do { - size_t bytes_rxed = xStreamBufferReceive(input_queue, - buf_ptr, - buf_len, - portMAX_DELAY); - buf_len -= bytes_rxed; - buf_ptr += bytes_rxed; - } while(buf_len > 0); + Both the audio pipeline and the ASR engine process use the same sample block length. ``appconfINTENT_SAMPLE_BLOCK_LENGTH`` and ``appconfAUDIO_PIPELINE_FRAME_ADVANCE`` are both 240. - /* Perform inference here */ - // rtos_printf("inference\n"); - } - } +More information about the Cyberon engine can be found in `the Speech Recognition - Cyberon `_ section. |newpage| diff --git a/doc/programming_guide/ffva/software_modifications.rst b/doc/programming_guide/ffva/software_modifications.rst index 28b87019..a9341cfe 100644 --- a/doc/programming_guide/ffva/software_modifications.rst +++ b/doc/programming_guide/ffva/software_modifications.rst @@ -144,45 +144,10 @@ With: It is also possible to add or remove stages. Refer to the RTOS Framework documentation on the generic pipeline sw_service. -Populating a Keyword Engine Block -------------------------------------- - -To add a keyword engine block, a user may populate the existing ``model_runner_manager()`` function with their model: - -.. code-block:: c - :caption: Model Runner (model_runner.c) - - configSTACK_DEPTH_TYPE model_runner_manager_stack_size = 287; - - void model_runner_manager(void *args) - { - StreamBufferHandle_t input_queue = (StreamBufferHandle_t)args; - - int16_t buf[appconfWW_FRAMES_PER_INFERENCE]; - - /* Perform any initialization here */ - - while (1) - { - /* Receive audio frames */ - uint8_t *buf_ptr = (uint8_t*)buf; - size_t buf_len = appconfWW_FRAMES_PER_INFERENCE * sizeof(int16_t); - do { - size_t bytes_rxed = xStreamBufferReceive(input_queue, - buf_ptr, - buf_len, - portMAX_DELAY); - buf_len -= bytes_rxed; - buf_ptr += bytes_rxed; - } while(buf_len > 0); - - /* Perform inference here */ - // rtos_printf("inference\n"); - } - } - -Populate initialization and inference engine calls where commented. After adding user code, the stack size of the task will need to be adjusted accordingly based on the engine being used. The input streambuffer must be emptied at least at the rate of the audio pipeline otherwise frames will be lost. +Changing the ASR engine +----------------------- +THE FFVA provides an example with a specific ASR engine. A different ASR engine can be used by updating and adding the necessary files in ``modules\asr``. Replacing Example Design Interfaces ----------------------------------- diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index afecdf8f..9f8be19d 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -188,9 +188,6 @@ #include "app_conf_check.h" -/* WW Config */ -#define appconfWW_FRAMES_PER_INFERENCE (240) - /* I/O and interrupt cores for Tile 0 */ /* Note, USB and SPI are mutually exclusive */ #define appconfXUD_IO_CORE 1 /* Must be kept off core 0 with the RTOS tick ISR */ From 505690ed897374ea60064d31f224992cbeebae77 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 10:14:02 +0000 Subject: [PATCH 083/288] Fix link --- doc/programming_guide/ffva/design.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index 97e2fb40..24316768 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -27,7 +27,7 @@ is on the same tile as the ASR engine, i.e. tile 0. Both the audio pipeline and the ASR engine process use the same sample block length. ``appconfINTENT_SAMPLE_BLOCK_LENGTH`` and ``appconfAUDIO_PIPELINE_FRAME_ADVANCE`` are both 240. -More information about the Cyberon engine can be found in `the Speech Recognition - Cyberon `_ section. +More information about the Cyberon engine can be found in :ref:`the Speech Recognition - Cyberon `_ section. |newpage| From 77a19b77f66d68488ea2372d80d7b531b38ca837 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 10:37:38 +0000 Subject: [PATCH 084/288] Update info for Windows --- doc/programming_guide/ffva/deploying/native_windows.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/programming_guide/ffva/deploying/native_windows.rst b/doc/programming_guide/ffva/deploying/native_windows.rst index b297a418..2f1f5807 100644 --- a/doc/programming_guide/ffva/deploying/native_windows.rst +++ b/doc/programming_guide/ffva/deploying/native_windows.rst @@ -55,6 +55,13 @@ Run the following commands in the root folder to build the |I2S| firmware: cd build ninja example_ffva_int_fixed_delay +Run the following commands in the root folder to build the |I2S| firmware with the Cyberon ASR engine: + +.. code-block:: console + + cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + cd build + ninja example_ffva_int_cyberon_fixed_delay Run the following commands in the root folder to build the USB firmware: @@ -74,6 +81,7 @@ Inside of the build folder root, after building the firmware, run one of: .. code-block:: console ninja flash_app_example_ffva_int_fixed_delay + ninja flash_app_example_ffva_int_cyberon_fixed_delay ninja flash_app_example_ffva_ua_adec_altarch Once flashed, the application will run. @@ -85,6 +93,7 @@ From the build folder run: .. code-block:: console xrun --xscope example_ffva_int_fixed_delay.xe + xrun --xscope example_ffva_int_cyberon_fixed_delay.xe xrun --xscope example_ffva_ua_adec_altarch.xe Upgrading the Firmware From fbea1ec43cc362a0283684b2ed3ee2673c9d7286 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 12:12:22 +0000 Subject: [PATCH 085/288] Add link --- doc/programming_guide/ffva/design.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index 24316768..354a6448 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -21,13 +21,14 @@ Support for ASR engine The ``example_ffva_int_cyberon_fixed_delay`` provides an example about how to include an ASR engine, the Cyberon DSPotter™. -All the considerations made in the section about FFD are still valid for the FFVA example. The only notable difference is that the pipeline output in the FFVA example +All the considerations made in the :ref:`section about the FFD devices `_ are still valid for the FFVA example. The only notable difference is that the pipeline output in the FFVA example is on the same tile as the ASR engine, i.e. tile 0. + .. note:: Both the audio pipeline and the ASR engine process use the same sample block length. ``appconfINTENT_SAMPLE_BLOCK_LENGTH`` and ``appconfAUDIO_PIPELINE_FRAME_ADVANCE`` are both 240. -More information about the Cyberon engine can be found in :ref:`the Speech Recognition - Cyberon `_ section. +More information about the Cyberon engine can be found in :ref:`sln_voice_ffd_speech_recognition_cyberon` section. |newpage| From 75616218e1d650755725ae1e142c7161216ca8a6 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 12:12:53 +0000 Subject: [PATCH 086/288] Add link --- doc/programming_guide/ffva/design.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index 354a6448..529c7b72 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -21,7 +21,7 @@ Support for ASR engine The ``example_ffva_int_cyberon_fixed_delay`` provides an example about how to include an ASR engine, the Cyberon DSPotter™. -All the considerations made in the :ref:`section about the FFD devices `_ are still valid for the FFVA example. The only notable difference is that the pipeline output in the FFVA example +Most of the considerations made in the :ref:`section about the FFD devices `_ are still valid for the FFVA example. The only notable difference is that the pipeline output in the FFVA example is on the same tile as the ASR engine, i.e. tile 0. .. note:: From bc56ddf60478df4f4542c2afa4ed462768354978 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 12:26:35 +0000 Subject: [PATCH 087/288] Fix link --- doc/programming_guide/ffva/design.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index 529c7b72..a332c1f7 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -21,7 +21,7 @@ Support for ASR engine The ``example_ffva_int_cyberon_fixed_delay`` provides an example about how to include an ASR engine, the Cyberon DSPotter™. -Most of the considerations made in the :ref:`section about the FFD devices `_ are still valid for the FFVA example. The only notable difference is that the pipeline output in the FFVA example +Most of the considerations made in the :ref:`section about the FFD devices` are still valid for the FFVA example. The only notable difference is that the pipeline output in the FFVA example is on the same tile as the ASR engine, i.e. tile 0. .. note:: From a13fb103404ec61b98e49c3acb5cdf70d14cdc81 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 13:29:02 +0000 Subject: [PATCH 088/288] Add info --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 444d224a..555b22ff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ XCORE-VOICE change log * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). + * CHANGED: Moved files in folders device_memory, gpio_ctrl, intent_engine and + intent_handler from examples/ffd/src to folder modules/asr. * CHANGED: Remove need to use external MCLK in FFVA INT examples * ADDED: lib_sw_pll submodule v1.1.0. From 9bfc6ce9edc122ae0a708882fcf88303b80711ed Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 13:59:15 +0000 Subject: [PATCH 089/288] Update MCLK info --- doc/programming_guide/howto.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/programming_guide/howto.rst b/doc/programming_guide/howto.rst index 40be9326..4bd33349 100644 --- a/doc/programming_guide/howto.rst +++ b/doc/programming_guide/howto.rst @@ -15,7 +15,7 @@ In the example design ``app_conf.h`` file, change ``appconfAUDIO_PIPELINE_SAMPLE |I2S| AEC reference input audio & USB processed audio output ************************************************************ -The FFVA example design includes 2 basic configurations; INT and UA. The INT configuration is setup with |I2S| for input and output audio. The UA configuration is setup with USB for input and output audio. This HOWTO explains how to modify the FFVA example design for |I2S| input audio and USB output audio. +The FFVA example design includes 2 basic configurations; INT and UA. The INT configuration is setup with |I2S| for input and output audio. The UA configuration is setup with USB for input and output audio. This HOWTO explains how to modify the FFVA example design for |I2S| input audio and USB output audio. In the ``ffva_ua.cmake`` file, changing the ``appconfAEC_REF_DEFAULT`` to ``appconfAEC_REF_I2S`` will result in the expected input frames. @@ -31,7 +31,7 @@ In the ``ffva_ua.cmake`` file, changing the ``appconfAEC_REF_DEFAULT`` to ``appc MIC_ARRAY_CONFIG_MCLK_FREQ=24576000 ) -For integrating with |I2S| there are a few other differences from the default UA configuration. FFVA was designed to be integrated with an Raspberry Pi for an AVS demo. And, due to that the INT config uses an externally generated ``MCLK``. When integrating with an external Raspberry Pi ``MCLK``, you will want the following ``FFVA_UA_COMPILE_DEFINITIONS``: +For integrating with |I2S| there are a few other differences from the default UA configuration. When integrating with an external Raspberry Pi ``BCLK`` and ``LRCLK``, you will want the following ``FFVA_UA_COMPILE_DEFINITIONS``: .. code-block:: cmake @@ -43,16 +43,16 @@ For integrating with |I2S| there are a few other differences from the default UA appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S appconfI2S_MODE=appconfI2S_MODE_SLAVE - appconfEXTERNAL_MCLK=1 + appconfEXTERNAL_MCLK=0 appconfI2S_AUDIO_SAMPLE_RATE=48000 MIC_ARRAY_CONFIG_MCLK_FREQ=12288000 ) ``appconfI2S_AUDIO_SAMPLE_RATE`` can also be 16000. Only 48k and 16k conversions is supported in FFVA. -If you enable ``appconfEXTERNAL_MCLK``, the FFVA example application will sit at initialization until we can lock on to that clock source, so it MUST be active during boot. +The default FFVA INT device doesn't require an external ``MCLK``, but this setting can be changed by setting ``appconfEXTERNAL_MCLK=1``. In this case the FFVA example application will sit at initialization until it can lock on to that clock source, so it MUST be active during boot. -Since the FFVA example application is not receiving reference audio through USB in this configuration, USB adaptive mode will not adapt to the input. By default, ffva will output the configured nominal rate. +Since the FFVA example application is not receiving reference audio through USB in this configuration, USB adaptive mode will not adapt to the input. By default, FFVA will output the configured nominal rate. If you enable ``appconfAEC_REF_DEFAULT=appconfAEC_REF_I2S`` and ``appconfI2S_MODE=appconfI2S_MODE_MASTER``. You need to invert ``I2S_DATA_IN`` and ``I2S_MIC_DATA`` in the ``bsp_config/XK_VOICE_L71/XK_VOICE_L71.xn`` file to have the reference audio play properly. From a00daa7bb0272e6a191dc68812c571b37300cc6f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 15 Mar 2024 16:13:26 +0000 Subject: [PATCH 090/288] Move DFU functions to dfu_common --- examples/asrc_demo/src/usb/usb_dfu.c | 12 +- examples/ffva/ffva.cmake | 1 + examples/ffva/src/dfu/dfu_common.c | 161 ++++ examples/ffva/src/dfu/dfu_common.h | 70 ++ examples/ffva/src/dfu/dfu_state_machine.c | 868 ++++++++++++++++++++++ examples/ffva/src/dfu/dfu_state_machine.h | 244 ++++++ examples/ffva/src/usb/usb_dfu.c | 143 +--- 7 files changed, 1359 insertions(+), 140 deletions(-) create mode 100644 examples/ffva/src/dfu/dfu_common.c create mode 100644 examples/ffva/src/dfu/dfu_common.h create mode 100644 examples/ffva/src/dfu/dfu_state_machine.c create mode 100644 examples/ffva/src/dfu/dfu_state_machine.h diff --git a/examples/asrc_demo/src/usb/usb_dfu.c b/examples/asrc_demo/src/usb/usb_dfu.c index b9c140aa..c442ba6a 100644 --- a/examples/asrc_demo/src/usb/usb_dfu.c +++ b/examples/asrc_demo/src/usb/usb_dfu.c @@ -80,14 +80,14 @@ void tud_dfu_download_cb(uint8_t alt, uint16_t block_num, uint8_t const* data, u if (dn_base_addr == 0) { total_len = 0; dn_base_addr = rtos_dfu_image_get_upgrade_addr(dfu_image_ctx); - bytes_avail = data_partition_base_addr - dn_base_addr; + bytes_avail = data_partition_base_addr - dn_base_addr; } /* fallthrough */ case 2: if (dn_base_addr == 0) { total_len = 0; dn_base_addr = data_partition_base_addr; - bytes_avail = rtos_qspi_flash_size_get(qspi_flash_ctx) - dn_base_addr; + bytes_avail = rtos_qspi_flash_size_get(qspi_flash_ctx) - dn_base_addr; } rtos_printf("Using addr 0x%x\nsize %u\n", dn_base_addr, bytes_avail); if(length > 0) { @@ -209,11 +209,3 @@ void tud_dfu_detach_cb(void) rtos_printf("Host detach, we should probably reboot\n"); reboot(); } - -static void reboot(void) -{ - rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 0x10000); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); - while(1) {;} -} diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index 477a197b..20a9e60a 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -7,6 +7,7 @@ file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/usb + ${CMAKE_CURRENT_LIST_DIR}/src/dfu ) include(${CMAKE_CURRENT_LIST_DIR}/bsp_config/bsp_config.cmake) diff --git a/examples/ffva/src/dfu/dfu_common.c b/examples/ffva/src/dfu/dfu_common.c new file mode 100644 index 00000000..8ed20d56 --- /dev/null +++ b/examples/ffva/src/dfu/dfu_common.c @@ -0,0 +1,161 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +#define DEBUG_UNIT DFU_COMMON +#ifndef DEBUG_PRINT_ENABLE_DFU_COMMON +#define DEBUG_PRINT_ENABLE_DFU_COMMON 0 +#endif +#include "debug_print.h" + +#include +#include +#include "quadflashlib.h" + +#include "dfu_common.h" +#include "rtos_dfu_image.h" +#include "rtos_qspi_flash.h" +#include "platform/driver_instances.h" + +static size_t bytes_avail = 0; +static uint32_t dn_base_addr = 0; +static size_t total_len = 0; + +uint32_t dfu_common_write_to_flash(uint8_t alt, + uint16_t block_num, + uint8_t const *data, + uint16_t length) +{ + rtos_printf("Received Alt %d BlockNum %d of length %d\n", alt, block_num, length); + uint32_t return_value = 0; + + unsigned data_partition_base_addr = rtos_dfu_image_get_data_partition_addr(dfu_image_ctx); + switch(alt) { + default: + case 0: + return_value = 3; //DFU_STATUS_ERR_WRITE + break; + case 1: + if (dn_base_addr == 0) { + total_len = 0; + dn_base_addr = rtos_dfu_image_get_upgrade_addr(dfu_image_ctx); + bytes_avail = data_partition_base_addr - dn_base_addr; + } + /* fallthrough */ + case 2: + if (dn_base_addr == 0) { + total_len = 0; + dn_base_addr = data_partition_base_addr; + bytes_avail = rtos_qspi_flash_size_get(qspi_flash_ctx) - dn_base_addr; + } + rtos_printf("Using addr 0x%x\nsize %u\n", dn_base_addr, bytes_avail); + if(length > 0) { + unsigned cur_addr = dn_base_addr + (block_num * length); + if((bytes_avail - total_len) >= length) { + rtos_printf("write %d at 0x%x\n", length, cur_addr); + + size_t sector_size = rtos_qspi_flash_sector_size_get(qspi_flash_ctx); + xassert(length == sector_size); + + uint8_t *tmp_buf = rtos_osal_malloc( sizeof(uint8_t) * sector_size); + rtos_qspi_flash_lock(qspi_flash_ctx); + { + rtos_qspi_flash_read( + qspi_flash_ctx, + tmp_buf, + cur_addr, + sector_size); + memcpy(tmp_buf, data, length); + rtos_qspi_flash_erase( + qspi_flash_ctx, + cur_addr, + sector_size); + rtos_qspi_flash_write( + qspi_flash_ctx, + (uint8_t *) tmp_buf, + cur_addr, + sector_size); + } + rtos_qspi_flash_unlock(qspi_flash_ctx); + rtos_osal_free(tmp_buf); + total_len += length; + } else { + rtos_printf("Insufficient space\n"); + return_value = 8; //DFU_STATUS_ERR_ADDRESS; + } + } + + return_value = 0; // DFU_STATUS_OK; + break; + } + + return return_value; +} + +uint32_t dfu_common_make_manifest() +{ + debug_printf("Download completed, enter manifestation\n"); + + /* Perform a read to ensure all writes have been flushed */ + uint32_t dummy = 0; + rtos_qspi_flash_read( + qspi_flash_ctx, + (uint8_t *)&dummy, + 0, + sizeof(dummy)); + + /* Reset download */ + dn_base_addr = 0; + + // flashing op for manifest is complete without error + // Application can perform checksum. + // Should it fail, return appropriate status such as errVERIFY. + return 0; // DFU_STATUS_OK +} + +uint16_t dfu_common_read_from_flash(uint8_t alt, + uint16_t block_num, + uint8_t *data, + uint16_t length) +{ + uint32_t endaddr = 0; + uint16_t retval = 0; + uint32_t addr = block_num * length; + + rtos_printf("Upload Alt %d BlockNum %d of length %d\n", alt, block_num, length); + + switch(alt) { + default: + break; + case 0: + if (rtos_dfu_image_get_factory_size(dfu_image_ctx) > 0) { + addr += rtos_dfu_image_get_factory_addr(dfu_image_ctx); + endaddr = rtos_dfu_image_get_factory_addr(dfu_image_ctx) + rtos_dfu_image_get_factory_size(dfu_image_ctx); + } + break; + case 1: + if (rtos_dfu_image_get_upgrade_size(dfu_image_ctx) > 0) { + addr += rtos_dfu_image_get_upgrade_addr(dfu_image_ctx); + endaddr = rtos_dfu_image_get_upgrade_addr(dfu_image_ctx) + rtos_dfu_image_get_upgrade_size(dfu_image_ctx); + } + break; + case 2: + if ((rtos_qspi_flash_size_get(qspi_flash_ctx) - rtos_dfu_image_get_data_partition_addr(dfu_image_ctx)) > 0) { + addr += rtos_dfu_image_get_data_partition_addr(dfu_image_ctx); + endaddr = rtos_qspi_flash_size_get(qspi_flash_ctx); /* End of flash */ + } + break; + } + + if (addr < endaddr) { + rtos_qspi_flash_read(qspi_flash_ctx, data, addr, length); + retval = length; + } + return retval; +} + +void reboot(void) +{ + rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 0x10000); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); + while(1) {;} +} \ No newline at end of file diff --git a/examples/ffva/src/dfu/dfu_common.h b/examples/ffva/src/dfu/dfu_common.h new file mode 100644 index 00000000..3273d3fe --- /dev/null +++ b/examples/ffva/src/dfu/dfu_common.h @@ -0,0 +1,70 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. + +#include + +// Define the delay to wait before rebooting the device after a successful download +#define DFU_REBOOT_DELAY_MS 100 +// Define the timeout for the download operation for UA +#define DOWNLOAD_TIMEOUT_MS 10 + +// Define variable timeouts for INT to try to bring execution time down +#define DOWNLOAD_TIMEOUT_ERASE_MS 10 +#define DOWNLOAD_TIMEOUT_WRITE_MS 3 +#define DOWNLOAD_TIMEOUT_BUFFER_MS 1 + +/** + * \brief Handle a DFU request to write some data to the flash memory. + * + * This function will write \p length bytes of \p data + * to the flash memory. The correct memory partition is selected based on + * the value of \p alt. The data is written to the flash memory at the + * address specified by \p block_num. + * + * \param[in] alt Interface to identify the memory partition to write to. + * \param[in] block_num The block number used to calculate the address to write to. + * \param[in] data Buffer containing \p length valid bytes of data. + * \param[in] length The number of bytes present in \p data. + * + * \return 0 if the write operation was successful, a non-zero error value otherwise. + */ +uint32_t dfu_common_write_to_flash(uint8_t alt, + uint16_t block_num, + uint8_t const *data, + uint16_t length); + +/** + * \brief Handle a DFU request to perform a manifestation phase. + * + * This function will ensure that all the data to be written to the flash memory + * are flushed, and it resets the necessary variables to prepare for the next + * download operation. + * + * \return 0 if the write operation was successful, a non-zero error value otherwise. +*/ +uint32_t dfu_common_make_manifest(); + +/** + * \brief Handle a DFU request to read some data from the flash memory. + * + * This function will read \p length bytes of \p data + * from the flash memory. The correct memory partition is selected based on + * the value of \p alt. The data is read from the flash memory at the + * address specified by \p block_num. + * + * \param[in] alt Interface to identify the memory partition to read from. + * \param[in] block_num The block number used to calculate the address to read from. + * \param[in] data Buffer to store the data read from the memory. + * \param[in] length The number of bytes to copy to \p data. + * + * \return 0 if the write operation was successful, a non-zero error value otherwise. + */ +uint16_t dfu_common_read_from_flash(uint8_t alt, + uint16_t block_num, + uint8_t *data, + uint16_t length); + +/** + * \brief Reboot the device. + */ +void reboot(void); diff --git a/examples/ffva/src/dfu/dfu_state_machine.c b/examples/ffva/src/dfu/dfu_state_machine.c new file mode 100644 index 00000000..497b76f8 --- /dev/null +++ b/examples/ffva/src/dfu/dfu_state_machine.c @@ -0,0 +1,868 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +#define DEBUG_UNIT DFU_STATE_MACHINE +#ifndef DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE +#define DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE 0 +#endif +#include "debug_print.h" + +// Compiler includes +#include +#include +#include "xassert.h" +#include "xcore/hwtimer.h" + +// Application includes +#include "dfu_state_machine.h" +#include "dfu_common.h" + +// FreeRTOS includes +#include "FreeRTOS.h" +#include "rtos_osal.h" +#include "task.h" + +#define XCORE_MS_TO_TICKS(ms) (XS1_TIMER_KHZ * ms); + +#define CLEAR_ALL_BITS 0xFFFFFFFF +#define REQUEST_COUNTER_INDEX 1 + +// Each notification bit matches the value given to the command by Table 3.2 +// in USB-DFU v1.1 e.g. DNLOAD uses bit 1 since it has value 1. + +// DFU_DETACH is value 0, handled by servicer. +#define DFU_INT_TASK_BIT_DNLOAD 0b00000010 +#define DFU_INT_TASK_BIT_UPLOAD 0b00000100 +#define DFU_INT_TASK_BIT_GETSTATUS 0b00001000 +#define DFU_INT_TASK_BIT_CLRSTATUS 0b00010000 +// DFU_GETSTATE is value 5, handled by servicer. +#define DFU_INT_TASK_BIT_ABORT 0b00100000 +#define DFU_INT_TASK_BIT_SETALTERNATE 0b10000000 // not a DFU spec. command + +typedef struct dfu_int_dfu_data_t +{ + TaskHandle_t task_handle; + SemaphoreHandle_t upload_semaphore; + dfu_int_alt_setting_t alt_setting; + dfu_int_state_t current_state; + dfu_int_status_t current_status; + bool download_or_manifest_in_progress; + bool move_to_error; + dfu_int_status_t move_to_error_status; + uint16_t transfer_block; + uint16_t data_xfer_length; + uint16_t download_block_number; + uint8_t frag_number; + uint8_t dfu_data_buffer[DFU_PAGE_SIZE]; + uint32_t previous_timeout_ms; + uint32_t timeout_start; +} dfu_int_dfu_data_t; + +static dfu_int_dfu_data_t dfu_data; + +/* DFU INT functions. These are called by the DFU servicer, and run on its RTOS + * task and thread of control.*/ + +void dfu_int_detach() +{ + /* + * This request is not valid in any state. + * It is non-compliant to issue a DFU_DETACH request at any point + * other than in the appIDLE state. Since we do not implement that + * state, we should move to the dfuERROR state on receipt. However, + * we will allow the user to force a reboot using this command. It + * is preferred that the user use DFU_REBOOT for this purpose. Since + * use of this command always forces a reboot, which resets the DFU + * state machine, we do not bother pushing the state machine into + * the dfuERROR state. + * + * ┌───────┐ + * DFU_DETACH │ 2 │ + * Any state ─────────────REBOOT──►│dfuIDLE│ + * └───────┘ + * + */ + debug_printf("Detach\n"); + reboot_xvf3800(DFU_REBOOT_DELAY_MS); +} + +void dfu_int_download(uint16_t length, const uint8_t *download_data) +{ + debug_printf("Download %d bytes\n", length); + xassert(length <= DFU_DATA_XFER_SIZE); + + // frag_addr takes values [0, 128] + uint16_t frag_addr = dfu_data.frag_number * DFU_DATA_XFER_SIZE; + + // Buffer starts empty and is cleared by state machine on write + + dfu_data.data_xfer_length = length; + if (length > 0) + { + memcpy(&(dfu_data.dfu_data_buffer[frag_addr]), download_data, length); + } + // else ZLP, so end of download. Buffer is cleared by state machine on reset + + xTaskNotifyGiveIndexed(dfu_data.task_handle, REQUEST_COUNTER_INDEX); + xTaskNotify(dfu_data.task_handle, DFU_INT_TASK_BIT_DNLOAD, eSetBits); +} + +size_t dfu_int_upload(uint8_t *upload_buffer, size_t upload_buffer_length) +{ + debug_printf("Upload Start\n"); + // Immediately signal the state machine to populate the data buffer. + xTaskNotifyGiveIndexed(dfu_data.task_handle, REQUEST_COUNTER_INDEX); + xTaskNotify(dfu_data.task_handle, DFU_INT_TASK_BIT_UPLOAD, eSetBits); + // Now we wait for the state machine to populate the buffer and tell us. + xSemaphoreTake(dfu_data.upload_semaphore, RTOS_OSAL_WAIT_FOREVER); + // Eat the data. This will be padded to the length of dfu_data_buffer. + // Note - we are only using the first 64 bytes of the 256-wide data_buffer. + memcpy(upload_buffer, dfu_data.dfu_data_buffer, upload_buffer_length); + // And return the number of bytes that were actually read. + debug_printf("Upload %d bytes\n", dfu_data.data_xfer_length); + return dfu_data.data_xfer_length; +} + +void dfu_int_get_status(dfu_int_get_status_packet_t *get_status_packet) +{ + /* + * This request drives a reasonable amount of the state machine in + * download and manifestation. See the below diagram for state + * transitions. + * This function is quite verbose/explicit and that is intentional. + * Remember - this function runs on the servicer and tells the host what the + * state machine is going to do next. It doesn't actually drive the state + * machine, other than to handle the transitions to the error state by + * setting a relevant flag. The state machine handles the actual transitions + * and behaviours of those states. + * + * x + * ┌───┐ │ + * DFU_GETSTATUS│ │ ▼ + * ┌───┐ │ ┌─┴──────┐ + * DFU_GETSTATUS│ │ └►│ 10 │ + * │ ┌─┴────────────┐ │dfuERROR│ + * └►│ 9 │ └────────┘ + * │dfuUPLOAD-IDLE│ ▲ + * └──────────────┘ DFU_GETSTATUS│ + * │ + * ┌───┐ DFU_GETSTATUS ┌──────┐ │ + * DFU_GETSTATUS│ │ (block incomplete)│ ▼ │ + * │ ┌─┴─────┐ ┌────────────┴─┐ ┌────────┴┐ + * └►│ 2 │ │ 3 │ │ 4 │ + * │dfuIDLE│ │dfuDNLOAD-SYNC│ │dfuDNBUSY│ + * └───────┘ └──────┬───────┘ └─────────┘ + * ▲ │ + * ┌─────────┘ │ DFU_GETSTATUS + * │ DFU_GETSTATUS │(block complete) + * │(manifestation complete) ▼ + * ┌┴───────────────┐ ┌──────────────┐ + * │ 6 │ │ 5 │ + * │dfuMANIFEST-SYNC├─┐ │dfuDNLOAD-IDLE├─┐ + * └────────────────┘ │ └──────────────┘ │ + * │ ▲ │ + * ┌───────────┐ │ └──┘ + * │ 7 │◄┘ DFU_GETSTATUS + * │dfuMANIFEST│ DFU_GETSTATUS + * └─────────┬─┘ (manifestation incomplete) + * │ + * x──────────┘ + * DFU_GETSTATUS + * + */ + bool send_notification = true; + switch (dfu_data.current_state) + { + case DFU_INT_DFU_UPLOAD_IDLE: + case DFU_INT_DFU_IDLE: + case DFU_INT_DFU_ERROR: + case DFU_INT_DFU_DNLOAD_IDLE: + { + get_status_packet->next_state = dfu_data.current_state; + get_status_packet->current_status = dfu_data.current_status; + get_status_packet->timeout_ms = 0; + break; + } + case DFU_INT_DFU_DNBUSY: + case DFU_INT_DFU_MANIFEST: + { + /* + * If we are here, then we will have previously communicated a timeout + * to the host. Has the host respected that (in which case the elapsed + * time will be more than the previously sent timeout) or not? + */ + uint32_t current_time = get_reference_time(); + uint32_t timeout_tks = XCORE_MS_TO_TICKS(dfu_data.previous_timeout_ms); + uint32_t timeout_end = dfu_data.timeout_start + timeout_tks; + if ((timeout_end - current_time) < timeout_tks) + { + get_status_packet->next_state = DFU_INT_DFU_ERROR; + get_status_packet->current_status = dfu_data.current_status; + get_status_packet->timeout_ms = 0; + /* + * If we're in these states, then the state machine task is busy + * doing these things, and the host has bothered us too early - we + * should tell the state machine to make the transition to the error + * state. This is checked after it finishes being busy. This handles + * this transition without sending a notification - by the time the + * task reads the notification it will already be out of these + * states so will not behave the way we want it to. + */ + dfu_data.move_to_error = true; + dfu_data.move_to_error_status = DFU_INT_DFU_STATUS_ERR_STALLEDPKT; + } + else + { + /* + * The timeout has expired but we're still in these states, which + * means that we're still busy (and underestimated the time we'd + * need). Send the same timeout again, and tell the host we're going + * to remain in these states. (Very strictly, what the standard says + * we should be doing is moving back into the _SYNC states and then + * moving into the DNBUSY/MANIFEST states, but our replies to this + * will in theory look the same. The only way the host can tell the + * difference is if they issue a GETSTATE before a GETSTATUS, which + * they're not likely to do, and even then it'll just tell them + * we're still busy.) + */ + get_status_packet->next_state = dfu_data.current_state; + get_status_packet->current_status = dfu_data.current_status; + get_status_packet->timeout_ms = dfu_data.previous_timeout_ms; + } + send_notification = false; + break; + } + case DFU_INT_DFU_DNLOAD_SYNC: + { + if (dfu_data.download_or_manifest_in_progress) + { + get_status_packet->next_state = DFU_INT_DFU_DNBUSY; + get_status_packet->current_status = dfu_data.current_status; + if (dfu_data.frag_number == (DFU_NUM_FRAGMENTS - 1)) + { + if (dfu_data.download_block_number % 16 == 0) // SECTOR / PAGE + { + // On this download, we will be erasing a sector and writing + get_status_packet->timeout_ms = DOWNLOAD_TIMEOUT_ERASE_MS; + } + else + { + // On this download, we will be writing + get_status_packet->timeout_ms = DOWNLOAD_TIMEOUT_WRITE_MS; + } + } + else + { + // On this download, we will just be storing in a buffer + get_status_packet->timeout_ms = DOWNLOAD_TIMEOUT_BUFFER_MS; + } + } + else + { + get_status_packet->next_state = DFU_INT_DFU_DNLOAD_IDLE; + get_status_packet->current_status = dfu_data.current_status; + get_status_packet->timeout_ms = 0; + } + break; + } + case DFU_INT_DFU_MANIFEST_SYNC: + { + if (dfu_data.download_or_manifest_in_progress) + { + get_status_packet->next_state = DFU_INT_DFU_MANIFEST; + get_status_packet->current_status = dfu_data.current_status; + get_status_packet->timeout_ms = 0; + } + else + { + get_status_packet->next_state = DFU_INT_DFU_IDLE; + get_status_packet->current_status = dfu_data.current_status; + get_status_packet->timeout_ms = 0; + } + break; + } + default: + { + // We have no idea where we are, but we shouldn't be there. Panic. + get_status_packet->next_state = DFU_INT_DFU_ERROR; + get_status_packet->current_status = dfu_data.current_status; + get_status_packet->timeout_ms = 0; + break; + } + } + // Record the time just before we send the notification - latest we can set + dfu_data.previous_timeout_ms = get_status_packet->timeout_ms; + dfu_data.timeout_start = get_reference_time(); + if (send_notification) + { + xTaskNotifyGiveIndexed(dfu_data.task_handle, REQUEST_COUNTER_INDEX); + xTaskNotify(dfu_data.task_handle, DFU_INT_TASK_BIT_GETSTATUS, eSetBits); + } + debug_printf("Get Status: Status %d, Timeout %d, Time %d, Next State %d\n", + get_status_packet->current_status, + get_status_packet->timeout_ms, + dfu_data.timeout_start, + get_status_packet->next_state); +} + +void dfu_int_clear_status() +{ + debug_printf("Clear Status\n"); + xTaskNotifyGiveIndexed(dfu_data.task_handle, REQUEST_COUNTER_INDEX); + xTaskNotify(dfu_data.task_handle, DFU_INT_TASK_BIT_CLRSTATUS, eSetBits); +} + +dfu_int_state_t dfu_int_get_state() +{ + debug_printf("Get State: %d\n", dfu_data.current_state); + return dfu_data.current_state; +} + +void dfu_int_abort() +{ + debug_printf("Abort\n"); + xTaskNotifyGiveIndexed(dfu_data.task_handle, REQUEST_COUNTER_INDEX); + xTaskNotify(dfu_data.task_handle, DFU_INT_TASK_BIT_ABORT, eSetBits); +} + +void dfu_int_set_alternate(dfu_int_alt_setting_t alt) +{ + debug_printf("Set Alternate: %d\n", alt); + dfu_data.alt_setting = alt; + xTaskNotifyGiveIndexed(dfu_data.task_handle, REQUEST_COUNTER_INDEX); + xTaskNotify(dfu_data.task_handle, DFU_INT_TASK_BIT_SETALTERNATE, eSetBits); +} + +void dfu_int_set_transfer_block(uint16_t transferblock) +{ + debug_printf("Set Transfer Block: %d\n", transferblock); + dfu_data.transfer_block = transferblock; +} + +uint16_t dfu_int_get_transfer_block() +{ + debug_printf("Get Transfer Block: %d\n", dfu_data.transfer_block); + return dfu_data.transfer_block; +} + +/* Some readability functions */ + +static void dfu_int_reset_download_buffer() +{ + dfu_data.data_xfer_length = 0; + dfu_data.frag_number = 0; + dfu_data.download_block_number = 0; + memset(dfu_data.dfu_data_buffer, 0, DFU_PAGE_SIZE); +} + +static void dfu_int_reset_state() +{ + dfu_data.current_state = DFU_INT_DFU_IDLE; + dfu_data.current_status = DFU_INT_DFU_STATUS_OK; + dfu_data.download_or_manifest_in_progress = false; + dfu_data.move_to_error = false; + dfu_data.move_to_error_status = DFU_INT_DFU_STATUS_OK; + dfu_int_reset_download_buffer(); +} + +static void dfu_int_error(dfu_int_status_t status) +{ + dfu_int_reset_state(); + dfu_data.current_state = DFU_INT_DFU_ERROR; + dfu_data.current_status = status; +} + +/* + * State machine function. This is a separate RTOS task. + * Has three notification boxes: "what event has occured", "how many events have + * occurred", and "extra data". "Extra data" is currently unused in favour of + * the dfu_data structure being accessed directly by the servicer task, but if + * this ever gets ported to a system where they don't have shared memory (i.e. + * all RTOS notification calls are going via an intertile context) then this + * could be used for the TRANSFERBLOCK and SETALTERNATE commands etc. - but the + * implementor would still need to work out how to handle the 64 byte download + * packet! + */ +void dfu_int_state_machine(void *args) +{ + /* Initialise semaphore and timer */ + dfu_data.upload_semaphore = xSemaphoreCreateBinary(); // Sem.s init empty + /* Initialise the state machine */ + dfu_data.task_handle = xTaskGetCurrentTaskHandle(); + dfu_data.alt_setting = DFU_INT_ALTERNATE_FACTORY; + dfu_int_reset_state(); + + /* Sit in a loop and wait for mail */ + while (1) + { + uint32_t notification_value; + xTaskNotifyWait( + CLEAR_ALL_BITS, + CLEAR_ALL_BITS, + ¬ification_value, + RTOS_OSAL_WAIT_FOREVER); + + /* + * At this point, we've woken up from a notification. + * notification_value should have exactly 1 bit set, which corresponds + * to a specific request. Therefore, we can do a switch case. + * If multiple bits are set, then we've had multiple requests without + * the time to process them. Because we will have lost information about + * the order of these requests, this is bad - go to an error state. + * + * However - we should never actually need to check this! Because: + * We still can't know if we've had multiple of the same request issued + * since the last time we awoke. Therefore, we should use one of our + * notification indices as a counting semaphore - we decrement when we + * start processing, the servicer increments when we've got mail. If + * this value is ever 2, bad things happen - so we go to an error state. + * + * If this is actually a problem we can implement a message queue + * instead, but I'm trying to keep the implementation lightweight for + * now. + */ + + uint32_t counter_value = ulTaskNotifyTakeIndexed( + REQUEST_COUNTER_INDEX, + pdFALSE, + RTOS_OSAL_NO_WAIT); + + if (counter_value != 1) // If it's >1, bad. If it's 0... also bad. + { + dfu_int_error(DFU_INT_DFU_STATUS_ERR_STALLEDPKT); + continue; // Restart from top of loop + } + + switch (notification_value) + { + case DFU_INT_TASK_BIT_DNLOAD: + { + /* + * This request is valid in states dfuIDLE and dfuDNLOAD-IDLE. + * If in any other state, pushes to dfuERROR with errSTALLED-PKT. + * If in dfuIDLE and length is 0, pushes to dfuERROR. + * If in dfuIDLE and length > 0, pushes to dfuDNLOAD-SYNC. + * If in dfuDNLOAD-IDLE and length > 0, pushes to dfuDNLOAD-SYNC. + * If in dfuDNLOAD-IDLE and length is 0, pushes to dfuMANIFEST-SYNC. + * + * TODO: There is provision in the specification that if in + * dfuDNLOAD-IDLE and length is 0, but the device does not think + * that it has enough data, then we can push to dfuERROR with + * status errNOTDONE. We do not currently implement this behaviour + * in INT or in USB. + * + * Any other state + * or X + * │ ┌────────┐ + * DFU_DNLOAD │ │ 10 │ + * └───►│dfuERROR│ + * └────────┘ + * X X + * │ │ + * DFU_DNLOAD │ DFU_DNLOAD │ + * (len = 0) │ │ + * ┌─┴─────┐ DFU_DNLOAD ┌────────────┴─┐ + * │ 2 │ (len > 0) │ 3 │ + * │dfuIDLE├───────────►│dfuDNLOAD-SYNC│ + * └───────┘ └──────────────┘ + * X ▲ + * │ │ DFU_DNLOAD + * │ DFU_DNLOAD │ (len > 0) + * │ │ + * ┌─┴──────────────┐ DFU_DNLOAD ┌──────┴───────┐ + * │ 6 │ (len = 0) │ 5 │ + * │dfuMANIFEST-SYNC│◄───────────┤dfuDNLOAD-IDLE│ + * └────────────────┘ └──────────────┘ + * + */ + if (dfu_data.current_state == DFU_INT_DFU_IDLE && + dfu_data.data_xfer_length != 0) + { + // Starting a download. Set the "in progress" flag. + dfu_data.download_or_manifest_in_progress = true; + // Then move to the sync state + dfu_data.current_state = DFU_INT_DFU_DNLOAD_SYNC; + // We don't do anything else until we get a GETSTATUS + } + else if (dfu_data.current_state == DFU_INT_DFU_DNLOAD_IDLE) + { + if (dfu_data.data_xfer_length != 0) + { + // Continuing a download. Set the "in progress" flag. + dfu_data.download_or_manifest_in_progress = true; + // Then move to the sync state + dfu_data.current_state = DFU_INT_DFU_DNLOAD_SYNC; + // We don't do anything else until we get a GETSTATUS + } + else // fill_level == 0, so end of download phase + { + // Time to manifest. Set the "in progress" flag. + dfu_data.download_or_manifest_in_progress = true; + // Fully reset the download buffer, just to be neat. + dfu_int_reset_download_buffer(); + // Then move to the sync state + dfu_data.current_state = DFU_INT_DFU_MANIFEST_SYNC; + // We don't do anything else until we get a GETSTATUS + } + } + else // IDLE and len == 0, or not in IDLE or DNLOAD_IDLE + { + dfu_int_error(DFU_INT_DFU_STATUS_ERR_STALLEDPKT); + } + break; + } // end of case DFU_INT_TASK_BIT_DNLOAD - returns to top of loop + case DFU_INT_TASK_BIT_UPLOAD: + { + /* + * This request is valid in states dfuIDLE and dfuUPLOAD-IDLE. + * If in any other state, pushes to dfuERROR with errSTALLED-PKT. + * First and foremost, send a packet. + * If there is a full fragment to send, send + * it and then push to dfuUPLOAD-IDLE. + * If there is less than a full fragment to + * send, send it and then push to dfuIDLE. + * + * Any other state + * DFU_UPLOAD │ + * (len < fragment) │ DFU_UPLOAD + * ┌────────────────────────────┐ ▼ + * │ │ ┌────────┐ + * │ DFU_UPLOAD │ │ 10 │ + * │ (len = fragment) │ │dfuERROR│ + * │ ┌────┐ │ ┌────┐ └────────┘ + * │ │ │ ▼ │ │ + * ┌─┴──────────┴─┐ │ ┌─────┴─┐ │ + * │ 9 │◄─┘ │ 2 │◄─┘ + * │dfuUPLOAD-IDLE│ │dfuIDLE│ DFU_UPLOAD + * └──────────────┘ └───┬───┘ (len < fragment) + * ▲ │ + * └───────────────────────┘ + * DFU_UPLOAD + * (len = fragment) + * + */ + // First, clear the buffer + memset(dfu_data.dfu_data_buffer, 0, DFU_PAGE_SIZE); + dfu_data.data_xfer_length = 0; + + if (dfu_data.current_state == DFU_INT_DFU_IDLE || + dfu_data.current_state == DFU_INT_DFU_UPLOAD_IDLE) + { + dfu_data.data_xfer_length = dfu_common_read_from_flash( + dfu_data.alt_setting, + dfu_data.transfer_block, + dfu_data.dfu_data_buffer, + DFU_DATA_XFER_SIZE); + + if (dfu_data.data_xfer_length < DFU_DATA_XFER_SIZE) + { + debug_printf("Fill level %d, resetting!\n", + dfu_data.data_xfer_length); + dfu_data.transfer_block = 0; + dfu_data.current_state = DFU_INT_DFU_IDLE; + } + else + { + dfu_data.transfer_block += 1; + dfu_data.current_state = DFU_INT_DFU_UPLOAD_IDLE; + } + } + else + { + dfu_int_error(DFU_INT_DFU_STATUS_ERR_STALLEDPKT); + } + /* + * The servicer is yielding until we give this, so we must give + * it back regardless of what happened. If an error occured, the + * data_buffer_fill_level will be 0, so the host _should_ stop + * trying to continue the upload process. That's the only way we can + * signal the conclusion of the upload process. + */ + xSemaphoreGive(dfu_data.upload_semaphore); + } + case DFU_INT_TASK_BIT_GETSTATUS: + { + /* + * This request drives a reasonable amount of the state machine in + * download and manifestation. See the below diagram for state + * transitions. + * This function is quite verbose/explicit and that is intentional. + * This function runs on the state machine itself, and drives + * behaviour and transitions thereof. + * + * x + * ┌───┐ │ + * DFU_GETSTATUS│ │ ▼ + * ┌───┐ │ ┌─┴──────┐ + * DFU_GETSTATUS│ │ └►│ 10 │ + * │ ┌─┴────────────┐ │dfuERROR│ + * └►│ 9 │ └────────┘ + * │dfuUPLOAD-IDLE│ ▲ + * └──────────────┘ DFU_GETSTATUS│ + * │ + * ┌───┐ DFU_GETSTATUS ┌──────┐ │ + * DFU_GETSTATUS│ │ (block incomplete)│ ▼ │ + * │ ┌─┴─────┐ ┌────────────┴─┐ ┌────────┴┐ + * └►│ 2 │ │ 3 │ │ 4 │ + * │dfuIDLE│ │dfuDNLOAD-SYNC│ │dfuDNBUSY│ + * └───────┘ └──────┬───────┘ └─────────┘ + * ▲ │ + * ┌─────────┘ │ DFU_GETSTATUS + * │ DFU_GETSTATUS │(block complete) + * │(manifestation complete) ▼ + * ┌┴───────────────┐ ┌──────────────┐ + * │ 6 │ │ 5 │ + * │dfuMANIFEST-SYNC├─┐ │dfuDNLOAD-IDLE├─┐ + * └────────────────┘ │ └──────────────┘ │ + * │ ▲ │ + * ┌───────────┐ │ └──┘ + * │ 7 │◄┘ DFU_GETSTATUS + * │dfuMANIFEST│ DFU_GETSTATUS + * └─────────┬─┘ (manifestation incomplete) + * │ + * x──────────┘ + * DFU_GETSTATUS + * + */ + switch (dfu_data.current_state) + { + case DFU_INT_DFU_UPLOAD_IDLE: + case DFU_INT_DFU_IDLE: + case DFU_INT_DFU_ERROR: + case DFU_INT_DFU_DNLOAD_IDLE: + { + // Do nothing - stay in the current state + break; + } + case DFU_INT_DFU_DNLOAD_SYNC: + { + if (dfu_data.download_or_manifest_in_progress) + { + /* + * We're in DNLOAD_SYNC and there is a download in progress. + * Therefore, we should write what's in the buffer to flash. + * While we're busy, if the host tries to talk to us again + * the servicer will handle it - if it should e.g. push us + * into an error state, the servicer will let us know. When + * we're done, we move back into DNLOAD_SYNC and clear the + * "download in progress" flag, ready to either receive + * another block or to move to manifestation. + */ + dfu_data.current_state = DFU_INT_DFU_DNBUSY; + /* + * From here on, we assume that DFU_DATA_XFER_SIZE is an + * integer factor of DFU_PAGE_SIZE. I'm not going to + * waste cycles asserting on this, so reader beware, please + * make sure this is always the case (unless you want to do + * some horrible maths!) + */ + dfu_int_status_t retval = DFU_INT_DFU_STATUS_OK; + + if (dfu_data.frag_number == (DFU_NUM_FRAGMENTS - 1)) + { + // We've assembled a full download buffer. Write it. + retval = dfu_common_write_to_flash( + dfu_data.alt_setting, + dfu_data.download_block_number, + dfu_data.dfu_data_buffer, + DFU_PAGE_SIZE); + memset(dfu_data.dfu_data_buffer, 0, DFU_PAGE_SIZE); + dfu_data.frag_number = 0; + dfu_data.download_block_number += 1; + } + else + { + dfu_data.frag_number += 1; + } + dfu_data.download_or_manifest_in_progress = false; + if (dfu_data.move_to_error) + { + /* + * If this is set, then while we've been busy doing a + * download the servicer has decided that something bad + * has happened and asked us to move to an error state + * once we're done being busy. This is practically a + * deferment of the state transition from DNBUSY to + * ERROR. + */ + + dfu_int_error(dfu_data.move_to_error_status); + } + else if (retval != DFU_INT_DFU_STATUS_OK) + { + dfu_int_error(retval); + } + else + { + dfu_data.current_state = DFU_INT_DFU_DNLOAD_SYNC; + } + } + else + { + /* + * We're in DNLOAD_SYNC but a download is not in progress. + * This means that we've finished downloading this block and + * should move to DNLOAD_IDLE, ready for either more blocks + * or a zero-length-packet to continue. + */ + dfu_data.current_state = DFU_INT_DFU_DNLOAD_IDLE; + } + break; + } + case DFU_INT_DFU_MANIFEST_SYNC: + { + if (dfu_data.download_or_manifest_in_progress) + { + /* + * We're in MANIFEST_SYNC and there's a manifest in + * progress. Therefore we've just moved from DNLOAD_IDLE, + * and should manifest what we've just downloaded. Do that. + * If the host tries to talk to us, the servicer will handle + * it, and will tell us if we need to move to an error state + * as a result of that. + */ + dfu_data.current_state = DFU_INT_DFU_MANIFEST; + dfu_int_status_t retval = dfu_common_make_manifest(); + dfu_data.download_or_manifest_in_progress = false; + if (dfu_data.move_to_error) + { + /* + * If this is set, then while we've been busy doing a + * manifest the servicer has decided that something bad + * has happened and asked us to move to an error state + * once we're done being busy. This is practically a + * deferment of the state transition from MANIFEST to + * ERROR. + */ + dfu_int_error(dfu_data.move_to_error_status); + } + else if (retval != DFU_INT_DFU_STATUS_OK) + { + dfu_int_error(retval); + } + else + { + dfu_data.current_state = DFU_INT_DFU_MANIFEST_SYNC; + } + } + else + { + /* + * We're in MANIFEST_SYNC but a manifest is not in progress. + * This means that we've finished manifesting and are done! + * Should move to DFU_IDLE. + */ + dfu_data.current_state = DFU_INT_DFU_IDLE; + } + break; + } + case DFU_INT_DFU_DNBUSY: + case DFU_INT_DFU_MANIFEST: + default: + { + /* + * I don't think we can actually ever get here in these states, + * because we never yield while we're in them, but here for + * completeness. This transition is actually catered for by + * deferment using the dfu_data.move_to_error flag. + */ + + dfu_int_error(DFU_INT_DFU_STATUS_ERR_STALLEDPKT); + break; + } + } // end of switch current_state + break; + } // end of case DFU_INT_TASK_BIT_GETSTATUS - returns to top of loop + case DFU_INT_TASK_BIT_CLRSTATUS: + { + /* + * This request is valid in state dfuERROR only. + * If in any other states, pushes to dfuERROR with errSTALLED-PKT. + * If in dfuERROR, sets status to OK and pushes to dfuIDLE. + * + * Any other state + * │ + * │ DFU_CLRSTATUS + * ▼ + * ┌────────┐ + * ┌────────────────┤ 10 │ + * │ │dfuERROR│ + * DFU_CLRSTATUS │ └────────┘ + * ▼ + * ┌───────┐ + * │ 2 │ + * │dfuIDLE│ + * └───────┘ + * + */ + if (dfu_data.current_state == DFU_INT_DFU_ERROR) + { + dfu_int_reset_state(); + } + else + { + dfu_int_error(DFU_INT_DFU_STATUS_ERR_STALLEDPKT); + } + break; + } // end of case DFU_INT_TASK_BIT_CLRSTATUS - returns to top of loop + case DFU_INT_TASK_BIT_ABORT: + { + /* + * This request is valid in dfuIDLE, dfuDNLOAD-IDLE, dfuUPLOAD-IDLE. + * If in any other states, pushes to dfuERROR with errSTALLED-PKT. + * If in dfuIDLE, do nothing. + * If in dfuDNLOAD-IDLE, push to dfuIDLE. + * If in dfuUPLOAD-IDLE, push to dfuIDLE. + * + * Any other state + * │ + * │ DFU_ABORT + * ▼ + * ┌────────┐ + * │ 10 │ + * │dfuERROR│ + * ┌──────┐ └────────┘ + * │ │ + * ┌──────────────┐ ┌───┴───┐ │ + * │ 9 │ DFU_ABORT │ 2 │ │ DFU_ABORT + * │dfuUPLOAD-IDLE├──────────►│dfuIDLE│◄─┘ + * └──────────────┘ └───────┘ + * ▲ + * │ + * │ + * │ + * │ ┌──────────────┐ + * │ DFU_ABORT │ 5 │ + * └────────────────┤dfuDNLOAD-IDLE│ + * └──────────────┘ + * + */ + if (dfu_data.current_state == DFU_INT_DFU_UPLOAD_IDLE || + dfu_data.current_state == DFU_INT_DFU_DNLOAD_IDLE) + { + dfu_int_reset_state(); + } + else + { + dfu_int_error(DFU_INT_DFU_STATUS_ERR_STALLEDPKT); + } + break; + } // end of case DFU_INT_TASK_BIT_ABORT + case DFU_INT_TASK_BIT_SETALTERNATE: + { + /* + * dfu_int_reset_state() purposefully doesn't change the alternate + * setting, so we use it here, regardless of the fact the servicer + * has updated this already + * + * To match the USB implementation, this request resets the state + * machine. Therefore, set the alternate setting, and then we call + * reset_state(). Moving to error resets everything else about the + * state machine, so no need to do anything else here in that + * instance. + */ + dfu_int_reset_state(); + break; + } // end of case DFU_INT_TASK_BIT_SETALTERNATE + } // end of switch + } // end of while loop +} // end of dfu_state_machine() diff --git a/examples/ffva/src/dfu/dfu_state_machine.h b/examples/ffva/src/dfu/dfu_state_machine.h new file mode 100644 index 00000000..3b93feb5 --- /dev/null +++ b/examples/ffva/src/dfu/dfu_state_machine.h @@ -0,0 +1,244 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. + +// Compiler includes +#include +#include + +// FreeRTOS includes +#include "FreeRTOS.h" +#include "task.h" +#include "semphr.h" +#include "dfu_common.h" + +/** + * \brief Defines the size of the internal data buffer. Should match the size + * of the payload transmitted over device control. + */ +#define DFU_DATA_XFER_SIZE 128 +/** + * \brief Defines the size of a flash page; used to assemble multiple payloads + * into one write operation. + */ +#define DFU_PAGE_SIZE 256 +/** + * \brief Defines the number of payloads required to fill one flash page. It is + * assumed that #DFU_DATA_XFER_SIZE is an integer factor of #DFU_PAGE_SIZE. + */ +#define DFU_NUM_FRAGMENTS (DFU_PAGE_SIZE / DFU_DATA_XFER_SIZE) + +/** + * \enum dfu_int_alt_setting_t + * \brief Sets up identifiers for whether to target the factory or upgrade alts. + * + * Host application users will specify 0 for factory, 1 for upgrade. + * + * \var dfu_int_alt_setting_t::DFU_INT_ALTERNATE_FACTORY + * Sets to target the "factory" alt. This alt is read-only. + * \var dfu_int_alt_setting_t::DFU_INT_ALTERNATE_UPGRADE + * Sets to target the "upgrade" alt. This alt can be both read and written. + * + */ +typedef enum dfu_int_alt_setting_t +{ + DFU_INT_ALTERNATE_FACTORY, + DFU_INT_ALTERNATE_UPGRADE +} dfu_int_alt_setting_t; + +/** + * \enum dfu_int_state_t + * \brief Sets up identifiers for the different states in the DFU state machine. + * + * The names of these states and their values are derived from the USB Device + * Class Specification for Device Firmware Upgrade Version 1.1. This enum + * should not be changed, unless a specification update is issued. + * + */ +typedef enum dfu_int_state_t +{ + DFU_INT_APP_IDLE, // unused + DFU_INT_APP_DETACH, // unused + DFU_INT_DFU_IDLE, + DFU_INT_DFU_DNLOAD_SYNC, + DFU_INT_DFU_DNBUSY, + DFU_INT_DFU_DNLOAD_IDLE, + DFU_INT_DFU_MANIFEST_SYNC, + DFU_INT_DFU_MANIFEST, + DFU_INT_DFU_MANIFEST_WAIT_RESET, + DFU_INT_DFU_UPLOAD_IDLE, + DFU_INT_DFU_ERROR +} dfu_int_state_t; + +/** + * \enum dfu_int_status_t + * \brief Sets up identifiers for statuses reported by the state machine. + * + * The names of these statuses and their values are derived from the USB Device + * Class Specification for Device Firmware Upgrade Version 1.1. This enum + * should not be changed, unless a specification update is issued. The meaning + * of each of these statuses may be found in the third table in section 6.1.2 + * of the above specification. + * + */ +typedef enum dfu_int_status_t +{ + DFU_INT_DFU_STATUS_OK, + DFU_INT_DFU_STATUS_ERR_TARGET, + DFU_INT_DFU_STATUS_ERR_FILE, + DFU_INT_DFU_STATUS_ERR_WRITE, + DFU_INT_DFU_STATUS_ERR_ERASE, + DFU_INT_DFU_STATUS_ERR_CHECK_ERASED, + DFU_INT_DFU_STATUS_ERR_PROG, + DFU_INT_DFU_STATUS_ERR_VERIFY, + DFU_INT_DFU_STATUS_ERR_ADDRESS, + DFU_INT_DFU_STATUS_ERR_NOTDONE, + DFU_INT_DFU_STATUS_ERR_FIRMWARE, + DFU_INT_DFU_STATUS_ERR_VENDOR, + DFU_INT_DFU_STATUS_ERR_USBR, + DFU_INT_DFU_STATUS_ERR_POR, + DFU_INT_DFU_STATUS_ERR_UNKNOWN, + DFU_INT_DFU_STATUS_ERR_STALLEDPKT +} dfu_int_status_t; + +/** + * \struct dfu_int_get_status_packet_t + * \brief Contains necessary fields to facilitate the GETSTATUS request. + * + * \var dfu_int_get_status_packet_t::next_state + * Indicates the next state that the state machine will move into after this + * request is processed. + * \var dfu_int_get_status_packet_t::current_status + * Indicates the current status of the state machine, _before_ this request is + * processed. + * \var dfu_int_get_status_packet_t::timeout_ms + * Indicates the number of milliseconds the host should wait before attempting + * another request. + * + */ +typedef struct dfu_int_get_status_packet_t +{ + dfu_int_state_t next_state; + dfu_int_status_t current_status; + uint32_t timeout_ms; +} dfu_int_get_status_packet_t; + +/** + * \brief Sends a DFU_DETACH request to the DFU state machine. + * + * This is implemented as a device reboot after #DFU_REBOOT_DELAY_MS ms, set by + * default as 100 ms. + */ +void dfu_int_detach(); + +/** + * \brief Sends a DFU_DNLOAD request to the DFU state machine. + * + * This function will copy \p length bytes of the \p download_data buffer + * into a buffer internal to dfu_state_machine.c, before notifying the state + * machine and returning. If \p length is given as 0, the state machine will + * regard this as the end of the download process and will move to the + * manifestation phase. + * + * \param[in] length Number of valid bytes of data in \p download_data. + * \param[in] download_data Buffer containing \p length valid bytes of data. + */ +void dfu_int_download(uint16_t length, const uint8_t *download_data); + +/** + * \brief Sends a DFU_UPLOAD request to the DFU state machine. + * + * The state machine will fill \p upload_buffer with the next transfer block of + * data, and this function will then return the number of bytes written. + * Should the function return less than \p upload_buffer_length , then the + * device is signalling the end of the upload process. + * + * \param[out] upload_buffer Buffer into which to place the upload data + * \param[in] upload_buffer_length Length of \p upload_buffer in bytes + * \return size_t Number of bytes written to buffer + */ +size_t dfu_int_upload(uint8_t *upload_buffer, size_t upload_buffer_length); + +/** + * \brief Sends a DFU_GETSTATUS request to the DFU state machine. + * + * This function will populate \p get_status_packet with required information. + * This will be handled by the calling task, rather than by the asynchronous + * state machine task. The calling task will predict the next state that the + * state machine will move into. The calling task will then usually signal the + * asynchronous task to action this request. This allows an instantaneous + * resonse to this request, rather than waiting for the state machine to + * process the request - which may be a non-trivial time if the state machine + * is currently writing to flash. + * + * \param[out] get_status_packet Pointer to the structure to be populated + */ +void dfu_int_get_status(dfu_int_get_status_packet_t *get_status_packet); + +/** + * \brief Sends a DFU_CLRSTATUS request to the DFU state machine. + */ +void dfu_int_clear_status(); + +/** + * \brief Reads and returns the current state of the state machine. + * + * This request is serviced instantaneously, so long as the calling task has + * access to the current state of the state machine (which in the current + * implementation it does). + * + * \return dfu_int_state_t The current state of the state machine. + */ +dfu_int_state_t dfu_int_get_state(); + +/** + * \brief Sends a DFU_ABORT request to the DFU state machine. + */ +void dfu_int_abort(); + +/** + * \brief Sets the alternate interface used in UPLOAD and DNLOAD operations. + * + * This request will also always reset the state machine. + * + * \param[in] alt Alternate setting to change to. + */ +void dfu_int_set_alternate(dfu_int_alt_setting_t alt); + +/** + * \brief Sets the transfer block number for use in an UPLOAD operation. + * + * The DFU_UPLOAD request will return the data found at this transfer block. + * The transfer block size for this implementation is 64 bytes. + * This number will automatically increment on every successful DFU_UPLOAD. + * This number will default to 0. Therefore, to read the entire image, it is + * not necessary to first set this value. + * + * \param[in] transferblock Transfer block to use in an UPLOAD operation. + */ +void dfu_int_set_transfer_block(uint16_t transferblock); + +/** + * \brief Retrieves the current transfer block number. + * + * Strictly, this number represents the next transfer block that the UPLOAD + * operation will return. If called after issuing a DFU_UPLOAD request, it + * will represent the transfer block of the previous request plus one. + * + * \return uint16_t Current transfer block setting. + */ +uint16_t dfu_int_get_transfer_block(); + +/** + * \brief RTOS task running the DFU state machine. + * + * Task should be created before attempting to use any of the above functions. + * Initialises the state machine and then proceeds into a loop, waiting for + * notifications on index 0 to determine which request has been issued. Will + * read a value in notification index 1 to determine how many requests have + * been issued since the last time the task awoke; must be exactly 1 or the + * state machine will move to the error state. Notification index 2 is + * currently unused. + * + * \param[in] args Unused + */ +void dfu_int_state_machine(void *args); diff --git a/examples/ffva/src/usb/usb_dfu.c b/examples/ffva/src/usb/usb_dfu.c index b9c140aa..6a1e5d23 100644 --- a/examples/ffva/src/usb/usb_dfu.c +++ b/examples/ffva/src/usb/usb_dfu.c @@ -28,26 +28,16 @@ #include #include #include - #include "FreeRTOS.h" #include "timers.h" -#include "platform/driver_instances.h" #include "tusb.h" - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF PROTYPES -//--------------------------------------------------------------------+ -static void reboot(void); +#include "dfu_common.h" //--------------------------------------------------------------------+ // DFU callbacks // Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc. //--------------------------------------------------------------------+ -static size_t total_len = 0; -static size_t bytes_avail = 0; -static uint32_t dn_base_addr = 0; - // Invoked right before tud_dfu_download_cb() (state=DFU_DNBUSY) or tud_dfu_manifest_cb() (state=DFU_MANIFEST) // Application return timeout in milliseconds (bwPollTimeout) for the next download/manifest operation. // During this period, USB host won't try to communicate with us. @@ -68,67 +58,11 @@ uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state) // Once finished flashing, application must call tud_dfu_finish_flashing() void tud_dfu_download_cb(uint8_t alt, uint16_t block_num, uint8_t const* data, uint16_t length) { - rtos_printf("Received Alt %d BlockNum %d of length %d\n", alt, block_num, length); - - unsigned data_partition_base_addr = rtos_dfu_image_get_data_partition_addr(dfu_image_ctx); - switch(alt) { - default: - case 0: - tud_dfu_finish_flashing(DFU_STATUS_ERR_WRITE); - break; - case 1: - if (dn_base_addr == 0) { - total_len = 0; - dn_base_addr = rtos_dfu_image_get_upgrade_addr(dfu_image_ctx); - bytes_avail = data_partition_base_addr - dn_base_addr; - } - /* fallthrough */ - case 2: - if (dn_base_addr == 0) { - total_len = 0; - dn_base_addr = data_partition_base_addr; - bytes_avail = rtos_qspi_flash_size_get(qspi_flash_ctx) - dn_base_addr; - } - rtos_printf("Using addr 0x%x\nsize %u\n", dn_base_addr, bytes_avail); - if(length > 0) { - unsigned cur_addr = dn_base_addr + (block_num * CFG_TUD_DFU_XFER_BUFSIZE); - if((bytes_avail - total_len) >= length) { - rtos_printf("write %d at 0x%x\n", length, cur_addr); - - size_t sector_size = rtos_qspi_flash_sector_size_get(qspi_flash_ctx); - xassert(CFG_TUD_DFU_XFER_BUFSIZE == sector_size); - - uint8_t *tmp_buf = rtos_osal_malloc( sizeof(uint8_t) * sector_size); - rtos_qspi_flash_lock(qspi_flash_ctx); - { - rtos_qspi_flash_read( - qspi_flash_ctx, - tmp_buf, - cur_addr, - sector_size); - memcpy(tmp_buf, data, length); - rtos_qspi_flash_erase( - qspi_flash_ctx, - cur_addr, - sector_size); - rtos_qspi_flash_write( - qspi_flash_ctx, - (uint8_t *) tmp_buf, - cur_addr, - sector_size); - } - rtos_qspi_flash_unlock(qspi_flash_ctx); - rtos_osal_free(tmp_buf); - total_len += length; - } else { - rtos_printf("Insufficient space\n"); - tud_dfu_finish_flashing(DFU_STATUS_ERR_ADDRESS); - } - } - - tud_dfu_finish_flashing(DFU_STATUS_OK); - break; - } + dfu_status_t error_code = dfu_common_write_to_flash(alt, + block_num, + data, + length); + tud_dfu_finish_flashing(error_code); } // Invoked when download process is complete, received DFU_DNLOAD (wLength=0) following by DFU_GETSTATUS (state=Manifest) @@ -136,23 +70,10 @@ void tud_dfu_download_cb(uint8_t alt, uint16_t block_num, uint8_t const* data, u // Once finished flashing, application must call tud_dfu_finish_flashing() void tud_dfu_manifest_cb(uint8_t alt) { - (void) alt; - rtos_printf("Download completed, enter manifestation\n"); - - /* Perform a read to ensure all writes have been flushed */ - uint32_t dummy = 0; - rtos_qspi_flash_read( - qspi_flash_ctx, - (uint8_t *)&dummy, - 0, - sizeof(dummy)); - - /* Reset download */ - dn_base_addr = 0; + (void)alt; - // flashing op for manifest is complete without error - // Application can perform checksum, should it fail, use appropriate status such as errVERIFY. - tud_dfu_finish_flashing(DFU_STATUS_OK); + dfu_state_t error_code = dfu_common_make_manifest(); + tud_dfu_finish_flashing(error_code); } // Invoked when received DFU_UPLOAD request @@ -160,40 +81,10 @@ void tud_dfu_manifest_cb(uint8_t alt) // Return the number of written bytes uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length) { - uint32_t endaddr = 0; - uint16_t retval = 0; - uint32_t addr = block_num * CFG_TUD_DFU_XFER_BUFSIZE; - - rtos_printf("Upload Alt %d BlockNum %d of length %d\n", alt, block_num, length); - - switch(alt) { - default: - break; - case 0: - if (rtos_dfu_image_get_factory_size(dfu_image_ctx) > 0) { - addr += rtos_dfu_image_get_factory_addr(dfu_image_ctx); - endaddr = rtos_dfu_image_get_factory_addr(dfu_image_ctx) + rtos_dfu_image_get_factory_size(dfu_image_ctx); - } - break; - case 1: - if (rtos_dfu_image_get_upgrade_size(dfu_image_ctx) > 0) { - addr += rtos_dfu_image_get_upgrade_addr(dfu_image_ctx); - endaddr = rtos_dfu_image_get_upgrade_addr(dfu_image_ctx) + rtos_dfu_image_get_upgrade_size(dfu_image_ctx); - } - break; - case 2: - if ((rtos_qspi_flash_size_get(qspi_flash_ctx) - rtos_dfu_image_get_data_partition_addr(dfu_image_ctx)) > 0) { - addr += rtos_dfu_image_get_data_partition_addr(dfu_image_ctx); - endaddr = rtos_qspi_flash_size_get(qspi_flash_ctx); /* End of flash */ - } - break; - } - - if (addr < endaddr) { - rtos_qspi_flash_read(qspi_flash_ctx, data, addr, length); - retval = length; - } - return retval; + return dfu_common_read_from_flash(alt, + block_num, + data, + length); } // Invoked when the Host has terminated a download or upload transfer @@ -209,11 +100,3 @@ void tud_dfu_detach_cb(void) rtos_printf("Host detach, we should probably reboot\n"); reboot(); } - -static void reboot(void) -{ - rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 0x10000); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); - while(1) {;} -} From d1060790978315a1fe189540f82b561e7defeac0 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 18 Mar 2024 09:58:31 +0000 Subject: [PATCH 091/288] DFU over USB still working --- examples/ffva/src/dfu/dfu_common.c | 3 +- examples/ffva/src/dfu/dfu_state_machine.c | 14 +++--- examples/ffva/src/dfu/dfu_state_machine.h | 52 +++++++++++------------ 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/examples/ffva/src/dfu/dfu_common.c b/examples/ffva/src/dfu/dfu_common.c index 8ed20d56..4ad5c58b 100644 --- a/examples/ffva/src/dfu/dfu_common.c +++ b/examples/ffva/src/dfu/dfu_common.c @@ -25,7 +25,7 @@ uint32_t dfu_common_write_to_flash(uint8_t alt, uint16_t length) { rtos_printf("Received Alt %d BlockNum %d of length %d\n", alt, block_num, length); - uint32_t return_value = 0; + uint32_t return_value = 0; // DFU_STATUS_OK unsigned data_partition_base_addr = rtos_dfu_image_get_data_partition_addr(dfu_image_ctx); switch(alt) { @@ -83,7 +83,6 @@ uint32_t dfu_common_write_to_flash(uint8_t alt, } } - return_value = 0; // DFU_STATUS_OK; break; } diff --git a/examples/ffva/src/dfu/dfu_state_machine.c b/examples/ffva/src/dfu/dfu_state_machine.c index 497b76f8..731d24bb 100644 --- a/examples/ffva/src/dfu/dfu_state_machine.c +++ b/examples/ffva/src/dfu/dfu_state_machine.c @@ -52,7 +52,7 @@ typedef struct dfu_int_dfu_data_t uint16_t data_xfer_length; uint16_t download_block_number; uint8_t frag_number; - uint8_t dfu_data_buffer[DFU_PAGE_SIZE]; + uint8_t dfu_data_buffer[DFU_SECTOR_SIZE]; uint32_t previous_timeout_ms; uint32_t timeout_start; } dfu_int_dfu_data_t; @@ -82,7 +82,7 @@ void dfu_int_detach() * */ debug_printf("Detach\n"); - reboot_xvf3800(DFU_REBOOT_DELAY_MS); + reboot(); } void dfu_int_download(uint16_t length, const uint8_t *download_data) @@ -351,7 +351,7 @@ static void dfu_int_reset_download_buffer() dfu_data.data_xfer_length = 0; dfu_data.frag_number = 0; dfu_data.download_block_number = 0; - memset(dfu_data.dfu_data_buffer, 0, DFU_PAGE_SIZE); + memset(dfu_data.dfu_data_buffer, 0, DFU_SECTOR_SIZE); } static void dfu_int_reset_state() @@ -541,7 +541,7 @@ void dfu_int_state_machine(void *args) * */ // First, clear the buffer - memset(dfu_data.dfu_data_buffer, 0, DFU_PAGE_SIZE); + memset(dfu_data.dfu_data_buffer, 0, DFU_SECTOR_SIZE); dfu_data.data_xfer_length = 0; if (dfu_data.current_state == DFU_INT_DFU_IDLE || @@ -650,7 +650,7 @@ void dfu_int_state_machine(void *args) dfu_data.current_state = DFU_INT_DFU_DNBUSY; /* * From here on, we assume that DFU_DATA_XFER_SIZE is an - * integer factor of DFU_PAGE_SIZE. I'm not going to + * integer factor of DFU_SECTOR_SIZE. I'm not going to * waste cycles asserting on this, so reader beware, please * make sure this is always the case (unless you want to do * some horrible maths!) @@ -664,8 +664,8 @@ void dfu_int_state_machine(void *args) dfu_data.alt_setting, dfu_data.download_block_number, dfu_data.dfu_data_buffer, - DFU_PAGE_SIZE); - memset(dfu_data.dfu_data_buffer, 0, DFU_PAGE_SIZE); + DFU_SECTOR_SIZE); + memset(dfu_data.dfu_data_buffer, 0, DFU_SECTOR_SIZE); dfu_data.frag_number = 0; dfu_data.download_block_number += 1; } diff --git a/examples/ffva/src/dfu/dfu_state_machine.h b/examples/ffva/src/dfu/dfu_state_machine.h index 3b93feb5..60c9c9b2 100644 --- a/examples/ffva/src/dfu/dfu_state_machine.h +++ b/examples/ffva/src/dfu/dfu_state_machine.h @@ -17,15 +17,15 @@ */ #define DFU_DATA_XFER_SIZE 128 /** - * \brief Defines the size of a flash page; used to assemble multiple payloads + * \brief Defines the size of a flash sector; used to assemble multiple payloads * into one write operation. */ -#define DFU_PAGE_SIZE 256 +#define DFU_SECTOR_SIZE 4056 /** - * \brief Defines the number of payloads required to fill one flash page. It is - * assumed that #DFU_DATA_XFER_SIZE is an integer factor of #DFU_PAGE_SIZE. + * \brief Defines the number of payloads required to fill one flash sector. It is + * assumed that #DFU_DATA_XFER_SIZE is an integer factor of #DFU_SECTOR_SIZE. */ -#define DFU_NUM_FRAGMENTS (DFU_PAGE_SIZE / DFU_DATA_XFER_SIZE) +#define DFU_NUM_FRAGMENTS (DFU_SECTOR_SIZE / DFU_DATA_XFER_SIZE) /** * \enum dfu_int_alt_setting_t @@ -124,7 +124,7 @@ typedef struct dfu_int_get_status_packet_t /** * \brief Sends a DFU_DETACH request to the DFU state machine. - * + * * This is implemented as a device reboot after #DFU_REBOOT_DELAY_MS ms, set by * default as 100 ms. */ @@ -132,13 +132,13 @@ void dfu_int_detach(); /** * \brief Sends a DFU_DNLOAD request to the DFU state machine. - * + * * This function will copy \p length bytes of the \p download_data buffer * into a buffer internal to dfu_state_machine.c, before notifying the state * machine and returning. If \p length is given as 0, the state machine will * regard this as the end of the download process and will move to the * manifestation phase. - * + * * \param[in] length Number of valid bytes of data in \p download_data. * \param[in] download_data Buffer containing \p length valid bytes of data. */ @@ -146,12 +146,12 @@ void dfu_int_download(uint16_t length, const uint8_t *download_data); /** * \brief Sends a DFU_UPLOAD request to the DFU state machine. - * + * * The state machine will fill \p upload_buffer with the next transfer block of - * data, and this function will then return the number of bytes written. + * data, and this function will then return the number of bytes written. * Should the function return less than \p upload_buffer_length , then the * device is signalling the end of the upload process. - * + * * \param[out] upload_buffer Buffer into which to place the upload data * \param[in] upload_buffer_length Length of \p upload_buffer in bytes * \return size_t Number of bytes written to buffer @@ -160,16 +160,16 @@ size_t dfu_int_upload(uint8_t *upload_buffer, size_t upload_buffer_length); /** * \brief Sends a DFU_GETSTATUS request to the DFU state machine. - * + * * This function will populate \p get_status_packet with required information. * This will be handled by the calling task, rather than by the asynchronous - * state machine task. The calling task will predict the next state that the + * state machine task. The calling task will predict the next state that the * state machine will move into. The calling task will then usually signal the * asynchronous task to action this request. This allows an instantaneous * resonse to this request, rather than waiting for the state machine to * process the request - which may be a non-trivial time if the state machine * is currently writing to flash. - * + * * \param[out] get_status_packet Pointer to the structure to be populated */ void dfu_int_get_status(dfu_int_get_status_packet_t *get_status_packet); @@ -181,56 +181,56 @@ void dfu_int_clear_status(); /** * \brief Reads and returns the current state of the state machine. - * - * This request is serviced instantaneously, so long as the calling task has + * + * This request is serviced instantaneously, so long as the calling task has * access to the current state of the state machine (which in the current * implementation it does). - * + * * \return dfu_int_state_t The current state of the state machine. */ dfu_int_state_t dfu_int_get_state(); /** - * \brief Sends a DFU_ABORT request to the DFU state machine. + * \brief Sends a DFU_ABORT request to the DFU state machine. */ void dfu_int_abort(); /** * \brief Sets the alternate interface used in UPLOAD and DNLOAD operations. - * + * * This request will also always reset the state machine. - * + * * \param[in] alt Alternate setting to change to. */ void dfu_int_set_alternate(dfu_int_alt_setting_t alt); /** * \brief Sets the transfer block number for use in an UPLOAD operation. - * + * * The DFU_UPLOAD request will return the data found at this transfer block. * The transfer block size for this implementation is 64 bytes. * This number will automatically increment on every successful DFU_UPLOAD. * This number will default to 0. Therefore, to read the entire image, it is * not necessary to first set this value. - * + * * \param[in] transferblock Transfer block to use in an UPLOAD operation. */ void dfu_int_set_transfer_block(uint16_t transferblock); /** * \brief Retrieves the current transfer block number. - * + * * Strictly, this number represents the next transfer block that the UPLOAD * operation will return. If called after issuing a DFU_UPLOAD request, it * will represent the transfer block of the previous request plus one. - * + * * \return uint16_t Current transfer block setting. */ uint16_t dfu_int_get_transfer_block(); /** * \brief RTOS task running the DFU state machine. - * + * * Task should be created before attempting to use any of the above functions. * Initialises the state machine and then proceeds into a loop, waiting for * notifications on index 0 to determine which request has been issued. Will @@ -238,7 +238,7 @@ uint16_t dfu_int_get_transfer_block(); * been issued since the last time the task awoke; must be exactly 1 or the * state machine will move to the error state. Notification index 2 is * currently unused. - * + * * \param[in] args Unused */ void dfu_int_state_machine(void *args); From 046af13247a9be52832f6ec373db032bb77e95c9 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 18 Mar 2024 14:22:43 +0000 Subject: [PATCH 092/288] Add info --- test/device_firmware_update/check_dfu_i2c.sh | 52 ++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/device_firmware_update/check_dfu_i2c.sh diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh new file mode 100644 index 00000000..0c52a956 --- /dev/null +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# To run this tests do the following: +# 1. Configure a Rapsberry-Pi: +# a. Install dfu-util on a Raspbnerry-Pi and add it to the path +# b. Clone vocalfusion-rpi-setup and run ./setup.sh xvf3800-int +# 2. Flash a voice-reference board with example_ffva_int_fixed_delay +# 3. Generate an upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download1.bin +# 4. Generate a different upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download2.bin +# 5. Copy the files download1.bin, download2.bin, emptyfile.bin and test_dfu_i2c.sh to the Raspberry-Pi +# 6. On the Raspberry-Pi, set the correct value of ITERATION_NUM and run this script: ./check_dfu_i2c.sh + +ITERATION_NUM=400 +UPGRADE_FILE_1="download1.bin" +UPGRADE_FILE_2="download2.bin" +EMPTY_FILE="emptyfile.bin" +UPLOAD_FILE="upload.bin" + +check_upgrade() { + + ./xvf_dfu --download $1 + ./xvf_dfu -r + + sleep 3 # 3s delay + + ./xvf_dfu --upload-upgrade $UPLOAD_FILE + revval=$(diff $UPLOAD_FILE $1) + echo "Output: $output" + if [[ $output == 0 ]]; then + echo "Upload is correct" + else + echo "Upload is incorrect" + exit 1 + fi + + sleep 3 # 3s delay + +} + +counter=0 +while [ $counter -lt $ITERATION_NUM ] +do + counter=$(( $counter + 1 )) + echo "DFU attempt number $counter" + + check_upgrade $UPGRADE_FILE_1 + + check_upgrade $UPGRADE_FILE_1 + + check_upgrade $EMPTY_FILE + +done From 71621f43b6abd8040c780448f5fc0e831f8ffcd5 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 18 Mar 2024 14:24:28 +0000 Subject: [PATCH 093/288] Add new file --- CHANGELOG.rst | 3 ++- test/device_firmware_update/emptyfile.bin | Bin 0 -> 4096 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 test/device_firmware_update/emptyfile.bin diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f966fbd6..1e161fca 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,8 @@ XCORE-VOICE change log * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). - * CHANGED: Remove need to use external MCLK in FFVA INT examples + * CHANGED: Remove need to use external MCLK in FFVA INT examples. + * ADDED: Support for DFU over I2C for FFVA INT example. * ADDED: lib_sw_pll submodule v1.1.0. diff --git a/test/device_firmware_update/emptyfile.bin b/test/device_firmware_update/emptyfile.bin new file mode 100644 index 0000000000000000000000000000000000000000..08e7df176454f3ee5eeda13efa0adaa54828dfd8 GIT binary patch literal 4096 ocmeIu0Sy2E0K%a6Pi+qe5hx58Fkrxd0RsjM7%*VKfPwdc0T2KH0RR91 literal 0 HcmV?d00001 From 8fd878110cb7ba804f9ad10b228e08f4af063687 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 18 Mar 2024 15:15:09 +0000 Subject: [PATCH 094/288] Add comments --- test/device_firmware_update/check_dfu_i2c.sh | 55 +++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index 0c52a956..b670a4a9 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -2,13 +2,14 @@ # To run this tests do the following: # 1. Configure a Rapsberry-Pi: -# a. Install dfu-util on a Raspbnerry-Pi and add it to the path -# b. Clone vocalfusion-rpi-setup and run ./setup.sh xvf3800-int -# 2. Flash a voice-reference board with example_ffva_int_fixed_delay +# a. Follow the instructions for XVF3800-INT on https://github.com/xmos/vocalfusion-rpi-setup?tab=readme-ov-file#setup +# b. Install dfu-util on a Raspbnerry-Pi and add it to the path +# 2. Connect a voice-reference board to the Raspberry-Pi expander +# 2. Connect an xTAG to voice-reference board from a host machine, and flash a voice-reference board with example_ffva_int_fixed_delay # 3. Generate an upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download1.bin # 4. Generate a different upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download2.bin -# 5. Copy the files download1.bin, download2.bin, emptyfile.bin and test_dfu_i2c.sh to the Raspberry-Pi -# 6. On the Raspberry-Pi, set the correct value of ITERATION_NUM and run this script: ./check_dfu_i2c.sh +# 5. Copy the files download1.bin, download2.bin, emptyfile.bin and check_dfu_i2c.sh to the Raspberry-Pi +# 6. On the Raspberry-Pi, set the desired value of ITERATION_NUM in check_dfu_i2c.sh and run this script: `source check_dfu_i2c.sh` ITERATION_NUM=400 UPGRADE_FILE_1="download1.bin" @@ -18,35 +19,61 @@ UPLOAD_FILE="upload.bin" check_upgrade() { + # Download upgrade image ./xvf_dfu --download $1 + # Reboot the device ./xvf_dfu -r - - sleep 3 # 3s delay + # 3s delay + sleep 3 + + # Upload upgrade image ./xvf_dfu --upload-upgrade $UPLOAD_FILE - revval=$(diff $UPLOAD_FILE $1) - echo "Output: $output" - if [[ $output == 0 ]]; then - echo "Upload is correct" + + # Compare uploaded and downloaded images + diff $UPLOAD_FILE $1 + if [[ $? == 0 ]]; then + echo "Upload of $1 is correct" else - echo "Upload is incorrect" + echo "Upload of $1 is incorrect" exit 1 fi - sleep 3 # 3s delay + # Delete downloaded image + rm $1 + # 1s delay + sleep 1 } counter=0 + +#Checking if the file exists +if [ ! -f $UPGRADE_FILE_1 ]; then + echo "File $UPGRADE_FILE_1 doesn't exist." + exit -1 +fi +if [ ! -f $UPGRADE_FILE_2 ]; then + echo "File $UPGRADE_FILE_2 doesn't exist." + exit -1 +fi +if [ ! -f $EMPTY_FILE ]; then + echo "File $EMPTY_FILE doesn't exist." + exit -1 +fi + while [ $counter -lt $ITERATION_NUM ] do counter=$(( $counter + 1 )) echo "DFU attempt number $counter" + # Download first upgrade image check_upgrade $UPGRADE_FILE_1 - check_upgrade $UPGRADE_FILE_1 + # Download second upgrade image + check_upgrade $UPGRADE_FILE_2 + # Download empty upgrade image check_upgrade $EMPTY_FILE done From 4a190c60de651313e786533ff4f1f05b0525248c Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 18 Mar 2024 15:43:30 +0000 Subject: [PATCH 095/288] Re-add remove function --- examples/asrc_demo/src/usb/usb_dfu.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/asrc_demo/src/usb/usb_dfu.c b/examples/asrc_demo/src/usb/usb_dfu.c index c442ba6a..492307e9 100644 --- a/examples/asrc_demo/src/usb/usb_dfu.c +++ b/examples/asrc_demo/src/usb/usb_dfu.c @@ -209,3 +209,11 @@ void tud_dfu_detach_cb(void) rtos_printf("Host detach, we should probably reboot\n"); reboot(); } + +static void reboot(void) +{ + rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 0x10000); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); + while(1) {;} +} \ No newline at end of file From 491212564a809d4f000d40a9de8887b040397d4e Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 20 Mar 2024 08:06:04 +0000 Subject: [PATCH 096/288] Add empty line --- examples/asrc_demo/src/usb/usb_dfu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/asrc_demo/src/usb/usb_dfu.c b/examples/asrc_demo/src/usb/usb_dfu.c index 492307e9..89f621fc 100644 --- a/examples/asrc_demo/src/usb/usb_dfu.c +++ b/examples/asrc_demo/src/usb/usb_dfu.c @@ -216,4 +216,4 @@ static void reboot(void) write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 0x10000); write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); while(1) {;} -} \ No newline at end of file +} From 42dd102f3c9480d5df60b1df7bd13f954e086c1b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 20 Mar 2024 14:14:06 +0000 Subject: [PATCH 097/288] I2C control works --- .../XK_VOICE_L71/platform/platform_conf.h | 13 +- .../XK_VOICE_L71/platform/platform_init.c | 27 +- .../XK_VOICE_L71/platform/platform_start.c | 30 +- examples/ffva/ffva.cmake | 1 + examples/ffva/src/cmd_map.h | 40 +++ examples/ffva/src/dfu/dfu_servicer.c | 263 +++++++++++++++ examples/ffva/src/dfu_cmds.h | 102 ++++++ examples/ffva/src/dfu_cmds_map.h | 29 ++ examples/ffva/src/main.c | 17 + examples/ffva/src/packet_queue.c | 110 +++++++ examples/ffva/src/packet_queue.h | 110 +++++++ examples/ffva/src/servicer.c | 311 ++++++++++++++++++ examples/ffva/src/servicer.h | 237 +++++++++++++ 13 files changed, 1261 insertions(+), 29 deletions(-) create mode 100644 examples/ffva/src/cmd_map.h create mode 100644 examples/ffva/src/dfu/dfu_servicer.c create mode 100644 examples/ffva/src/dfu_cmds.h create mode 100644 examples/ffva/src/dfu_cmds_map.h create mode 100644 examples/ffva/src/packet_queue.c create mode 100644 examples/ffva/src/packet_queue.h create mode 100644 examples/ffva/src/servicer.c create mode 100644 examples/ffva/src/servicer.h diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index 56797e2a..fa9235ed 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -109,9 +109,10 @@ * MCLK will also default to be external if this is set on * the XVF3610_Q60A board. */ -#define appconfI2C_CTRL_ENABLED 0 +#define appconfI2C_CTRL_ENABLED 1 #endif /* appconfI2C_CTRL_ENABLED */ +#define APP_CONTROL_TRANSPORT_COUNT appconfI2C_CTRL_ENABLED #ifndef appconfEXTERNAL_MCLK #if appconfI2C_CTRL_ENABLED #define appconfEXTERNAL_MCLK 1 @@ -162,6 +163,10 @@ #define appconfSPI_TASK_PRIORITY (configMAX_PRIORITIES/2) #endif /* appconfSPI_TASK_PRIORITY */ + +#define appconfDEVICE_CONTROL_I2C_PRIORITY (configMAX_PRIORITIES-1) + + /*****************************************/ /* DFU Settings */ /*****************************************/ @@ -195,14 +200,14 @@ #ifndef BOARD_QSPI_SPEC /* Set up a default SPI spec if the app has not provided * one explicitly. - * Note: The version checks only work in XTC Tools >15.2.0 - * By default FL_QUADDEVICE_W25Q64JW is used + * Note: The version checks only work in XTC Tools >15.2.0 + * By default FL_QUADDEVICE_W25Q64JW is used */ #ifdef __XMOS_XTC_VERSION_MAJOR__ #if (__XMOS_XTC_VERSION_MAJOR__ == 15) \ && (__XMOS_XTC_VERSION_MINOR__ >= 2) \ && (__XMOS_XTC_VERSION_PATCH__ >= 0) -/* In XTC >15.2.0 some SFDP support enables a generic +/* In XTC >15.2.0 some SFDP support enables a generic * default spec */ #define BOARD_QSPI_SPEC FL_QUADDEVICE_DEFAULT diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index 233f168f..3d837bdb 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -11,6 +11,8 @@ #include "platform/platform_init.h" #include "adaptive_rate_adjust.h" #include "usb_support.h" +#include "device_control.h" +#include "device_control.h" static void mclk_init(chanend_t other_tile_c) { @@ -106,15 +108,14 @@ static void i2c_init(void) { static rtos_driver_rpc_t i2c_rpc_config; -#if appconfI2C_CTRL_ENABLED -#if ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_init(i2c_slave_ctx, (1 << appconfI2C_IO_CORE), PORT_I2C_SCL, PORT_I2C_SDA, appconf_CONTROL_I2C_DEVICE_ADDR); #endif -#else + #if ON_TILE(I2C_TILE_NO) rtos_intertile_t *client_intertile_ctx[1] = {intertile_ctx}; rtos_i2c_master_init( @@ -135,7 +136,6 @@ static void i2c_init(void) &i2c_rpc_config, intertile_ctx); #endif -#endif } static void spi_init(void) @@ -317,6 +317,23 @@ static void uart_init(void) tmr_tx); #endif } +extern device_control_t *device_control_i2c_ctx; + +void control_init() { + control_ret_t ret = CONTROL_SUCCESS; +#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_TILE_NO) + ret = device_control_init(device_control_i2c_ctx, + DEVICE_CONTROL_HOST_MODE, + 1,//(NUM_TILE_0_SERVICERS + NUM_TILE_1_SERVICERS - 1), // GPO servicer does not register with I2C or SPI device control context + NULL, 0); + xassert(ret == CONTROL_SUCCESS); + + ret = device_control_start(device_control_i2c_ctx, + -1, + -1); + xassert(ret == CONTROL_SUCCESS); +#endif +} void platform_init(chanend_t other_tile_c) { @@ -332,5 +349,5 @@ void platform_init(chanend_t other_tile_c) i2s_init(); usb_init(); uart_init(); - + control_init(); } diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index 67f61e7e..2c7ddd23 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -17,7 +17,7 @@ #include "usb_support.h" #if appconfI2C_CTRL_ENABLED -#include "app_control/app_control.h" +//#include "app_control/app_control.h" #include "device_control_i2c.h" #endif @@ -47,18 +47,20 @@ static void flash_start(void) static void i2c_master_start(void) { -#if !appconfI2C_CTRL_ENABLED +//#if !appconfI2C_CTRL_ENABLED rtos_i2c_master_rpc_config(i2c_master_ctx, appconfI2C_MASTER_RPC_PORT, appconfI2C_MASTER_RPC_PRIORITY); #if ON_TILE(I2C_TILE_NO) rtos_i2c_master_start(i2c_master_ctx); #endif -#endif +//#endif } static void audio_codec_start(void) { -#if !appconfI2C_CTRL_ENABLED + printintln(100); + +//#if !appconfI2C_CTRL_ENABLED #if appconfI2S_ENABLED int ret = 0; #if ON_TILE(I2C_TILE_NO) @@ -71,21 +73,8 @@ static void audio_codec_start(void) rtos_intertile_rx_data(intertile_ctx, &ret, sizeof(ret)); #endif #endif -#endif -} - -static void i2c_slave_start(void) -{ -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) - rtos_i2c_slave_start(i2c_slave_ctx, - device_control_i2c_ctx, - (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, - (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, - (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, - (rtos_i2c_slave_tx_done_cb_t) NULL, - appconfI2C_INTERRUPT_CORE, - appconfI2C_TASK_PRIORITY); -#endif +printintln(111); +//#endif } static void spi_start(void) @@ -156,11 +145,12 @@ void platform_start(void) gpio_start(); flash_start(); i2c_master_start(); - i2c_slave_start(); audio_codec_start(); spi_start(); mics_start(); i2s_start(); usb_start(); uart_start(); + //i2c_slave_start(); + } diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index 477a197b..c5926b40 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -44,6 +44,7 @@ set(APP_LINK_OPTIONS set(APP_COMMON_LINK_LIBRARIES inferencing_tflite_micro rtos::freertos_usb + rtos::sw_services::device_control lib_src lib_sw_pll ) diff --git a/examples/ffva/src/cmd_map.h b/examples/ffva/src/cmd_map.h new file mode 100644 index 00000000..9f56b0ff --- /dev/null +++ b/examples/ffva/src/cmd_map.h @@ -0,0 +1,40 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. + +#pragma once + +#include +#include "device_control_shared.h" +#include "packet_queue.h" + +// Types of commands in the Command ID enum space - +// - Dedicated: Commands dedicated only for the resource +// - For SHF this is further spilt into dedicated SHF commands and dedicated custom commands +// - Shared: Commands shared between a resource and its servicer. These are present in the servicer's command map only though respources process them as well. (Used for special commands protocol) +// - External: space reserved for customers to add commands. +#define DEDICATED_COMMANDS_START_OFFSET (0) +#define SHARED_COMMANDS_START_OFFSET (90) +#define EXTERNAL_COMMANDS_START_OFFSET (110) +// Servicers will have commands in the above 4 categories. +// Resources will have commands in the dedicated, reserved and broadcast categories. + + +typedef enum +{ + CMD_READ_ONLY, + CMD_READ_WRITE, + CMD_WRITE_ONLY +}cmd_rw_type_t; + +typedef struct +{ + uint8_t cmd_id; + uint8_t num_vals; + uint8_t bytes_per_val; + uint8_t cmd_rw_type; +}control_cmd_info_t; + +typedef struct { + int32_t num_commands; + control_cmd_info_t *commands; +}command_map_t; diff --git a/examples/ffva/src/dfu/dfu_servicer.c b/examples/ffva/src/dfu/dfu_servicer.c new file mode 100644 index 00000000..9c0c4067 --- /dev/null +++ b/examples/ffva/src/dfu/dfu_servicer.c @@ -0,0 +1,263 @@ +// Copyright 2023-2024 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +#define DEBUG_UNIT DFU_SERVICER +#ifndef DEBUG_PRINT_ENABLE_DFU_SERVICER +#define DEBUG_PRINT_ENABLE_DFU_SERVICER 1 +#endif +#include "debug_print.h" + +#include +#include +#include +#include + +#include "platform/platform_conf.h" +#include "servicer.h" +#include "dfu_cmds.h" +#include "device_control_i2c.h" + +//#include "dfu_common.h" // for reboot() +//#include "dfu_state_machine.h" + +void dfu_servicer_init(servicer_t *servicer) +{ + #include "dfu_cmds_map.h" // Included instead of directly adding code since this file is autogenerated. + // Servicer resource info + static control_resource_info_t dfu_res_info[NUM_RESOURCES_DFU_SERVICER]; + + memset(servicer, 0, sizeof(servicer_t)); + servicer->id = DFU_CONTROLLER_SERVICER_RESID; + servicer->start_io = 0; + servicer->num_resources = NUM_RESOURCES_DFU_SERVICER; + + servicer->res_info = &dfu_res_info[0]; + // Servicer resource + servicer->res_info[0].resource = DFU_CONTROLLER_SERVICER_RESID; + servicer->res_info[0].control_pkt_queue.depth = 0; + servicer->res_info[0].control_pkt_queue.pkts = NULL; + servicer->res_info[0].command_map.num_commands = NUM_DFU_CONTROLLER_SERVICER_RESID_CMDS; + servicer->res_info[0].command_map.commands = dfu_controller_servicer_resid_cmd_map; + +} + +static void i2c_slave_start(void) +{ +#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) + #include "print.h" + printintln(222); + + rtos_i2c_slave_start(i2c_slave_ctx, + device_control_i2c_ctx, + (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, + (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, + (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, + (rtos_i2c_slave_tx_done_cb_t) NULL, + NULL, + NULL, + appconfI2C_INTERRUPT_CORE, + appconfI2C_TASK_PRIORITY); +#endif +} + +void dfu_servicer(void *args) { + device_control_servicer_t servicer_ctx; + + servicer_t *servicer = (servicer_t*)args; + xassert(servicer != NULL); + + for(int i=0; inum_resources; i++) { + servicer->res_info[i].control_pkt_queue.queue_wr_index = 0; + } + control_resid_t *resources = (control_resid_t*)pvPortMalloc(servicer->num_resources * sizeof(control_resid_t)); + for(int i=0; inum_resources; i++) + { + resources[i] = servicer->res_info[i].resource; + } + + if(appconfI2C_CTRL_ENABLED > 0) + { + control_ret_t dc_ret; + debug_printf("Calling device_control_servicer_register(), servicer ID %d, on tile %d, core %d.\n", servicer->id, THIS_XCORE_TILE, rtos_core_id_get()); + + dc_ret = device_control_servicer_register(&servicer_ctx, + device_control_ctxs, + 1, + resources, servicer->num_resources); + debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); + } + + i2c_slave_start(); + + vPortFree(resources); + printintln(350); + + // The first call to device_control_servicer_cmd_recv triggers the device_control_i2c_start_cb() through I2S slave ISR call somehow. device_control_i2c_start_cb() and device_control_servicer_cmd_recv() have to be in separate logical cores + // and be scheduled simultaneously for this to work. + if(APP_CONTROL_TRANSPORT_COUNT > 0) + { + for(;;){ + device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); + } + } + else + { + for(;;){ + vTaskDelay(pdMS_TO_TICKS(100)); + } + } + /*xTaskCreate( + dfu_int_state_machine, + "DFU state machine task", + RTOS_THREAD_STACK_SIZE(dfu_int_state_machine), + NULL, + uxTaskPriorityGet(NULL), // Same priority so should run after this task + NULL + );*/ + + if(appconfI2C_CTRL_ENABLED > 0) + { + for(;;){ + device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); + } + } + else + { + for(;;){ + vTaskDelay(pdMS_TO_TICKS(100)); + } + } +} + +control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) +{ + control_ret_t ret = CONTROL_SUCCESS; + uint8_t cmd_id = CONTROL_CMD_CLEAR_READ(cmd); + + memset(payload, 0, payload_len); + + debug_printf("dfu_servicer_read_cmd, cmd_id: %d.\n", cmd_id); + + switch (cmd_id) + { + case DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD: + { + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD\n"); + size_t upload_len = 0;//dfu_int_upload(&payload[2], DFU_DATA_XFER_SIZE); + + payload[0] = upload_len & 0xFF; + payload[1] = (upload_len >> 8) & 0xFF; + // Rest of payload is filled by dfu_int_upload function + break; + } + + case DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS: + { + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS\n"); + //dfu_int_get_status_packet_t retval; + //dfu_int_get_status(&retval); + + uint8_t time_high, time_mid, time_low; + //time_low = (uint8_t)(retval.timeout_ms & 0xFF); + //time_mid = (uint8_t)((retval.timeout_ms >> 8) & 0xFF); + //time_high = (uint8_t)((retval.timeout_ms >> 16) & 0xFF); + + //payload[0] = retval.current_status; + payload[1] = time_low; + payload[2] = time_mid; + payload[3] = time_high; + //payload[4] = retval.next_state; + + break; + } + + case DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE: + { + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE\n"); + uint8_t state = 0;//dfu_int_get_state(); + payload[0] = state; + break; + } + + case DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK: + { + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK\n"); + uint16_t transferblock = 0;//dfu_int_get_transfer_block(); + + uint8_t tb_high, tb_low; + tb_low = (uint8_t)(transferblock & 0xFF); + tb_high = (uint8_t)((transferblock >> 8) & 0xFF); + + payload[0] = tb_low; + payload[1] = tb_high; + + break; + } + + default: + { + debug_printf("DFU_CONTROLLER_SERVICER UNHANDLED COMMAND!!!\n"); + ret = CONTROL_BAD_COMMAND; + break; + } + } + + return ret; +} + +control_ret_t dfu_servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) +{ + control_ret_t ret = CONTROL_SUCCESS; + + uint8_t cmd_id = CONTROL_CMD_CLEAR_READ(cmd); + debug_printf("dfu_servicer_write_cmd cmd_id %d.\n", cmd_id); + + switch (cmd_id) + { + case DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH: + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH\n"); + //dfu_int_detach(); + break; + + case DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD: + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD\n"); + + uint16_t dnload_length = payload[0] + (payload[1] << 8); + const uint8_t * dnload_data = &payload[2]; + //dfu_int_download(dnload_length, dnload_data); + break; + + case DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS: + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS\n"); + //dfu_int_clear_status(); + break; + + case DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT: + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT\n"); + //dfu_int_abort(); + break; + + case DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE: + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE\n"); + //dfu_int_set_alternate(payload[0]); + break; + + case DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK: + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK\n"); + + uint16_t const transferblock = (payload[1] << 8) + payload[0]; + //dfu_int_set_transfer_block(transferblock); + break; + + case DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT: + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT\n"); + //reboot(); + break; + + default: + debug_printf("DFU_CONTROLLER_SERVICER UNHANDLED COMMAND!!!\n"); + ret = CONTROL_BAD_COMMAND; + break; + } + + return ret; +} diff --git a/examples/ffva/src/dfu_cmds.h b/examples/ffva/src/dfu_cmds.h new file mode 100644 index 00000000..b412901a --- /dev/null +++ b/examples/ffva/src/dfu_cmds.h @@ -0,0 +1,102 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. + +/*********************************/ +/* AUTOGENERATED. DO NOT MODIFY! */ +/* Generated using template cmd_map.h.jinja */ +/*********************************/ + +#pragma once + +#include + +// Note: The enums are wrapped around a #ifndef block to support autogenerating cmd_map files from the +// BeClearSuperHandsFree.h defines. The defines in BeClearSuperHandsFree.h are visible in the device +// but not in the host, so the #ifndef is needed to keep the cmd_map files common between device and host. + +// DFU_CONTROLLER_SERVICER_RESID commands +enum e_dfu_controller_servicer_resid_cmds +{ +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH + DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH = 0, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD + DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD = 1, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD + DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD = 2, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS + DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS = 3, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS + DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS = 4, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE + DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE = 5, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT + DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT = 6, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE + DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE = 64, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK + DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK = 65, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION + DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION = 88, +#endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT + DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT = 89, +#endif + NUM_DFU_CONTROLLER_SERVICER_RESID_CMDS = 11 +}; + +// DFU_CONTROLLER_SERVICER_RESID number of elements +// number of values of type dfu_controller_servicer_resid_dfu_detach_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH +#define DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH_NUM_VALUES (1) +// number of values of type dfu_controller_servicer_resid_dfu_dnload_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD +#define DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD_NUM_VALUES (130) +// number of values of type dfu_controller_servicer_resid_dfu_upload_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD +#define DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD_NUM_VALUES (130) +// number of values of type dfu_controller_servicer_resid_dfu_getstatus_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS +#define DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS_NUM_VALUES (5) +// number of values of type dfu_controller_servicer_resid_dfu_clrstatus_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS +#define DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS_NUM_VALUES (1) +// number of values of type dfu_controller_servicer_resid_dfu_getstate_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE +#define DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE_NUM_VALUES (1) +// number of values of type dfu_controller_servicer_resid_dfu_abort_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT +#define DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT_NUM_VALUES (1) +// number of values of type dfu_controller_servicer_resid_dfu_setalternate_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE +#define DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE_NUM_VALUES (1) +// number of values of type dfu_controller_servicer_resid_dfu_transferblock_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK +#define DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK_NUM_VALUES (2) +// number of values of type dfu_controller_servicer_resid_dfu_getversion_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION +#define DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION_NUM_VALUES (3) +// number of values of type dfu_controller_servicer_resid_dfu_reboot_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT +#define DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT_NUM_VALUES (1) + +// DFU_CONTROLLER_SERVICER_RESID types +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH +typedef uint8_t dfu_controller_servicer_resid_dfu_detach_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD +typedef uint8_t dfu_controller_servicer_resid_dfu_dnload_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD +typedef uint8_t dfu_controller_servicer_resid_dfu_upload_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS +typedef uint8_t dfu_controller_servicer_resid_dfu_getstatus_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS +typedef uint8_t dfu_controller_servicer_resid_dfu_clrstatus_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE +typedef uint8_t dfu_controller_servicer_resid_dfu_getstate_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT +typedef uint8_t dfu_controller_servicer_resid_dfu_abort_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE +typedef uint8_t dfu_controller_servicer_resid_dfu_setalternate_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK +typedef uint8_t dfu_controller_servicer_resid_dfu_transferblock_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION +typedef uint8_t dfu_controller_servicer_resid_dfu_getversion_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT +typedef uint8_t dfu_controller_servicer_resid_dfu_reboot_t; diff --git a/examples/ffva/src/dfu_cmds_map.h b/examples/ffva/src/dfu_cmds_map.h new file mode 100644 index 00000000..020f0a3d --- /dev/null +++ b/examples/ffva/src/dfu_cmds_map.h @@ -0,0 +1,29 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. + +/*********************************/ +/* AUTOGENERATED. DO NOT MODIFY! */ +/* Generated using template cmd_map_impl.h.jinja */ +/*********************************/ + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-variable" + +// DFU_CONTROLLER_SERVICER_RESID command map +// This array may be unused as servicers can be moved between tiles +// Unused variable warnings are suppressed in this header file +static control_cmd_info_t dfu_controller_servicer_resid_cmd_map[] = +{ + { DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD, 130, sizeof(uint8_t), CMD_WRITE_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD, 130, sizeof(uint8_t), CMD_READ_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS, 5, sizeof(uint8_t), CMD_READ_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE, 1, sizeof(uint8_t), CMD_READ_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK, 2, sizeof(uint8_t), CMD_READ_WRITE }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION, 3, sizeof(uint8_t), CMD_READ_ONLY }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, +}; +#pragma clang diagnostic pop \ No newline at end of file diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index d38aeba8..47a95224 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -19,9 +19,11 @@ #include "app_conf.h" #include "platform/platform_init.h" #include "platform/driver_instances.h" +#include "platform/platform_conf.h" #include "usb_support.h" #include "usb_audio.h" #include "audio_pipeline.h" +#include "servicer.h" /* Headers used for the WW intent engine */ #if appconfINTENT_ENABLED @@ -372,6 +374,21 @@ void startup_task(void *arg) gpio_test(gpio_ctx_t0); #endif +#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) + // Initialise control related things + servicer_t servicer_dfu; + dfu_servicer_init(&servicer_dfu); + + xTaskCreate( + dfu_servicer, + "DFU servicer", + RTOS_THREAD_STACK_SIZE(servicer_task), + &servicer_dfu, + appconfDEVICE_CONTROL_I2C_PRIORITY, + NULL + ); +#endif + #if appconfINTENT_ENABLED && ON_TILE(0) led_task_create(appconfLED_TASK_PRIORITY, NULL); #endif diff --git a/examples/ffva/src/packet_queue.c b/examples/ffva/src/packet_queue.c new file mode 100644 index 00000000..6ef40fe0 --- /dev/null +++ b/examples/ffva/src/packet_queue.c @@ -0,0 +1,110 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +#include +#include +#include "debug_print.h" +#include "device_control_shared.h" +#include "packet_queue.h" + +// Return pointer to the first free pkt in the queue +control_ret_t queue_get_free_pkt(control_pkt_t **pkt, control_pkt_queue_t *control_pkt_queue) +{ + // queue_wr_index should always point to the first free packet that can be used. Since commands are always processed in order, + // we shouldn't look beyond queue_wr_index when searching for a free packet. + *pkt = &control_pkt_queue->pkts[control_pkt_queue->queue_wr_index]; + if((*pkt)->pkt_status != PKT_FREE) + { + if ((*pkt)->pkt_status == PKT_DONE && // Resource is done processing the pkt + ((*pkt)->free_when_done) // pkt set to be freed when completed + ) + { + // If this pkt was used for a special command original payload ptr might have been overwritten. + if((*pkt)->save_payload_ptr != NULL) // Payload ptr was overwritten earlier for a special command + { + // Restore original payload ptr + (*pkt)->payload = (*pkt)->save_payload_ptr; + (*pkt)->save_payload_ptr = NULL; + } + (*pkt)->pkt_status = PKT_FREE; + } + else { + debug_printf("ERROR: Control packet queue FULL\n"); + (*pkt) = NULL; + return SERVICER_QUEUE_FULL; + } + } + return CONTROL_SUCCESS; +} + +// Add the packet at queue_wr_index to queue. +void queue_add_pkt(control_pkt_queue_t *control_pkt_queue, int32_t free_when_done) +{ + control_pkt_queue->pkts[control_pkt_queue->queue_wr_index].pkt_status = PKT_WAIT; + control_pkt_queue->pkts[control_pkt_queue->queue_wr_index].free_when_done = free_when_done; + + // Update queue_wr_index + control_pkt_queue->queue_wr_index = (control_pkt_queue->queue_wr_index + 1) % control_pkt_queue->depth; +} + +control_ret_t queue_write_command_to_free_packet(control_pkt_queue_t *control_pkt_queue, control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) { + // Make sure we're writing to a free packet + control_pkt_t *pkt; + control_ret_t ret = queue_get_free_pkt(&pkt, control_pkt_queue); + if(ret != CONTROL_SUCCESS) + { + return ret; + } + pkt->res_id = resid; + pkt->cmd_id = cmd; + pkt->payload_len = payload_len; + if(!IS_CONTROL_CMD_READ(pkt->cmd_id)) { // Copy payload for write commands. + memcpy(pkt->payload, &payload[0], pkt->payload_len * sizeof(uint8_t)); + } + // Add packet to the queue + /** free_when_done set to 1 for both read and write packets. This is to ensure + * that the packet queue doesn't get full if the host gives up on a read packet + * and stops retrying for the read response. While this would never happen with our + * host app design, this would make a more robust design. + */ + queue_add_pkt(control_pkt_queue, 1); + + //debug_printf("Cmd queue write index = %d\n", control_pkt_queue->queue_wr_index); + return CONTROL_SUCCESS; +} + +// Check if command is present in the packet. +// Return pkt pointer if present, NULL otherwise +control_pkt_t* queue_check_packet(control_pkt_queue_t *pkt_queue, control_cmd_t cmd) +{ + for(int i=0; idepth; i++) + { + if(pkt_queue->pkts[i].cmd_id == cmd) + { + if(pkt_queue->pkts[i].pkt_status != PKT_FREE) + { + return &pkt_queue->pkts[i]; + } + } + } + return NULL; +} + +// Return the packet that queue_rd_index is pointing to +control_pkt_t* queue_read_packet(control_pkt_queue_t *pkt_queue) +{ + // Packets read in order so only check packet at read_pointer location + if(pkt_queue->pkts[pkt_queue->queue_rd_index].pkt_status == PKT_WAIT) + { + return &pkt_queue->pkts[pkt_queue->queue_rd_index]; + } + return NULL; +} + +void queue_set_packet_done(control_pkt_queue_t *pkt_queue) +{ + // Set the packet the current queue_rd_index is pointing to to DONE. Increment queue_rd_index + pkt_queue->pkts[pkt_queue->queue_rd_index].pkt_status = PKT_DONE; + pkt_queue->queue_rd_index = (pkt_queue->queue_rd_index + 1) % pkt_queue->depth; +} + + diff --git a/examples/ffva/src/packet_queue.h b/examples/ffva/src/packet_queue.h new file mode 100644 index 00000000..1c0f69f3 --- /dev/null +++ b/examples/ffva/src/packet_queue.h @@ -0,0 +1,110 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +#pragma once +#include "device_control_shared.h" + +/** + * Clears the read bit on a command code + * + * \param[in,out] c The command code to clear the read bit on. + */ +#define CONTROL_CMD_CLEAR_READ(c) ((c) & ~0x80) + +/** + * @brief Control packet status + * + */ +typedef enum { + PKT_FREE, /// Packet is free and available for the servicer to write into + PKT_WAIT, /// Packet contains a command written by the servicer and is waiting to be processed by the resource + PKT_DONE /// Packet contains a command that has been processed by the underlying resource +}pkt_status_t; + +/** + * @brief Control packet structure + * + */ +typedef struct { + uint8_t res_id; /// Resource ID of the command in the packet + uint8_t cmd_id; /// Command ID for the command in the packet + int32_t payload_len; /// packet payload length in bytes + pkt_status_t pkt_status; /// packet status + void *payload; /// packet payload pointer + uint8_t *save_payload_ptr; /// Holds a copy of the original payload ptr when the original payload_ptr gets overwritten during special command processing + int32_t free_when_done; /// Free packet once its done by the resource. When looking for a free packet, the pkt queue function queue_get_free_pkt() will free a done packet only if this flag is set to 1. +}control_pkt_t; + +/** + * @brief Control packet queue structure + * + */ +typedef struct { + control_pkt_t *pkts; /// Array of packets that make up this queue + int32_t depth; /// Queue depth in number of packets + int32_t queue_wr_index; /// Queue current write index. This points to the packet that will be written to when the servicer attempts to add a packet to the queue. + int32_t queue_rd_index; /// Queue current read index. This points to the packet that the resource will read when it wants to service a command. +}control_pkt_queue_t; + +/** + * @brief Add a command to the pkt queue. + * + * @param control_pkt_queue Pointer to the packet queue object + * @param resid Command resource ID + * @param cmd Command command ID + * @param payload Command payload buffer + * @param payload_len Payload buffer length + * @return CONTROL_SUCCESS if command added to the queue, SERVICER_QUEUE_FULL if queue is full and command cannot be added + */ +control_ret_t queue_write_command_to_free_packet(control_pkt_queue_t *control_pkt_queue, control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); + +/** + * @brief Check if a command is present in the packet queue + * + * @param pkt_queue Pointer to the pkt queue objects + * @param cmd Command ID + * @return Pointer to the pkt object holding the command if command is present in the queue, NULL otherwise. + */ +control_pkt_t* queue_check_packet(control_pkt_queue_t *pkt_queue, control_cmd_t cmd); + +/** + * @brief Return the packet that queue's current read pointer is pointing to + * + * @param pkt_queue Pointer to the pkt queue object + * @return Pointer to the packet object if read pointer is pointing to a valid packet, NULL otherwise. + */ +control_pkt_t* queue_read_packet(control_pkt_queue_t *pkt_queue); + +/** + * @brief Set the packet which is at the current_rd_index in the queue to DONE. + * Increment the current_rd_index to point to the next packet that needs processing + * + * @param pkt_queue Pointer to the packet queue object + */ +void queue_set_packet_done(control_pkt_queue_t *pkt_queue); + +// Return pointer to the first free pkt in the queue +/** + * @brief Return pointer to a free packet in the control packet queue + * + * This function returns a free packet pointer which can then be written with a command by + * the servicer and added to the queue. + * Since packets are written to the queue in order, this function only looks at the packet at the queue_wr_index + * and returns the pointer to it if that packet is free or can be freed. + * + * @param pkt free packet pointer that is returned for use by the servicer + * @param control_pkt_queue Pointer to the control packet queue + * @return control_ret_t CONTROL_SUCCESS if a free packet pointer is updated in *pkt, SERVICER_QUEUE_FULL + * if the queue is full. + */ +control_ret_t queue_get_free_pkt(control_pkt_t **pkt, control_pkt_queue_t *control_pkt_queue); + +/** + * @brief Add packet to the control packet queue + * + * This command adds the packet at queue_wr_index to the queue. This means that the packet has now been written + * with a command from the servicer and is waiting for being serviced by the resource. + * + * @param control_pkt_queue Pointer to the control packet queue + * @param free_when_done When set to 1, queue_get_free_pkt() frees the pkt when the resource marks it as Done. + */ +void queue_add_pkt(control_pkt_queue_t *control_pkt_queue, int32_t free_when_done); diff --git a/examples/ffva/src/servicer.c b/examples/ffva/src/servicer.c new file mode 100644 index 00000000..f6afdae4 --- /dev/null +++ b/examples/ffva/src/servicer.c @@ -0,0 +1,311 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +#define DEBUG_UNIT SERVICER_TASK +#ifndef DEBUG_PRINT_ENABLE_SERVICER_TASK + #define DEBUG_PRINT_ENABLE_SERVICER_TASK 1 +#endif +#include "debug_print.h" +#include +#include +#include "platform/platform_conf.h" +#include "device_control_i2c.h" +#include "servicer.h" +//#include "control_init.h" + + +#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +static device_control_t device_control_i2c_ctx_s; +device_control_t *device_control_i2c_ctx = (device_control_t *) &device_control_i2c_ctx_s; +device_control_t *device_control_ctxs[APP_CONTROL_TRANSPORT_COUNT] = { + (device_control_t *) &device_control_i2c_ctx_s, +}; +#endif +static void i2c_slave_start(void) +{ +#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) + #include "print.h" + printintln(222); + + rtos_i2c_slave_start(i2c_slave_ctx, + device_control_i2c_ctx, + (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, + (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, + (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, + (rtos_i2c_slave_tx_done_cb_t) NULL, + NULL, + NULL, + appconfI2C_INTERRUPT_CORE, + appconfI2C_TASK_PRIORITY); +#endif +} + +// Generic Servicer task. +void servicer_task(void *args) { + device_control_servicer_t servicer_ctx; + + servicer_t *servicer = (servicer_t*)args; + xassert(servicer != NULL); + + for(int i=0; inum_resources; i++) { + servicer->res_info[i].control_pkt_queue.queue_wr_index = 0; + } + + control_resid_t *resources = (control_resid_t*)pvPortMalloc(servicer->num_resources * sizeof(control_resid_t)); + for(int i=0; inum_resources; i++) + { + resources[i] = servicer->res_info[i].resource; + } + + if(appconfI2C_CTRL_ENABLED > 0) + { + control_ret_t dc_ret; + debug_printf("Calling device_control_servicer_register(), servicer ID %d, on tile %d, core %d.\n", servicer->id, THIS_XCORE_TILE, rtos_core_id_get()); + + dc_ret = device_control_servicer_register(&servicer_ctx, + device_control_ctxs, + appconfI2C_CTRL_ENABLED, + resources, servicer->num_resources); + debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); + } + + // Start I2C and SPI slave. + // This ends up calling device_control_resources_register() which has a strange non-yielding implementation, + // where it waits for servicers to register with the device control context. That's why, control_start_io_tasks() + // is not called from platform_start(). Additionally, if there is only one xcore core dedicated for all RTOS tasks, + // this design will not work, since device_control_resources_register() will not yield and the servicers wouldn't get + // scheduled so they could register with the device control leading to an eventual timeout error from device_control_resources_register(). + printintln(333); + if(1)//servicer->start_io == (int32_t)1) + { + i2c_slave_start(); + } + vPortFree(resources); + printintln(350); + + // The first call to device_control_servicer_cmd_recv triggers the device_control_i2c_start_cb() through I2S slave ISR call somehow. device_control_i2c_start_cb() and device_control_servicer_cmd_recv() have to be in separate logical cores + // and be scheduled simultaneously for this to work. + if(APP_CONTROL_TRANSPORT_COUNT > 0) + { + for(;;){ + device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); + } + } + else + { + for(;;){ + vTaskDelay(pdMS_TO_TICKS(100)); + } + } +} + +//-----------------Servicer read write callback functions-----------------------// +DEVICE_CONTROL_CALLBACK_ATTR +control_ret_t read_cmd(control_resid_t resid, control_cmd_t cmd, uint8_t *payload, size_t payload_len, void *app_data) +{ + control_ret_t ret = CONTROL_SUCCESS; + servicer_t *servicer = (servicer_t*)app_data; + + // For read commands, payload[0] is reserved from status. So payload_len is one more than the payload_len stored in the resource command map + payload_len -= 1; + uint8_t *payload_ptr = &payload[1]; //Excluding the status byte, which is updated later. + + debug_printf("Servicer ID %d on tile %d received READ command %02x for resid %02x\n\t",servicer->id, THIS_XCORE_TILE, cmd, resid); + debug_printf("The command is requesting %d bytes\n\t", payload_len); + + + control_resource_info_t *current_res_info = get_res_info(resid, servicer); + xassert(current_res_info != NULL); // This should never happen + control_cmd_info_t *current_cmd_info; + ret = validate_cmd(¤t_cmd_info, current_res_info, cmd, payload_ptr, payload_len); + if(ret != CONTROL_SUCCESS) + { + payload[0] = ret; // Update status in byte 0 + return ret; + } + // Check if command is for the servicer itself + if(current_res_info->resource == servicer->res_info[0].resource) + { + ret = servicer_read_cmd(current_res_info, cmd, payload_ptr, payload_len); + payload[0] = ret; + return ret; + } + + int32_t cmd_handled = 0; + // If special command then handle through special command handler + /*ret = special_read_cmd_handler(current_res_info, cmd, payload_ptr, payload_len, &cmd_handled); + if(cmd_handled) // Command handled by the special command handler + { + payload[0] = ret; + return ret; + }*/ + + // Read from one of the underlying resources + ret = servicer_read_from_resource(current_res_info, cmd, payload_ptr, payload_len); + payload[0] = ret; + return ret; +} + +DEVICE_CONTROL_CALLBACK_ATTR +control_ret_t write_cmd(control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len, void *app_data) +{ + control_ret_t ret = CONTROL_SUCCESS; + servicer_t *servicer = (servicer_t*)app_data; + //debug_printf("Device control WRITE. Servicer ID %d\n\t", servicer->id); + + debug_printf("Servicer ID %d on tile %d received WRITE command %02x for resid %02x\n\t", servicer->id, THIS_XCORE_TILE, cmd, resid); + debug_printf("The command has %d bytes\n\t", payload_len); + + control_resource_info_t *current_res_info = get_res_info(resid, servicer); + xassert(current_res_info != NULL); + control_cmd_info_t *current_cmd_info; + ret = validate_cmd(¤t_cmd_info, current_res_info, cmd, payload, payload_len); + if(ret != CONTROL_SUCCESS) + { + return ret; + } + // Check if command is for the servicer itself + if(current_res_info->resource == servicer->res_info[0].resource) + { + ret = servicer_write_cmd(current_res_info, cmd, payload, payload_len); + return ret; + } + + // If special command then handle through special command handler + /*int32_t cmd_handled = 0; + ret = special_write_cmd_handler(current_res_info, cmd, payload, payload_len, &cmd_handled); + if(cmd_handled) // Command handled by the special command handler + { + return ret; + }*/ + + // Command is for one of the underlying resources + ret = servicer_write_to_resource(current_res_info, cmd, payload, payload_len); + return ret; +} + +// Initialise packet payload pointers to point to valid memory. + + +//-----------------Servicer helper functions-----------------------// +// Return a pointer to the control_cmd_info_t structure for a given command ID. Return NULL if command not found in the +// command map for the resource. +control_cmd_info_t* get_cmd_info(uint8_t cmd_id, const control_resource_info_t *res_info) +{ + for(int i=0; icommand_map.num_commands; i++) + { + if(res_info->command_map.commands[i].cmd_id == cmd_id) + { + return &res_info->command_map.commands[i]; + } + } + return NULL; +} + +// Return a pointer to the servicer's control_resource_info_t structure for a given resource ID. +// Return NULL if the resource ID is not found in the list of resources serviced by the servicer. +control_resource_info_t* get_res_info(control_resid_t resource, const servicer_t *servicer) +{ + for(int res=0; resnum_resources; res++) + { + // Get the cmd_info for the current command + if(servicer->res_info[res].resource == resource) + { + return &servicer->res_info[res]; + } + } + return NULL; +} + +// Validate the command from the host against that commands information in the stored command_map +control_ret_t validate_cmd(control_cmd_info_t **cmd_info, + control_resource_info_t *res_info, + control_cmd_t cmd, + const uint8_t *payload, + size_t payload_len) +{ + control_ret_t ret = CONTROL_SUCCESS; + *cmd_info = get_cmd_info(CONTROL_CMD_CLEAR_READ(cmd), res_info); + if(*cmd_info == NULL) + { + return SERVICER_WRONG_COMMAND_ID; + } + + // Validate non special command length + // Don't do payload check for special commands since for the last filter chunk, host might request less than the payload length specified in the cmd_map + + if(payload_len != (*cmd_info)->bytes_per_val * (*cmd_info)->num_vals) + { + return SERVICER_WRONG_COMMAND_LEN; + } + // Payload validation happens either on the host or in the specific command handlers. No generic payload validation is done in the servicer. + + return ret; +} + +// Process write commands directed to the servicer resource +control_ret_t servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) +{ + // Handle write commands directed to a servicer resource + switch(res_info->resource) + { + case DFU_CONTROLLER_SERVICER_RESID: + return dfu_servicer_write_cmd(res_info, cmd, payload, payload_len); + break; + } + return CONTROL_SUCCESS; +} + +// Process read commands directed to the servicer resource +control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) +{ + control_ret_t ret = CONTROL_SUCCESS; + // Handle read commands directed to a servicer resource + switch(res_info->resource) + { + case DFU_CONTROLLER_SERVICER_RESID: + ret = dfu_servicer_read_cmd(res_info, cmd, payload, payload_len); + break; + } + return ret; +} + +// Forward read command to underlying resource +control_ret_t servicer_read_from_resource(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) +{ + // Process read cmd from underlying resource + + // Check if the read command is already in the queue + control_pkt_t* cmd_pkt = queue_check_packet(&res_info->control_pkt_queue, cmd); + // If cmd already in queue + if(cmd_pkt != NULL) + { + switch(cmd_pkt->pkt_status) + { + case PKT_DONE: + debug_printf("Read cmd and status DONE found in queue\n"); + memcpy(payload, cmd_pkt->payload, payload_len); + cmd_pkt->pkt_status = PKT_FREE; + return CONTROL_SUCCESS; + case PKT_WAIT: + debug_printf("Read cmd with status PKT_WAIT found in queue\n"); + return SERVICER_COMMAND_RETRY; + default: + xassert(0); + } + } + + // Otherwise add command to queue + control_ret_t ret = queue_write_command_to_free_packet(&res_info->control_pkt_queue, res_info->resource, cmd, payload, payload_len); + if (ret == CONTROL_SUCCESS) // Since we've just added the command to queue return retry for host to query for response later + { + return SERVICER_COMMAND_RETRY; + } + return ret; +} + +// Forward write command to underlying resource +control_ret_t servicer_write_to_resource(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) +{ + control_ret_t ret = queue_write_command_to_free_packet(&res_info->control_pkt_queue, res_info->resource, cmd, payload, payload_len); + return ret; +} diff --git a/examples/ffva/src/servicer.h b/examples/ffva/src/servicer.h new file mode 100644 index 00000000..40cf7127 --- /dev/null +++ b/examples/ffva/src/servicer.h @@ -0,0 +1,237 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +#pragma once +#include "device_control.h" +#include "packet_queue.h" +#include "cmd_map.h" + +#define DFU_CONTROLLER_SERVICER_RESID 240 +#define NUM_RESOURCES_DFU_SERVICER (1) // DFU servicer + +extern device_control_t *device_control_i2c_ctx; +extern device_control_t *device_control_ctxs[1]; + +// For handling special commands (Not prototyped so isn't final) +typedef struct { + uint8_t *special_commands_buffer; + int32_t special_cmd_in_progress; /// Flag indicating if a special command is already in progress + int32_t start_host_coeff_index; /// stat coefficient index requested by the host + int32_t special_cmds_buf_size; /// special data chunk size plus extra space for cases when the chunk size is not a multiple of the payload length sent by the host + int32_t special_cmd_payload_size; /// Expected special command payload size in samples + int32_t start_buf_coeff_index; /// Absolute filter coefficient index in special_commands_buffer[0] + int32_t end_buf_coeff_index; /// Absolute index of the last valid filter coefficient index in the special_commands_buffer buffer + /** No. of coefficients worth of overflow space added to the special cmds buffer to accomodate the special commands buffer size not being a multiple of no. of coefficients + * sent per control packet. Used for error checking in the special commands handler.*/ + int32_t special_cmds_buf_overflow_size; +}special_cmd_handler_t; + +// Structure encapsulating all the information about a resource +typedef struct +{ + control_resid_t resource; + command_map_t command_map; + control_pkt_queue_t control_pkt_queue; + special_cmd_handler_t special_cmd_handler; +}control_resource_info_t; + +typedef struct { + uint32_t start_io; // set to 1 on one servicer per tile to make it responsible for starting the IO tasks on that tile. + int32_t id; // Unique ID for the servicer. Used for debugging. + // Num resources + int32_t num_resources; + // Resource ID and command map for every resource + control_resource_info_t *res_info; +}servicer_t; + +// Servicer tasks +/** + * @brief Generic servicer task. + * The servicers that only receive and process requests from the host are implemented as the generic servicer. + * + * \param servicer Pointer to the Servicer's state data structure + */ +void servicer_task(void *servicer); + +/** + * @brief IO config servicer task. + * \param servicer Pointer to the Servicer's state data structure + */ +void io_config_servicer(void *args); + +/** + * @brief GPO servicer task. + * + * This task handles GPO commands and drives the pins on the GPO port. + * + * \param servicer Pointer to the Servicer's state data structure + */ +void gpo_servicer(void *args); + +/** + * @brief Application servicer task. + * + * This task handles Application level commands such as firmware information. + * + * \param servicer Pointer to the Servicer's state data structure + */ +void application_servicer(void *args); + +/** + * @brief DFU servicer task. + * + * This task handles DFU commands from the device control interface and relays + * them to the internal DFU INT state machine. + * + * \param args Pointer to the Servicer's state data structure + */ +void dfu_servicer(void *args); + +// Servicer initialization functions +/** + * @brief DFU servicer initialisation function. + * \param servicer Pointer to the Servicer's state data structure + */ +void dfu_servicer_init(servicer_t *servicer); + +// Servicer device_control callback functions +/** + * @brief Device control callback function to handle a read command. + * + * @param resid Resource ID of the command + * @param cmd Command ID of the command + * @param payload Pointer to the payload buffer that needs to be updated with the read command response. + * @param payload_len Length of the payload buffer + * @param app_data Application specific data. + * @return CONTROL_SUCCESS is command is handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +extern DEVICE_CONTROL_CALLBACK_ATTR +control_ret_t read_cmd(control_resid_t resid, control_cmd_t cmd, uint8_t *payload, size_t payload_len, void *app_data); + +/** + * @brief Device control callback function to handle a write command + * + * @param resid Resource ID of the command + * @param cmd Command ID of the command + * @param payload Pointer to the payload buffer containing the payload the host wants to write to the device. + * @param payload_len Length of the payload buffer + * @param app_data Application specific data + * @return CONTROL_SUCCESS is command is handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +extern DEVICE_CONTROL_CALLBACK_ATTR +control_ret_t write_cmd(control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len, void *app_data); + +// Servicer helper functions +/** + * @brief Check if a command exists in the command map for a given resource and return the pointer to the cmd info object for the given command + * + * @param cmd_id Command ID for which the control_cmd_info_t object is required. + * @param res_info Pointer to the resource info which contains all the command info objects for a given resource. + * @return Pointer to a control_cmd_info_t object when one is found for the given Command ID, NULL otherwise. + */ +control_cmd_info_t* get_cmd_info(uint8_t cmd_id, const control_resource_info_t *res_info); + +/** + * @brief Check if a resource ID is one of the resources supported by a servicer and return a pointer to the resource info object. + * + * @param resource Resource ID that needs to be checked. + * @param servicer Pointer to the servicer state structure that holds all the resource info objects for the servicer. + * @return Pointer to the resource_info_t object when one is found for a given resource ID, NULL otherwise. + */ +control_resource_info_t* get_res_info(control_resid_t resource, const servicer_t *servicer); + +/** + * @brief Validate the command received in the servicer and return a pointer to the command info if the command is valid. + * + * All checks related to the command such as command ID being valid, correctness of the payload length, + * validation of the actual payload for write commands are done in this function. + * + * @param cmd_info Pointer in which the address of the command info is returned if this is a valid command/ + * @param res_info Resource info of the resource the command is meant for. + * @param cmd Command ID of the command that needs validating + * @param payload Payload buffer of the command that needs validating. + * @param payload_len Payload length of the payload. + * @return CONTROL_SUCCESS if the command is found to be valid for the given resource. control_ret_t error otherwise + * indicating the error code of the specific validation check that failed. + */ +control_ret_t validate_cmd(control_cmd_info_t **cmd_info, + control_resource_info_t *res_info, + control_cmd_t cmd, + const uint8_t *payload, + size_t payload_len); + +/** + * @brief Function for handling write commands directed to the servicer resource itself. + * + * @param resid Command Resource ID + * @param cmd Command Command ID + * @param payload Command write payload buffer + * @param payload_len Length of the payload buffer + * @return CONTROL_SUCCESS if write command processed successfully. contro_ret_t error status otherwise. + */ +control_ret_t servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); + +/** + * @brief Function for handling read commands directed to the servicer resource itself. + * + * @param resid Command Resource ID + * @param cmd Command Command ID + * @param payload Payload buffer to populate with the read response + * @param payload_len Length of the payload buffer + * @return CONTROL_SUCCESS if read command processed successfully. contro_ret_t error status otherwise. + */ +control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); + +/** + * @brief Function for getting a read command processed by the servicer's underlying resource + * + * @param res_info Command Resource ID + * @param cmd Command command IO + * @param payload Payload buffer to populate with the read response + * @param payload_len Length of the payload buffer + * @return CONTROL_SUCCESS if read command processed successfully. contro_ret_t error status otherwise. + */ +control_ret_t servicer_read_from_resource(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); + +/** + * @brief Function for getting a write command processed by the servicer's underlying resource + * + * @param res_info Command Resource ID + * @param cmd Command command IO + * @param payload Command write payload buffer + * @param payload_len Length of the payload buffer + * @return CONTROL_SUCCESS if write command processed successfully. contro_ret_t error status otherwise. + */ +control_ret_t servicer_write_to_resource(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); + +/** + * @brief DFU servicer read command handler + * + * Handles read commands dedicated to the DFU servicer resource + * + * @param res_info Resource info of the current command + * @param cmd Command ID of this command + * @param payload Pointer to the payload that contains the write data + * @param payload_len Length in bytes of the write command payload + * @return control_ret_t CONTROL_SUCCESS if command handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); + +/** + * @brief DFU servicer write command handler + * + * Handles write commands dedicated to the DFU servicer resource + * + * @param res_info Resource info of the current command + * @param cmd Command ID of this command + * @param payload Pointer to the payload that contains the write data + * @param payload_len Length in bytes of the write command payload + * @return control_ret_t CONTROL_SUCCESS if command handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +control_ret_t dfu_servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); + + +void hid_in_servicer(void *args); From fb5784f1a67db228a5e282d3c94ac6889b463226 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 20 Mar 2024 15:50:03 +0000 Subject: [PATCH 098/288] Clean up code --- .../XK_VOICE_L71/platform/platform_start.c | 10 -- examples/ffva/src/dfu/dfu_servicer.c | 106 +++++++------ examples/ffva/src/main.c | 2 +- examples/ffva/src/servicer.c | 146 +----------------- examples/ffva/src/servicer.h | 53 ------- 5 files changed, 59 insertions(+), 258 deletions(-) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index 2c7ddd23..6ecb12b4 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -17,7 +17,6 @@ #include "usb_support.h" #if appconfI2C_CTRL_ENABLED -//#include "app_control/app_control.h" #include "device_control_i2c.h" #endif @@ -47,20 +46,15 @@ static void flash_start(void) static void i2c_master_start(void) { -//#if !appconfI2C_CTRL_ENABLED rtos_i2c_master_rpc_config(i2c_master_ctx, appconfI2C_MASTER_RPC_PORT, appconfI2C_MASTER_RPC_PRIORITY); #if ON_TILE(I2C_TILE_NO) rtos_i2c_master_start(i2c_master_ctx); #endif -//#endif } static void audio_codec_start(void) { - printintln(100); - -//#if !appconfI2C_CTRL_ENABLED #if appconfI2S_ENABLED int ret = 0; #if ON_TILE(I2C_TILE_NO) @@ -73,8 +67,6 @@ static void audio_codec_start(void) rtos_intertile_rx_data(intertile_ctx, &ret, sizeof(ret)); #endif #endif -printintln(111); -//#endif } static void spi_start(void) @@ -151,6 +143,4 @@ void platform_start(void) i2s_start(); usb_start(); uart_start(); - //i2c_slave_start(); - } diff --git a/examples/ffva/src/dfu/dfu_servicer.c b/examples/ffva/src/dfu/dfu_servicer.c index 9c0c4067..cd16fcd4 100644 --- a/examples/ffva/src/dfu/dfu_servicer.c +++ b/examples/ffva/src/dfu/dfu_servicer.c @@ -40,25 +40,6 @@ void dfu_servicer_init(servicer_t *servicer) } -static void i2c_slave_start(void) -{ -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) - #include "print.h" - printintln(222); - - rtos_i2c_slave_start(i2c_slave_ctx, - device_control_i2c_ctx, - (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, - (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, - (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, - (rtos_i2c_slave_tx_done_cb_t) NULL, - NULL, - NULL, - appconfI2C_INTERRUPT_CORE, - appconfI2C_TASK_PRIORITY); -#endif -} - void dfu_servicer(void *args) { device_control_servicer_t servicer_ctx; @@ -86,10 +67,26 @@ void dfu_servicer(void *args) { debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); } - i2c_slave_start(); - + #if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) + // Start I2C slave. + // This ends up calling device_control_resources_register() which has a strange non-yielding implementation, + // where it waits for servicers to register with the device control context. That's why, control_start_io_tasks() + // is not called from platform_start(). Additionally, if there is only one xcore core dedicated for all RTOS tasks, + // this design will not work, since device_control_resources_register() will not yield and the servicers wouldn't get + // scheduled so they could register with the device control leading to an eventual timeout error from device_control_resources_register(). + + rtos_i2c_slave_start(i2c_slave_ctx, + device_control_i2c_ctx, + (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, + (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, + (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, + (rtos_i2c_slave_tx_done_cb_t) NULL, + NULL, + NULL, + appconfI2C_INTERRUPT_CORE, + appconfI2C_TASK_PRIORITY); +#endif vPortFree(resources); - printintln(350); // The first call to device_control_servicer_cmd_recv triggers the device_control_i2c_start_cb() through I2S slave ISR call somehow. device_control_i2c_start_cb() and device_control_servicer_cmd_recv() have to be in separate logical cores // and be scheduled simultaneously for this to work. @@ -142,7 +139,8 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD\n"); - size_t upload_len = 0;//dfu_int_upload(&payload[2], DFU_DATA_XFER_SIZE); + // TODO: Undo the change below when the rest of DFU code is included + size_t upload_len = 0; //dfu_int_upload(&payload[2], DFU_DATA_XFER_SIZE); payload[0] = upload_len & 0xFF; payload[1] = (upload_len >> 8) & 0xFF; @@ -153,19 +151,20 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS\n"); - //dfu_int_get_status_packet_t retval; - //dfu_int_get_status(&retval); - - uint8_t time_high, time_mid, time_low; - //time_low = (uint8_t)(retval.timeout_ms & 0xFF); - //time_mid = (uint8_t)((retval.timeout_ms >> 8) & 0xFF); - //time_high = (uint8_t)((retval.timeout_ms >> 16) & 0xFF); - - //payload[0] = retval.current_status; - payload[1] = time_low; - payload[2] = time_mid; - payload[3] = time_high; - //payload[4] = retval.next_state; + // TODO: Enable lines below when the rest of DFU code is included + // dfu_int_get_status_packet_t retval; + // dfu_int_get_status(&retval); + + // uint8_t time_high, time_mid, time_low; + // time_low = (uint8_t)(retval.timeout_ms & 0xFF); + // time_mid = (uint8_t)((retval.timeout_ms >> 8) & 0xFF); + // time_high = (uint8_t)((retval.timeout_ms >> 16) & 0xFF); + + // payload[0] = retval.current_status; + // payload[1] = time_low; + // payload[2] = time_mid; + // payload[3] = time_high; + // payload[4] = retval.next_state; break; } @@ -173,7 +172,8 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE\n"); - uint8_t state = 0;//dfu_int_get_state(); + // TODO: Undo the change below when the rest of DFU code is included + uint8_t state = 0; // dfu_int_get_state(); payload[0] = state; break; } @@ -181,7 +181,8 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK\n"); - uint16_t transferblock = 0;//dfu_int_get_transfer_block(); + // TODO: Undo the change below when the rest of DFU code is included + uint16_t transferblock = 0; //dfu_int_get_transfer_block(); uint8_t tb_high, tb_low; tb_low = (uint8_t)(transferblock & 0xFF); @@ -215,42 +216,47 @@ control_ret_t dfu_servicer_write_cmd(control_resource_info_t *res_info, control_ { case DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH\n"); - //dfu_int_detach(); + // TODO: Enable the line below when the rest of DFU code is included + // dfu_int_detach(); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD\n"); - - uint16_t dnload_length = payload[0] + (payload[1] << 8); - const uint8_t * dnload_data = &payload[2]; - //dfu_int_download(dnload_length, dnload_data); + // TODO: Enable the line below when the rest of DFU code is included + // uint16_t dnload_length = payload[0] + (payload[1] << 8); + // const uint8_t * dnload_data = &payload[2]; + // dfu_int_download(dnload_length, dnload_data); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS\n"); - //dfu_int_clear_status(); + // TODO: Enable the line below when the rest of DFU code is included + // dfu_int_clear_status(); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT\n"); - //dfu_int_abort(); + // TODO: Enable the line below when the rest of DFU code is included + // dfu_int_abort(); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE\n"); - //dfu_int_set_alternate(payload[0]); + // TODO: Enable the line below when the rest of DFU code is included + // dfu_int_set_alternate(payload[0]); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK\n"); - - uint16_t const transferblock = (payload[1] << 8) + payload[0]; - //dfu_int_set_transfer_block(transferblock); + // TODO: Enable lines below when the rest of DFU code is included + // uint16_t const transferblock = (payload[1] << 8) + payload[0]; + // dfu_int_set_transfer_block(transferblock); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT\n"); - //reboot(); + // TODO: Enable the line below when the rest of DFU code is included + // reboot(); break; default: diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 47a95224..dd732005 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -382,7 +382,7 @@ void startup_task(void *arg) xTaskCreate( dfu_servicer, "DFU servicer", - RTOS_THREAD_STACK_SIZE(servicer_task), + RTOS_THREAD_STACK_SIZE(dfu_servicer), &servicer_dfu, appconfDEVICE_CONTROL_I2C_PRIORITY, NULL diff --git a/examples/ffva/src/servicer.c b/examples/ffva/src/servicer.c index f6afdae4..c3c9808e 100644 --- a/examples/ffva/src/servicer.c +++ b/examples/ffva/src/servicer.c @@ -20,83 +20,6 @@ device_control_t *device_control_ctxs[APP_CONTROL_TRANSPORT_COUNT] = { (device_control_t *) &device_control_i2c_ctx_s, }; #endif -static void i2c_slave_start(void) -{ -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) - #include "print.h" - printintln(222); - - rtos_i2c_slave_start(i2c_slave_ctx, - device_control_i2c_ctx, - (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, - (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, - (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, - (rtos_i2c_slave_tx_done_cb_t) NULL, - NULL, - NULL, - appconfI2C_INTERRUPT_CORE, - appconfI2C_TASK_PRIORITY); -#endif -} - -// Generic Servicer task. -void servicer_task(void *args) { - device_control_servicer_t servicer_ctx; - - servicer_t *servicer = (servicer_t*)args; - xassert(servicer != NULL); - - for(int i=0; inum_resources; i++) { - servicer->res_info[i].control_pkt_queue.queue_wr_index = 0; - } - - control_resid_t *resources = (control_resid_t*)pvPortMalloc(servicer->num_resources * sizeof(control_resid_t)); - for(int i=0; inum_resources; i++) - { - resources[i] = servicer->res_info[i].resource; - } - - if(appconfI2C_CTRL_ENABLED > 0) - { - control_ret_t dc_ret; - debug_printf("Calling device_control_servicer_register(), servicer ID %d, on tile %d, core %d.\n", servicer->id, THIS_XCORE_TILE, rtos_core_id_get()); - - dc_ret = device_control_servicer_register(&servicer_ctx, - device_control_ctxs, - appconfI2C_CTRL_ENABLED, - resources, servicer->num_resources); - debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); - } - - // Start I2C and SPI slave. - // This ends up calling device_control_resources_register() which has a strange non-yielding implementation, - // where it waits for servicers to register with the device control context. That's why, control_start_io_tasks() - // is not called from platform_start(). Additionally, if there is only one xcore core dedicated for all RTOS tasks, - // this design will not work, since device_control_resources_register() will not yield and the servicers wouldn't get - // scheduled so they could register with the device control leading to an eventual timeout error from device_control_resources_register(). - printintln(333); - if(1)//servicer->start_io == (int32_t)1) - { - i2c_slave_start(); - } - vPortFree(resources); - printintln(350); - - // The first call to device_control_servicer_cmd_recv triggers the device_control_i2c_start_cb() through I2S slave ISR call somehow. device_control_i2c_start_cb() and device_control_servicer_cmd_recv() have to be in separate logical cores - // and be scheduled simultaneously for this to work. - if(APP_CONTROL_TRANSPORT_COUNT > 0) - { - for(;;){ - device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); - } - } - else - { - for(;;){ - vTaskDelay(pdMS_TO_TICKS(100)); - } - } -} //-----------------Servicer read write callback functions-----------------------// DEVICE_CONTROL_CALLBACK_ATTR @@ -129,20 +52,7 @@ control_ret_t read_cmd(control_resid_t resid, control_cmd_t cmd, uint8_t *payloa payload[0] = ret; return ret; } - - int32_t cmd_handled = 0; - // If special command then handle through special command handler - /*ret = special_read_cmd_handler(current_res_info, cmd, payload_ptr, payload_len, &cmd_handled); - if(cmd_handled) // Command handled by the special command handler - { - payload[0] = ret; - return ret; - }*/ - - // Read from one of the underlying resources - ret = servicer_read_from_resource(current_res_info, cmd, payload_ptr, payload_len); - payload[0] = ret; - return ret; + return CONTROL_ERROR; } DEVICE_CONTROL_CALLBACK_ATTR @@ -169,18 +79,7 @@ control_ret_t write_cmd(control_resid_t resid, control_cmd_t cmd, const uint8_t ret = servicer_write_cmd(current_res_info, cmd, payload, payload_len); return ret; } - - // If special command then handle through special command handler - /*int32_t cmd_handled = 0; - ret = special_write_cmd_handler(current_res_info, cmd, payload, payload_len, &cmd_handled); - if(cmd_handled) // Command handled by the special command handler - { - return ret; - }*/ - - // Command is for one of the underlying resources - ret = servicer_write_to_resource(current_res_info, cmd, payload, payload_len); - return ret; + return CONTROL_ERROR; } // Initialise packet payload pointers to point to valid memory. @@ -268,44 +167,3 @@ control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t } return ret; } - -// Forward read command to underlying resource -control_ret_t servicer_read_from_resource(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len) -{ - // Process read cmd from underlying resource - - // Check if the read command is already in the queue - control_pkt_t* cmd_pkt = queue_check_packet(&res_info->control_pkt_queue, cmd); - // If cmd already in queue - if(cmd_pkt != NULL) - { - switch(cmd_pkt->pkt_status) - { - case PKT_DONE: - debug_printf("Read cmd and status DONE found in queue\n"); - memcpy(payload, cmd_pkt->payload, payload_len); - cmd_pkt->pkt_status = PKT_FREE; - return CONTROL_SUCCESS; - case PKT_WAIT: - debug_printf("Read cmd with status PKT_WAIT found in queue\n"); - return SERVICER_COMMAND_RETRY; - default: - xassert(0); - } - } - - // Otherwise add command to queue - control_ret_t ret = queue_write_command_to_free_packet(&res_info->control_pkt_queue, res_info->resource, cmd, payload, payload_len); - if (ret == CONTROL_SUCCESS) // Since we've just added the command to queue return retry for host to query for response later - { - return SERVICER_COMMAND_RETRY; - } - return ret; -} - -// Forward write command to underlying resource -control_ret_t servicer_write_to_resource(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) -{ - control_ret_t ret = queue_write_command_to_free_packet(&res_info->control_pkt_queue, res_info->resource, cmd, payload, payload_len); - return ret; -} diff --git a/examples/ffva/src/servicer.h b/examples/ffva/src/servicer.h index 40cf7127..4c7b6b2c 100644 --- a/examples/ffva/src/servicer.h +++ b/examples/ffva/src/servicer.h @@ -43,38 +43,7 @@ typedef struct { control_resource_info_t *res_info; }servicer_t; -// Servicer tasks -/** - * @brief Generic servicer task. - * The servicers that only receive and process requests from the host are implemented as the generic servicer. - * - * \param servicer Pointer to the Servicer's state data structure - */ -void servicer_task(void *servicer); -/** - * @brief IO config servicer task. - * \param servicer Pointer to the Servicer's state data structure - */ -void io_config_servicer(void *args); - -/** - * @brief GPO servicer task. - * - * This task handles GPO commands and drives the pins on the GPO port. - * - * \param servicer Pointer to the Servicer's state data structure - */ -void gpo_servicer(void *args); - -/** - * @brief Application servicer task. - * - * This task handles Application level commands such as firmware information. - * - * \param servicer Pointer to the Servicer's state data structure - */ -void application_servicer(void *args); /** * @brief DFU servicer task. @@ -183,28 +152,6 @@ control_ret_t servicer_write_cmd(control_resource_info_t *res_info, control_cmd_ */ control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); -/** - * @brief Function for getting a read command processed by the servicer's underlying resource - * - * @param res_info Command Resource ID - * @param cmd Command command IO - * @param payload Payload buffer to populate with the read response - * @param payload_len Length of the payload buffer - * @return CONTROL_SUCCESS if read command processed successfully. contro_ret_t error status otherwise. - */ -control_ret_t servicer_read_from_resource(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); - -/** - * @brief Function for getting a write command processed by the servicer's underlying resource - * - * @param res_info Command Resource ID - * @param cmd Command command IO - * @param payload Command write payload buffer - * @param payload_len Length of the payload buffer - * @return CONTROL_SUCCESS if write command processed successfully. contro_ret_t error status otherwise. - */ -control_ret_t servicer_write_to_resource(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); - /** * @brief DFU servicer read command handler * From d9debf462fe74b07d8f04f6c309558f3330dfd5b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 20 Mar 2024 15:55:13 +0000 Subject: [PATCH 099/288] More clean-up --- examples/ffva/src/dfu_cmds.h | 16 ++-------- examples/ffva/src/dfu_cmds_map.h | 8 +---- examples/ffva/src/packet_queue.c | 6 ++-- examples/ffva/src/packet_queue.h | 50 ++++++++++++++++---------------- examples/ffva/src/servicer.c | 3 +- examples/ffva/src/servicer.h | 2 +- 6 files changed, 33 insertions(+), 52 deletions(-) diff --git a/examples/ffva/src/dfu_cmds.h b/examples/ffva/src/dfu_cmds.h index b412901a..a4c82915 100644 --- a/examples/ffva/src/dfu_cmds.h +++ b/examples/ffva/src/dfu_cmds.h @@ -1,16 +1,11 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2024 XMOS LIMITED. // This Software is subject to the terms of the XCORE VocalFusion Licence. -/*********************************/ -/* AUTOGENERATED. DO NOT MODIFY! */ -/* Generated using template cmd_map.h.jinja */ -/*********************************/ - #pragma once #include -// Note: The enums are wrapped around a #ifndef block to support autogenerating cmd_map files from the +// Note: The enums are wrapped around a #ifndef block to support autogenerating cmd_map files from the // BeClearSuperHandsFree.h defines. The defines in BeClearSuperHandsFree.h are visible in the device // but not in the host, so the #ifndef is needed to keep the cmd_map files common between device and host. @@ -44,9 +39,6 @@ enum e_dfu_controller_servicer_resid_cmds #ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK = 65, #endif -#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION - DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION = 88, -#endif #ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT = 89, #endif @@ -72,8 +64,6 @@ enum e_dfu_controller_servicer_resid_cmds #define DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE_NUM_VALUES (1) // number of values of type dfu_controller_servicer_resid_dfu_transferblock_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK #define DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK_NUM_VALUES (2) -// number of values of type dfu_controller_servicer_resid_dfu_getversion_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION -#define DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION_NUM_VALUES (3) // number of values of type dfu_controller_servicer_resid_dfu_reboot_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT #define DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT_NUM_VALUES (1) @@ -96,7 +86,5 @@ typedef uint8_t dfu_controller_servicer_resid_dfu_abort_t; typedef uint8_t dfu_controller_servicer_resid_dfu_setalternate_t; // type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK typedef uint8_t dfu_controller_servicer_resid_dfu_transferblock_t; -// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION -typedef uint8_t dfu_controller_servicer_resid_dfu_getversion_t; // type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT typedef uint8_t dfu_controller_servicer_resid_dfu_reboot_t; diff --git a/examples/ffva/src/dfu_cmds_map.h b/examples/ffva/src/dfu_cmds_map.h index 020f0a3d..8412a10c 100644 --- a/examples/ffva/src/dfu_cmds_map.h +++ b/examples/ffva/src/dfu_cmds_map.h @@ -1,11 +1,6 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2024 XMOS LIMITED. // This Software is subject to the terms of the XCORE VocalFusion Licence. -/*********************************/ -/* AUTOGENERATED. DO NOT MODIFY! */ -/* Generated using template cmd_map_impl.h.jinja */ -/*********************************/ - #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-variable" @@ -23,7 +18,6 @@ static control_cmd_info_t dfu_controller_servicer_resid_cmd_map[] = { DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, { DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, { DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK, 2, sizeof(uint8_t), CMD_READ_WRITE }, - { DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION, 3, sizeof(uint8_t), CMD_READ_ONLY }, { DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, }; #pragma clang diagnostic pop \ No newline at end of file diff --git a/examples/ffva/src/packet_queue.c b/examples/ffva/src/packet_queue.c index 6ef40fe0..fe900915 100644 --- a/examples/ffva/src/packet_queue.c +++ b/examples/ffva/src/packet_queue.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2024 XMOS LIMITED. // This Software is subject to the terms of the XCORE VocalFusion Licence. #include #include @@ -25,7 +25,7 @@ control_ret_t queue_get_free_pkt(control_pkt_t **pkt, control_pkt_queue_t *contr (*pkt)->payload = (*pkt)->save_payload_ptr; (*pkt)->save_payload_ptr = NULL; } - (*pkt)->pkt_status = PKT_FREE; + (*pkt)->pkt_status = PKT_FREE; } else { debug_printf("ERROR: Control packet queue FULL\n"); @@ -48,7 +48,7 @@ void queue_add_pkt(control_pkt_queue_t *control_pkt_queue, int32_t free_when_don control_ret_t queue_write_command_to_free_packet(control_pkt_queue_t *control_pkt_queue, control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) { // Make sure we're writing to a free packet - control_pkt_t *pkt; + control_pkt_t *pkt; control_ret_t ret = queue_get_free_pkt(&pkt, control_pkt_queue); if(ret != CONTROL_SUCCESS) { diff --git a/examples/ffva/src/packet_queue.h b/examples/ffva/src/packet_queue.h index 1c0f69f3..b9ca8be8 100644 --- a/examples/ffva/src/packet_queue.h +++ b/examples/ffva/src/packet_queue.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2024 XMOS LIMITED. // This Software is subject to the terms of the XCORE VocalFusion Licence. #pragma once #include "device_control_shared.h" @@ -12,7 +12,7 @@ /** * @brief Control packet status - * + * */ typedef enum { PKT_FREE, /// Packet is free and available for the servicer to write into @@ -22,13 +22,13 @@ typedef enum { /** * @brief Control packet structure - * + * */ typedef struct { uint8_t res_id; /// Resource ID of the command in the packet uint8_t cmd_id; /// Command ID for the command in the packet int32_t payload_len; /// packet payload length in bytes - pkt_status_t pkt_status; /// packet status + pkt_status_t pkt_status; /// packet status void *payload; /// packet payload pointer uint8_t *save_payload_ptr; /// Holds a copy of the original payload ptr when the original payload_ptr gets overwritten during special command processing int32_t free_when_done; /// Free packet once its done by the resource. When looking for a free packet, the pkt queue function queue_get_free_pkt() will free a done packet only if this flag is set to 1. @@ -36,7 +36,7 @@ typedef struct { /** * @brief Control packet queue structure - * + * */ typedef struct { control_pkt_t *pkts; /// Array of packets that make up this queue @@ -47,28 +47,28 @@ typedef struct { /** * @brief Add a command to the pkt queue. - * - * @param control_pkt_queue Pointer to the packet queue object - * @param resid Command resource ID - * @param cmd Command command ID - * @param payload Command payload buffer - * @param payload_len Payload buffer length + * + * @param control_pkt_queue Pointer to the packet queue object + * @param resid Command resource ID + * @param cmd Command command ID + * @param payload Command payload buffer + * @param payload_len Payload buffer length * @return CONTROL_SUCCESS if command added to the queue, SERVICER_QUEUE_FULL if queue is full and command cannot be added */ control_ret_t queue_write_command_to_free_packet(control_pkt_queue_t *control_pkt_queue, control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); /** * @brief Check if a command is present in the packet queue - * + * * @param pkt_queue Pointer to the pkt queue objects - * @param cmd Command ID + * @param cmd Command ID * @return Pointer to the pkt object holding the command if command is present in the queue, NULL otherwise. */ control_pkt_t* queue_check_packet(control_pkt_queue_t *pkt_queue, control_cmd_t cmd); /** * @brief Return the packet that queue's current read pointer is pointing to - * + * * @param pkt_queue Pointer to the pkt queue object * @return Pointer to the packet object if read pointer is pointing to a valid packet, NULL otherwise. */ @@ -77,7 +77,7 @@ control_pkt_t* queue_read_packet(control_pkt_queue_t *pkt_queue); /** * @brief Set the packet which is at the current_rd_index in the queue to DONE. * Increment the current_rd_index to point to the next packet that needs processing - * + * * @param pkt_queue Pointer to the packet queue object */ void queue_set_packet_done(control_pkt_queue_t *pkt_queue); @@ -85,26 +85,26 @@ void queue_set_packet_done(control_pkt_queue_t *pkt_queue); // Return pointer to the first free pkt in the queue /** * @brief Return pointer to a free packet in the control packet queue - * - * This function returns a free packet pointer which can then be written with a command by + * + * This function returns a free packet pointer which can then be written with a command by * the servicer and added to the queue. * Since packets are written to the queue in order, this function only looks at the packet at the queue_wr_index * and returns the pointer to it if that packet is free or can be freed. - * - * @param pkt free packet pointer that is returned for use by the servicer - * @param control_pkt_queue Pointer to the control packet queue + * + * @param pkt free packet pointer that is returned for use by the servicer + * @param control_pkt_queue Pointer to the control packet queue * @return control_ret_t CONTROL_SUCCESS if a free packet pointer is updated in *pkt, SERVICER_QUEUE_FULL - * if the queue is full. + * if the queue is full. */ control_ret_t queue_get_free_pkt(control_pkt_t **pkt, control_pkt_queue_t *control_pkt_queue); /** * @brief Add packet to the control packet queue - * - * This command adds the packet at queue_wr_index to the queue. This means that the packet has now been written + * + * This command adds the packet at queue_wr_index to the queue. This means that the packet has now been written * with a command from the servicer and is waiting for being serviced by the resource. - * - * @param control_pkt_queue Pointer to the control packet queue + * + * @param control_pkt_queue Pointer to the control packet queue * @param free_when_done When set to 1, queue_get_free_pkt() frees the pkt when the resource marks it as Done. */ void queue_add_pkt(control_pkt_queue_t *control_pkt_queue, int32_t free_when_done); diff --git a/examples/ffva/src/servicer.c b/examples/ffva/src/servicer.c index c3c9808e..c4f95af3 100644 --- a/examples/ffva/src/servicer.c +++ b/examples/ffva/src/servicer.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2024 XMOS LIMITED. // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT SERVICER_TASK #ifndef DEBUG_PRINT_ENABLE_SERVICER_TASK @@ -10,7 +10,6 @@ #include "platform/platform_conf.h" #include "device_control_i2c.h" #include "servicer.h" -//#include "control_init.h" #if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) diff --git a/examples/ffva/src/servicer.h b/examples/ffva/src/servicer.h index 4c7b6b2c..4d801419 100644 --- a/examples/ffva/src/servicer.h +++ b/examples/ffva/src/servicer.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2024 XMOS LIMITED. // This Software is subject to the terms of the XCORE VocalFusion Licence. #pragma once #include "device_control.h" From ba997a5b0f7a4a73aa9e6ba8095266b1f810972d Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 20 Mar 2024 16:31:10 +0000 Subject: [PATCH 100/288] Add missing define --- .../bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h | 8 ++++++++ .../ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h index 45f9ad25..222b3cfb 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h @@ -105,6 +105,14 @@ #define appconfI2C_CTRL_ENABLED 0 #endif /* appconfI2C_CTRL_ENABLED */ +#ifndef appconfI2C_CTRL_ENABLED +#define appconfI2C_CTRL_ENABLED 1 +#endif /* appconfI2C_CTRL_ENABLED */ + +#ifndef APP_CONTROL_TRANSPORT_COUNT +#define APP_CONTROL_TRANSPORT_COUNT appconfI2C_CTRL_ENABLED +#endif // APP_CONTROL_TRANSPORT_COUNT + #ifndef appconfEXTERNAL_MCLK #if appconfI2C_CTRL_ENABLED #define appconfEXTERNAL_MCLK 1 diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index fa9235ed..a75ed946 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -103,7 +103,7 @@ #ifndef appconfI2C_CTRL_ENABLED /* - * When this is enabled on the XVF3610_Q60A board, the board + * When this is enabled on the XCORE-VOICE-L71 board, the board * cannot function as an I2C master and will not configure the * DAC. In this case the DAC should be configured externally. * MCLK will also default to be external if this is set on @@ -112,7 +112,10 @@ #define appconfI2C_CTRL_ENABLED 1 #endif /* appconfI2C_CTRL_ENABLED */ +#ifndef APP_CONTROL_TRANSPORT_COUNT #define APP_CONTROL_TRANSPORT_COUNT appconfI2C_CTRL_ENABLED +#endif // APP_CONTROL_TRANSPORT_COUNT + #ifndef appconfEXTERNAL_MCLK #if appconfI2C_CTRL_ENABLED #define appconfEXTERNAL_MCLK 1 From de76bc393faf00d6448990d7890b265f460fdaa7 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Wed, 20 Mar 2024 17:01:05 +0000 Subject: [PATCH 101/288] Tidy up defines --- .../bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h | 8 -------- .../ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h | 8 ++++---- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h index 222b3cfb..e5cb0147 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h @@ -105,20 +105,12 @@ #define appconfI2C_CTRL_ENABLED 0 #endif /* appconfI2C_CTRL_ENABLED */ -#ifndef appconfI2C_CTRL_ENABLED -#define appconfI2C_CTRL_ENABLED 1 -#endif /* appconfI2C_CTRL_ENABLED */ - #ifndef APP_CONTROL_TRANSPORT_COUNT #define APP_CONTROL_TRANSPORT_COUNT appconfI2C_CTRL_ENABLED #endif // APP_CONTROL_TRANSPORT_COUNT #ifndef appconfEXTERNAL_MCLK -#if appconfI2C_CTRL_ENABLED #define appconfEXTERNAL_MCLK 1 -#else -#define appconfEXTERNAL_MCLK 0 -#endif /* appconfI2C_CTRL_ENABLED */ #endif /* appconfEXTERNAL_MCLK */ #ifndef appconf_CONTROL_I2C_DEVICE_ADDR diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index a75ed946..cd829445 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -102,6 +102,7 @@ #endif /* appconfPIPELINE_AUDIO_SAMPLE_RATE */ #ifndef appconfI2C_CTRL_ENABLED +#if ! ASR_CYBERON /* * When this is enabled on the XCORE-VOICE-L71 board, the board * cannot function as an I2C master and will not configure the @@ -110,6 +111,9 @@ * the XVF3610_Q60A board. */ #define appconfI2C_CTRL_ENABLED 1 +#else +#define appconfI2C_CTRL_ENABLED 0 +#endif #endif /* appconfI2C_CTRL_ENABLED */ #ifndef APP_CONTROL_TRANSPORT_COUNT @@ -117,11 +121,7 @@ #endif // APP_CONTROL_TRANSPORT_COUNT #ifndef appconfEXTERNAL_MCLK -#if appconfI2C_CTRL_ENABLED #define appconfEXTERNAL_MCLK 1 -#else -#define appconfEXTERNAL_MCLK 0 -#endif /* appconfI2C_CTRL_ENABLED */ #endif /* appconfEXTERNAL_MCLK */ #ifndef appconf_CONTROL_I2C_DEVICE_ADDR From 1dd9be7e10f4704ff2946f53a7071415a456f616 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 08:33:37 +0000 Subject: [PATCH 102/288] Clean up --- .../ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h | 6 +++--- .../ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c | 6 +++--- examples/ffva/ffva.cmake | 2 ++ examples/ffva/src/{ => control}/cmd_map.h | 0 examples/ffva/src/{ => control}/packet_queue.c | 0 examples/ffva/src/{ => control}/packet_queue.h | 0 examples/ffva/src/{ => control}/servicer.c | 2 +- examples/ffva/src/{ => control}/servicer.h | 6 ++++-- examples/ffva/src/{ => dfu_i2c}/dfu_cmds.h | 0 examples/ffva/src/{ => dfu_i2c}/dfu_cmds_map.h | 2 +- examples/ffva/src/{dfu => dfu_i2c}/dfu_servicer.c | 5 +++-- 11 files changed, 17 insertions(+), 12 deletions(-) rename examples/ffva/src/{ => control}/cmd_map.h (100%) rename examples/ffva/src/{ => control}/packet_queue.c (100%) rename examples/ffva/src/{ => control}/packet_queue.h (100%) rename examples/ffva/src/{ => control}/servicer.c (99%) rename examples/ffva/src/{ => control}/servicer.h (97%) rename examples/ffva/src/{ => dfu_i2c}/dfu_cmds.h (100%) rename examples/ffva/src/{ => dfu_i2c}/dfu_cmds_map.h (97%) rename examples/ffva/src/{dfu => dfu_i2c}/dfu_servicer.c (98%) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index cd829445..b5677812 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -166,9 +166,9 @@ #define appconfSPI_TASK_PRIORITY (configMAX_PRIORITIES/2) #endif /* appconfSPI_TASK_PRIORITY */ - -#define appconfDEVICE_CONTROL_I2C_PRIORITY (configMAX_PRIORITIES-1) - +#ifndef appconfDEVICE_CONTROL_I2C_PRIORITY +#define appconfDEVICE_CONTROL_I2C_PRIORITY (configMAX_PRIORITIES-1) +#endif // appconfDEVICE_CONTROL_I2C_PRIORITY /*****************************************/ /* DFU Settings */ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index 3d837bdb..bec9d330 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -12,7 +12,7 @@ #include "adaptive_rate_adjust.h" #include "usb_support.h" #include "device_control.h" -#include "device_control.h" +#include "servicer.h" static void mclk_init(chanend_t other_tile_c) { @@ -320,11 +320,11 @@ static void uart_init(void) extern device_control_t *device_control_i2c_ctx; void control_init() { - control_ret_t ret = CONTROL_SUCCESS; #if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_TILE_NO) + control_ret_t ret = CONTROL_SUCCESS; ret = device_control_init(device_control_i2c_ctx, DEVICE_CONTROL_HOST_MODE, - 1,//(NUM_TILE_0_SERVICERS + NUM_TILE_1_SERVICERS - 1), // GPO servicer does not register with I2C or SPI device control context + (NUM_TILE_0_SERVICERS + NUM_TILE_1_SERVICERS), NULL, 0); xassert(ret == CONTROL_SUCCESS); diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index c5926b40..3be351a0 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -6,6 +6,8 @@ file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src + ${CMAKE_CURRENT_LIST_DIR}/src/control + ${CMAKE_CURRENT_LIST_DIR}/src/dfu_i2c ${CMAKE_CURRENT_LIST_DIR}/src/usb ) diff --git a/examples/ffva/src/cmd_map.h b/examples/ffva/src/control/cmd_map.h similarity index 100% rename from examples/ffva/src/cmd_map.h rename to examples/ffva/src/control/cmd_map.h diff --git a/examples/ffva/src/packet_queue.c b/examples/ffva/src/control/packet_queue.c similarity index 100% rename from examples/ffva/src/packet_queue.c rename to examples/ffva/src/control/packet_queue.c diff --git a/examples/ffva/src/packet_queue.h b/examples/ffva/src/control/packet_queue.h similarity index 100% rename from examples/ffva/src/packet_queue.h rename to examples/ffva/src/control/packet_queue.h diff --git a/examples/ffva/src/servicer.c b/examples/ffva/src/control/servicer.c similarity index 99% rename from examples/ffva/src/servicer.c rename to examples/ffva/src/control/servicer.c index c4f95af3..ca435e77 100644 --- a/examples/ffva/src/servicer.c +++ b/examples/ffva/src/control/servicer.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT SERVICER_TASK #ifndef DEBUG_PRINT_ENABLE_SERVICER_TASK - #define DEBUG_PRINT_ENABLE_SERVICER_TASK 1 + #define DEBUG_PRINT_ENABLE_SERVICER_TASK 0 #endif #include "debug_print.h" #include diff --git a/examples/ffva/src/servicer.h b/examples/ffva/src/control/servicer.h similarity index 97% rename from examples/ffva/src/servicer.h rename to examples/ffva/src/control/servicer.h index 4d801419..517f1e7f 100644 --- a/examples/ffva/src/servicer.h +++ b/examples/ffva/src/control/servicer.h @@ -5,8 +5,10 @@ #include "packet_queue.h" #include "cmd_map.h" -#define DFU_CONTROLLER_SERVICER_RESID 240 -#define NUM_RESOURCES_DFU_SERVICER (1) // DFU servicer +#define DFU_CONTROLLER_SERVICER_RESID (240) +#define NUM_RESOURCES_DFU_SERVICER (1) // DFU servicer +#define NUM_TILE_0_SERVICERS (1) // only DFU servicer is used +#define NUM_TILE_1_SERVICERS (0) // no control servicer extern device_control_t *device_control_i2c_ctx; extern device_control_t *device_control_ctxs[1]; diff --git a/examples/ffva/src/dfu_cmds.h b/examples/ffva/src/dfu_i2c/dfu_cmds.h similarity index 100% rename from examples/ffva/src/dfu_cmds.h rename to examples/ffva/src/dfu_i2c/dfu_cmds.h diff --git a/examples/ffva/src/dfu_cmds_map.h b/examples/ffva/src/dfu_i2c/dfu_cmds_map.h similarity index 97% rename from examples/ffva/src/dfu_cmds_map.h rename to examples/ffva/src/dfu_i2c/dfu_cmds_map.h index 8412a10c..7ce373e7 100644 --- a/examples/ffva/src/dfu_cmds_map.h +++ b/examples/ffva/src/dfu_i2c/dfu_cmds_map.h @@ -20,4 +20,4 @@ static control_cmd_info_t dfu_controller_servicer_resid_cmd_map[] = { DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK, 2, sizeof(uint8_t), CMD_READ_WRITE }, { DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, }; -#pragma clang diagnostic pop \ No newline at end of file +#pragma clang diagnostic pop diff --git a/examples/ffva/src/dfu/dfu_servicer.c b/examples/ffva/src/dfu_i2c/dfu_servicer.c similarity index 98% rename from examples/ffva/src/dfu/dfu_servicer.c rename to examples/ffva/src/dfu_i2c/dfu_servicer.c index cd16fcd4..0c72e5c2 100644 --- a/examples/ffva/src/dfu/dfu_servicer.c +++ b/examples/ffva/src/dfu_i2c/dfu_servicer.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT DFU_SERVICER #ifndef DEBUG_PRINT_ENABLE_DFU_SERVICER -#define DEBUG_PRINT_ENABLE_DFU_SERVICER 1 +#define DEBUG_PRINT_ENABLE_DFU_SERVICER 0 #endif #include "debug_print.h" @@ -16,6 +16,7 @@ #include "dfu_cmds.h" #include "device_control_i2c.h" +// TODO: Enable lines below when the rest of DFU code is included //#include "dfu_common.h" // for reboot() //#include "dfu_state_machine.h" @@ -37,7 +38,6 @@ void dfu_servicer_init(servicer_t *servicer) servicer->res_info[0].control_pkt_queue.pkts = NULL; servicer->res_info[0].command_map.num_commands = NUM_DFU_CONTROLLER_SERVICER_RESID_CMDS; servicer->res_info[0].command_map.commands = dfu_controller_servicer_resid_cmd_map; - } void dfu_servicer(void *args) { @@ -102,6 +102,7 @@ void dfu_servicer(void *args) { vTaskDelay(pdMS_TO_TICKS(100)); } } + // TODO: Enable lines below when the rest of DFU code is included /*xTaskCreate( dfu_int_state_machine, "DFU state machine task", From a6fdf81a27d72354fd910104636224e514a47f60 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 08:34:31 +0000 Subject: [PATCH 103/288] Fix name --- examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index b5677812..e3537a9b 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -104,7 +104,7 @@ #ifndef appconfI2C_CTRL_ENABLED #if ! ASR_CYBERON /* - * When this is enabled on the XCORE-VOICE-L71 board, the board + * When this is enabled on the XK-VOICE-L71 board, the board * cannot function as an I2C master and will not configure the * DAC. In this case the DAC should be configured externally. * MCLK will also default to be external if this is set on From c8a8738a7bef2fe91c832b5f981fda89bb354b85 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 09:03:14 +0000 Subject: [PATCH 104/288] Fix board name --- examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c | 2 +- examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h | 2 +- examples/ffva/src/app_conf_check.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c index 1c72c4f3..edd95332 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c @@ -13,7 +13,7 @@ #include "dac3101.h" #include "pcal6408a.h" -/* I2C io expander address on XCF3610_Q60A board */ +/* I2C io expander address on XK-VOICE-L71 board */ #define IOEXP_I2C_ADDR PCAL6408A_I2C_ADDR /* IO expander pinout */ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index e3537a9b..0a55ccfd 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -108,7 +108,7 @@ * cannot function as an I2C master and will not configure the * DAC. In this case the DAC should be configured externally. * MCLK will also default to be external if this is set on - * the XVF3610_Q60A board. + * the XK-VOICE-L71 board. */ #define appconfI2C_CTRL_ENABLED 1 #else diff --git a/examples/ffva/src/app_conf_check.h b/examples/ffva/src/app_conf_check.h index c0e04d7d..753eab69 100644 --- a/examples/ffva/src/app_conf_check.h +++ b/examples/ffva/src/app_conf_check.h @@ -22,7 +22,7 @@ #if XK_VOICE_L71 #if appconfSPI_OUTPUT_ENABLED -#error SPI audio output not currently supported on XVF3610 board +#error SPI audio output not currently supported on XK-VOICE-L71 board #endif #endif From 46be401f0f0cc31e5ed1c3f5a84b883fb63cd8eb Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 09:53:41 +0000 Subject: [PATCH 105/288] Reorganize files --- examples/ffva/src/control/servicer.c | 2 +- examples/ffva/src/control/servicer.h | 33 ----------------------- examples/ffva/src/dfu_i2c/dfu_servicer.c | 2 ++ examples/ffva/src/dfu_i2c/dfu_servicer.h | 34 ++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 34 deletions(-) create mode 100644 examples/ffva/src/dfu_i2c/dfu_servicer.h diff --git a/examples/ffva/src/control/servicer.c b/examples/ffva/src/control/servicer.c index ca435e77..0b545f68 100644 --- a/examples/ffva/src/control/servicer.c +++ b/examples/ffva/src/control/servicer.c @@ -10,7 +10,7 @@ #include "platform/platform_conf.h" #include "device_control_i2c.h" #include "servicer.h" - +#include "dfu_servicer.h" #if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) static device_control_t device_control_i2c_ctx_s; diff --git a/examples/ffva/src/control/servicer.h b/examples/ffva/src/control/servicer.h index 517f1e7f..9a4e8a19 100644 --- a/examples/ffva/src/control/servicer.h +++ b/examples/ffva/src/control/servicer.h @@ -5,8 +5,6 @@ #include "packet_queue.h" #include "cmd_map.h" -#define DFU_CONTROLLER_SERVICER_RESID (240) -#define NUM_RESOURCES_DFU_SERVICER (1) // DFU servicer #define NUM_TILE_0_SERVICERS (1) // only DFU servicer is used #define NUM_TILE_1_SERVICERS (0) // no control servicer @@ -153,34 +151,3 @@ control_ret_t servicer_write_cmd(control_resource_info_t *res_info, control_cmd_ * @return CONTROL_SUCCESS if read command processed successfully. contro_ret_t error status otherwise. */ control_ret_t servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); - -/** - * @brief DFU servicer read command handler - * - * Handles read commands dedicated to the DFU servicer resource - * - * @param res_info Resource info of the current command - * @param cmd Command ID of this command - * @param payload Pointer to the payload that contains the write data - * @param payload_len Length in bytes of the write command payload - * @return control_ret_t CONTROL_SUCCESS if command handled successfully, - * otherwise control_ret_t error status indicating the error. - */ -control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); - -/** - * @brief DFU servicer write command handler - * - * Handles write commands dedicated to the DFU servicer resource - * - * @param res_info Resource info of the current command - * @param cmd Command ID of this command - * @param payload Pointer to the payload that contains the write data - * @param payload_len Length in bytes of the write command payload - * @return control_ret_t CONTROL_SUCCESS if command handled successfully, - * otherwise control_ret_t error status indicating the error. - */ -control_ret_t dfu_servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); - - -void hid_in_servicer(void *args); diff --git a/examples/ffva/src/dfu_i2c/dfu_servicer.c b/examples/ffva/src/dfu_i2c/dfu_servicer.c index 0c72e5c2..1044b537 100644 --- a/examples/ffva/src/dfu_i2c/dfu_servicer.c +++ b/examples/ffva/src/dfu_i2c/dfu_servicer.c @@ -13,6 +13,8 @@ #include "platform/platform_conf.h" #include "servicer.h" +#include "dfu_servicer.h" + #include "dfu_cmds.h" #include "device_control_i2c.h" diff --git a/examples/ffva/src/dfu_i2c/dfu_servicer.h b/examples/ffva/src/dfu_i2c/dfu_servicer.h new file mode 100644 index 00000000..92c009ba --- /dev/null +++ b/examples/ffva/src/dfu_i2c/dfu_servicer.h @@ -0,0 +1,34 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. +#pragma once + +#define DFU_CONTROLLER_SERVICER_RESID (240) +#define NUM_RESOURCES_DFU_SERVICER (1) // DFU servicer + +/** + * @brief DFU servicer read command handler + * + * Handles read commands dedicated to the DFU servicer resource + * + * @param res_info Resource info of the current command + * @param cmd Command ID of this command + * @param payload Pointer to the payload that contains the write data + * @param payload_len Length in bytes of the write command payload + * @return control_ret_t CONTROL_SUCCESS if command handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_cmd_t cmd, uint8_t *payload, size_t payload_len); + +/** + * @brief DFU servicer write command handler + * + * Handles write commands dedicated to the DFU servicer resource + * + * @param res_info Resource info of the current command + * @param cmd Command ID of this command + * @param payload Pointer to the payload that contains the write data + * @param payload_len Length in bytes of the write command payload + * @return control_ret_t CONTROL_SUCCESS if command handled successfully, + * otherwise control_ret_t error status indicating the error. + */ +control_ret_t dfu_servicer_write_cmd(control_resource_info_t *res_info, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); \ No newline at end of file From 47665e41369d2bd2e9fa62a673bc83f072b0c20a Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 11:45:58 +0000 Subject: [PATCH 106/288] Clean up --- .../platform/platform_conf.h | 8 +- .../platform/platform_init.c | 2 +- .../platform/platform_start.c | 8 +- .../XK_VOICE_L71/platform/platform_init.c | 2 +- .../XK_VOICE_L71/platform/platform_start.c | 2 +- examples/ffva/ffva.cmake | 3 +- examples/ffva/src/FreeRTOSConfig.h | 2 +- examples/ffva/src/control/servicer.c | 2 +- .../ffva/src/{dfu_i2c => dfu_int}/dfu_cmds.h | 0 .../src/{dfu_i2c => dfu_int}/dfu_cmds_map.h | 0 .../ffva/src/{dfu => dfu_int}/dfu_common.c | 15 ++- .../ffva/src/{dfu => dfu_int}/dfu_common.h | 0 .../src/{dfu_i2c => dfu_int}/dfu_servicer.c | 97 +++++++------------ .../src/{dfu_i2c => dfu_int}/dfu_servicer.h | 0 .../src/{dfu => dfu_int}/dfu_state_machine.c | 3 +- .../src/{dfu => dfu_int}/dfu_state_machine.h | 2 +- examples/ffva/src/main.c | 2 +- 17 files changed, 65 insertions(+), 83 deletions(-) rename examples/ffva/src/{dfu_i2c => dfu_int}/dfu_cmds.h (100%) rename examples/ffva/src/{dfu_i2c => dfu_int}/dfu_cmds_map.h (100%) rename examples/ffva/src/{dfu => dfu_int}/dfu_common.c (83%) rename examples/ffva/src/{dfu => dfu_int}/dfu_common.h (100%) rename examples/ffva/src/{dfu_i2c => dfu_int}/dfu_servicer.c (70%) rename examples/ffva/src/{dfu_i2c => dfu_int}/dfu_servicer.h (100%) rename examples/ffva/src/{dfu => dfu_int}/dfu_state_machine.c (99%) rename examples/ffva/src/{dfu => dfu_int}/dfu_state_machine.h (99%) diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h index e5cb0147..bc5f6587 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_conf.h @@ -101,12 +101,12 @@ #define appconfPIPELINE_AUDIO_SAMPLE_RATE 16000 #endif /* appconfPIPELINE_AUDIO_SAMPLE_RATE */ -#ifndef appconfI2C_CTRL_ENABLED -#define appconfI2C_CTRL_ENABLED 0 -#endif /* appconfI2C_CTRL_ENABLED */ +#ifndef appconfI2C_DFU_ENABLED +#define appconfI2C_DFU_ENABLED 0 +#endif /* appconfI2C_DFU_ENABLED */ #ifndef APP_CONTROL_TRANSPORT_COUNT -#define APP_CONTROL_TRANSPORT_COUNT appconfI2C_CTRL_ENABLED +#define APP_CONTROL_TRANSPORT_COUNT appconfI2C_DFU_ENABLED #endif // APP_CONTROL_TRANSPORT_COUNT #ifndef appconfEXTERNAL_MCLK diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c index a2650e0b..ffbb0e61 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c @@ -88,7 +88,7 @@ static void gpio_init(void) static void i2c_init(void) { -#if appconfI2C_CTRL_ENABLED +#if appconfI2C_DFU_ENABLED #if ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_init(i2c_slave_ctx, (1 << appconfI2C_IO_CORE), diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c index 1bb8729a..7ed48db8 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c @@ -16,7 +16,7 @@ #include "aic3204.h" #include "usb_support.h" -#if appconfI2C_CTRL_ENABLED +#if appconfI2C_DFU_ENABLED #include "app_control/app_control.h" #include "device_control_i2c.h" #endif @@ -47,7 +47,7 @@ static void flash_start(void) static void i2c_master_start(void) { -#if !appconfI2C_CTRL_ENABLED +#if !appconfI2C_DFU_ENABLED rtos_i2c_master_rpc_config(i2c_master_ctx, appconfI2C_MASTER_RPC_PORT, appconfI2C_MASTER_RPC_PRIORITY); #if ON_TILE(I2C_TILE_NO) @@ -58,7 +58,7 @@ static void i2c_master_start(void) static void audio_codec_start(void) { -#if !appconfI2C_CTRL_ENABLED +#if !appconfI2C_DFU_ENABLED #if appconfI2S_ENABLED int ret = 0; #if ON_TILE(I2C_TILE_NO) @@ -124,7 +124,7 @@ static void mics_start(void) static void i2s_start(void) { #if appconfI2S_ENABLED - rtos_i2s_rpc_config(i2s_ctx, appconfI2S_RPC_PORT, appconfI2S_RPC_PRIORITY); + rtos_i2s_rpc_config(i2s_ctx, appconfI2S_RPC_PORT, appconfI2S_RPC_PRIORITY); #if ON_TILE(I2S_TILE_NO) if (appconfI2S_AUDIO_SAMPLE_RATE == 3*appconfAUDIO_PIPELINE_SAMPLE_RATE) { diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index bec9d330..ebf58b26 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -108,7 +108,7 @@ static void i2c_init(void) { static rtos_driver_rpc_t i2c_rpc_config; -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_DFU_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_init(i2c_slave_ctx, (1 << appconfI2C_IO_CORE), PORT_I2C_SCL, diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index 6ecb12b4..38514669 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -16,7 +16,7 @@ #include "dac3101.h" #include "usb_support.h" -#if appconfI2C_CTRL_ENABLED +#if appconfI2C_DFU_ENABLED #include "device_control_i2c.h" #endif diff --git a/examples/ffva/ffva.cmake b/examples/ffva/ffva.cmake index 259e5961..7e63f13b 100644 --- a/examples/ffva/ffva.cmake +++ b/examples/ffva/ffva.cmake @@ -7,9 +7,8 @@ file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/control - ${CMAKE_CURRENT_LIST_DIR}/src/dfu_i2c + ${CMAKE_CURRENT_LIST_DIR}/src/dfu_int ${CMAKE_CURRENT_LIST_DIR}/src/usb - ${CMAKE_CURRENT_LIST_DIR}/src/dfu ) include(${CMAKE_CURRENT_LIST_DIR}/bsp_config/bsp_config.cmake) diff --git a/examples/ffva/src/FreeRTOSConfig.h b/examples/ffva/src/FreeRTOSConfig.h index 4e081461..5d0afd45 100644 --- a/examples/ffva/src/FreeRTOSConfig.h +++ b/examples/ffva/src/FreeRTOSConfig.h @@ -29,7 +29,7 @@ your application. */ #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define configUSE_TASK_NOTIFICATIONS 1 -#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 +#define configTASK_NOTIFICATION_ARRAY_ENTRIES 2 #define configUSE_MUTEXES 1 #define configUSE_RECURSIVE_MUTEXES 1 #define configUSE_COUNTING_SEMAPHORES 1 diff --git a/examples/ffva/src/control/servicer.c b/examples/ffva/src/control/servicer.c index 0b545f68..0cb6007c 100644 --- a/examples/ffva/src/control/servicer.c +++ b/examples/ffva/src/control/servicer.c @@ -12,7 +12,7 @@ #include "servicer.h" #include "dfu_servicer.h" -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_DFU_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) static device_control_t device_control_i2c_ctx_s; device_control_t *device_control_i2c_ctx = (device_control_t *) &device_control_i2c_ctx_s; device_control_t *device_control_ctxs[APP_CONTROL_TRANSPORT_COUNT] = { diff --git a/examples/ffva/src/dfu_i2c/dfu_cmds.h b/examples/ffva/src/dfu_int/dfu_cmds.h similarity index 100% rename from examples/ffva/src/dfu_i2c/dfu_cmds.h rename to examples/ffva/src/dfu_int/dfu_cmds.h diff --git a/examples/ffva/src/dfu_i2c/dfu_cmds_map.h b/examples/ffva/src/dfu_int/dfu_cmds_map.h similarity index 100% rename from examples/ffva/src/dfu_i2c/dfu_cmds_map.h rename to examples/ffva/src/dfu_int/dfu_cmds_map.h diff --git a/examples/ffva/src/dfu/dfu_common.c b/examples/ffva/src/dfu_int/dfu_common.c similarity index 83% rename from examples/ffva/src/dfu/dfu_common.c rename to examples/ffva/src/dfu_int/dfu_common.c index 4ad5c58b..74b02537 100644 --- a/examples/ffva/src/dfu/dfu_common.c +++ b/examples/ffva/src/dfu_int/dfu_common.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT DFU_COMMON #ifndef DEBUG_PRINT_ENABLE_DFU_COMMON -#define DEBUG_PRINT_ENABLE_DFU_COMMON 0 +#define DEBUG_PRINT_ENABLE_DFU_COMMON 1 #endif #include "debug_print.h" @@ -151,10 +151,21 @@ uint16_t dfu_common_read_from_flash(uint8_t alt, return retval; } +#define WDT_PRESCALER_MILLISECONDS (24000 - 1) // Set prescaler to tick every millisecond, assuming 24MHz XIN. Note max value 65535. + void reboot(void) { rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_WRAP_NUM, WDT_PRESCALER_MILLISECONDS); write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 0x10000); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_NUM, 0); // Reset counter write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); - while(1) {;} + //while(1) {;} +//#define WDT_PRESCALER_MILLISECONDS (24000 - 1) // Set prescaler to tick every millisecond, assuming 24MHz XIN. Note max value 65535. + +// write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_WRAP_NUM, WDT_PRESCALER_MILLISECONDS); +// write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 100); // Set WDT trigger after this many ticks. Note this is an 11b field so 2047 max +// write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_NUM, 0); // Reset counter +// write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); + //debug_printf("Rebooting!\n"); } \ No newline at end of file diff --git a/examples/ffva/src/dfu/dfu_common.h b/examples/ffva/src/dfu_int/dfu_common.h similarity index 100% rename from examples/ffva/src/dfu/dfu_common.h rename to examples/ffva/src/dfu_int/dfu_common.h diff --git a/examples/ffva/src/dfu_i2c/dfu_servicer.c b/examples/ffva/src/dfu_int/dfu_servicer.c similarity index 70% rename from examples/ffva/src/dfu_i2c/dfu_servicer.c rename to examples/ffva/src/dfu_int/dfu_servicer.c index 1044b537..6f17387a 100644 --- a/examples/ffva/src/dfu_i2c/dfu_servicer.c +++ b/examples/ffva/src/dfu_int/dfu_servicer.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT DFU_SERVICER #ifndef DEBUG_PRINT_ENABLE_DFU_SERVICER -#define DEBUG_PRINT_ENABLE_DFU_SERVICER 0 +#define DEBUG_PRINT_ENABLE_DFU_SERVICER 1 #endif #include "debug_print.h" @@ -18,9 +18,8 @@ #include "dfu_cmds.h" #include "device_control_i2c.h" -// TODO: Enable lines below when the rest of DFU code is included -//#include "dfu_common.h" // for reboot() -//#include "dfu_state_machine.h" +#include "dfu_common.h" // for reboot() +#include "dfu_state_machine.h" void dfu_servicer_init(servicer_t *servicer) { @@ -57,7 +56,7 @@ void dfu_servicer(void *args) { resources[i] = servicer->res_info[i].resource; } - if(appconfI2C_CTRL_ENABLED > 0) + if(appconfI2C_DFU_ENABLED > 0) { control_ret_t dc_ret; debug_printf("Calling device_control_servicer_register(), servicer ID %d, on tile %d, core %d.\n", servicer->id, THIS_XCORE_TILE, rtos_core_id_get()); @@ -69,7 +68,7 @@ void dfu_servicer(void *args) { debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); } - #if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) + #if appconfI2C_DFU_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) // Start I2C slave. // This ends up calling device_control_resources_register() which has a strange non-yielding implementation, // where it waits for servicers to register with the device control context. That's why, control_start_io_tasks() @@ -90,31 +89,16 @@ void dfu_servicer(void *args) { #endif vPortFree(resources); - // The first call to device_control_servicer_cmd_recv triggers the device_control_i2c_start_cb() through I2S slave ISR call somehow. device_control_i2c_start_cb() and device_control_servicer_cmd_recv() have to be in separate logical cores - // and be scheduled simultaneously for this to work. - if(APP_CONTROL_TRANSPORT_COUNT > 0) - { - for(;;){ - device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); - } - } - else - { - for(;;){ - vTaskDelay(pdMS_TO_TICKS(100)); - } - } - // TODO: Enable lines below when the rest of DFU code is included - /*xTaskCreate( + xTaskCreate( dfu_int_state_machine, "DFU state machine task", RTOS_THREAD_STACK_SIZE(dfu_int_state_machine), NULL, uxTaskPriorityGet(NULL), // Same priority so should run after this task NULL - );*/ - - if(appconfI2C_CTRL_ENABLED > 0) + ); + + if(appconfI2C_DFU_ENABLED > 0) { for(;;){ device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); @@ -142,8 +126,7 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_UPLOAD\n"); - // TODO: Undo the change below when the rest of DFU code is included - size_t upload_len = 0; //dfu_int_upload(&payload[2], DFU_DATA_XFER_SIZE); + size_t upload_len = dfu_int_upload(&payload[2], DFU_DATA_XFER_SIZE); payload[0] = upload_len & 0xFF; payload[1] = (upload_len >> 8) & 0xFF; @@ -154,20 +137,19 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATUS\n"); - // TODO: Enable lines below when the rest of DFU code is included - // dfu_int_get_status_packet_t retval; - // dfu_int_get_status(&retval); - - // uint8_t time_high, time_mid, time_low; - // time_low = (uint8_t)(retval.timeout_ms & 0xFF); - // time_mid = (uint8_t)((retval.timeout_ms >> 8) & 0xFF); - // time_high = (uint8_t)((retval.timeout_ms >> 16) & 0xFF); - - // payload[0] = retval.current_status; - // payload[1] = time_low; - // payload[2] = time_mid; - // payload[3] = time_high; - // payload[4] = retval.next_state; + dfu_int_get_status_packet_t retval; + dfu_int_get_status(&retval); + + uint8_t time_high, time_mid, time_low; + time_low = (uint8_t)(retval.timeout_ms & 0xFF); + time_mid = (uint8_t)((retval.timeout_ms >> 8) & 0xFF); + time_high = (uint8_t)((retval.timeout_ms >> 16) & 0xFF); + + payload[0] = retval.current_status; + payload[1] = time_low; + payload[2] = time_mid; + payload[3] = time_high; + payload[4] = retval.next_state; break; } @@ -175,8 +157,7 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_GETSTATE\n"); - // TODO: Undo the change below when the rest of DFU code is included - uint8_t state = 0; // dfu_int_get_state(); + uint8_t state = dfu_int_get_state(); payload[0] = state; break; } @@ -184,8 +165,7 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK\n"); - // TODO: Undo the change below when the rest of DFU code is included - uint16_t transferblock = 0; //dfu_int_get_transfer_block(); + uint16_t transferblock = dfu_int_get_transfer_block(); uint8_t tb_high, tb_low; tb_low = (uint8_t)(transferblock & 0xFF); @@ -219,47 +199,40 @@ control_ret_t dfu_servicer_write_cmd(control_resource_info_t *res_info, control_ { case DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_DETACH\n"); - // TODO: Enable the line below when the rest of DFU code is included - // dfu_int_detach(); + dfu_int_detach(); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_DNLOAD\n"); - // TODO: Enable the line below when the rest of DFU code is included - // uint16_t dnload_length = payload[0] + (payload[1] << 8); - // const uint8_t * dnload_data = &payload[2]; - // dfu_int_download(dnload_length, dnload_data); + uint16_t dnload_length = payload[0] + (payload[1] << 8); + const uint8_t * dnload_data = &payload[2]; + dfu_int_download(dnload_length, dnload_data); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_CLRSTATUS\n"); - // TODO: Enable the line below when the rest of DFU code is included - // dfu_int_clear_status(); + dfu_int_clear_status(); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT\n"); - // TODO: Enable the line below when the rest of DFU code is included - // dfu_int_abort(); + dfu_int_abort(); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE\n"); - // TODO: Enable the line below when the rest of DFU code is included - // dfu_int_set_alternate(payload[0]); + dfu_int_set_alternate(payload[0]); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK\n"); - // TODO: Enable lines below when the rest of DFU code is included - // uint16_t const transferblock = (payload[1] << 8) + payload[0]; - // dfu_int_set_transfer_block(transferblock); + uint16_t const transferblock = (payload[1] << 8) + payload[0]; + dfu_int_set_transfer_block(transferblock); break; case DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT: debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT\n"); - // TODO: Enable the line below when the rest of DFU code is included - // reboot(); + reboot(); break; default: diff --git a/examples/ffva/src/dfu_i2c/dfu_servicer.h b/examples/ffva/src/dfu_int/dfu_servicer.h similarity index 100% rename from examples/ffva/src/dfu_i2c/dfu_servicer.h rename to examples/ffva/src/dfu_int/dfu_servicer.h diff --git a/examples/ffva/src/dfu/dfu_state_machine.c b/examples/ffva/src/dfu_int/dfu_state_machine.c similarity index 99% rename from examples/ffva/src/dfu/dfu_state_machine.c rename to examples/ffva/src/dfu_int/dfu_state_machine.c index 731d24bb..121284d4 100644 --- a/examples/ffva/src/dfu/dfu_state_machine.c +++ b/examples/ffva/src/dfu_int/dfu_state_machine.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT DFU_STATE_MACHINE #ifndef DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE -#define DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE 0 +#define DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE 1 #endif #include "debug_print.h" @@ -390,7 +390,6 @@ void dfu_int_state_machine(void *args) dfu_data.task_handle = xTaskGetCurrentTaskHandle(); dfu_data.alt_setting = DFU_INT_ALTERNATE_FACTORY; dfu_int_reset_state(); - /* Sit in a loop and wait for mail */ while (1) { diff --git a/examples/ffva/src/dfu/dfu_state_machine.h b/examples/ffva/src/dfu_int/dfu_state_machine.h similarity index 99% rename from examples/ffva/src/dfu/dfu_state_machine.h rename to examples/ffva/src/dfu_int/dfu_state_machine.h index 60c9c9b2..1cce4189 100644 --- a/examples/ffva/src/dfu/dfu_state_machine.h +++ b/examples/ffva/src/dfu_int/dfu_state_machine.h @@ -20,7 +20,7 @@ * \brief Defines the size of a flash sector; used to assemble multiple payloads * into one write operation. */ -#define DFU_SECTOR_SIZE 4056 +#define DFU_SECTOR_SIZE 4096 /** * \brief Defines the number of payloads required to fill one flash sector. It is * assumed that #DFU_DATA_XFER_SIZE is an integer factor of #DFU_SECTOR_SIZE. diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index dd732005..42d88e1e 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -374,7 +374,7 @@ void startup_task(void *arg) gpio_test(gpio_ctx_t0); #endif -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_DFU_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) // Initialise control related things servicer_t servicer_dfu; dfu_servicer_init(&servicer_dfu); From 39fdcfcb25aa920f33170aa19e9c9d6b0b307b8f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 11:59:46 +0000 Subject: [PATCH 107/288] Compilation error to be fixed --- .../platform/platform_start.c | 3 +- .../XK_VOICE_L71/platform/platform_init.c | 2 +- examples/ffva/src/control/servicer.h | 19 ------------- examples/ffva/src/dfu_int/dfu_servicer.c | 28 ++++++------------- examples/ffva/src/dfu_int/dfu_servicer.h | 17 +++++++++++ 5 files changed, 29 insertions(+), 40 deletions(-) diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c index 7ed48db8..3e5fcc54 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c @@ -22,6 +22,7 @@ #endif extern void i2s_rate_conversion_enable(void); +extern device_control_t *device_control_i2c_ctx; static void gpio_start(void) { @@ -76,7 +77,7 @@ static void audio_codec_start(void) static void i2c_slave_start(void) { -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_DFU_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_start(i2c_slave_ctx, device_control_i2c_ctx, (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index ebf58b26..2947fb7c 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -320,7 +320,7 @@ static void uart_init(void) extern device_control_t *device_control_i2c_ctx; void control_init() { -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_TILE_NO) +#if appconfI2C_DFU_ENABLED && ON_TILE(I2C_TILE_NO) control_ret_t ret = CONTROL_SUCCESS; ret = device_control_init(device_control_i2c_ctx, DEVICE_CONTROL_HOST_MODE, diff --git a/examples/ffva/src/control/servicer.h b/examples/ffva/src/control/servicer.h index 9a4e8a19..e5b63278 100644 --- a/examples/ffva/src/control/servicer.h +++ b/examples/ffva/src/control/servicer.h @@ -43,25 +43,6 @@ typedef struct { control_resource_info_t *res_info; }servicer_t; - - -/** - * @brief DFU servicer task. - * - * This task handles DFU commands from the device control interface and relays - * them to the internal DFU INT state machine. - * - * \param args Pointer to the Servicer's state data structure - */ -void dfu_servicer(void *args); - -// Servicer initialization functions -/** - * @brief DFU servicer initialisation function. - * \param servicer Pointer to the Servicer's state data structure - */ -void dfu_servicer_init(servicer_t *servicer); - // Servicer device_control callback functions /** * @brief Device control callback function to handle a read command. diff --git a/examples/ffva/src/dfu_int/dfu_servicer.c b/examples/ffva/src/dfu_int/dfu_servicer.c index 6f17387a..8dd73987 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.c +++ b/examples/ffva/src/dfu_int/dfu_servicer.c @@ -18,9 +18,11 @@ #include "dfu_cmds.h" #include "device_control_i2c.h" -#include "dfu_common.h" // for reboot() +#include "dfu_common.h" #include "dfu_state_machine.h" +extern device_control_t *device_control_i2c_ctx; + void dfu_servicer_init(servicer_t *servicer) { #include "dfu_cmds_map.h" // Included instead of directly adding code since this file is autogenerated. @@ -56,17 +58,14 @@ void dfu_servicer(void *args) { resources[i] = servicer->res_info[i].resource; } - if(appconfI2C_DFU_ENABLED > 0) - { - control_ret_t dc_ret; - debug_printf("Calling device_control_servicer_register(), servicer ID %d, on tile %d, core %d.\n", servicer->id, THIS_XCORE_TILE, rtos_core_id_get()); + control_ret_t dc_ret; + debug_printf("Calling device_control_servicer_register(), servicer ID %d, on tile %d, core %d.\n", servicer->id, THIS_XCORE_TILE, rtos_core_id_get()); - dc_ret = device_control_servicer_register(&servicer_ctx, + dc_ret = device_control_servicer_register(&servicer_ctx, device_control_ctxs, 1, resources, servicer->num_resources); - debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); - } + debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); #if appconfI2C_DFU_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) // Start I2C slave. @@ -98,17 +97,8 @@ void dfu_servicer(void *args) { NULL ); - if(appconfI2C_DFU_ENABLED > 0) - { - for(;;){ - device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); - } - } - else - { - for(;;){ - vTaskDelay(pdMS_TO_TICKS(100)); - } + for(;;){ + device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); } } diff --git a/examples/ffva/src/dfu_int/dfu_servicer.h b/examples/ffva/src/dfu_int/dfu_servicer.h index 92c009ba..558358ab 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.h +++ b/examples/ffva/src/dfu_int/dfu_servicer.h @@ -5,6 +5,23 @@ #define DFU_CONTROLLER_SERVICER_RESID (240) #define NUM_RESOURCES_DFU_SERVICER (1) // DFU servicer +/** + * @brief DFU servicer task. + * + * This task handles DFU commands from the device control interface and relays + * them to the internal DFU INT state machine. + * + * \param args Pointer to the Servicer's state data structure + */ +void dfu_servicer(void *args); + +// Servicer initialization functions +/** + * @brief DFU servicer initialisation function. + * \param servicer Pointer to the Servicer's state data structure + */ +void dfu_servicer_init(servicer_t *servicer); + /** * @brief DFU servicer read command handler * From 2d0b0e48f6e0aa812629c21efa0a44b75a15f9f1 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 12:54:26 +0000 Subject: [PATCH 108/288] Review comments --- .../XK_VOICE_L71/platform/platform_start.c | 19 +++ examples/ffva/src/control/cmd_map.h | 1 - examples/ffva/src/control/packet_queue.c | 110 ------------------ examples/ffva/src/control/packet_queue.h | 110 ------------------ examples/ffva/src/control/servicer.h | 24 +--- examples/ffva/src/dfu_i2c/dfu_servicer.c | 53 +-------- 6 files changed, 28 insertions(+), 289 deletions(-) delete mode 100644 examples/ffva/src/control/packet_queue.c delete mode 100644 examples/ffva/src/control/packet_queue.h diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index 6ecb12b4..b8aea7f6 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -17,6 +17,7 @@ #include "usb_support.h" #if appconfI2C_CTRL_ENABLED +#include "servicer.h" #include "device_control_i2c.h" #endif @@ -69,6 +70,22 @@ static void audio_codec_start(void) #endif } +static void i2c_slave_start(void) +{ +#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) + rtos_i2c_slave_start(i2c_slave_ctx, + device_control_i2c_ctx, + (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, + (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, + (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, + (rtos_i2c_slave_tx_done_cb_t) NULL, + NULL, + NULL, + appconfI2C_INTERRUPT_CORE, + appconfI2C_TASK_PRIORITY); +#endif +} + static void spi_start(void) { #if appconfSPI_OUTPUT_ENABLED && ON_TILE(SPI_OUTPUT_TILE_NO) @@ -143,4 +160,6 @@ void platform_start(void) i2s_start(); usb_start(); uart_start(); + // I2C slave can be started only after i2c_master_start() is completed + i2c_slave_start(); } diff --git a/examples/ffva/src/control/cmd_map.h b/examples/ffva/src/control/cmd_map.h index 9f56b0ff..f8494ee8 100644 --- a/examples/ffva/src/control/cmd_map.h +++ b/examples/ffva/src/control/cmd_map.h @@ -5,7 +5,6 @@ #include #include "device_control_shared.h" -#include "packet_queue.h" // Types of commands in the Command ID enum space - // - Dedicated: Commands dedicated only for the resource diff --git a/examples/ffva/src/control/packet_queue.c b/examples/ffva/src/control/packet_queue.c deleted file mode 100644 index fe900915..00000000 --- a/examples/ffva/src/control/packet_queue.c +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. -#include -#include -#include "debug_print.h" -#include "device_control_shared.h" -#include "packet_queue.h" - -// Return pointer to the first free pkt in the queue -control_ret_t queue_get_free_pkt(control_pkt_t **pkt, control_pkt_queue_t *control_pkt_queue) -{ - // queue_wr_index should always point to the first free packet that can be used. Since commands are always processed in order, - // we shouldn't look beyond queue_wr_index when searching for a free packet. - *pkt = &control_pkt_queue->pkts[control_pkt_queue->queue_wr_index]; - if((*pkt)->pkt_status != PKT_FREE) - { - if ((*pkt)->pkt_status == PKT_DONE && // Resource is done processing the pkt - ((*pkt)->free_when_done) // pkt set to be freed when completed - ) - { - // If this pkt was used for a special command original payload ptr might have been overwritten. - if((*pkt)->save_payload_ptr != NULL) // Payload ptr was overwritten earlier for a special command - { - // Restore original payload ptr - (*pkt)->payload = (*pkt)->save_payload_ptr; - (*pkt)->save_payload_ptr = NULL; - } - (*pkt)->pkt_status = PKT_FREE; - } - else { - debug_printf("ERROR: Control packet queue FULL\n"); - (*pkt) = NULL; - return SERVICER_QUEUE_FULL; - } - } - return CONTROL_SUCCESS; -} - -// Add the packet at queue_wr_index to queue. -void queue_add_pkt(control_pkt_queue_t *control_pkt_queue, int32_t free_when_done) -{ - control_pkt_queue->pkts[control_pkt_queue->queue_wr_index].pkt_status = PKT_WAIT; - control_pkt_queue->pkts[control_pkt_queue->queue_wr_index].free_when_done = free_when_done; - - // Update queue_wr_index - control_pkt_queue->queue_wr_index = (control_pkt_queue->queue_wr_index + 1) % control_pkt_queue->depth; -} - -control_ret_t queue_write_command_to_free_packet(control_pkt_queue_t *control_pkt_queue, control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len) { - // Make sure we're writing to a free packet - control_pkt_t *pkt; - control_ret_t ret = queue_get_free_pkt(&pkt, control_pkt_queue); - if(ret != CONTROL_SUCCESS) - { - return ret; - } - pkt->res_id = resid; - pkt->cmd_id = cmd; - pkt->payload_len = payload_len; - if(!IS_CONTROL_CMD_READ(pkt->cmd_id)) { // Copy payload for write commands. - memcpy(pkt->payload, &payload[0], pkt->payload_len * sizeof(uint8_t)); - } - // Add packet to the queue - /** free_when_done set to 1 for both read and write packets. This is to ensure - * that the packet queue doesn't get full if the host gives up on a read packet - * and stops retrying for the read response. While this would never happen with our - * host app design, this would make a more robust design. - */ - queue_add_pkt(control_pkt_queue, 1); - - //debug_printf("Cmd queue write index = %d\n", control_pkt_queue->queue_wr_index); - return CONTROL_SUCCESS; -} - -// Check if command is present in the packet. -// Return pkt pointer if present, NULL otherwise -control_pkt_t* queue_check_packet(control_pkt_queue_t *pkt_queue, control_cmd_t cmd) -{ - for(int i=0; idepth; i++) - { - if(pkt_queue->pkts[i].cmd_id == cmd) - { - if(pkt_queue->pkts[i].pkt_status != PKT_FREE) - { - return &pkt_queue->pkts[i]; - } - } - } - return NULL; -} - -// Return the packet that queue_rd_index is pointing to -control_pkt_t* queue_read_packet(control_pkt_queue_t *pkt_queue) -{ - // Packets read in order so only check packet at read_pointer location - if(pkt_queue->pkts[pkt_queue->queue_rd_index].pkt_status == PKT_WAIT) - { - return &pkt_queue->pkts[pkt_queue->queue_rd_index]; - } - return NULL; -} - -void queue_set_packet_done(control_pkt_queue_t *pkt_queue) -{ - // Set the packet the current queue_rd_index is pointing to to DONE. Increment queue_rd_index - pkt_queue->pkts[pkt_queue->queue_rd_index].pkt_status = PKT_DONE; - pkt_queue->queue_rd_index = (pkt_queue->queue_rd_index + 1) % pkt_queue->depth; -} - - diff --git a/examples/ffva/src/control/packet_queue.h b/examples/ffva/src/control/packet_queue.h deleted file mode 100644 index b9ca8be8..00000000 --- a/examples/ffva/src/control/packet_queue.h +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. -#pragma once -#include "device_control_shared.h" - -/** - * Clears the read bit on a command code - * - * \param[in,out] c The command code to clear the read bit on. - */ -#define CONTROL_CMD_CLEAR_READ(c) ((c) & ~0x80) - -/** - * @brief Control packet status - * - */ -typedef enum { - PKT_FREE, /// Packet is free and available for the servicer to write into - PKT_WAIT, /// Packet contains a command written by the servicer and is waiting to be processed by the resource - PKT_DONE /// Packet contains a command that has been processed by the underlying resource -}pkt_status_t; - -/** - * @brief Control packet structure - * - */ -typedef struct { - uint8_t res_id; /// Resource ID of the command in the packet - uint8_t cmd_id; /// Command ID for the command in the packet - int32_t payload_len; /// packet payload length in bytes - pkt_status_t pkt_status; /// packet status - void *payload; /// packet payload pointer - uint8_t *save_payload_ptr; /// Holds a copy of the original payload ptr when the original payload_ptr gets overwritten during special command processing - int32_t free_when_done; /// Free packet once its done by the resource. When looking for a free packet, the pkt queue function queue_get_free_pkt() will free a done packet only if this flag is set to 1. -}control_pkt_t; - -/** - * @brief Control packet queue structure - * - */ -typedef struct { - control_pkt_t *pkts; /// Array of packets that make up this queue - int32_t depth; /// Queue depth in number of packets - int32_t queue_wr_index; /// Queue current write index. This points to the packet that will be written to when the servicer attempts to add a packet to the queue. - int32_t queue_rd_index; /// Queue current read index. This points to the packet that the resource will read when it wants to service a command. -}control_pkt_queue_t; - -/** - * @brief Add a command to the pkt queue. - * - * @param control_pkt_queue Pointer to the packet queue object - * @param resid Command resource ID - * @param cmd Command command ID - * @param payload Command payload buffer - * @param payload_len Payload buffer length - * @return CONTROL_SUCCESS if command added to the queue, SERVICER_QUEUE_FULL if queue is full and command cannot be added - */ -control_ret_t queue_write_command_to_free_packet(control_pkt_queue_t *control_pkt_queue, control_resid_t resid, control_cmd_t cmd, const uint8_t *payload, size_t payload_len); - -/** - * @brief Check if a command is present in the packet queue - * - * @param pkt_queue Pointer to the pkt queue objects - * @param cmd Command ID - * @return Pointer to the pkt object holding the command if command is present in the queue, NULL otherwise. - */ -control_pkt_t* queue_check_packet(control_pkt_queue_t *pkt_queue, control_cmd_t cmd); - -/** - * @brief Return the packet that queue's current read pointer is pointing to - * - * @param pkt_queue Pointer to the pkt queue object - * @return Pointer to the packet object if read pointer is pointing to a valid packet, NULL otherwise. - */ -control_pkt_t* queue_read_packet(control_pkt_queue_t *pkt_queue); - -/** - * @brief Set the packet which is at the current_rd_index in the queue to DONE. - * Increment the current_rd_index to point to the next packet that needs processing - * - * @param pkt_queue Pointer to the packet queue object - */ -void queue_set_packet_done(control_pkt_queue_t *pkt_queue); - -// Return pointer to the first free pkt in the queue -/** - * @brief Return pointer to a free packet in the control packet queue - * - * This function returns a free packet pointer which can then be written with a command by - * the servicer and added to the queue. - * Since packets are written to the queue in order, this function only looks at the packet at the queue_wr_index - * and returns the pointer to it if that packet is free or can be freed. - * - * @param pkt free packet pointer that is returned for use by the servicer - * @param control_pkt_queue Pointer to the control packet queue - * @return control_ret_t CONTROL_SUCCESS if a free packet pointer is updated in *pkt, SERVICER_QUEUE_FULL - * if the queue is full. - */ -control_ret_t queue_get_free_pkt(control_pkt_t **pkt, control_pkt_queue_t *control_pkt_queue); - -/** - * @brief Add packet to the control packet queue - * - * This command adds the packet at queue_wr_index to the queue. This means that the packet has now been written - * with a command from the servicer and is waiting for being serviced by the resource. - * - * @param control_pkt_queue Pointer to the control packet queue - * @param free_when_done When set to 1, queue_get_free_pkt() frees the pkt when the resource marks it as Done. - */ -void queue_add_pkt(control_pkt_queue_t *control_pkt_queue, int32_t free_when_done); diff --git a/examples/ffva/src/control/servicer.h b/examples/ffva/src/control/servicer.h index 9a4e8a19..5753a00b 100644 --- a/examples/ffva/src/control/servicer.h +++ b/examples/ffva/src/control/servicer.h @@ -2,7 +2,6 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #pragma once #include "device_control.h" -#include "packet_queue.h" #include "cmd_map.h" #define NUM_TILE_0_SERVICERS (1) // only DFU servicer is used @@ -11,27 +10,18 @@ extern device_control_t *device_control_i2c_ctx; extern device_control_t *device_control_ctxs[1]; -// For handling special commands (Not prototyped so isn't final) -typedef struct { - uint8_t *special_commands_buffer; - int32_t special_cmd_in_progress; /// Flag indicating if a special command is already in progress - int32_t start_host_coeff_index; /// stat coefficient index requested by the host - int32_t special_cmds_buf_size; /// special data chunk size plus extra space for cases when the chunk size is not a multiple of the payload length sent by the host - int32_t special_cmd_payload_size; /// Expected special command payload size in samples - int32_t start_buf_coeff_index; /// Absolute filter coefficient index in special_commands_buffer[0] - int32_t end_buf_coeff_index; /// Absolute index of the last valid filter coefficient index in the special_commands_buffer buffer - /** No. of coefficients worth of overflow space added to the special cmds buffer to accomodate the special commands buffer size not being a multiple of no. of coefficients - * sent per control packet. Used for error checking in the special commands handler.*/ - int32_t special_cmds_buf_overflow_size; -}special_cmd_handler_t; +/** + * Clears the read bit on a command code + * + * \param[in,out] c The command code to clear the read bit on. + */ +#define CONTROL_CMD_CLEAR_READ(c) ((c) & ~0x80) // Structure encapsulating all the information about a resource typedef struct { control_resid_t resource; command_map_t command_map; - control_pkt_queue_t control_pkt_queue; - special_cmd_handler_t special_cmd_handler; }control_resource_info_t; typedef struct { @@ -43,8 +33,6 @@ typedef struct { control_resource_info_t *res_info; }servicer_t; - - /** * @brief DFU servicer task. * diff --git a/examples/ffva/src/dfu_i2c/dfu_servicer.c b/examples/ffva/src/dfu_i2c/dfu_servicer.c index 1044b537..0f24151f 100644 --- a/examples/ffva/src/dfu_i2c/dfu_servicer.c +++ b/examples/ffva/src/dfu_i2c/dfu_servicer.c @@ -1,4 +1,4 @@ -// Copyright 2023-2024 XMOS LIMITED. +// Copyright 2024 XMOS LIMITED. // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT DFU_SERVICER #ifndef DEBUG_PRINT_ENABLE_DFU_SERVICER @@ -36,8 +36,6 @@ void dfu_servicer_init(servicer_t *servicer) servicer->res_info = &dfu_res_info[0]; // Servicer resource servicer->res_info[0].resource = DFU_CONTROLLER_SERVICER_RESID; - servicer->res_info[0].control_pkt_queue.depth = 0; - servicer->res_info[0].control_pkt_queue.pkts = NULL; servicer->res_info[0].command_map.num_commands = NUM_DFU_CONTROLLER_SERVICER_RESID_CMDS; servicer->res_info[0].command_map.commands = dfu_controller_servicer_resid_cmd_map; } @@ -48,9 +46,6 @@ void dfu_servicer(void *args) { servicer_t *servicer = (servicer_t*)args; xassert(servicer != NULL); - for(int i=0; inum_resources; i++) { - servicer->res_info[i].control_pkt_queue.queue_wr_index = 0; - } control_resid_t *resources = (control_resid_t*)pvPortMalloc(servicer->num_resources * sizeof(control_resid_t)); for(int i=0; inum_resources; i++) { @@ -69,41 +64,8 @@ void dfu_servicer(void *args) { debug_printf("Out of device_control_servicer_register(), servicer ID %d, on tile %d. servicer_ctx address = 0x%x\n", servicer->id, THIS_XCORE_TILE, &servicer_ctx); } - #if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) - // Start I2C slave. - // This ends up calling device_control_resources_register() which has a strange non-yielding implementation, - // where it waits for servicers to register with the device control context. That's why, control_start_io_tasks() - // is not called from platform_start(). Additionally, if there is only one xcore core dedicated for all RTOS tasks, - // this design will not work, since device_control_resources_register() will not yield and the servicers wouldn't get - // scheduled so they could register with the device control leading to an eventual timeout error from device_control_resources_register(). - - rtos_i2c_slave_start(i2c_slave_ctx, - device_control_i2c_ctx, - (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, - (rtos_i2c_slave_rx_cb_t) device_control_i2c_rx_cb, - (rtos_i2c_slave_tx_start_cb_t) device_control_i2c_tx_start_cb, - (rtos_i2c_slave_tx_done_cb_t) NULL, - NULL, - NULL, - appconfI2C_INTERRUPT_CORE, - appconfI2C_TASK_PRIORITY); -#endif vPortFree(resources); - // The first call to device_control_servicer_cmd_recv triggers the device_control_i2c_start_cb() through I2S slave ISR call somehow. device_control_i2c_start_cb() and device_control_servicer_cmd_recv() have to be in separate logical cores - // and be scheduled simultaneously for this to work. - if(APP_CONTROL_TRANSPORT_COUNT > 0) - { - for(;;){ - device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); - } - } - else - { - for(;;){ - vTaskDelay(pdMS_TO_TICKS(100)); - } - } // TODO: Enable lines below when the rest of DFU code is included /*xTaskCreate( dfu_int_state_machine, @@ -114,17 +76,8 @@ void dfu_servicer(void *args) { NULL );*/ - if(appconfI2C_CTRL_ENABLED > 0) - { - for(;;){ - device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); - } - } - else - { - for(;;){ - vTaskDelay(pdMS_TO_TICKS(100)); - } + for(;;){ + device_control_servicer_cmd_recv(&servicer_ctx, read_cmd, write_cmd, servicer, RTOS_OSAL_WAIT_FOREVER); } } From 6e4b5d9f21bf928a12fa58550e61e14134d03ee3 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 13:19:15 +0000 Subject: [PATCH 109/288] Fix more merge conflicts --- .../XCORE-AI-EXPLORER/platform/platform_start.c | 1 - .../bsp_config/XK_VOICE_L71/platform/platform_conf.h | 10 +++++----- .../bsp_config/XK_VOICE_L71/platform/platform_init.c | 3 ++- .../bsp_config/XK_VOICE_L71/platform/platform_start.c | 8 +------- examples/ffva/src/dfu_int/dfu_servicer.h | 2 ++ examples/ffva/src/main.c | 2 +- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c index 3e5fcc54..88bddc69 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c @@ -22,7 +22,6 @@ #endif extern void i2s_rate_conversion_enable(void); -extern device_control_t *device_control_i2c_ctx; static void gpio_start(void) { diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index 0a55ccfd..fa664b0e 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -101,7 +101,7 @@ #define appconfPIPELINE_AUDIO_SAMPLE_RATE 16000 #endif /* appconfPIPELINE_AUDIO_SAMPLE_RATE */ -#ifndef appconfI2C_CTRL_ENABLED +#ifndef appconfI2C_DFU_ENABLED #if ! ASR_CYBERON /* * When this is enabled on the XK-VOICE-L71 board, the board @@ -110,14 +110,14 @@ * MCLK will also default to be external if this is set on * the XK-VOICE-L71 board. */ -#define appconfI2C_CTRL_ENABLED 1 +#define appconfI2C_DFU_ENABLED 1 #else -#define appconfI2C_CTRL_ENABLED 0 +#define appconfI2C_DFU_ENABLED 0 #endif -#endif /* appconfI2C_CTRL_ENABLED */ +#endif /* appconfI2C_DFU_ENABLED */ #ifndef APP_CONTROL_TRANSPORT_COUNT -#define APP_CONTROL_TRANSPORT_COUNT appconfI2C_CTRL_ENABLED +#define APP_CONTROL_TRANSPORT_COUNT appconfI2C_DFU_ENABLED #endif // APP_CONTROL_TRANSPORT_COUNT #ifndef appconfEXTERNAL_MCLK diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c index 2947fb7c..e153cd8e 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_init.c @@ -14,6 +14,8 @@ #include "device_control.h" #include "servicer.h" +extern device_control_t *device_control_i2c_ctx; + static void mclk_init(chanend_t other_tile_c) { #if !appconfEXTERNAL_MCLK && ON_TILE(1) @@ -317,7 +319,6 @@ static void uart_init(void) tmr_tx); #endif } -extern device_control_t *device_control_i2c_ctx; void control_init() { #if appconfI2C_DFU_ENABLED && ON_TILE(I2C_TILE_NO) diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c index e346fba7..73f05f60 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_start.c @@ -16,14 +16,8 @@ #include "dac3101.h" #include "usb_support.h" -<<<<<<< HEAD -#if appconfI2C_DFU_ENABLED -======= -#if appconfI2C_CTRL_ENABLED #include "servicer.h" ->>>>>>> feature/i2c_control #include "device_control_i2c.h" -#endif extern void i2s_rate_conversion_enable(void); @@ -76,7 +70,7 @@ static void audio_codec_start(void) static void i2c_slave_start(void) { -#if appconfI2C_CTRL_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) +#if appconfI2C_DFU_ENABLED && ON_TILE(I2C_CTRL_TILE_NO) rtos_i2c_slave_start(i2c_slave_ctx, device_control_i2c_ctx, (rtos_i2c_slave_start_cb_t) device_control_i2c_start_cb, diff --git a/examples/ffva/src/dfu_int/dfu_servicer.h b/examples/ffva/src/dfu_int/dfu_servicer.h index 558358ab..94006928 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.h +++ b/examples/ffva/src/dfu_int/dfu_servicer.h @@ -2,6 +2,8 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #pragma once +#include "servicer.h" + #define DFU_CONTROLLER_SERVICER_RESID (240) #define NUM_RESOURCES_DFU_SERVICER (1) // DFU servicer diff --git a/examples/ffva/src/main.c b/examples/ffva/src/main.c index 42d88e1e..62843874 100644 --- a/examples/ffva/src/main.c +++ b/examples/ffva/src/main.c @@ -23,7 +23,7 @@ #include "usb_support.h" #include "usb_audio.h" #include "audio_pipeline.h" -#include "servicer.h" +#include "dfu_servicer.h" /* Headers used for the WW intent engine */ #if appconfINTENT_ENABLED From 68b004fdf9f1dcfa923cfb29074c46da0c574024 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 13:45:14 +0000 Subject: [PATCH 110/288] Fix errors --- examples/ffva/src/dfu_int/dfu_common.c | 1 + examples/ffva/src/dfu_int/dfu_servicer.c | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/ffva/src/dfu_int/dfu_common.c b/examples/ffva/src/dfu_int/dfu_common.c index 74b02537..afb2a3d6 100644 --- a/examples/ffva/src/dfu_int/dfu_common.c +++ b/examples/ffva/src/dfu_int/dfu_common.c @@ -153,6 +153,7 @@ uint16_t dfu_common_read_from_flash(uint8_t alt, #define WDT_PRESCALER_MILLISECONDS (24000 - 1) // Set prescaler to tick every millisecond, assuming 24MHz XIN. Note max value 65535. +// TODO: Check the reboot function void reboot(void) { rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); diff --git a/examples/ffva/src/dfu_int/dfu_servicer.c b/examples/ffva/src/dfu_int/dfu_servicer.c index 359a93cc..192e30fa 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.c +++ b/examples/ffva/src/dfu_int/dfu_servicer.c @@ -21,8 +21,6 @@ #include "dfu_common.h" #include "dfu_state_machine.h" -extern device_control_t *device_control_i2c_ctx; - void dfu_servicer_init(servicer_t *servicer) { #include "dfu_cmds_map.h" // Included instead of directly adding code since this file is autogenerated. From 3b14ea5804fc146d50624c9003c437ec9ca41219 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 16:02:09 +0000 Subject: [PATCH 111/288] Add version command --- examples/ffva/src/app_conf.h | 6 ++++++ examples/ffva/src/dfu_int/dfu_cmds.h | 7 +++++++ examples/ffva/src/dfu_int/dfu_cmds_map.h | 1 + examples/ffva/src/dfu_int/dfu_common.c | 23 +++++++++-------------- examples/ffva/src/dfu_int/dfu_servicer.c | 8 ++++++++ 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index afecdf8f..c58f681c 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -4,6 +4,12 @@ #ifndef APP_CONF_H_ #define APP_CONF_H_ +/* Applciation version */ +/* This values are only used the xvf_dfu host app for FFVA-INT*/ +#define VERSION_MAJOR 255 +#define VERSION_MINOR 254 +#define VERSION_PATCH 253 + /* Intertile port settings */ #define appconfUSB_AUDIO_PORT 0 #define appconfGPIO_T0_RPC_PORT 1 diff --git a/examples/ffva/src/dfu_int/dfu_cmds.h b/examples/ffva/src/dfu_int/dfu_cmds.h index a4c82915..464ffa1f 100644 --- a/examples/ffva/src/dfu_int/dfu_cmds.h +++ b/examples/ffva/src/dfu_int/dfu_cmds.h @@ -39,6 +39,9 @@ enum e_dfu_controller_servicer_resid_cmds #ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK = 65, #endif +#ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION + DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION = 88, +#endif #ifndef DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT = 89, #endif @@ -64,6 +67,8 @@ enum e_dfu_controller_servicer_resid_cmds #define DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE_NUM_VALUES (1) // number of values of type dfu_controller_servicer_resid_dfu_transferblock_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK #define DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK_NUM_VALUES (2) +// number of values of type dfu_controller_servicer_resid_dfu_getversion_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION +#define DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION_NUM_VALUES (3) // number of values of type dfu_controller_servicer_resid_dfu_reboot_t expected by DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT #define DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT_NUM_VALUES (1) @@ -86,5 +91,7 @@ typedef uint8_t dfu_controller_servicer_resid_dfu_abort_t; typedef uint8_t dfu_controller_servicer_resid_dfu_setalternate_t; // type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK typedef uint8_t dfu_controller_servicer_resid_dfu_transferblock_t; +// type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION +typedef uint8_t dfu_controller_servicer_resid_dfu_getversion_t; // type expected by DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT typedef uint8_t dfu_controller_servicer_resid_dfu_reboot_t; diff --git a/examples/ffva/src/dfu_int/dfu_cmds_map.h b/examples/ffva/src/dfu_int/dfu_cmds_map.h index 7ce373e7..7e7d85b4 100644 --- a/examples/ffva/src/dfu_int/dfu_cmds_map.h +++ b/examples/ffva/src/dfu_int/dfu_cmds_map.h @@ -18,6 +18,7 @@ static control_cmd_info_t dfu_controller_servicer_resid_cmd_map[] = { DFU_CONTROLLER_SERVICER_RESID_DFU_ABORT, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, { DFU_CONTROLLER_SERVICER_RESID_DFU_SETALTERNATE, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, { DFU_CONTROLLER_SERVICER_RESID_DFU_TRANSFERBLOCK, 2, sizeof(uint8_t), CMD_READ_WRITE }, + { DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION, 3, sizeof(uint8_t), CMD_READ_ONLY }, { DFU_CONTROLLER_SERVICER_RESID_DFU_REBOOT, 1, sizeof(uint8_t), CMD_WRITE_ONLY }, }; #pragma clang diagnostic pop diff --git a/examples/ffva/src/dfu_int/dfu_common.c b/examples/ffva/src/dfu_int/dfu_common.c index afb2a3d6..e9c996b0 100644 --- a/examples/ffva/src/dfu_int/dfu_common.c +++ b/examples/ffva/src/dfu_int/dfu_common.c @@ -151,22 +151,17 @@ uint16_t dfu_common_read_from_flash(uint8_t alt, return retval; } -#define WDT_PRESCALER_MILLISECONDS (24000 - 1) // Set prescaler to tick every millisecond, assuming 24MHz XIN. Note max value 65535. - -// TODO: Check the reboot function +// TODO: Check if lines commented out below are necessary void reboot(void) { + //#define WDT_PRESCALER_MILLISECONDS (24000 - 1) // Set prescaler to tick every millisecond, assuming 24MHz XIN. Note max value 65535. rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_WRAP_NUM, WDT_PRESCALER_MILLISECONDS); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 0x10000); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_NUM, 0); // Reset counter + //write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_WRAP_NUM, WDT_PRESCALER_MILLISECONDS); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 100); + //write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_NUM, 0); // Reset counter write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); - //while(1) {;} -//#define WDT_PRESCALER_MILLISECONDS (24000 - 1) // Set prescaler to tick every millisecond, assuming 24MHz XIN. Note max value 65535. - -// write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_WRAP_NUM, WDT_PRESCALER_MILLISECONDS); -// write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 100); // Set WDT trigger after this many ticks. Note this is an 11b field so 2047 max -// write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_NUM, 0); // Reset counter -// write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); - //debug_printf("Rebooting!\n"); + // If we are running DFU over I2C return to the application, so we can close the I2C connection + #if ! appconfI2C_DFU_ENABLED + while(1) {;} +#endif } \ No newline at end of file diff --git a/examples/ffva/src/dfu_int/dfu_servicer.c b/examples/ffva/src/dfu_int/dfu_servicer.c index 192e30fa..8681086d 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.c +++ b/examples/ffva/src/dfu_int/dfu_servicer.c @@ -141,6 +141,14 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c break; } + case DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION: + { + debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION\n"); + static const uint8_t version[3] = {VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH}; + memcpy(payload, &version, sizeof(version)); + break; + } + default: { debug_printf("DFU_CONTROLLER_SERVICER UNHANDLED COMMAND!!!\n"); From 0568f0a9b88e98ece923af8814f0a942887a3f2f Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 16:08:50 +0000 Subject: [PATCH 112/288] Update reboot --- examples/ffva/src/dfu_int/dfu_common.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/ffva/src/dfu_int/dfu_common.c b/examples/ffva/src/dfu_int/dfu_common.c index e9c996b0..5ab0affb 100644 --- a/examples/ffva/src/dfu_int/dfu_common.c +++ b/examples/ffva/src/dfu_int/dfu_common.c @@ -154,13 +154,12 @@ uint16_t dfu_common_read_from_flash(uint8_t alt, // TODO: Check if lines commented out below are necessary void reboot(void) { - //#define WDT_PRESCALER_MILLISECONDS (24000 - 1) // Set prescaler to tick every millisecond, assuming 24MHz XIN. Note max value 65535. rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); - //write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_WRAP_NUM, WDT_PRESCALER_MILLISECONDS); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_WRAP_NUM, (24000 - 1)); write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 100); - //write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_NUM, 0); // Reset counter + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_NUM, 0); // Reset counter write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); - // If we are running DFU over I2C return to the application, so we can close the I2C connection + // If we are running DFU over I2C return to the application, so that we can close the I2C connection #if ! appconfI2C_DFU_ENABLED while(1) {;} #endif From cc0df25e4c542d5411a024e5f09248c8cac3d65d Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 16:12:30 +0000 Subject: [PATCH 113/288] Fix comments --- examples/ffva/src/app_conf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index c58f681c..617d573b 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -4,8 +4,8 @@ #ifndef APP_CONF_H_ #define APP_CONF_H_ -/* Applciation version */ -/* This values are only used the xvf_dfu host app for FFVA-INT*/ +/* Application version numbers */ +/* This values are only used by the xvf_dfu host app for FFVA-INT*/ #define VERSION_MAJOR 255 #define VERSION_MINOR 254 #define VERSION_PATCH 253 From 539e582d9b068ff18e8a8481c07a6a064ddabec6 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Thu, 21 Mar 2024 16:57:51 +0000 Subject: [PATCH 114/288] Remove comment --- examples/ffva/src/dfu_int/dfu_common.c | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/ffva/src/dfu_int/dfu_common.c b/examples/ffva/src/dfu_int/dfu_common.c index 5ab0affb..ef0ddaa8 100644 --- a/examples/ffva/src/dfu_int/dfu_common.c +++ b/examples/ffva/src/dfu_int/dfu_common.c @@ -151,7 +151,6 @@ uint16_t dfu_common_read_from_flash(uint8_t alt, return retval; } -// TODO: Check if lines commented out below are necessary void reboot(void) { rtos_printf("Reboot initiated by tile:0x%x\n", get_local_tile_id()); From 3808407ac7b2a16adf808050d4676451e24a7e77 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 08:19:23 +0000 Subject: [PATCH 115/288] Disable debug printouts --- examples/ffva/src/dfu_int/dfu_common.c | 2 +- examples/ffva/src/dfu_int/dfu_servicer.c | 2 +- examples/ffva/src/dfu_int/dfu_state_machine.c | 2 +- examples/ffva/src/usb/adaptive_rate_adjust.c | 2 +- examples/ffva/src/usb/usb_audio.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/ffva/src/dfu_int/dfu_common.c b/examples/ffva/src/dfu_int/dfu_common.c index ef0ddaa8..ceb52f7c 100644 --- a/examples/ffva/src/dfu_int/dfu_common.c +++ b/examples/ffva/src/dfu_int/dfu_common.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT DFU_COMMON #ifndef DEBUG_PRINT_ENABLE_DFU_COMMON -#define DEBUG_PRINT_ENABLE_DFU_COMMON 1 +#define DEBUG_PRINT_ENABLE_DFU_COMMON 0 #endif #include "debug_print.h" diff --git a/examples/ffva/src/dfu_int/dfu_servicer.c b/examples/ffva/src/dfu_int/dfu_servicer.c index 8681086d..a293f822 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.c +++ b/examples/ffva/src/dfu_int/dfu_servicer.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT DFU_SERVICER #ifndef DEBUG_PRINT_ENABLE_DFU_SERVICER -#define DEBUG_PRINT_ENABLE_DFU_SERVICER 1 +#define DEBUG_PRINT_ENABLE_DFU_SERVICER 0 #endif #include "debug_print.h" diff --git a/examples/ffva/src/dfu_int/dfu_state_machine.c b/examples/ffva/src/dfu_int/dfu_state_machine.c index 121284d4..5b1fdff1 100644 --- a/examples/ffva/src/dfu_int/dfu_state_machine.c +++ b/examples/ffva/src/dfu_int/dfu_state_machine.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XCORE VocalFusion Licence. #define DEBUG_UNIT DFU_STATE_MACHINE #ifndef DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE -#define DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE 1 +#define DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE 0 #endif #include "debug_print.h" diff --git a/examples/ffva/src/usb/adaptive_rate_adjust.c b/examples/ffva/src/usb/adaptive_rate_adjust.c index fd72ba6b..b26de51a 100644 --- a/examples/ffva/src/usb/adaptive_rate_adjust.c +++ b/examples/ffva/src/usb/adaptive_rate_adjust.c @@ -2,7 +2,7 @@ // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define DEBUG_UNIT ADAPTIVE_USB -#define DEBUG_PRINT_ENABLE_ADAPTIVE_USB 1 +#define DEBUG_PRINT_ENABLE_ADAPTIVE_USB 0 // Taken from usb_descriptors.c #define USB_AUDIO_EP 0x01 diff --git a/examples/ffva/src/usb/usb_audio.c b/examples/ffva/src/usb/usb_audio.c index 97e5fdde..aef12af7 100644 --- a/examples/ffva/src/usb/usb_audio.c +++ b/examples/ffva/src/usb/usb_audio.c @@ -26,7 +26,7 @@ * */ #define DEBUG_UNIT USB_AUDIO -#define DEBUG_PRINT_ENABLE_USB_AUDIO 1 +#define DEBUG_PRINT_ENABLE_USB_AUDIO 0 #include #include From 831485c886fdb794015d4ea534eec9d0ee322f17 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 08:27:39 +0000 Subject: [PATCH 116/288] Fix comments --- examples/ffva/src/app_conf.h | 19 ++++++++++++++----- examples/ffva/src/dfu_int/dfu_servicer.c | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/examples/ffva/src/app_conf.h b/examples/ffva/src/app_conf.h index 617d573b..26edf503 100644 --- a/examples/ffva/src/app_conf.h +++ b/examples/ffva/src/app_conf.h @@ -4,11 +4,20 @@ #ifndef APP_CONF_H_ #define APP_CONF_H_ -/* Application version numbers */ -/* This values are only used by the xvf_dfu host app for FFVA-INT*/ -#define VERSION_MAJOR 255 -#define VERSION_MINOR 254 -#define VERSION_PATCH 253 +/** + * Application version numbers + * These values can be read by the xvf_dfu host app + * The xvf_dfu app is used with the FFVA-INT device only + */ +#ifndef APP_VERSION_MAJOR +#define APP_VERSION_MAJOR 255 +#endif +#ifndef APP_VERSION_MINOR +#define APP_VERSION_MINOR 254 +#endif +#ifndef APP_VERSION_PATCH +#define APP_VERSION_PATCH 253 +#endif /* Intertile port settings */ #define appconfUSB_AUDIO_PORT 0 diff --git a/examples/ffva/src/dfu_int/dfu_servicer.c b/examples/ffva/src/dfu_int/dfu_servicer.c index a293f822..dc1768c3 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.c +++ b/examples/ffva/src/dfu_int/dfu_servicer.c @@ -144,7 +144,7 @@ control_ret_t dfu_servicer_read_cmd(control_resource_info_t *res_info, control_c case DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION: { debug_printf("DFU_CONTROLLER_SERVICER_RESID_DFU_GETVERSION\n"); - static const uint8_t version[3] = {VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH}; + static const uint8_t version[3] = {APP_VERSION_MAJOR, APP_VERSION_MINOR, APP_VERSION_PATCH}; memcpy(payload, &version, sizeof(version)); break; } From 0112237d913858cf1f302bd3efc4f5ab7e2d4899 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 09:33:45 +0000 Subject: [PATCH 117/288] Remove warnings --- examples/ffva/src/usb/usb_audio.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/ffva/src/usb/usb_audio.c b/examples/ffva/src/usb/usb_audio.c index aef12af7..5daa6d3c 100644 --- a/examples/ffva/src/usb/usb_audio.c +++ b/examples/ffva/src/usb/usb_audio.c @@ -744,7 +744,9 @@ bool tud_audio_set_itf_cb(uint8_t rhport, { (void) rhport; uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex)); +#if DEBUG_PRINT_ENABLE_USB_AUDIO uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue)); +#endif #if AUDIO_OUTPUT_ENABLED if (itf == ITF_NUM_AUDIO_STREAMING_SPK) { @@ -774,8 +776,9 @@ bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, { (void) rhport; uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex)); +#if DEBUG_PRINT_ENABLE_USB_AUDIO uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue)); - +#endif #if AUDIO_OUTPUT_ENABLED if (itf == ITF_NUM_AUDIO_STREAMING_SPK) { spkr_interface_open = false; From 31d41d9688e51f7a4e8107f2628a9b9bdbdb855b Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 09:35:21 +0000 Subject: [PATCH 118/288] Update test --- test/device_firmware_update/check_dfu_i2c.sh | 29 ++++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index b670a4a9..5b671d8e 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -3,15 +3,17 @@ # To run this tests do the following: # 1. Configure a Rapsberry-Pi: # a. Follow the instructions for XVF3800-INT on https://github.com/xmos/vocalfusion-rpi-setup?tab=readme-ov-file#setup -# b. Install dfu-util on a Raspbnerry-Pi and add it to the path -# 2. Connect a voice-reference board to the Raspberry-Pi expander -# 2. Connect an xTAG to voice-reference board from a host machine, and flash a voice-reference board with example_ffva_int_fixed_delay -# 3. Generate an upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download1.bin -# 4. Generate a different upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download2.bin -# 5. Copy the files download1.bin, download2.bin, emptyfile.bin and check_dfu_i2c.sh to the Raspberry-Pi -# 6. On the Raspberry-Pi, set the desired value of ITERATION_NUM in check_dfu_i2c.sh and run this script: `source check_dfu_i2c.sh` - -ITERATION_NUM=400 +# b. Clone on the Raspberry Pi the repo host_xvf_control using `git clone https://github.com/xmos/host_xvf_control` +# c. In the file host_xvf_control/src/dfu/transport_config.yaml update the value of I2C_ADDRESS from 0x2C to 0x42 +# d. Build the xvf_dfu host application on a Raspberry-Pi using the instructions in https://github.com/xmos/host_xvf_control +# 2. Connect a XK-VOICE-L71 board to the Raspberry-Pi expander +# 3. Connect an xTAG to XK-VOICE-L71 board from a host machine, and flash the board with the application example_ffva_int_fixed_delay +# 4. Generate an upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download1.bin +# 5. Generate a different upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download2.bin +# 6. Copy the files download1.bin, download2.bin, emptyfile.bin and check_dfu_i2c.sh to the host_xvf_control/build folder on the Raspberry-Pi +# 7. On the Raspberry-Pi, set the desired value of ITERATION_NUM in check_dfu_i2c.sh and run this script: `source check_dfu_i2c.sh` + +ITERATION_NUM=2 UPGRADE_FILE_1="download1.bin" UPGRADE_FILE_2="download2.bin" EMPTY_FILE="emptyfile.bin" @@ -31,7 +33,10 @@ check_upgrade() { ./xvf_dfu --upload-upgrade $UPLOAD_FILE # Compare uploaded and downloaded images - diff $UPLOAD_FILE $1 + upload_size=$(stat -c '%s' $UPLOAD_FILE) + # The uploaded file is not padded, + # so don't include the padding of the downloaded image + cmp -n $upload_size $UPLOAD_FILE $1 if [[ $? == 0 ]]; then echo "Upload of $1 is correct" else @@ -39,8 +44,8 @@ check_upgrade() { exit 1 fi - # Delete downloaded image - rm $1 + # Delete uploaded images + rm $UPLOAD_FILE # 1s delay sleep 1 From 3169d5264d2fadeb5ed9c6d6d30a4e2cbd720ddf Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 09:36:28 +0000 Subject: [PATCH 119/288] Update fwk_rtos --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1e161fca..20b536b7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ XCORE-VOICE change log * ADDED: FFVA INT example with Cyberon speech recognition engine and model (DSpotter v2.2.18.0). * CHANGED: Remove need to use external MCLK in FFVA INT examples. + * CHANGED: Updated submodule fwk_rtos to version 3.1.0 from 3.0.5. * ADDED: Support for DFU over I2C for FFVA INT example. * ADDED: lib_sw_pll submodule v1.1.0. From b6bb99348ba7acf2e8cb513054d375eda5677400 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 09:38:21 +0000 Subject: [PATCH 120/288] Update fwk_rtos --- modules/rtos | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rtos b/modules/rtos index 9923425b..7be57246 160000 --- a/modules/rtos +++ b/modules/rtos @@ -1 +1 @@ -Subproject commit 9923425bfdfd494b2f49bd57b47caed98cde22ee +Subproject commit 7be57246f7541202b29f7cbab350a8b0e9a084f1 From c92e1974e739a6c6f9a2aac04fac3ef7140deaf7 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 09:47:45 +0000 Subject: [PATCH 121/288] Fix reboot --- examples/ffva/src/dfu_int/dfu_common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/ffva/src/dfu_int/dfu_common.c b/examples/ffva/src/dfu_int/dfu_common.c index ceb52f7c..e562a8e7 100644 --- a/examples/ffva/src/dfu_int/dfu_common.c +++ b/examples/ffva/src/dfu_int/dfu_common.c @@ -14,6 +14,7 @@ #include "rtos_dfu_image.h" #include "rtos_qspi_flash.h" #include "platform/driver_instances.h" +#include "platform/platform_conf.h" // needed for appconfI2C_DFU_ENABLED static size_t bytes_avail = 0; static uint32_t dn_base_addr = 0; @@ -158,7 +159,7 @@ void reboot(void) write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_COUNT_NUM, 100); write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_PRESCALER_NUM, 0); // Reset counter write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_WATCHDOG_CFG_NUM, (1 << XS1_WATCHDOG_COUNT_ENABLE_SHIFT) | (1 << XS1_WATCHDOG_TRIGGER_ENABLE_SHIFT) ); - // If we are running DFU over I2C return to the application, so that we can close the I2C connection + // If running DFU over I2C this function returns to the application, so that the I2C connection can be closed #if ! appconfI2C_DFU_ENABLED while(1) {;} #endif From 43cbe675aec6a154e6bc2271509ceb0077cc600c Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 10:04:45 +0000 Subject: [PATCH 122/288] Add a new command --- test/device_firmware_update/check_dfu_i2c.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index 5b671d8e..087e5109 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -24,11 +24,14 @@ check_upgrade() { # Download upgrade image ./xvf_dfu --download $1 # Reboot the device - ./xvf_dfu -r + ./xvf_dfu --reboot # 3s delay sleep 3 + # Read app version, just for debugging + ./xvf_dfu --version + # Upload upgrade image ./xvf_dfu --upload-upgrade $UPLOAD_FILE From 5e676921214b6a6d1e42e004d821287962762501 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 11:36:44 +0000 Subject: [PATCH 123/288] Update comments --- test/device_firmware_update/check_dfu_i2c.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/device_firmware_update/check_dfu_i2c.sh b/test/device_firmware_update/check_dfu_i2c.sh index 087e5109..f0049ce5 100644 --- a/test/device_firmware_update/check_dfu_i2c.sh +++ b/test/device_firmware_update/check_dfu_i2c.sh @@ -1,13 +1,15 @@ #!/bin/bash -# To run this tests do the following: +# To run this test do the following: # 1. Configure a Rapsberry-Pi: # a. Follow the instructions for XVF3800-INT on https://github.com/xmos/vocalfusion-rpi-setup?tab=readme-ov-file#setup # b. Clone on the Raspberry Pi the repo host_xvf_control using `git clone https://github.com/xmos/host_xvf_control` # c. In the file host_xvf_control/src/dfu/transport_config.yaml update the value of I2C_ADDRESS from 0x2C to 0x42 -# d. Build the xvf_dfu host application on a Raspberry-Pi using the instructions in https://github.com/xmos/host_xvf_control -# 2. Connect a XK-VOICE-L71 board to the Raspberry-Pi expander -# 3. Connect an xTAG to XK-VOICE-L71 board from a host machine, and flash the board with the application example_ffva_int_fixed_delay +# d. Build the xvf_dfu host application on a Raspberry-Pi using the instructions in https://github.com/xmos/host_xvf_control/blob/main/README.rst +# 2. Prepare the hardware +# a. Attach an XK-VOICE-L71 board to the Raspberry-Pi expander +# b. Connect an xTAG to XK-VOICE-L71 board and to a host machine +# 3. Build the application example_ffva_int_fixed_delay and flash it to the board # 4. Generate an upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download1.bin # 5. Generate a different upgrade image using `xflash --upgrade 1 example_ffva_int_fixed_delay.xe --factory-version 15.2 -o download2.bin # 6. Copy the files download1.bin, download2.bin, emptyfile.bin and check_dfu_i2c.sh to the host_xvf_control/build folder on the Raspberry-Pi @@ -23,6 +25,7 @@ check_upgrade() { # Download upgrade image ./xvf_dfu --download $1 + # Reboot the device ./xvf_dfu --reboot @@ -56,7 +59,7 @@ check_upgrade() { counter=0 -#Checking if the file exists +# Checking if necessary files exist if [ ! -f $UPGRADE_FILE_1 ]; then echo "File $UPGRADE_FILE_1 doesn't exist." exit -1 @@ -73,7 +76,9 @@ fi while [ $counter -lt $ITERATION_NUM ] do counter=$(( $counter + 1 )) + echo "------------------------" echo "DFU attempt number $counter" + echo "------------------------" # Download first upgrade image check_upgrade $UPGRADE_FILE_1 From 433bdc1f4260df125efcdcd9ecc9e369977a8898 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 12:44:09 +0000 Subject: [PATCH 124/288] Update copyright notices --- .../bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c | 2 +- .../bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c | 2 +- examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c | 2 +- .../ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h | 2 +- examples/ffva/src/control/cmd_map.h | 4 ++-- examples/ffva/src/control/servicer.c | 2 +- examples/ffva/src/control/servicer.h | 2 +- examples/ffva/src/dfu_int/dfu_cmds.h | 2 +- examples/ffva/src/dfu_int/dfu_cmds_map.h | 2 +- examples/ffva/src/dfu_int/dfu_common.c | 2 +- examples/ffva/src/dfu_int/dfu_common.h | 2 +- examples/ffva/src/dfu_int/dfu_servicer.c | 2 +- examples/ffva/src/dfu_int/dfu_servicer.h | 2 +- examples/ffva/src/dfu_int/dfu_state_machine.c | 2 +- examples/ffva/src/dfu_int/dfu_state_machine.h | 2 +- examples/ffva/src/usb/adaptive_rate_adjust.c | 2 +- examples/ffva/src/usb/usb_audio.c | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c index ffbb0e61..70116ab8 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_init.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* System headers */ diff --git a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c index 88bddc69..846b0055 100644 --- a/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c +++ b/examples/ffva/bsp_config/XCORE-AI-EXPLORER/platform/platform_start.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* System headers */ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c b/examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c index edd95332..e3bda5f1 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/dac_port.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* System headers */ diff --git a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h index fa664b0e..f8b6cab8 100644 --- a/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h +++ b/examples/ffva/bsp_config/XK_VOICE_L71/platform/platform_conf.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef PLATFORM_CONF_H_ diff --git a/examples/ffva/src/control/cmd_map.h b/examples/ffva/src/control/cmd_map.h index f8494ee8..71467e20 100644 --- a/examples/ffva/src/control/cmd_map.h +++ b/examples/ffva/src/control/cmd_map.h @@ -1,5 +1,5 @@ -// Copyright 2022-2023 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// Copyright 2022-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #pragma once diff --git a/examples/ffva/src/control/servicer.c b/examples/ffva/src/control/servicer.c index 0cb6007c..9a93d115 100644 --- a/examples/ffva/src/control/servicer.c +++ b/examples/ffva/src/control/servicer.c @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #define DEBUG_UNIT SERVICER_TASK #ifndef DEBUG_PRINT_ENABLE_SERVICER_TASK #define DEBUG_PRINT_ENABLE_SERVICER_TASK 0 diff --git a/examples/ffva/src/control/servicer.h b/examples/ffva/src/control/servicer.h index c18a75ba..11ebd21c 100644 --- a/examples/ffva/src/control/servicer.h +++ b/examples/ffva/src/control/servicer.h @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #pragma once #include "device_control.h" #include "cmd_map.h" diff --git a/examples/ffva/src/dfu_int/dfu_cmds.h b/examples/ffva/src/dfu_int/dfu_cmds.h index 464ffa1f..186f35c0 100644 --- a/examples/ffva/src/dfu_int/dfu_cmds.h +++ b/examples/ffva/src/dfu_int/dfu_cmds.h @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #pragma once diff --git a/examples/ffva/src/dfu_int/dfu_cmds_map.h b/examples/ffva/src/dfu_int/dfu_cmds_map.h index 7e7d85b4..be59a2e7 100644 --- a/examples/ffva/src/dfu_int/dfu_cmds_map.h +++ b/examples/ffva/src/dfu_int/dfu_cmds_map.h @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-variable" diff --git a/examples/ffva/src/dfu_int/dfu_common.c b/examples/ffva/src/dfu_int/dfu_common.c index e562a8e7..3801aafb 100644 --- a/examples/ffva/src/dfu_int/dfu_common.c +++ b/examples/ffva/src/dfu_int/dfu_common.c @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #define DEBUG_UNIT DFU_COMMON #ifndef DEBUG_PRINT_ENABLE_DFU_COMMON #define DEBUG_PRINT_ENABLE_DFU_COMMON 0 diff --git a/examples/ffva/src/dfu_int/dfu_common.h b/examples/ffva/src/dfu_int/dfu_common.h index 3273d3fe..197b6605 100644 --- a/examples/ffva/src/dfu_int/dfu_common.h +++ b/examples/ffva/src/dfu_int/dfu_common.h @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/examples/ffva/src/dfu_int/dfu_servicer.c b/examples/ffva/src/dfu_int/dfu_servicer.c index dc1768c3..dfc5cc18 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.c +++ b/examples/ffva/src/dfu_int/dfu_servicer.c @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #define DEBUG_UNIT DFU_SERVICER #ifndef DEBUG_PRINT_ENABLE_DFU_SERVICER #define DEBUG_PRINT_ENABLE_DFU_SERVICER 0 diff --git a/examples/ffva/src/dfu_int/dfu_servicer.h b/examples/ffva/src/dfu_int/dfu_servicer.h index 94006928..2f2e240f 100644 --- a/examples/ffva/src/dfu_int/dfu_servicer.h +++ b/examples/ffva/src/dfu_int/dfu_servicer.h @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #pragma once #include "servicer.h" diff --git a/examples/ffva/src/dfu_int/dfu_state_machine.c b/examples/ffva/src/dfu_int/dfu_state_machine.c index 5b1fdff1..ee354efd 100644 --- a/examples/ffva/src/dfu_int/dfu_state_machine.c +++ b/examples/ffva/src/dfu_int/dfu_state_machine.c @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #define DEBUG_UNIT DFU_STATE_MACHINE #ifndef DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE #define DEBUG_PRINT_ENABLE_DFU_STATE_MACHINE 0 diff --git a/examples/ffva/src/dfu_int/dfu_state_machine.h b/examples/ffva/src/dfu_int/dfu_state_machine.h index 1cce4189..2c52de53 100644 --- a/examples/ffva/src/dfu_int/dfu_state_machine.h +++ b/examples/ffva/src/dfu_int/dfu_state_machine.h @@ -1,5 +1,5 @@ // Copyright 2024 XMOS LIMITED. -// This Software is subject to the terms of the XCORE VocalFusion Licence. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. // Compiler includes #include diff --git a/examples/ffva/src/usb/adaptive_rate_adjust.c b/examples/ffva/src/usb/adaptive_rate_adjust.c index b26de51a..f25e47ca 100644 --- a/examples/ffva/src/usb/adaptive_rate_adjust.c +++ b/examples/ffva/src/usb/adaptive_rate_adjust.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define DEBUG_UNIT ADAPTIVE_USB diff --git a/examples/ffva/src/usb/usb_audio.c b/examples/ffva/src/usb/usb_audio.c index 5daa6d3c..c6606dff 100644 --- a/examples/ffva/src/usb/usb_audio.c +++ b/examples/ffva/src/usb/usb_audio.c @@ -1,4 +1,4 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2022-2024 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* * The MIT License (MIT) From fedde581a29f836de73af725469bcae51b2077f6 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 22 Mar 2024 12:44:57 +0000 Subject: [PATCH 125/288] Remove extra line --- CHANGELOG.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 20b536b7..d97fd51e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,7 +11,6 @@ XCORE-VOICE change log * ADDED: Support for DFU over I2C for FFVA INT example. * ADDED: lib_sw_pll submodule v1.1.0. - 2.2.0 ----- From 9ffc7e397108b96c662ad705f6c1c0c817d9e2a3 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Mon, 25 Mar 2024 13:59:05 +0000 Subject: [PATCH 126/288] Add info --- .../ffva/deploying/linux_macos.rst | 63 ++++++++++++++++++- .../ffva/deploying/native_windows.rst | 1 + doc/shared/introduction.rst | 60 +++++++++--------- examples/ffva/ffva_int.cmake | 3 + 4 files changed, 96 insertions(+), 31 deletions(-) diff --git a/doc/programming_guide/ffva/deploying/linux_macos.rst b/doc/programming_guide/ffva/deploying/linux_macos.rst index 117121bf..5709beaa 100644 --- a/doc/programming_guide/ffva/deploying/linux_macos.rst +++ b/doc/programming_guide/ffva/deploying/linux_macos.rst @@ -69,6 +69,9 @@ From the build folder run: Upgrading the Firmware ====================== +UA variant +---------- + The UA variants of this application contain DFU over the USB DFU Class V1.1 transport method. To create an upgrade image from the build folder run: @@ -77,7 +80,7 @@ To create an upgrade image from the build folder run: make create_upgrade_img_example_ffva_ua_adec_altarch -Once the application is running, a USB DFU v1.1 tool can be used to perform various actions. This example will demonstrate with dfu-util commands. Installation instructions for respective operating system can be found `here `__ +Once the application is running, a USB DFU v1.1 tool can be used to perform various actions. This example will demonstrate with dfu-util commands. Installation instructions for respective operating system can be found `here `__. To verify the device is running run: @@ -131,6 +134,64 @@ The data partition image can be written by running: Note that the data partition will always be at the address specified in the initial flashing call. +INT variant +----------- + +The INT variants of this application contain DFU over |I2C|. + +To create an upgrade image from the build folder run: + +.. code-block:: console + + make create_upgrade_img_example_ffva_int_fixed_delay + +Once the application is running, the *xvf_dfu* tool can be used to perform various actions. Installation instructions for respective operating system can be found `here `__. + +Before running the *xvf_dfu* host application, the ``I2C_ADDRESS`` value in the file ``transport_config.yaml`` located in the same folder as the binary file ``xvf_dfu`` must be updated. This value must match the one set for ``appconf_CONTROL_I2C_DEVICE_ADDR`` in the ``platform_conf.h`` file. + +The DFU interprets the flash as 3 separate partitions, the read only factory image, the read/write upgrade image, and the read/write data partition containing the filesystem. + +The factory image can be read back by running: + +.. code-block:: console + + xvf_dfu --upload-factory readback_factory_img.bin + +The factory image can not be written to. + +From the build folder, the upgrade image can be written by running: + +.. code-block:: console + + xvf_dfu -d example_ffva_int_fixed_delay_upgrade.bin + +The upgrade image can be read back by running: + +.. code-block:: console + + xvf_dfu --upload-upgrade readback_upgrade_img.bin + +The device can be rebooted remotely by running + +.. code-block:: console + + xvf_dfu --reboot + +On system reboot, the upgrade image will always be loaded if valid. If the upgrade image is invalid, the factory image will be loaded. To revert back to the factory image, you can upload an file containing the word 0xFFFFFFFF. + +The FFVA-INT variants include some version numbers: + + - *APP_VERSION_MAJOR* + - *APP_VERSION_MINOR* + - *APP_VERSION_PATCH* + +These values are defined in the ``app_conf.h`` file and they can read by running: + +.. code-block:: console + + xvf_dfu --version + +The data partition image cannot be read or write using the *xvf_dfu* host application. Debugging the Firmware ====================== diff --git a/doc/programming_guide/ffva/deploying/native_windows.rst b/doc/programming_guide/ffva/deploying/native_windows.rst index b297a418..5d857160 100644 --- a/doc/programming_guide/ffva/deploying/native_windows.rst +++ b/doc/programming_guide/ffva/deploying/native_windows.rst @@ -91,6 +91,7 @@ Upgrading the Firmware ====================== The UA variants of this application contain DFU over the USB DFU Class V1.1 transport method. +In this section DFU over |I2C| for the INT variants is not covered. The INT variants require an |I2C| connection to the host, and Windows doesn't support this feature. To create an upgrade image from the build folder run: diff --git a/doc/shared/introduction.rst b/doc/shared/introduction.rst index 7b1f7aa0..ccb23e39 100644 --- a/doc/shared/introduction.rst +++ b/doc/shared/introduction.rst @@ -1,11 +1,11 @@ ################### -Product Description +Product Description ################### The XCORE-VOICE Solution consists of example designs and a C-based SDK for the development of audio front-end applications to support far-field voice use cases on the xcore.ai family of chips (XU316). The XCORE-VOICE examples are currently based on FreeRTOS or bare-metal, leveraging the flexibility of the xcore.ai platform and providing designers with a familiar environment to customize and develop products. -XCORE-VOICE example designs include turn-key solutions to enable easier product development for smart home applications such as light switches, thermostats, and home appliances. xcore.ai’s unique architecture providing powerful signal processing and accelerated AI capabilities combined with the XCORE-VOICE framework allows designers to incorporate keyword, event detection, or advanced local dictionary support to create a complete voice interface solution. Bridging designs including PDM microphone to host aggregation are also included showcasing the use of xcore.ai as an interfacing and bridging solution for deployment in existing systems. +XCORE-VOICE example designs include turn-key solutions to enable easier product development for smart home applications such as light switches, thermostats, and home appliances. xcore.ai’s unique architecture providing powerful signal processing and accelerated AI capabilities combined with the XCORE-VOICE framework allows designers to incorporate keyword, event detection, or advanced local dictionary support to create a complete voice interface solution. Bridging designs including PDM microphone to host aggregation are also included showcasing the use of xcore.ai as an interfacing and bridging solution for deployment in existing systems. The C SDK is composed of the following components: @@ -13,7 +13,7 @@ The C SDK is composed of the following components: - Libraries core to DSP applications, including vectorized math and voice processing DSP. These libraries support bare-metal and RTOS application development. - Libraries for speech recognition applications. These libraries support bare-metal and RTOS application development. - Libraries that enable multi-core FreeRTOS development on xcore including a wide array of RTOS drivers and middleware. -- Pre-build and validated audio processing pipelines. +- Pre-build and validated audio processing pipelines. - Code Examples - Examples showing a variety of xcore features based on bare-metal and FreeRTOS programming. - Documentation - Tutorials, references and API guides. @@ -23,43 +23,43 @@ The C SDK is composed of the following components: :alt: component diagram ############ -Key Features +Key Features ############ The XCORE-VOICE Solution takes advantage of the flexible software-defined xcore-ai architecture to support numerous far-field voice use cases through the available example designs and the ability to construct user-defined audio pipeline from the SW components and libraries in the C-based SDK. -These include: +These include: **Voice Processing components** -- Two PDM microphone interfaces -- Digital signal processing pipeline -- Full duplex, stereo, Acoustic Echo Cancellation (AEC) -- Reference audio via |I2S| with automatic bulk delay insertion -- Point noise suppression via interference canceller -- Switchable stationary noise suppressor -- Programmable Automatic Gain Control (AGC) -- Flexible audio output routing and filtering +- Two PDM microphone interfaces +- Digital signal processing pipeline +- Full duplex, stereo, Acoustic Echo Cancellation (AEC) +- Reference audio via |I2S| with automatic bulk delay insertion +- Point noise suppression via interference canceller +- Switchable stationary noise suppressor +- Programmable Automatic Gain Control (AGC) +- Flexible audio output routing and filtering - Support for Sensory, Cyberon or other 3rd party Automatic Speech Recognition (ASR) software **Device Interface components** -- Full speed USB2.0 compliant device supporting USB Audio Class (UAC) 2.0 -- Flexible Peripheral Interfaces -- Programmable digital general-purpose inputs and outputs +- Full speed USB2.0 compliant device supporting USB Audio Class (UAC) 2.0 +- Flexible Peripheral Interfaces +- Programmable digital general-purpose inputs and outputs **Example Designs utilizing above components** -- Far-Field Voice Local Command -- Low Power Far-Field Voice Local Command -- Far-Field Voice Assistance +- Far-Field Voice Local Command +- Low Power Far-Field Voice Local Command +- Far-Field Voice Assistance **Firmware Management** -- Boot from QSPI Flash -- Default firmware image for power-on operation -- Option to boot from a local host processor via SPI -- Device Firmware Update (DFU) via USB or other transport +- Boot from QSPI Flash +- Default firmware image for power-on operation +- Option to boot from a local host processor via SPI +- Device Firmware Update (DFU) via USB or I2C **Power Consumption** @@ -70,11 +70,11 @@ These include: Obtaining the Hardware ###################### -The XK-VOICE-L71 DevKit and Hardware Manual can be obtained from the |HARDWARE_URL| product information page. +The XK-VOICE-L71 DevKit and Hardware Manual can be obtained from the |HARDWARE_URL| product information page. The XK-VOICE-L71 is based on the: `XU316-1024-QF60A `_ -The XCORE-AI-EXPLORER DevKit and Hardware Manual used in the :ref:`Microphone Aggregation ` example can be obtained from the |HARDWARE_URL| product information page. +The XCORE-AI-EXPLORER DevKit and Hardware Manual used in the :ref:`Microphone Aggregation ` example can be obtained from the |HARDWARE_URL| product information page. Learn more about the `The XMOS XS3 Architecture `_ @@ -93,16 +93,16 @@ It is recommended that you download and install the latest release of the `XTC T xcc --version ************************** -Application Demonstrations +Application Demonstrations ************************** -If you only want to run the example designs, pre-built firmware and other software can be downloaded from the |SOFTWARE_URL| product information page. +If you only want to run the example designs, pre-built firmware and other software can be downloaded from the |SOFTWARE_URL| product information page. *********** Source Code *********** -If you wish to modify the example designs, a zip archive of all source code can be downloaded from the |SOFTWARE_URL| product information page. +If you wish to modify the example designs, a zip archive of all source code can be downloaded from the |SOFTWARE_URL| product information page. See the :ref:`Programming Guide ` for information on: @@ -113,11 +113,11 @@ See the :ref:`Programming Guide ` for information o Cloning the Repository ====================== -Alternatively, the source code can be obtained by cloning the public GitHub repository. +Alternatively, the source code can be obtained by cloning the public GitHub repository. .. note:: - Cloning requires a `GitHub `_ account configured with `SSH key authentication `_. + Cloning requires a `GitHub `_ account configured with `SSH key authentication `_. Run the following `git` command to clone the repository and all submodules: diff --git a/examples/ffva/ffva_int.cmake b/examples/ffva/ffva_int.cmake index 9fb4a84d..3097fa1d 100644 --- a/examples/ffva/ffva_int.cmake +++ b/examples/ffva/ffva_int.cmake @@ -11,6 +11,7 @@ set(FFVA_INT_COMPILE_DEFINITIONS MIC_ARRAY_CONFIG_MCLK_FREQ=12288000 ) +query_tools_version() foreach(FFVA_AP ${FFVA_PIPELINES_INT}) #********************** # Tile Targets @@ -63,6 +64,8 @@ foreach(FFVA_AP ${FFVA_PIPELINES_INT}) #********************** create_run_target(example_ffva_int_${FFVA_AP}) create_debug_target(example_ffva_int_${FFVA_AP}) + message(variable="${XTC_VERSION_MAJOR}") + create_upgrade_img_target(example_ffva_int_${FFVA_AP} ${XTC_VERSION_MAJOR} ${XTC_VERSION_MINOR}) #********************** # Create data partition support targets From 059206baeb56b05cc5876f929b23ace4938fa563 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Tue, 26 Mar 2024 08:02:32 +0000 Subject: [PATCH 127/288] Add information about DFU --- doc/programming_guide/ffva/design.rst | 178 ++++++++++ .../ffva/diagrams/dfu_download.plantuml | 24 ++ .../ffva/diagrams/dfu_download.plantuml.png | Bin 0 -> 32094 bytes .../ffva/diagrams/dfu_reboot.plantuml | 10 + .../ffva/diagrams/dfu_reboot.plantuml.png | Bin 0 -> 9725 bytes .../ffva/diagrams/dfu_state.drawio | 326 ++++++++++++++++++ .../ffva/diagrams/dfu_state.drawio.png | Bin 0 -> 124186 bytes .../ffva/diagrams/dfu_upload.plantuml | 14 + .../ffva/diagrams/dfu_upload.plantuml.png | Bin 0 -> 17438 bytes .../ffva/tables/dfu_cmds_table.csv | 12 + 10 files changed, 564 insertions(+) create mode 100644 doc/programming_guide/ffva/diagrams/dfu_download.plantuml create mode 100644 doc/programming_guide/ffva/diagrams/dfu_download.plantuml.png create mode 100644 doc/programming_guide/ffva/diagrams/dfu_reboot.plantuml create mode 100644 doc/programming_guide/ffva/diagrams/dfu_reboot.plantuml.png create mode 100644 doc/programming_guide/ffva/diagrams/dfu_state.drawio create mode 100644 doc/programming_guide/ffva/diagrams/dfu_state.drawio.png create mode 100644 doc/programming_guide/ffva/diagrams/dfu_upload.plantuml create mode 100644 doc/programming_guide/ffva/diagrams/dfu_upload.plantuml.png create mode 100644 doc/programming_guide/ffva/tables/dfu_cmds_table.csv diff --git a/doc/programming_guide/ffva/design.rst b/doc/programming_guide/ffva/design.rst index bab10acd..944aa587 100644 --- a/doc/programming_guide/ffva/design.rst +++ b/doc/programming_guide/ffva/design.rst @@ -70,4 +70,182 @@ The application consists of a PDM microphone input which is fed through the XMOS :scale: 80 % :alt: ffva diagram +.. _dfu_design: + +*********************************** +Device Firmware update (DFU) Design +*********************************** + +The Device Firmware Update (DFU) allows updating the firmware of the device from a host computer, +and it can be performed over |I2C| or USB. This interface closely follows the principles set out in +`version 1.1 of the Universal Serial Bus Device Class Specification for Device +Firmware Upgrade `_, including implementing +the state machine and command structure described there. + +The DFU process is internally managed by the DFU controller module within the firmware. +This module is tasked with overseeing the DFU state machine and executing DFU operations. +The list of states and transactions are represented in the diagram in :numref:`dfu_state_machine`. + +.. _dfu_state_machine: + +.. figure:: diagrams/dfu_state.drawio.png + :width: 100% + + State diagram of the DFU operations + +The main differences with the state diagram in `version 1.1 of Universal Serial Bus Device Class Specification for Device Firmware Upgrade `_ are: + + * the ``appIDLE`` and ``appDETACH`` states are not implemented, and the device is started in the ``dfuIDLE`` state + * the device goes into the ``dfuIDLE`` state when a ``SET_ALTERNATE`` message is received + * the device is rebooted when a ``DFU_DETACH`` command is received. + +The DFU allows the following operations: + + * download of an upgrade image to the device + * upload of factory and upgrade images from the device + * reboot of the device. + +The rest of this section describes the message sequence charts of the supported operations. + +A message sequence chart of the download operation is below: + +.. figure:: images/dfu_download.plantuml.png + :width: 75% + + Message sequence chart of the download operation + +.. note:: + + The end of the image transfer is indicated by a ``DFU_DNLOAD`` message of size 0. + +.. note:: + + The ``DFU_DETACH`` message is used to trigger the reboot. + +.. note:: + + For the |I2C| implementation, specification of the block number in download is not supported; all downloads must start with block number 0 and must be run to completion. The device will track this progress internally. + +A message sequence chart of the reboot operation is below: + +.. figure:: images/dfu_reboot.plantuml.png + + Message sequence chart of the reboot operation + +.. note:: + + The ``DFU_DETACH`` message is used to trigger the reboot. + +.. raw:: latex + + \newpage + +A message sequence chart of the upload operation is below: + +.. figure:: images/dfu_upload.plantuml.png + :width: 75% + + Message sequence chart of the upload operation + +.. note:: + + The end of the image transfer is indicated by a ``DFU_UPLOAD`` message of size less than the transport medium maximum; this is 4096 bytes in UA and 128 bytes in INT. + +.. _dfu_usb_interface_design: + +DFU over USB implementation +--------------------------- + +The UA variant of the device make use of a USB connection for handling DFU operations. +This interface is a relatively standard, specification-compliant implementation. +The implementation is encapsulated within the tinyUSB library, which provides a USB stack for the |project|. + +.. _dfu_i2c_interface_design: + +DFU over |I2C| implementation +----------------------------- + +The INT variant of the device presents a DFU interface that may be controlled +over |I2C|. + +The INT DFU state machine is driven by use of control commands, as described in +:ref:`Control Plane Detailed Design`. The DFU state +machine has its own servicer, which then interacts with a separate RTOS task in +order to asynchronously perform flash read/write operations. + +Mirroring the USB DFU specification, the INT implementation supports a set of 9 +control commands intended to drive the state machine, along with an additional 2 +utility commands: + +.. _tab_dfu_cmds: +.. csv-table:: DFU commands + :class: longtable + :file: ./tables/dfu_cmds_table.csv + :widths: 25, 3, 8, 24, 40 + :header-rows: 1 + +These commands are then used to drive the state machine described in the +:ref:`Device Firmware update (DFU) Design`. + +When writing a custom compliant host application, the use of XMOS' **fwk_rtos** +library is advised; the **device_control** library provided there gives a host +API that can communicate effectively with the |project|, as demonstrated in +the *xvf_host* application. However, a description of the |I2C| bus activity +during the execution of the above DFU commands is provided below, in the +instance that usage of the **device_control** library is inconvenient or +impossible. + +The |project|'s |I2C| address is set by default as 0x42. This may be +confirmed by examination of the ``appconf_CONTROL_I2C_DEVICE_ADDR`` define in the +``platform_conf.h`` file. The |project|'s |I2C| address may also be altered by editing this file. +The DFU resource has an internal "resource ID" of 0xF0. This maps to the +register that read/write operations on the DFU resource should target - +therefore, the register to write to will always be 0xF0. + +To issue a write command (e.g. DFU_SETALTERNATE): + +* First, set up a write to the device address. For a default device + configuration, a write operation will always start by a write token to 0x42 + (START, 7 bits of address [0x42], R/W bit [0 to specify write]), wait for ACK, + followed by specifying the register to write [Resource ID 0xF0] + (and again wait for ACK). +* Then, write the command ID (in this example, 64 [0x40]) from the above table. +* Then, write the total transfer size, *including the register byte*. In this + example, that will be 4 bytes (register byte, command ID, length byte, and 1 + byte of payload), so write 0x04. +* Finally, send the payload - e.g. 1 to set the alternate setting to "upgrade". +* The full sequence for this write command will therefore be START, 7 bits of + address [0x42], 0 (to specify write), hold for ACK, 0xF0, hold for ACK, 0x40, + hold for ACK, 0x04, hold for ACK, 0x01, hold for ACK, STOP. +* To complete the transaction, the device must then be queried; set up a read to + 0x42 (START, 7 bits of address [0x42], R/W bit [1 to specify read], wait for + ACK). The device will clock-stretch until it is ready, at which point it will + release the clock and transmit one byte of status information. This will be a + value from the enum ``control_ret_t`` from ``device_control_shared.h``, + found in ``modules\rtos\modules\sw_services\device_control\api``. + +To issue a read command (e.g. DFU_GETSTATUS): + +* Set up a write to the device; as above, this will mean sending START, + 7 bits of device address [0x42], 0 (to specify write), hold for ACK. Send the + DFU resource ID [0xF0], hold for ACK. +* Then, write the command ID (in this example, 3), bitwise ANDed with 0x80 (to + specify this as a read command) - in this example therefore 0x83 should be + sent, and hold for ACK. +* Then, write the total length of the expected reply. In this example, the + command has a payload of 5 bytes. The device will also prepend the payload + with a status byte. Therefore, the expected reply length will be 6 bytes + [0x06]. Hold for ACK. +* Then, issue a repeated START. Follow this with a read from the device: + the repeated START, 7 bits of device address [0x42], 1 (to specify read), hold + for ACK. The device will clock-stretch until it is ready. It will then send + a status byte (from the enum ``control_ret_t`` as described above), followed + by a payload of requested data - in this example, the device will send 5 + bytes. ACK each received byte. After the last expected byte, issue a STOP. + +It is heavily advised that those wishing to write a custom host application to +drive the DFU process for the |project| over |I2C| familiarise themselves with +`version 1.1 of the Universal Serial Bus Device Class Specification for Device +Firmware Upgrade `_. + |newpage| diff --git a/doc/programming_guide/ffva/diagrams/dfu_download.plantuml b/doc/programming_guide/ffva/diagrams/dfu_download.plantuml new file mode 100644 index 00000000..4fa6c256 --- /dev/null +++ b/doc/programming_guide/ffva/diagrams/dfu_download.plantuml @@ -0,0 +1,24 @@ +@startuml +participant Host as H +participant Device as D +H -> D : SET_INTERFACE (ALT=1) +D --> H +H -> D : DFU_GETSTATUS +D --> H : Status=OK, State=dfuIDLE, Timeout=0ms +loop until image is complete: n from 0 to N-1 + H -> D : DFU_DNLOAD (BlockNum=n, Size>0) + D --> H + H -> D : DFU_GETSTATUS + D --> H : Status=OK, State=dfuDNBUSY, Timeout=10ms + H -> D : DFU_GETSTATUS + D --> H : Status=OK, State=dfuDNLOAD-IDLE, Timeout=0ms +end +H -> D : DFU_DNLOAD (BlockNum=N, Size=0) +D --> H +H -> D : DFU_GETSTATUS +D --> H : Status=OK, State=dfuMANIFEST +H -> D : DFU_GETSTATUS +D --> H : Status=OK, State=dfuIDLE, Timeout=0ms +H -> D : DFU_DETACH +D --> H +@enduml diff --git a/doc/programming_guide/ffva/diagrams/dfu_download.plantuml.png b/doc/programming_guide/ffva/diagrams/dfu_download.plantuml.png new file mode 100644 index 0000000000000000000000000000000000000000..8abb70db679d31b15936125f0dc664b6cc5d72d3 GIT binary patch literal 32094 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/3.wav b/examples/ffva/filesystem_support/english_usa/3.wav new file mode 100644 index 0000000000000000000000000000000000000000..9965a91397e91463a59b2bf6f5fe67b65d20796f GIT binary patch literal 30842 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/4.wav b/examples/ffva/filesystem_support/english_usa/4.wav new file mode 100644 index 0000000000000000000000000000000000000000..b00cbb937dd894bc0c9fe99dc3e3416548d9547e GIT binary patch literal 27884 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'Vo+vlmgLbgbH{ z{h?dxGgCnm^H{NKGyF2n_3Y;h@G)dOmd{i=gRSq)n`~iT6Y8hD@3-67BXFs2g<`d+ z1{(+S)N@;ILw z?SE=sQh>f-YiT1l1A8uUDUYjrD9VYO%pZn_G2}`3pn|~ z_$=vk^*V5*%+&5uwiL&plelz926=%$mZU0d%6}DpQV&6)7t!HFlJuymNjt-LwKi21 zCiTM~u}9n*+ZOXmi^W!9-)MW=G_)?#w6kh#mB=)|KHIw8D;D}=H_)B@YWkk%p~pjA z<&I;;;v@1?3Ln{EqCNM{bIP^alfi27T=6*R2JtWWpZw4CT5lfpjE@y1t7N`a`gGj` z)n-XM{G-qvD(VZ++r(>;K&GI{bdEi>DW!pE+G?BOna0gRYtfSe&vf%LuAT1X)FJ*Y zQ6T*!=VW1$ulP~nHanjg%kCHE;Do51$b>%;CNU+RQ1=A)3GWVe4y1u-qzk2giw_VN zP$MWA@38fvTG>!lGj%(qL8>NdgiyY{a1c8qzNvVj?xESM8YnLkcM^RRZIF&vYJG-l z(=#?CpuJGR)Ijn8ZAO@nI&)ulGZSy_kObBcD*eJS33%^Bo8 z=n{D!(U&+QR1)8V5;=}9W%B42%q6Y_Z6zv~Op@Lbe@Jq2H9}H79(c zH6@A%l68a@@;x|i0sj$dF0GLwt|e`BlMc20fTOpkADe{sC;Z5tiB{NAVIMaT+~l)4 zLEs_HVj6Y^N#bX*9hj}O53`aD!P!mc(68Rh^kiJI60b8Tx_2?sfH%W>j$PBy( zeg@Z*^TjWu-Q~OFXJy+Z@5$5nHLMalNU$POS}e_wa*|i#$v{8mN!ut~s#}`Jn(69U zihm>r$yfMvtOYtsn9Po$irn`cHp|<_uJtGCb~hX_pSL<~gYAQDGpym(N0w8T+tyL` z?am~qi;Sj3)LHK#?>=f7bAby-j$>D01x%8ZNg{#MJ4sF@mjTUkKoTbHAw|JK7bO`4 zwGykSzxau`Ufe?bnY7|*XqwQIkK|^sJ)ly_FurVa*2}zNb}_S|MknSv@)SP_w4NGJ zb53);Sqo@z59mLceeAE?Tkd~Kd>qFzMRa%i2wlxAJLQzo2sGQzxiY>K(O^-pHI~=W|Lvlg|*!k&f6# z`~dL_jN_JQt9Y5Dlk}3*DwWAzgLZybEETOG4&nrU8=Hh7*iCc->W@AJHES!vASN^t zUrwwd14R==%i(OGo+dsm=_f6dM#~~(=~BDowKz!x^))n5_=)?ONusLU6P#o09xG+h zTT;!f&7I9}%)eQREg{ws);rb>w&!-e^Dmdv-O($dZo_XVmhQwNd^w~P5yTrvDv1}{ z;9BX$VBrv|ks$N?-jvhw8raDtEy<@!ny?=X~ygjJDsRL9iIA&8Sx*4;D zeG2(Opg$so$SY7N4&Y~qsbmr9EBZ{vk++~~d7VI^7E6Pweh0sVokc02t8PFIeFJ&} z%?BM|C+yjq;E8|)DO^V^A+`{ygps%fq^}9v3e~|&pa$B3nD~M8M887q*&p8vt1uau znhK%|)OypP);ow?M14bH$Oc?$?S&g9<-Elqkw0`;ps; z54eDh!rZ77Ta8Ka1^86_4t5Cbi%0}H|2KDp`@$vgW5Jm*7JlRGWu(A?6)*}QS>6f% zKrMMFvJjM{-?_K!dDa7-mr|~Po6ap_r!Z6K3)D&qqn&gZ5I}l<3BMfFYm9eu6M@!2 z5g8D4=~x?l44#Y|@Ia`Z-opm~jnt7`N^T~>(Lz3gnd?amB8Cyei9n(NvS>`$bVy4& zfDS^H;Dcz3To$5$fttd%@>U^28;DCZ;j|G0hJCg5|Haicjox0m&2?=gp%G{y&f9cgSn`;=V+ z>|Q=|l-bGbVLmcCb|!m`{mg!3-vZ6oo;$-O0t=J+|LJFcqXujP=EhQR6>*mEA*YcW z$n~K3>&bh>XhIF=aSvaH$Kf}z1neAWz#l+!-UK@R8E`L5hMN4ppxw}*V;={tehZvQ zOTL&}$syb!pa}m*86|;gKbjo_$4K~Z9Q^ATb~lUy_%AL9w40USNVo&pMqhah^zmw8 zDdK`>)M4~C`V4&pPctvDxc6Y*&*5I&k6`gPz;uX#_`8Jdz}A2(sSWJ;U8o!_5NPfx zdxteZPTexJ6IAZ6V5#^dGFH4^GG9tbFUvN`J1X`oo+_k@DYAv)1U!dNXIT0l<^eO0 zTJ1b$?p{BvZe=~vpsinAtFIkb|J1y}aRHnoFTIax4L=O~CK@L1t=^+8^}VMbrdRux zYLEKVsqZSa@>7yiq9WoVK~s@j$mdw(o#xiMT07GnuN`+GZ{wzCop+_z z&)Wp|glAMs<}xVE9l;0i1JaY4^V7MTKwsrB7G@p$nj0)Y(i3Q0?LqB2f|${dxPxpZ znJl|4_sWaqo8>|B&9bqw6xklx3E5dlipda1k?*jj$R2($x0?-TOPC7KBWNa>P3DI2 zCx!MX+DvNrRX1k`DCP!7NaLH06Ux+WlP}<((|Dn@`$%y2I&{df6aIWE{{+^~$)|u2J-uGVCVs1fS^F)wsxG1>S+dA`qhKg8f0s0b_ynC3T3w^y?ni2whj1IaK!?&Ym1 z^^dA1m#Ip|mdr0NH94B(?pb^nky#O~wdj2eWyTR9y~5(c)54q~GmUxr>8fb*4L#7h z#^w_s4-vOMy8VsYDw2Q@>QT089CbNpgrwrqx`gEV&J|RNjY(1`8L_*G& zYqq7bes7JBX|(A{O;+9YM$WR=VfMJ#-!NQKrTn716i{kh9^5HdY1{$^s@=-B;_a9} zzmb0CdFL`XGwqaZnr*%9z5TlLo+pa6B4@}3nXl%f?!MobfDFS-!_fdNpvX_9AFsQp z=_ZdOy08!J-D|5$y5?`o8ulsqqw|v```M4X)qSm-sIyoX#Xfz%ko(ak&0e=iYKbHc zi{Bmo)W44+OUSkNFnuhX3?B3QS+<W{s}ttuAR23s?|+puiKe}0`p)iGz9 zoloeNFh3qD+d-XuNjZnUp*Gu%jeAY`9phGU)%Xlq3e;U585ra8 zpm3u&;JdDw`nj|Y-^v^5Gj6G~x4o;a#=6C(a74Hkdt&GjTsw3u87SSL_*FgC=el-? z?u0hlr?0w7u|lSmtR*AyJ?J3t?eyiRau!BL_wu@2KK32;e;$0`q1bg0~1WvNLq z2e|igSBRA0C8m0=n99(Wt>)WdbN_@}=`laKZxw^hx?Fv&L)5V&g`lU_t zY}Kx7bfpkQDr6^Bm$cXP=l$0QxcnRR7qyF2Q>1QuId7yEIn%AZ&7n<5)5oR-mIm7o zr_B2oa~1LvR}gK*Eu^<(ujJYCOj%!PbMXOOFC@@jr@}hB@u&L5bsg(BHx95ac6DP$ zqieO3XfD1b zO;ltl$EsSWHY*=0mMQisPASZaFvV<{TguB)6|6E=RV;55*Ac(sKchdG*4B;X9rDg) zPs{m~dq4a7r_G-h=h#`psI=<}OpsT3XUhB%En`24JONKuq$Hm#>4#bAVbc^a17G*e~-bhq>?U3DYwrqIm zs4}+vUF8;2O2cP+CL<*_N&OYil`i$qx~~42hB(7R{RN+Xsv+`^;y!qP&g7nA+takB z!PxM7V}^OXEz$Yh-9WYDk7MoNy~!z>LJlB2_)VgbI1U-1cBYPgO#3nE^iA(4*K?=E zmF&U24c=2!DV0L|v7Px$1SP`7Bcu(oc;yVuJ6)`wzh9E>n<`88NW71jiO8A0t}9kw zOO2(OZLht|>F4E`w@5$HKKW(PPL~^I7#{~-3CuNi4eD#$88A%OLm44!f_w=*5$rip zb@A)UPo*CgyuXsM_)*~9`bYU$h(#m9bS`6`*s&dY4!}nC8vdb|v`u!zVC{UwYu;08 z$l3emd;0B1e?Ix`ZRh;V>JrOAcUxhEa$<~rq zP&Ko7Y{B>33txtOuK%*VU`I9Xe1rB-Jk}-$>(-EKdElDDs1OP(BQA+h@CdI^W+(;uQ%MYY9h(lhr)#)e zYK&`@eTRLY^RWA%XN9LHc%LcezVHjS0#70`3AJdPL?#ba{;b-gs#HpqcNB$+>xyHt zL!xvPT$A*#UXS~{(`?&hJ!?yK_V6<7Vl;)EE-P2d^=$&Dhekzpiy0H$D&knkVdLw7 z;W|>>#_lMReOdM1@6Gz>=KJrj8gJyMZz^2mt|G^%&j*@XeCac7?2rk`!(`nvTmKc6 z=8K~b8@T)zZ-+g;b(g<$G40>CE57wFlh^ zIbirl{Rx}mA#77E&&}$_&Na^}I+tfwB6Wf0Uu-%@gY&az7P}K0CmEplq0EGIjk(J4 z>VBGdl}WM@GqLljPu@q~w(e@%YIAi{2kQi9g-1(is94&}t;N4e&L|$LPO5ik0wBZh zH{U;WLp5jQUQr>&@XwjA-uq5}>tBsm8#Xm@R;Tlt=K|H7yNLIaM{1k=dK&HoUJHH| z9v=NUszvytppL*qE%GTv#ZBuA|H|(Fsp|FCv>jIyF1Nk2`29T7CwC2hNm>`?>Aq;9 z_xIrwjt|=3<7(&F7T0})JR!x8-p_elaVz-R?VFitd*4;(o+?VOm9vX=Pon=w7})G{ z+~~x&9ZvsL+dePus9~YHwd`l24Rxm;Es=a{_2tD3N_c$y@w}B|8j<~{IW%f~xOR7ec-LJ^4E3+PU9JX~cFEb}PR#Foo z1^JGU!#_kHNcw0(^*8j1I*0m`(x|*5?<<~()Oro}5lz;H#HPz;%G}Sau+%$9b{d>yNjW z#;R^W?j{!SUyvvKa@4no_>keoq#!o9rE#q0H}=<>@*lPNj@-xZ`2Rj!8-06p`qi)7 z8=g6X=vc|Ni0mHQM&*vK89c0e+vHj8kHtr5x4C^wi!v9zDth92M5Mc388c7hp=F<% z)*?T&>A~wm-vk$j701s@nwr!pzGrZ_zQ1;zW~6Kl|Hf)FjW2IejQto~5MQAF?k;>< zzO+8r9ffyP1?Wc_nBd~bk?|v&503p9?5k^(-6l_=1Utj=t*%YgiK@YMOcQEVSzb4_ zw;XmvQ=_2Hk;-b=yL=8lSGGa(Mcd2gBcv|oYRk2s)oN*hVDfBs46@ajvl`mf{#8BL z6x|SS??)vGo$yLgj6$ayVi*xLFi06#>SypB2frULLdskrmFbvgK^v=T3Df6Fq-sjd z%SPOO+>^}rCdVn#b*}>r;o_K`alPY4#!ioI75gIcSzw^-t23)ArT9kP!1qfZL|*i{ zH28K=hHv3!Q$y1yAuFV7uk=aBr@x%?e$=nSY6ptC?T-0^&#YYj;YeEFTmDx=uV`-! zdeGrz&zy-hF+!PdK;*HwfM)(JTXiz@INR0O`efK;y+)g>NtWHG4Yh|09r@?;zkGY1 z{p$0U&r5PwmTYX8?JXltXpRR?icD!<)HPkpUJv0T3-1`rf0S9#~?R>uP;u$xLhevEzD zkiO^f__XOy`nv_UfZ&LQc&WhoOBxD|N5^o>ub(GXb`(at{4Sk@j~D zt7?mDA2pDc?zY?Zv(9Ai1TG5ui-;g*VGmFzmPLFar<3Wp0XvL^kqO|28zy>!H%He( znlX;<;~sk_x!O64oY8L5J{4}%he3O>xfk(;58MY**^z1q4SYN;7^j3;y z-6H&S_nD&=<#_g9@)6C_FK)Px`3tv>qcj9?*W!Y&Jc&8_OyXW#YTe{z5;x&GRSbY;ZsB^ z={eaHS-m)j5TUd9LCjOnOGm0Dxaqx_vZuM{Q~lWhqb6h*T6S-(SL7Js%r<sJ;uD&%lrmbOWnin*99$HdyE!cjRZ zv##Y&s;p`(vGwu%g=u|T#f)p+y>&+X>WCYmuHg56-NjPJg^H&?N`E93&o16pm{>HT za-Fq{Ktd+iYfZiOqTiK3GH8GyREH`yh)(0Pv4aT5dOf2Yvu*e7(_JgwUguVO4(uW^ z-C1~!eg*|)8}UXesCbP@bwIXAoCYa8SBN3PQE!f;z2gtpdCzojYwu9%6?+%`PM#O{ z6z7p2h?ztY9*K<=#&XVgu-Z9e4;uWHWP&;(vV}we&!uu;Vih*n& zZaet@StJG0kz>W-WP8jHdBm^cUND^ZvFp2|lWUpxEHj3ep;Dp?=>7wB8}t(Wf4Upm zcRmNz9%;5P(rIk;)Ff0tD)W9wWPH}caktamxMR*jKjolM^cZlzp)OtzW$%uI& z_x((w)wa#W^6b{1AATO2dE@i0FMR%lniP7z{Jh`opc&y2vAq-WT3nB>2xWAerQ5N? zoY=d=CU0m{lT>r59<^MxrPxo{$2bRiW4Ki06p%Z=W4p-lvJx;GM8Iw3jfP-3D1YzLbYrz_jB202k;w^d6oMiE%dk0%k_C!9}c6fFp&Dz7eaFMeu41ROwK7?) zQq%I+q6g>|ZV6RlkF6h6{-tnGQBL`n+V1AJ_Cl(L?62<=>28+R+!8ke+;&$(x9CTb zoV{mNa1r*sEWc}B|8FM>R#yCNn~ly^?$_P)dk{D`tTxIWIWH*5XP!6<{Z;7B&UQyZ zg-EEsW6`?~c-niWx%J++Y!XJ2V~Ag|FK8kWE-8d6TDiC<`8$yS)ZZ~&D{xe``@Fk@ zcQEzZ`^huU`-1t7j3Xn(6GgSeNn!&jz^p7lTCvwb_c+V+;r4Uk+*K|F5=l=1b#{XE z$2*~Sz=3@o+|qmLNGg!7WzE71GzM#jiEt-zL-bAbj@X0dLkETd+^<|awkJKDilKjD zqXilIGb(}H{TWybNV+<#3tY z6VA(J@b@66u@|O;UHTi*m;4C%R027id{3B&O5o)lV>v=Hn?pIh8JM8}b?Tpz~fO>!-?MO)k3Q{5-&bXLdhgzOC|ev6C|Eh0iO z4G>HMWL@g8O5q>=4`BxABm=PJNC_}kQ~t*s+!A3A@&|A_CSW^llG_qaR7eQeeVD~TdbUVJNh&;hkGd$pr;|r@H-Jo{!ac3G~+mY5pXW6`BZK@OEc@3 zp^%oP1{GKizW>XR7(J5+f)R8e&A1O_=CJ&INM_8V$HSY{Lh1)Sl`ZC$3&YW+coX?U zY>|wR#z_B>6o~JNwi7XEKh8?s^61^ooL1XPD`h?6nC3AuELQ^kU$#lpl|3~18jU7h zRiiMh9YRB~H`rKwChi1AGZ9P1I)O5H8#yVs z_)=KuKEhGZh(cj+7(suX4{3V}VTh0>#KW2&0tsyCkUS{I7Gu9)4xl1dLLUwq=(*m) zG`^6#4&6h3aAjN*Hw5-nPhjHqL06EW=m_Y2vk&Ege`zNafX^U8P{VuFZs7O?9(Il3 zfvz*_ARVp+sBii34SSGFNDO)&eFy1o<*>teAyZf+6!Eb@2i@ev{C+-E*a3ZpwnJLr zGc*ADmRte-ufF${f$dO*cU%U#q%_h#q*|`wVq5_4ppeuv}6*vb*Sp}?S z9`Iaw;QyCHU!V$56ovv3`4#$ij21M|vB3}Ku11&uy45K32N0bduyE`t8VbZ!Ea)*3 zZVp=y`br`52fK!Q&G$#{p$z84ZxI&a1R)|?;U07!5(>YFKCX0Yqj^7s z1np-Cb_n|y`yFclP4_zRQ_xw4p9#uYPksS^mp4PE;C)a%v*C)LLtSWB;FUjPO>o_p z!8tyMJJ@KX8{7jUxr1yJdw`KM2y>U=+4=kpAr1EYPxyJ_IOwE*lL+w3V{i?&7b)Yr zaG#jhkYLx9-bE{z7mN)uUSpBVXa;D|M&dM4LzIDzx&cqYRsrXGP}s-s1O4THUI3cu z$nIx(_9*u!aG;%m`niJ|;clA_{D>3>dIEci_5-?TF?3|=3h9osfRpS38Irzy2#~VZ zfGz!35X0&_2K`8WMh~K=&?RUqSWktZwT*y&I*WxkK?+Y@h8M%1Euh;@rjUeeMjjyr z2oIfmTEG=5fIIfjK%JHf7omSfOTkwNg6C~FpjKzXI=}Hh#R$4ib%yIZ`Tt4Vb;xvB z*WmzL<_IT*Swa^f0gm>d>CJ=DlnZTuPrePhFAiL(9L9bNp2%t-x)#8vB0>rO9eQXa zf~)lbaLTQKfPaX5Ly92X?;0qC8zIwDgJcR*0VBgo`zfNu{1 zjjS^yE_R0G)BiECKt+QSBMm<70UT-Y_iutlAb030kvmum016af%EToT>t+BT1vCV} z0|h_?0|q*v8#3In3tAx!c|ZkxKqv?Y ziJ&_e4<>?HU^W;9lEFCccoJ9yMu7HU827(9per~AHo?mtmP~+C(SBecbBqe1^Wbc( z2hkU~O`l{YBmM9cdgG0X%HkblrWa4W^o^N}Q?4jsZwq!QQ(NGf_3TLtxW7`-2HJ98j; zP@J#$flBRr%uA^u^3Mvj=aRFh`x`k{xK7fFKgbbT=VrSpPKdTLKb_s(Z>X)j*0PsS z?~;>$VL9&sX7$+MIQ%Sgo_Y%!@C>RaJ5@LbX>LtqLPZ0xE{uxqgSLT3JP;2e9G;tu z4>}#T1YX>D_eW2VhtVvepKCQyj?DIC5n5`adz!c(KgZRR8V>W>wMag$<%_7NmV1D~ z+whZkb@|K9f8frdF zt&s*R3-JM#7|R0X4 zvU!LR#8KE4+`^_)y{s>Igkn8Y>Fnh>&IAzSWgXB-mU%V-ep1?=2U(-F$kB>Ok-Wqc z>9+I)=T48Wz$Vk9iMA-$K%^HjpSZ+-1iv;swNH|dR+lm(8t*t}q8BB%#Eo>S^_OiW zI4SKX`N>9D4%;7LB4Gd@K|i{Tu2|$4z6Tu(jc7W$fqY3W$CB_8)P(J%1>}0_KH5zj zg->;T@bpI(BOSpQ5Rb>P;Y<|vl@5W4$WUexp#Xq7hOe0jD z{Y)m>5hTG97C|lpFKP(9L+pTN>K}9w(v8{0ve*LjHnW&DBGVWjI$H38&`{sWPT(sh zzy~1$Wr zNEA8Pa}2b_KkzzW31lDVSLopzgnTF^Npct+M)Z`Dy!W>t~3b9Z`Wu{L^-n?lj$TPT=GS9iLqPjojBG?)KuWZ z%G^>apE*c`iEgl2&Q+uy=rJ|X59>v*b$#;8;hiU1vYXfvwikU8+VPG^By*76hJN5| zgb?ZYLokOfB6H|OTqkUTr+OB+YndLn zg3s^*kVW?kKHX^C)FXkxpLO-JxWHGys*~;(` z9X;y0!U5d_w+nKiPUN8~qiQGnJ!8>dY*o-toYQbSR z3EJU)uol?|?r=UxIGX?ug6rTU7>h_iIwOE9;AQX>y$qJakL+A#DJw<*rbqo@G4qUh z4CiA;v^{V$G-tIQq72pxoMn14P2on=hSmTjyPw|6Oa-&CGw5PAl@>GepawmSNP(T1 z%`}78Kpv6;`m&2z9Na~AB7eXwXk=qy6q1CFLmt5n><`YG2O}4dMW6+HmWhP907ZpJ zFuREffB~QcTm(y5if+YBgqg@O@Rrds?U^Gi3FmNa$!GY?G4@}sT_(5^E`ljA3T#2< zBdrlRQVgQd3urYGhUOu5KqIrTD`;1wBXSZshs;L1p$(t`n2_`6Z*&+oA2lL6tREiD zd(N%I<#;b*Ee{vY7b%2``I*ECo}X~OFrIJ6HXxnB7`Tk}rmwriwyur;)KhhtCTq1% zMf1}1(k2zgO5f^pRTrx|n*NwCId4)$Y$(zm?1eS-5Xs+748Z$gl}IYch3~lj^uPhgR&*b>8PDT&5e<|ak?xg0S6)(| z(q?J1G`OaXx=QIIeD7KdShU41%u4c5MwXYtpuDwor0YHV3|v@)u6 zQ&CD$NXePf#PSql>*}?2(UxQf>N(49z|!~!MOP$eq+?}_e6%7}K3Void``gQY*Tlj zW`=o2l2*6ddDDK%*1~?qxtBahC$qJ19{P&7ClHFKaMz{CSIdnOjW|_A3*@{}XfHO9 z+U~A#R@ysSn^}OBvf)m*s~dTr5`mt?df`IJGDpQo!R)%SezUC%l>8dC-Hl~7fT-XdwlZs-tTWRU*v8s z{$WaCM#u%eB_S)KXC@ABo7-t@m;PPilIC{EZr(TA8qiL6P7;ayv`w!p%(rB=G}mIc+h%W|l!uj#qz?Q`~*oUJt0{%v^gsDX9D)v93qd+$GfokAXm zpN-fZj)f@vh2ACVKa$-9WaqdiSyt9&7&FU=-HL7TUpKgCqhj*{l(QI&_QFLSGByK5s% z;@hOj>PRovuRhc#rh8mQTvXiI*ytt;qyL2O3OwY!SJ9DINTpaYQ)!txyZ+;mX93Ue zzCZHW_2$5n3s0YanOzcQIc5S5`?T)UGOp#@R-0R|XxS>k6uHHJgHnSv zI{g|z^{A3*S?Kq~PY=F+{L?kZJKv}9Oxd*tA^S(XL_6PScVM50jj_`cS|)ajkBizG zxJCD`Bo>P&TQww?8U9ZD`Q+=hFZaI`e_8ge@K;#wQDcB@7yKw%p!*y2y2*l;bK7Tk z8rS)7$1!bJB@T|<>1R`>;K9!M)ma5h#=JE3$8N8izASis=u^V4pMTes1egZ9C-M(# zs{H#!jEURO{8pRG?T)pnZ}uZ5C1i>rK=BfZzLRv8x(ajXo6Q;nFNpWv@!3mTv@2fUhz9GZR_`} zwBNt4W`8d{U{p0w&IKSxn5~L4yb34?`4cXSyc(VtEbu$4Yp1}u_@D(n)p5%Fv;Jf4 zyz1=gw{`1nu^w+Y9e*MA)=C0)hP8^g88$A|63PoZ8(5+Ffbp!E##5z|QYQcG&rz>u zJ?;Fef7+&;YlSU}yBaS#LZmN(GMmhcUlbP*e?MVz^S{lB#MJ2j{QD`~h|-x+Co--q zXq_=K?eO=CKWp<}mfbKOsajOuhCCrSshbnfGvrd}@bJ@-Q==Ng&jdnUs(2G9aV1zT z*ZwKHpBtPW`!ncQaAxnkfRbEeP(A8iMl4fx^_v{pGjd-{kN7i*&6+vlwuRMrx0OZV zp_J2ntzv9W{I5gb->2Dr_W!-}_sNXG1?e>o_e9_@ybW`5X= zfNoxc zPpo%0U0>ATjyhb^ zplYKmlK+rBm2MZwcnBh4d_7ZKXYG?LrpDi9i*1E#kEby}+U8pKqK3TY$_EcA9UH+bz)+8f55i(}6vKQ>pj@U&wFyItgSk$>2h zwtqU$Y-4T`5c1v6&p>Mur7O?@&Z#Ct`S_yw1p)ahaz~lLIb(!*n!)<7+Bf>@ zfVI(2n~sdb!ejjRdMk7f6;}`&z3P#JH z>lML|V+2jl#fFDX^0}iCsxsx(f)x5`{jIY4f_HiGISHA6fB%!-BY#!RT(<+=D&UJF z<==F@1KUS>#T<|97BbC0(r`u7AezTsx7eyjRU*}j+SnS_IHi23ajL1vHlC^h?}-uO zP*pdty*@Jx*K{eWjdGJzE;%bGN0(FQ?AIET>rd7FH9f7JP&>YEkmaDOhQ13N=qciu zFhnlV$NbKM z7s{U@JDcBWyRpU3=#Bx;H1kDgfZnY#%d0LFBxG#;Vfj&*o|*HmutRxX-DR4mI2edW zKW?%)c41RR3uCM7<{g`y2+Gmd$UpG2nYPx!l^OY|8Ml6>{P^+PlD)QYT=}(X(0I!= z47nw`tnxMRgI7eEV*A9pBN7AEURUKYyoHpH#jkcOOOD=-9(Kerkemkc zc%wxg@dH_o#_Ss(ay=x+Z>M&!{IoP#)?R#*F;#B+Gv)iWYjft2Qc`|sVS98U7>1pXhQ7YoH*(-Y%4a+;6msBLGI8yVWPT4@4 zzq_R17QqvQibK>6|2Gj##OXj>|4S|rk0X{c$&THP4W_wOElLOF*Z%#Pbuz;{^K0IW z>Ta%Ih?K7o9g;29`uNoZ=|kEEtAqar=LCK7?Vw)E|Bt@uxNgm~%r;-BZ)&QmHPzj2 zyk$9Ti*U_nB8Y1eZW{*VexxV0G}! z&?lie!Kr>py1t4QvI6NC;Sh$X?^E18XVTyJ+{l7EB`Ye8rh|=lZ0kKUF)wkPyp^hj zS6WcV=&Mc02v@*CFSTl)s0!pcsfGnLjTJ3QmgR5%Ys*w*=4Fz9+ZLr&^|fta+7i>m z8uY%F^T>OycDb@tmL@3>uIBAPC9EIG+DaO0>*7tHKfDd#An;|RgRACNZ>_vu_OSF$)hgRTDB@cM--L6eJ{qM#=BqOt)1B7TX;$lg=#FdR z<#%~I=?~Tn)39nq)or7(+`s&9#rNuI^-s)Y*3OPmf|F zC#zJFy@Uf^p@zFX_Gji?)03*!#`p?N#f^&6N^@<5`LTmfrNJuf6Yqs!mFTR*B}-Fm zQ+gCm`94{UWWOL5FN59a*<`S5jboU-t!;yKto4j7+p*uhfs2ickO{<8;ZI3N`7y;c zB~bfnx@xjihZSd~b4By`eQ^zv#A@hS9u>L6MLPP}jaHd;v30yX-+7r_K#L$3^J4zQ z2Hq%vN*pMgrT9lVR5?w#T6sXBlf4zb!Vdv0o5YNyKa+O5vEgb>ld6|h#+pv1MYTt3 z#+WpgQSNulC-{UFFqdhPi3c0d&iD*sJg+laxZpS++j=+%J46Vc8eAZG`!O|hM&UDp`DST;9pRP_+uFM3LK&lH)$Vb z-{vs5wz!Xa>X^UCW;_E&u(@CvyNqeYbYOg$E=)I815yz^+70|mE8TzXzihAV>CPJW zR8MaTqrcK~xY&3Pb%4C*Hj`e=4bUHZi_av!5*r9F{20=}Zee~g2VokLiWTG2iS0xM zeiw^Ib;xqy0O!zM#BIT6(Gam%yi#;t=pzW@t-*`1mw0m?AsE10fv?1lARWLTHkT=< z6X{%P5xt5bxCnhSNCG&QfA|3+(Q8;Nagdk8e=Ha$+%BvaY~}H>HL#B9!airc8K=9K z{gUOArPP{YJ<+(bF2jU3&az)4AJ8cb#&oB<(-hs0b-{xOi6-Fd2nm0HAV4sV=Z`-@ z3(#-acYGT0hM34}%|Fhc!n=x*&`w{cF3@dZ5;`59OZ-QyB^+E2|3JqgEx;S71!nLI zF(ST53f#(UqVmZTZjF1DyO^w@%9s(bmV0{{m$i5TlVLi$j9tgN*>YG0P9cks$>2O& zM9-pDc=E`FWPkEM@|mZBGSiXFM|u=Jj}Bns*sgFexQ@)mMiQyK1|FZc2lvCm(LG2k zr#JABQ^+oKD)tpSgi+{FGz#5>F2ok&BM1X8m$!xghChRALnFQh+l4ko`=J}qUFd#v z4jPFbMUI0$u#5rBdpek@V(zeWpaSd%`GC)9J4MJDv;=EQ%;ClG+w$M?HWHn%_Fysd zlPdAtB@zoq%iLQZ)*2S*`8(f;T$gvaS_T~RR}%Zn2f3EPMXi9)hO++H+I z;2{QL667reuo?S_{^hykzVFmIHrP5?Pgq)8)%N2~G0CUy)6wj|@EQ1wjN#On(_AYT zpqJ5btS`0!-3~eNQm$U=x9!3%3(v;J4{FB9xbBJQ4N%c*}-C9G*X8?!ng6V`1=GUfV%)>`u@n}96$LU)Y^d9;nJ%}M#7wf}~<#5b|NAYNG2CowA7kuZ7 zcrUoV+0Blj>YYx@`T9W9fZ9(rzBPTTk5}KWZP(Dkdcm>DZKrktBk@u+S5}~qsG6%r zDl6o)^tsqa=*O!^S-6KeNUb1k&Q*4mt(on({iO4vyVUcI*@WyM^ujaZLDFxsXvGTU zG8L&hqH3+Ike%YZrdQa%FwaxzNVYU@NU6&)wJ`0i-Cg_2)TUvz#c7}CPNJuS*Lb?1 zqvStX6U7mQTfRs(SaMvroOcB^u$Rd+#}?~`#`SfTHNMqPs=ihSnqnI^Sw=bDkek>f z>>U4wxUGDhYNGbKKFe#i*Gk=4b$`Wwk|3diC`NtH|d}U(78$tN5XHcpWet_6hU#_x)~g>R+nw%ff|Qu@B5=*IEl^+GNZs zJzRp74k-(+I8eF1_I;zqIfeE^1NcwGLd9{lM(3?RrCY4wDGMe42wGqir_(oS>|Wz- zj4NML_PA_$`6J`;ntvN^*jBq2GH20J!3p_IEoNBgyU1^apT&29Pl;E6_J*R9WU$~I z{s8o(|GFsKP0LmDG4p5hG)tECs^b}%#4bZ4cw2>^C5egvHPD{c#%l*_bm~b;lWZB6 zXA8y7!;7@ilkW<0c5rw*vK?bw8%Yse$J!8IqOV}SI7U_{zobxgA52C+ zx5qU^*VI(pE0Gs;$@!aoGRIQzsW?DHC$)m*K3Uy@&VuwZ0ie(|F6iYh^U6PuZ=1ya6Sd{90^CDi_> z`=vXgsaK4VeCK~dgP3e*Z*y&}xawksqb#$uxb#{1!m8$VyDg`kpQ$m(UA|FTuIi+V z^IGpc&~V6b(mP52O5Ik`O?pgpl>ZVdf`{o7o+;!Tx4^yA<#v4{2hkLJ7RkUm@}!*i zdQY}MFAvkFc=l-c8QNfJid9#P^OiKRS)DM>cu&Wvcj_3|2>(%r(|m7 z;d;u^1o{bjC=TmZ`h;U}&zPKX#{TxP zV306C`Q6YMI<1LM{K(iA(LW=aM?4Cf9hB>RP_cr479J$M?Vsz5DkkJ#&774nCVOJu z-@->F3ytb}hqF1@!(T0Juf_~(0t3Sqhi8R04~qAD=xxuAXR|2wQilxp!==uu05kZs_>G;3aoe} zs)9?Y32wDh?6_j@WskK5S*2FgcFf-37~uNi9!cF}&1f*cK=fUvR`2jq`;Q5E8BrV^ z5c?$VX}mQ4PK+?LP(M(ZMZL77RDH}5eoB1w=yuaP8y}QDK<>`DQ~WeM?R3fShKo)m zJSFWB_`Rv6b#9x~)`Qx1>NL6Qye?DQ84^B(5AiS5FIW6SEOxjmZsnlqKYliT>+)&! zyQlAdexCmOdTxHHp?0?ICR;4(rKA0qheky7j_eQ>9lbm1MVKtWs{2RYOPGzD;7PKN z)oOB9_peDX4XL|YH?cm={K^^0TtYrz=Q)3^f~OTM<+sL#;36GNC3(tSBkf6+*NtV3 zm(3e3^Q|SeaHr9|oYJ#ppaOf$(+cm4o5<9PeC0y*S&dxVMe{^8PqA29Asor+`fZq6 z*F0Mj^MCcLP2X$I)@V&H8c16XnFMz6dnjTJcSD=T&26!}ZAr&tUCO%ob#2!6M)WPO zMqwJe(VUn&@nzk$Hy5%l_q-Btw)hBsyvGgr?rQdf;-SW#*1iIlzbMYsY;lXkHh(&g z?w#JhZNIu6GdmA!QAH0@7AYH`3s69r4hzX z4gY!i^1P(4WW(eORPDU-eLwnz`K0N(s@1CDs%6S(E~B~#J&)W%Z=unrMPrd6g4Q+-`SLxW|IBhNL1Y(_<~n~``tnK(>z zb@n|U}n^q_t;yk-H4)7Mb9Bj)d&ncl#>x|!t3HUH=RS#g{Po_5TeEI1x_|9; zecGI?h`j$wV(ParC&l~KYxRM?>7n-c@oj!~*w-PceW&*I?Ps*>&~kZfO0ZiuMPkI} z(Gm8Aweh7OKRI_yo<6@eH$I1-+qG~-xw7WJI#uIlORA%Y`i6evR|qAdh2p{TA)220 z^EyhsSXHS$sndGB(G{tWa`9j%Wwv6S?6|NE#%E@_pWELwHdcp~T`2S_m{c&UaAeV- z;)K%I#yNH8tR0;L-GeA0;4;^O7$Hx1K=@63NVZ9lrWm9+sW`9ttQC6&d(F_b(|*#@ zx)j}G^(*-{$x`uE@ni8j(Fx%xVXe50!l-Vg-J+I%P-7|Kl-_NYh9Z|Bt_^N7b^@7@wji($9o^woJHiMNSPl!*V7t)K; zk&^k61u~;Tsj@3yDXz$0$)lCUs!i%t)nVmi<#XkG2&?$iD9OJ zk+?;WB&|@qQg_hJ@M>!q<}<;k-g~}&hGx7{Bg+zQ#jWfOQtBu%r`AoXo>eiTG@DYLrY<}^Bsx^Iv^XAQrf_=!uGr#B^bOL>hdg~E*K9OcJ(KFVwz?06!aVcD8 zdI}hVEI_^R5@HxXM6gQmLLe2k5@rg%@LTh?;iYH-G8(R64pC0>wEL%Pu4|aXqfntSSWcbStKozW=juAJ4$8J|0Jd2iz2CT z1%C&zkc(PS_>i9C8RlN&lsIZ_ezqajUzV$u8p}}YQERq!jBUOBhU2C45=VzQJ>8j2 za0jvzyF$F;Ul6tx&z5xJ?%P&2Q5GPZC!H+u7oQR;1ygu)Z~|Qkn=;ANPV$;7+cC@j z#&*JX)0SiFVvlm1agK7g^USCJu+tC+c8b?mSS|LIrOVqYXQ=1g1ry1G8t#FwfQb(5EX^(cyblh|#I9aFO-IrYF zNu$D<|Jb1*8;Qhv;QjHRxScmqFhJ0be~QnMwBc9HGkTugn0p56lKSw1iU!`1mATMC>Q-6a9%CJd^v}$A{y8 zuqoJ1bOmx3e1?AT7Q3D8!FFKpvl=)SPK3MQLs$X-Pl!6;5x5f`6U5P|8{vE7OeCa6D!bGlFA6b~4SFYTC>^WLra$tz@6WX5bk}Li!?CK>=uk_#4}d-#RpN$IkY{Kk z+7^9{1frElCAta=Lurm*5pex75NrU)po6^$?n41HlYYz$lbdl>SO)5vwu9PWiS@EQ$4DEJfp1m#F7!bg(0vF-=Xa3t+BNW!hKgnN^V zFaeaZ4z>$N3NM6S921-X9)k58&l>>T@EG!l<7$^8eLxULwj2acK@8}|jq_G`hdY;L zkFo8zr_O@G@BwRPKe8`5;&>KRKneG@?cp>q3V8@#uugcC<4Cq6?GQDnft$fpq!02H zzeYnHSO+%4&m0T=ioMBMb|E+i3)xuk zjhm@8P>hTKN8opG39JGspe@iL9@rdKAWqPP&4ca1zwkKMXAe2zco*CcKXHoIO^zgL z$u0$7Ku1o!>I6u577T`K;6RR2S_^J*bmLwiMlNzAw~^xx+i{%pIB<$vt9_AlkPKVG z^{@lCLUO=)j%H<;>o5scfF7LIEQfJ07PLZ+Atv?{n+e{78e}s#4R3=rpal|uo&nRC zz8wFwfLr_1nJ{=2268)1f8-1$r~Qy3&>IXzt}_(n1>bSR+&FlgqpD^z@4#Ck3faiK zW%hw>+&o+W{JH0hVAq3L=neP{&S6dTQ}_(^1u4t|wu(i-0mK*W%xq?Eqi48YXVA6i zAx6TO(9zgp?#i{uOva13g&iQ;u%p~#p%R~nyk~Pb9XFG_2s#U$N+W$+msuLy-<#21A`}; z7VtU}<@9CG6s9BE3d1oSQta90>5XRctFaq&Z_i<793I3saP8m6h~YhS2Ywmep_efy zIAVM)*o`b=6DU1+hqnbKG|SYpI}th7ABxpiRN^xY8d1K5THxh0wfZ-hs1!nOeuqM3hO(x2{NA9!`y;jk!7d>m4Y>N0j&TZvDtVo zETQ_*Vz2`rhpmM#X@VXB2VvJR4~S#0Q;XPLSOCt#DCQ3{2ku58#?4CFk2b>XXc@X2 ztYTI$|H6$x!?BU6?09Akvm5rs;?P()i|NkXgG*6w^a5PZ&Syu#1z;??4V?+InckeW z>5G0xuY;rX5IPjh!%V0F#xskU7tjaoik$~v=$-UVcncF_KjAVaiER%?B3!FL2-L%R zScCkFyo5HkoP7inkO9bD=nZ?rWAHT`#_g|r;6_-?w&CVgJhA~ygkfCugMzbw6A*;6 zqiV2(ixDq#^X&zzhI5b_G#x32=Qyjk2)T#yIBU=ZECvfv3#P_~ay1dpz)NHRTEkh^ zLiQnhiZyTX9QPd3mkOipR0p~z^PU;bs=;2QJ9-mMMPsn3 z_y^pG)nTu&|FAOb8XAL4=W0nNL6$whK4J3dYc$5FnR@yzy@aEti`c<%F?<6jaEf|A zbSFxqov^29EVuto#I9gJ(OTsH`$Q*XDL4RI!%OTNE+euIX0Rq^JnIh@BaKKL`UvGR ze8d9&Rl#-Pby1FJvv{s#rgV>Vl;{G!3J4J)Zbn;DdDd$U{Tr-}W6Wjs#_Am7i0Vo8 z;g;prr#6-AhG!4+1{}s83ciYa$WAF1a#_>G>YM7RN`dT`c$N5}Xf>DV25=VD-#ya# z&VI`lVa;uP)NrpMq0!eOvafZD+{?-R)H?P$up&z^FHWaO6p;Kv-eE$4-{dINWH11d z%q6-ll}cW5+uZBOIL~eBDH9BqAz8>=RD!?cEf;!8e5EGw8PRXyW8nm01-}C?lz5H3 zM|!~@^i0nPce!h@yVAYQ-Oe5B&UCkR@wVl1dA#i7t zc5=CkvDs{;uAPhyS<2iqq^p{BFF@>n2uV2grEG zW_Jmet~%r|4oWrbPz{nZs!96hTC*5K^IZIU-APafuiBO1H5=~F9lG4RKM1I))s5dDguQiKupO!7nsf*UT~anfM)bNvNg324#5tiPw6*~`PP~CgYL7` zR=OiyLsd|5bU$Vx%tb@_E5t78e(4NJj<}DcP`XgQRPjMW*-52WoZiHP6Xrc7t-{+5%FV*Z;c<^x^K1s8y(AD%m=B`y;s{YpB zw}-g-u1D5@#+!|29I0@s$gJ$G-YqK=Y!D2TW~w4JAu5Sti>yfeFEM~t+BcbxS=YN3 zc{aKiSxJ*$_0XEz=C__gtWwZMIFNsdtKHa&&kgdTPXHw_c7FZGgh=f(>G@G?*ZHQW={4BpB|D)Kd^pc+y z)Pj3dwkMn(PJ76$PKEs+d%4TNB%&?(W2L<`&-A$BE7Q9{X%A8^Xc8E)mY$u~L%+ttrZ88!oZk5?;v%wLDKMvUlIeu`w7yt(4L?6tTZ z-y1pLsdaR5)VPLG0y@*R$Go$4W%WOGuk1US>xdP`(WTTiO@5d8^|l!euU$4lpkZG~lOS5X1|!JM?mkQ& z=nK=yW%hICHuYa?y44)7Z{qN!meZM@{+>$uGVoxJ__+M9SNni3zGGEec?5D150eTs zGc>)WJY<{gY4!99dsR>K99Nusr?r1=ym52oulk3sn=l=_MP&1zie!=)akX%Tc%P!X zHc!(}wv#}>T-X~#!KYMHcf4b)CAm&iqcAmCyvTKwhHUG6;!0zi2tLT$Xh!PN^yj<| zc*S}x^%`$z=eyQ-hPPH7A?|{ExI)Wc#B||RQH*d8Zv&c1zjO3!+*H5Ft_6JD?chM? zcQf8M6zum*#U_~#WX$>UB5yQX<#R1`xM8`d6v-i`sXv8mk2~K)?H483PIhu_VT!15P=7;-_4%!^53>_IL^`0+nL90wHD=AwNZwS&aAGvk%^zN6r4HB%dhI#e&)}@a; z$7ioU{+*R18VGg@zj;rH+SJq@le9N_+r7?y%;Dj7 z<^_(bI&b66^0u{e98DpDpTt+<<-9;?U){jK%E(W#J>$G%Vj>J-m%_Wmyp2U8Z|e^s z*EtsCqx%Zew>~M&aj)Hj%J0Im%f&@s5+4Qr*EjnPxkJ`T_6be*(6FV-C+bM+eyy`2 zZJOnRVS)ffQ!W?gK$n<%RXiyb8lP2ZD`n-;i@Oatoe>}%pu_?t)G9lAWdMr zKjr@|q;GV;*c*|*e7=hFI5I4cxG#W?Cc4SC=m`g$Vx#IPBWhaJnX@rfY3tjm79&mR90dQCxe;679r1OW56*T)0)%LOoX3 z#p{}PfREMZv=8k)L4QxZM?OZ9BaGxL@pz=1wJ|dI6EP4Cd_vSyqF1!n{xY2K`{?Uy zSm@QqtDin!WfQJtb8LPMkL$R?yYlHd=YBp*(`0PU?^F_5>{pOops#q;$nALO3A`Tf zz-uQW6tlI-UU$9L>YHi1saDGar1wNyxm{NWYdwChN49~M>*fWPN!A=|x^<7u+X=YZ zKpJ-76t~SVl2x$r>>@hZ-O(OxePo%+(Y7BNO6$7UJ+4nR&#_%|{B=cn{?NTa7QRL( zmfB^0iZ9Az8b2?s59YhuAo3FFT4<+fJ88$Lm5MrvUR)%Kk!+TIR+K9PRozsl)T6Y? zx=Fg7+JR~yzbf2^j{t3``SzQ2KPo?zuPGf>v@`E!4wWk?$|?P)d{=o_V`{C`+SMIH zNx7>09ALsv2-=JPlRT5ol&#_Fzm`iEOWSi*WC_AXA_uEQ3DgDGF=MEa9;v5_98SJ) z?{i;tC%Kop(w&%dwq0r6Wu9nOnNM&$(H4{2^r*hPajGTD(qI{23wAys`OFcRf;7c0 z;6r$;1z$x$(*AO&=&UMIMXUFyFKQ<1O!~>33G1jg>Bi_=dc}I{4TXl*KHCg;IDV+V zwuLrQQzG9bSdR2$no|x}hNY}_e5KNOu|ivZzGPl;b;-VpZq@T@XPfMGT63oDkMp^E zoo64tmHi7(f#=98>?d)F7sOjn{3Vq9Btd6E7ycIBT;3jDFJ2|#CYtlYdGQ42@1tGO zLtF%Yp1JOcbpLXcT6;HcsGDUPY}!zJs5-7Ht17v6fBhJ9ptX}t{@2lUfJJdW@7>$IUg!r$J9Ns=CL-z^OFJo^uu$RgYJeyEa3e-z!yR zb*q)a?yT;Vp1Ue`Uz`Tl-!a}WucNM5$6+pP};*WFLKsB2Q!?JiPzOu3>vwTIn1x9@^_vG%;a#aL=yM%B_AnNm=K2*x+yR{TCI zi0}bsYZV@XXF{F#7M2SY%}V?VJbm5=bm1Nz0G!Oz%vCze;%HuPEYNS&Ez_oGCTjSa z9ypJewGsLz!xB@rc@$Mh4+H#wi{bc0)*+&dbmqiz{dt#oa(*MfNH7<6Z;S8)%nXB> zot7AFcG=ys7dVI<^6mHAN89JyUALVjNrV}_WH*2w#C7@)v&GP+3)E8T1?p+)1L|+; zT6MnKU(>BA(7w|N42O*#<{HZ_I*1toniS#qU>0I+BJL1Rh!kQTk-}2qB0LJa16WHA z+Jeo1-Lm3+cnn>kYPxE! zwrD9Y>l&sF{fTYQ_Y8Pn#(3-Rfx_dfGH(&ow?`imA$TBJb zhfAeA023JpsvS10KZy>qoBfJ2gu9o!k?YMpj6vYkm2RN#v7a{LJ%1NEuLD2W+r z{Y3q0Ib!~7dT0zZRO{?@kF+bbOSN~kG_Z}|>5(DP^x9l%NvCq?GHW;Ef&BnX(fLFw z8OLei&gJhE%n^Eu?ubOf%eZ1D~;A$}-I78Q!RMDIm$q6b1hVVWRR;LP97-N-&d zT)|(XTkxdY-z+vx(yO$Sw0)W$4bsGDA8P06#QLlHXqeFf<6P4M^LdM!in1m!JE4~E z$67?JCYQ5ka$LD-+)nOIE{D66bC1nqZzF36F|iMjswJ!@{3A9SwODiMchmuk%Cy4R zr_a{i*Iv_nR-aUtst;(UYG>(=0KRy}6iyd53Qh_Z3MPQNujiNW9RxcCxG+T+BT5i;3f+a_0$+YQx01b=!0{K% zHTr^Op=p9)KXBV`YZhw8!OuyJRQp1^LTA=Z)L+rZ8fF_yO=m1|;Nx!tQss=Nvrdu% z4#r)@P2%q6F6P>B4+773J$Z(xV2yjBnG#v2;}C(4OvB(lik>=;ff=QNk#4d%_}4dYq3 z@3@J;k9Ft#!fpiIPXIjXIb;=ik-d-em^*~e7FtD(l1a8wJGtF4+ru^sC74()Jj=Vz ze#GiT_pFDg_2wnULx42rX$EMz)dlJo>bGj7UZ!c+M1cKdXm9KK^vjJ+rWKY)R381_ zn#bg$=g_Zy9#FVcT!-7UNLC8I1w7&tKjEu$=h}h?gtYf!)Q2vaYi% ztks~eaGGo+dr2;P5xbT>jg!go;a=fJ@EBeZKSgj;cv-YctQBvPWJr{7DpyLH#FNEo zqCTNQ@C(0$JCabvz;pQUK5z{17sL98aXzDTDHP5ox zQrXl=IuS752xLL|SPH(EHJ)f97LuRInQW-Sa>_Xax$$68JGkGtGF}vKJZ~y5fJg8g zc>cU_UVmN~Ps&qsOM!Rb$IapF*vK2`v9I z3nP9dI$%9skq+$f?0EJVV8Uz3R#=}?WE9y&JSLKecp`>yBN$c%>lP~!s@;6>kmsNx zR|^>N0l+ZVfuhnvQ1F=!f6oP6G91*IiqJ7Y>i%WMG1=C!*8k`+bS-tBnoC(Mw=4^Q zr}Eq!XKpv8nNFDIn=sQ$W1=wueij+n~KLkF1DLIquBzA-U zyTuv+81*9Diao%lU@AZ~H=#Kw0Vi&A}Hg4AEkXuz`9F&1kk{RnslmDDs) zSXw}pS{_;MTY6#U(=ADsF3VVI93`Vfz}67aztd@S6&(c(oqWK*{jt9=2hd{}%6iIT zpgW?ENFs&oh3xz6TsFq>gjq4{680VTVbH80*vCi{v6twBEGZY4;`6YNu;w^A0g53t z)^ngpQAQttzKo$ThBk_#TIz;^(WbQf@jzk!u8iS;w|-;BjwSQqeT*me9G zUtplSlwonghxp?r0^m8?rDQ+XHre62?%+I^cV-v4_kUW(_a}G=PO2WER7SZvdXpMsHyD zt8oFSmi&W^%sG4wYcA_0)`MCAp?l5}12TUPdx@n&MwktHD%Q(N2~C`vb6+Y`||%1D9zBvlAFB7f=Lb z1%Fsi!>l_QE9_+sqhh8qBbiLh1K2KW0mV#3$-srU1B{3WlnzV`6==moz^PaWmNE;A z#~z;>_&&!=FjnP|w{_M+AJMPRtR#;)Q=vHz)b{fpV) z_h2sLu`kF3te9z-GyVXwYFCyXcf>cM(@2f>Vy^%{orj$SO{Ayz9DFg1!T{Yq7*+&# z+JPnlN*@N^>N#XMd0H3c=sEcHl}xyG7n6l=C3YZx>u2UC z>>1( z;+N=2=Ev5n#9gu%XDDw_gBnlRu`7^0M1df*jrc-*VfN9Jtuufh#K+G7cWVtw2ENuD zCe1n8&`%c}h%R?9In0H}W|*nb2DXn%*%x*y)@ee3d2IEX3+L+t_edKu-5EobGi%J2tPA8Q$!if6OxvE{&P^1`0sZ}Dx+bEv6gK*v`Mnr00H zX5J;%1y(2UplIMp{K^`D9brO%=hKY4;A5FyhG9-24*nFFKx0u46Ab&j4OmWG>s0o!$6&;9&g?UeXsw zNPwI{PcRE~5kG?ee~P66BWVQ^F@GXgd(6vbRballl%3m)n?c+x6h0p-BEqro@mfv)Pig5Aarfk$}F zh_Q=UD%`~%eDMy<3r^P*lnNZJBCHI?laIc`)p$4!Dv18T4*LZCn_IC0hWM3Gcrob83kw+1{^9?aGc_+3B3_-nu~i~t71MDVK9!1J9)-ViTL;5V1T|3*M` z*o%x{49Wg%n`wh#}S(R|>_O#@BILe!3S zVvjH%u!0o0$3ZLxe9>9-KL+3>82@#MS9!<{vbZp8E3g}C&}>+j*AO+20jDw^>w*|| z8g$3{V`H&oh<HOuj0UOdcrk(!A2K=s;4(-bh5Dt&~vJWdm-cj|FQ-CMK!2niZBJds}end-`IlA zXAQ)1D_9mA#@vAHU}xG;7{op`y!TI7?Fh(ZHiNYc#QuZTw1aphz^lO;5fVeZP5@gQ z5BC!SbFB**Pz&$?5$J@fkr3;K8JQ3Pcj3Zaa2Qi3{Js>{Efn@c1KiRAR0i)c!`eK9 zli&?|kpBPYAq(Ek13UYUvfyg{FgqA~F>&ByMR%5 z8tm&F*wPb}39Iaalwj-U(a#7mb0KHA1#2}J>jx+3DMmn~9So6k1XPaV@m8>-!Pt73 zjR7g(ldF&ymJSN1C(wAT82b%&+YT%w52D5`Y%_QZ1GWpaL-ml!-T}tS4al_aVM$mg z){W(0{@5cx?cbw1nA4AFB;>+JAu|d=EMV)+MjnvWe1Z7Y0eOy&xd|N`d5n%xGtc4G z8_+(T0=dx{v>V1lLq;$TJkT6q=A;6zbtl-D3swbIvksVZ3g!^A0$8}AOb9c9nF4<{ zKu_^};1zy{vvwD{f{){#|DWGQ5Q~7>BqM(!-;#FlB$5P~{Va|YlooQiT;6dWkAIXu zOpq=(A{Yr(hFyFK|7V^f7ZLAJ2;E@$#S&)PqMfgDQZ{v7?F{QQw~G~NtzX(+b-Jjk z`u^1n(!VvGqf!`K|ibEY?$`h*nn#IsfFR->^RpbobOTjYH74Z$pK%3Jx zf7ujE;>1b9-}slgSJ_{PHLR_`QQQe9XCC$k?2sSS$OaNx@;b+s=gyZ1w1OhxMUhT4 zUtA^}8QVooH7z!r)M4sp%KM6fmP;+mS{qwtHZN$p z)O08nI-x`;LbZv9z@H` z+qJ2xG0N5suXd)br0u3+bH}%?KYFJ3#cP`l^_HWkhd|sR!Vlv9Ht%6Je@Olktq|b6 z$!r-*gg7*1T4#vUozXl}8`MV48C|m>2lU8>qZ)hzD66sf0%46PO420xC=p6x#a7`~ z!M{8k(2M(D4a%Oh65iw}GN1gL{epwIJNXj@ulTuKG5HAP&{k?BRcpMbY3ca@)m3}t z$xc^}BIdGx;t8vM89w+s25KD5o6?hgMZoXmo zMSHOKopOCgRhwRfBXHfab zsIfQ3ZyDbf#~rE+6}r)a3)aWlgUaPCvucKvM1HmZ^eSWayTtVTOjTY>Sx&2qelEGn zHcUo&&hy zmC)9KCmZHLdD?m&aT#KJMu2$NxtaJaV`_Ito2VtE;X~!$MTPmbUwdWee*Sgg_ ztG-{mswJa$CfX)(avkG!+1tU3ai?7FIC|RF31j(txJ973I!k@F!q|dSg$v4+ z^&uS_H7Dsv&>!v;mD_YUjC32|cQ)jG*np69pD!**lEIN-M=2Xqvi^V}$s9*FTeGbk zsHMIqUWoQN2YFZdQ$Ew3dwIhEv89a(s)i=4+O4Ld@kvWpLtBM^(Xzs%s=dltDBC90 zeSm*Ta98MquoEF`{JP!lJ05ndbbTzR-A+r=sm_i&)rG}L-|u~IFK(`KY^za^r7IzJ zH_=I!21X-javc|Zd%)c2H~ohOzjIF)Eu`dK?`k%c1QyRLp^7rTeakgwJ_c`wgRu1BfoErOTxFsfefM574f0hT&ajx(x>xJ2=@17weE3enC>ts`zg0~LK9nac$ z2`>oyY?r!Jc(lvscrEc#dp>iSB0Q+STRY@SXjV>^DVO~%vdFt4v|(WT8kLLwlw}S! zTrgGkIizREmblQ-{|>GWzw9&1d9x5hBebUOAk|S_DkWutET^@yK7%H}auKg)_uy8O z%J9Q(i9tt+}D%H~dUkDNLy{wnu}-AfAk(YIEf;lX#yaLN#8aU`t! zXgFP=c5Q6V-}Aoh^~$%KGnKgkg)8bF^qjR07A|v~DRb~J`Fsme56Bz*V(`>~ap9MP z4*A%*bn(O{vvNttiLQ;Rx9UJ$x8XQFj0}OOaECgiyxC$^jOr=qPVG3+n%wH9B(!OU zE`p%E* zzqM9h>+CgTv7*FchnJ4aofOj7vLN|I|3|@%A%Vf`d?&h|up1>PCblsfES)ALwU_u! zILanPl#M5=e`s%QUD48ByS*&A_@|1eZ610S^pb>F-&1lXn}o(DFbo#Y*G##%6?W?9mEt6Z!b>ixNRfFpGbvBw7lFtPp zb`PWu9)_S_qu&kjj~?EyF0k1v!?~aES93z!hvJaWcy2@9j(l-JXpykuLsLp$9}P-1 z!j%qoo?RiUV#4|z0TMo7H3m-94%55?_nZ`fQZD~~NNDvd36C`u`oRE9QXDG%vu zP^<7?=Mta5u)h=m{s~xf(+njtH zQzRIn=v&rkDw&XXIKA|R?%BgML)P-r|9}Y+OFd)Xwi)Wq4qXysH&Q%WHfHS@Y6NFM zfP5t{QQfb;v3UHq!(aTr=<{!ve5+G;Uex_#*#mP6vwiOx?w=4=8cv7o@?YyQ%7I}& zG`wk#s*9=8RGqI2YT~v2(|)L%&;%KuTBZQ*|AFHwQ8=01S9NTbkdOx1UO_d$9SLj+2-@J z&kpZYxy4;7yKVOuTWIm=ZEYD|zAw-EA@H3?hBkXt;fU%hiktd)vdrGyCniD~BO9h1 z(KB+|&|Uq@{02F#B|Ej>6p9*4acaJI-rc-U1q3K#S~~vF++bpb51oGY2=}WASmY;{ zua-@+JInb_@6}!I9@j2cXxpB*I<-CQ2<|@E+par6iSbRGba95`9Jd_rtN^c&31L(E zO^iqh9}#lZSMN;n6R7fDyfvpR;q&D8N8X%z!(=eoaRvIy7{y|J4#!It7JO)+YRH_} z`q9GJo-=0kGVnq8vjN%B z`=ad!EDQE_Pmp}aWZIyXrKN(r><^pMZ)7aXGUiqlUa!sX^f#vo@?BE`wnTgzG-KFL z!*<22j+pO%R+cQff}PUmDbt#5YF3pC%N~^9s-E32wk@J3%J2X!oUIq%(-D}@mCn#kXwRO__ngUuIP?!{nrd_jn9okNymfrGt0&*lSv53$>N z+Vol1sV?djsBZMEQuX%s(@fKMfF{pPluS(Fm5B_tpB&e^+;E%esg}2RUG$2V_qmUd z)!XrSjm${BzH52?_L85z%Cc{LjLhBst-17ALvDAx#gUgFjqn{GCK|A3(6GUDba7;O z&{B_f+ex5OJF45b`=gYGB?(04G%YXB_ zLS*!fc0B z$F_`7jN^=%KWtzm>2uEhF|Ja}+Cr=67rptiBlq&>jo+4(ov(k`K1P$zL<()CY4THn z>0wv;jqEonEG@8Gp5ydEU_t%#ue;tiWmiopDg2)Ly|idzIlr!@)zbaSpv7y1R~)9h zw#sMv{^Xw)5EJm#_o7#md!x%4C$_^0o38>Erv>-278z&XMcHG9yYsOgdYLNXM4iirA{=n%)LO>wIOrZUR%l(Te*!J$93O{pwrg|H6NP&oK{$^8>pMfu49t_cwe~ z6TSUZTYEQXcm|8v9_=At@qZAWaW^JLza0g zcAt|Vm?Bzi^Phc%<5DMqqtre?!s7dpTj9G(9-Fu7#`GC`B&utwM?Hsn_`M;TM|yip z9Cn#=3ltJR*_TVhTxYwzl@ZP%jz8PB3&(NxVy`XUx)VLiI`*|41b$XWT~FPK#?ID; z&eq;Y;~OT4vs4l%ec^8F{Th@g4|=Wf%y9K|TqAO2k7P`S^u9OBd+lplw>7_SE^phf zRIB%zoUpZg(&4Jxao^>^@gYn=na@9-gIr3)Y*vc?QrFey_S&9`LnY6Owih)QcUKfN z?&|8&0PCN<({`{c<)P1045HAICdq-J`XRnViV0}<=a6?d||5vXtS%U44+%3#2 zU4-&w^X(c-d1dL7(!FK3D-P8xZ_H7cR3>8s7Q;`pt92P8AL1ts{1_M-kmD2K`Mb+4 z`!LaQ_IKu$NdwQD$Gg{d4r`B9%vAi?KC^Rd_x!#tU9$NgGnV*;drMF)5=s7)tdmR; zdkEurJJ}mqyU;bd#=OS3RzF`iS{J8pFcg`L@Xax;=oqV>JjFT3oyzOw{mOsEKhHnI z>*Gk+1mOt%eeu?v6b-s|Vde$qTJsr86lJ3L*8PkhR)&u!mXo8{X7*eT&9-H~A)=rY ztsf$oXzMZhD>Z@IN=>FmSrL41!+faAm*8s9=DS6XWAA5=V&m*gTS=$!=vkE~l#E8U>UpPJi zUUT24fdhTp(PxbCX}PPS5BzIw+2!BJo4o_v zT^*%+z?R~pNB3SGb_u!s_fqZ00oJMA4*fZOqE-BTzgNSyw2b~|Jux)?rr)Y=ze>~f zuSUf7Y}MvmdeajF;&1r98}_N^j6T)6*R8(Y-QxSPm&eO+{3$z3Jjy*TZuRM`Ft2-A z>-SVMI6&5peJ z>d6y)eiyWLLwnq9uP3#!4UHD*3W#o<>h{0f_>NP#)$vItUZdNj{)I7Py9|!%E}u$^ zPYldn2rf!d{1om}>6^3?JJq1PO}h7erh2{Bt+l?5n6P?HTTCFPqV|ja_ZizOgq{GiEfB#lqlm?5k z=UC;`!n>8%%ul1o*Q{r;2WHmYSL1}GLttK1E1w2c-%IkTeLnATLrF~XrpMMBs#~4! z$G*aHACZ2l!+GXUA9H_OhSgc=MdV~Bf-D>JBcPRURcN*5U7M~lw#imLCg*jtTH5dF zZfc)Yua#GEl_6c7KBw$2Zx@fSW_$nAepP2QjUyZpv#lfj_eN~3xyJg^x;XrSUoXK@ zF;)pT&84lj_@Av`p12czyF9M!mHh9bG>W`cKK{qGEF%+B9rt-&9+Ld&Ujt;b?^kP7 zNay+u8XfTIQ|6OWm0h9R)^uE#*X`~_kMnuVre`nux3u`a=bAX1Tj~g`Xl#$7wuB_r zY!d{mZ^G8s_#8OY`XQLI-b3%$RpNHd0hPtM@NfFtO%Gb!4Sd-Bb)P>&-H*^%$Cvb# zSt$w=Ncf8Gb$?p_JIJZMqWo6{AFtIrdXImS3;(+y+d~x8dEcICcd7nk>n*UM@K)x9 z!k5kqLKvUsY*iWNn1E4MOZ5dI_x;y}><&K|oN0X%(8cc;wb4_bKSL3!D0X_5>*IiU z?tZsN%U^H)HLo%eoN;I52IhqE)hr=Cc4=A8i44TqO`GTkf-crvRQqk!}gZz<e&M%*7a@;sKR)z&Z0hCGJU)nBRQw?Cs*BO(m^09O#oe4TC-K zQ^?N16@l|YoWZRFf~`G$`e;*SnGZvD5^nK)@uMHZ-Ykfp5dY!z`IKjc%{>#@d*$Z~ zWxGtJ8gWvv#phWoN>RJlhkz&5TGn_Glul2o{8bp^>1UZ%FSfZ~!*120GS*He8_k{ z=vm~OmdWe#emHVHk1KbST&(iPVs($Grmp{r+Lxz-L55swSx9!+q@ZP*M($pvOI#iG zROI`{7aEwuFX}~pMcIa;ljS{Ji?}$Jao2Z$5)Z4=y?+Nf0^bB&2pkf$KH!jF9q)Pi zDMTmm4sb|cu&MIHKkUoESIb`{yb1gMCHrM%hC9Iy38<=%T*maE$8yukrj#ESZfkb? zMh3e>-v`#x*A~Qzr_SDtyV`^LXX-Dl9&aiGcAKRnxV+qPl_fdKlkcuAHlQAPz4l)j zxIf^1z_`FR0jvDn7Q)b%(kN#^oYEDD_LS_#-&5ZvChU1TENNq=P`=eAI2PCzR4o&) zQ!=$*G*|YkXv;NHIV?j0+l2%Nnv9d>^Y%Z^3FN52*gCrC*)^tHcA*0tj#5kcC;K;# zg-iA9^1Kz=6Pbn`ekTJ42ZRKy4S4Gx>z8I(YWPOqK?Lws_5vBsx%u5vha@(8bNJn# zZ%6;FE5)40oL#Cg`wIRUUWfV#W;-6+u6Xoh9j_7okAubqEH)LQHQh&Cv#@@?QUp=A zxW)^gY~rw+shCmjt}1bJ>~yvsyH&W1jn;MYS?+K1Kj#0`zp;N)zwegc23b`f+b8#v zzev5@$BU|`rGI|;F7^G9Z`$+@#dGcP&TIBJ_8_h;DiXt#k?t8)3GN}-P(wGrdVyE` zXLx-gW^wP_eUMzQGvP;S*=k((E2f`u?JEN+rd4^lAF#>nQ+B2>0S(hI7Jusq|44tW z|5>Ze_nW1b(amhchbqDHVQI8ycNTO^+P_Wn<|!qmz&&RdQxj;?H!avpDu zd}O;gV%^@zF|Enxf`5V4%N#{d5!I&fbVhZed|K2-g?aUjb)MXwfYtjjWm=OOAXl@^3(Am|Q=vf0gVyBU}e1L0(J4RNiznIllZ|f?vl`+cMoL(-_tO%fMT&q4RK(E^? z7dYEjRxN1QXqdC(IjIgv|9Pchw{=e9|A=?$g{z771In6Ij*BZq`r zpbzuY*A%uU+#1-x&=hU!IaxKns<(5VCzSiitrH4lghKU$Eq8nm`YrI|eE<6Fu@o6q z+GA8PnlA5`W^lu+3i9jx`S1IXuYA((-w$)TmTjb4P`d@++c)HqL98 zPpPllce(FkpGg*@X^VCm{RkVQe2|>nTYGF#z`ve9S||Pd-u?H9Y+K3ks(a42uKzr9 zgiEptX#G266Vi83I8w6Z&~d?? z!us;(1r}VvZ>R%IL%nPJ&iBQ9y?h#5B2An&P_-T}Q3AkBo^~E6Da>Slc1TW6Zt?qJ z)}7*Ol{1}^>!c@_e*$(X&jBF>bBH(qtD*7p^6{Q)zNn}~ zTl>c6XyDy||13WlgW}D$b=GkDyT`K{{-!Vnl%Ou!qEGgk@7>EM*r&JmD|0K;N!=i( zga}1T!A!yG?pcwM7nmOVvt8=h-zzfr7QU+3=wRFlo+f-xX{{V7zY<^aHX#VPKp)j# z^g7^mS>IFD3Hzx44ATsx5LS~Y@Rf3k6@F| zAg{@Bni%71Gq6N?@3piyhnr0L_UcHgA66#M6H`6is!YWxS%?3qf4@k}%>en1vVrys zu2-J#+z4?!FhZsa6i13*?-7ul8v$LSRJ~jKGkyG zcZT;Dy`K66D%n=iMzy~K{<<>q@?uNzD!X%&XE%3Ocp}AtexSPK6vO0X?1*Z{N0!j@e?0pj`3u2Z(hMXPdpXAJtcC9`*4Sji)s!RZa}Z!Z|7 zeqa&!C2f;(zyvggQq%=Ht^SfGiJnL_!~2r8)jtd`%%r8G@iJ2#OA_C(y*=@sTAZ6_ z#l13*HlcQ_C+jiOFt06Mxu!M7Ir>|gIVz6WgNDl!g{SVa%9$md@@$!tGl;A@dFP6Q zZF?Qx+}@l)ND}?PVqgGYrGFp|=}-P+JldnWr<&m^g4~VEL~UlOex6r9bF6W#dOB_Z zkGW(|wkMGL&VLr$$P3U36$>_DuG|)IrpQd@Enob(A8!{~(@kH9WQYC*) z_TJ3>S+(+-7w@dFIF`AK*nj*XF;AKaS^_or3+5qniDVU_wdkH`-l~q1CHOa@zbZsm z*R<7Zl(DI12N5IR<)^VetRHuWKPI|?6S+d%Rn657Fkq&mrWU3e#?g8~qhZp>%~*5g ztT>B}am+10SE$cT$ZC__F0ZtRs5oj5a^GdI^Ap9J(ikudw1615AaQsK9iUmIJ*V+z zs!`pDhs1GO(6|iuP0fujG>6G~${j(+jbnqjgZymq23U^>cpAMyvs6FGXfYKS%ME?? zUfSMFD@wwiC~c&M+(_q_iU!4d^Mq_q_JX|YMZ3!b>}tNAxK8Q{W`K#n36d2p zUP?7q=W6C^1~U7p%fu~0OWV}n^{GauAyT`V+K${2pK}k{M%)U%yEp~-A)|2>eNlZ{ zch<1WxY797&`e)l`yaELT8KABen^$vFqftBdWoi>N$#thXL*~8YLs898s&;+d+>-j zNU94lwh?4#a?%2D3;4@@l|$$y;w4?kY*MdhcG8#0Qo>GdS0!qi z=x^vxX*a22@dGj^c=5NmIea?gPg;48x`~#mrkeG-4EAJkLEUmK^J ztskP_steJ!Q0LHH$T`?7rM9$&8|!ja-YV-*JfN^;p;)9V`(#_<3G%|nl&cv>#G~Ovw7#gr8nC?|NSGYahGvpn{;C1wbM#&UeUko z|0w@wXTSbv2WCCBh;CJ;yVB)l^&d^G3Nn&8TpIayN^sA-d+(>UNb30Tm*#Uxvu0yo zG-;K-|7NE@e!i&%Wmh--z3P*L>X}=mZLj#HFY~EH)yyGk7F~l53!gfMt{@C-5l^oXsGI9|2h5ksk8yU2~|V0##+1kbrT$U|FGpt zD$fy7V_>g_2Z&Sh`{Kb*^pEGf2}{4KqBSmjmuf0Hw(v@P`)8lCdQySftMZNV%)fq0 z2giqWIxMr1y`*`1tH|7e6?OPL+f%RfkHpfF%4bjhOj85qyfVAU`Ql)jOP#>%B6_O^ zQeN5@5se1M_3s ztxu<9Rn=PG-`q>g0^cn-G06_rSB0S)Xfst^cWT%ah6fwA3;_s`QH1Wwm;}tZDAC zeqhhe>Kcnn*SzfdHlw6Qn7vC;{qIZ`A62;Z4H4fkkdYvne7&N-`B45FYwh-XWZfOr!1$PLU)1M| z^k<0p&#uku<@#K<@$-(nuGQ~$S>0f^xo5c0VQti^lF5nt^Qi!`dOKU=_kI4=k=Kgz z9TIia_l8eG&C{K@-Y44?1plh|`oZ|=euX;tZL9ElH?-;OmHfnyNiU{-w&yMZ$;>h8 z1O7$4m`{8jl_=!Qlb6zuK&fq7$wB){SE*~ReplcG`I8;@f826P_%7S=@0N__&f2A6 zsmn5dfZnRfqNZ?TYE|YbFiky_(o?@x3rsB|e|AXfC{N;V-wD@$&gaE7D|Mx=U!Gzt_-X5B(O<1M!u~bg8Q!t7X-eHfc*3|Jcl>d4E6TtPqxxhlM^xP5v&*y({cfMbbF6m#J@V5MgU&YvmO>ME>Oo(f_LU zF(@8({kf5*4L^B&X{tkym86f%m`a;YwspP-P1w3u&wpl2S0R9 z`74$tYxkNfT-(!nWgT~qld^>K#B;9){*^TYTPAjF-4yss!Q}YFbIDx6M!`(&qBf$*Y{QeWLu@J z<#(hu;Emgyq06paPL`%*{mmc3UBHTYckzzQxdqkv3)C_?ma3=jZgJN5)zs2-T5u4? z2@|#ZgYE@U{#yeF1wOM5^Q)#$;`bNT%B-E6YwPZuZ<|;iSu!?%k>k0yCOWjv$B-V? z>O}sqqHNQgZWU1~AN^d<>&){x+nj-tFZf0bGa&)bYR+$Zu1#Kp$KLgwOaC7H`_%ct zGAr_4cuoB@_E7%fRQ&6WRHEn+e}PCM?;!U*N@1I{q+h2Bc5}7S4^o12c;ziHg9K<>7soY=ws$^4fu{*|` z6%|k`D|BRNoAA0mtN15{11k3sNz7>WXr5pGbjKs{fZUv%Zv5#VQL|^G%;q_DznLGn z9GSsc5$*|ILinQ4b-GvV>;md{^7n1OsbYVAGG0X#D5$5jXkq&JzxKk#>@j7f^v89- zs=uQl+gwhf!Zl6l67s02X;7f$H$SCdP+mgG@Y248nI*M6lzfNlU9K$%DFy5~Jd~bJ z57GE|hlFpc_p{Eltf_mG-ll=c#uJ-esHfxPp|50gmNW5fv{> z6Uq*8>rJO>2oa5{^#~ah(#||kY*J>m|3ha`RqT|aRfR75NO7z3kW4m=@gH1cdi?{9 zBWgAAvU&<~O?gW^-Hbt@OM=d5t8ohor~Ijt+U74(a+Vu_VMHC}pgX&`Yv%Hd_QjVx zz2#`>mAk?oNxM3{%x!`{jU1u`ts~R~lc;+I3<>q&hXnkZA2( z{Y%{&_21U~YMj9iDcnL9MmC@!Rc}>butWlJ`I3FFILhV!Ke3 zQXFB+v=6r5vh}FgS=C;OGDL13FGaakNNX$~baQ8v zvbo1{ZNc4O{!^3rCw#ep&cQOs0b_el$I!|(gx+Kih|0S z?nZoVwz++O#k{Ho+-2lExk>d#+tE@p=xf;X&{CiMnu}CV^$t^%PlZn_pYh&p%oFuE zr55j0xl3D>729&`PJ2vMWhLg?D(}%9_x~EwHTa9wpZcx{eOc2tVnpy7-6LV0?V4@AFid^fa@~AI)seTB?aw)%iRLAj-(WS$ z6(mQR;Qn0xy6|jaYWWN|5Sog$xg_@z52PF=4^N@gs=rK6U73C`Qy|@|qRN_-C6{~K zGO9MX&vOJD=RoBdCv zkE=>(h_D9i~LM~UIUEMqIl=h@}zUoy~eX$p_)ho{Qh+e`sEgzZpF#CP}t%|ek zR2e7>rA_W<6=REs7k{gm=H4d2YMk5ciDuUe(aK(Y0aZzVVK!>}=@=CTWc$E!P`03g zsJvsp=x)bVc}g7fs$M(Hyb&>zhgGH85#}lWTZ4}V%9b8lKh;2WyrIM#?tR6w$ZRn6 z(8dux#cPf$6$2~MDjPVCI)2*wIc|AGWt=w3w{PIvfJT1Tyt=D{k)NE7n=L+-zH#C@?~@#KJ`4?G{F61ar7ezQj^E-W)oEB;CsSAW?iFOg<=nA_J8TEp zJTP4Gm+rey+OkS=O5$zH-Svfh!Nav-GuQ&5jnW4XqQ29|m_&_DQ%-pStMk8#@8tou z-<4AxNp6X)@SJsCu)lWB0>SFnA-tT2F zj?>g8=7<}dNwyfN)Zk-`#eiaCUxn(a_4H z>WIWbQ%D|))R9y+eO68W#nSz@i~rM90}g|7&CzNdMaR#L^N$Lc+%@n)0xo~gQF zi~2r(Q0(H$uB=m;UUlCw*xBAW(e;*Nk+GWjmX_AK)?)98Mz`uFk{}G{wc<4qmb@Kj)ta`}w&X#a|HnC}r4QayD&XMycbNYGgI=%k{i+fo*wZ zTl+C*W6w+0i_LajbFOtq2v3!zM6BwFHo^48XR0;Hccs@NZAYe!`n%3z+UeE7tI*h1 z|BmU7KNUIGuPVH1w4HK3aN3-`+-La_XoM45$ zcFp>WBRv}Xxr)aXHS8|;Y5taw#yhzkyjvWjyut>Pjp(PU2h3a593lwpci*n+Ss7B5 zZ@0RpLCYGjbKRR<9X*?b`^sRVqiUkIktxnw@?Gxp)HFdmi)pOxqU&XRXj*OZGtSoO znF=gRJmG$1|5lZ4ALR^o?Q%tUCJR^5)#~P6YkVI1JTV{8SJ4JkCyfzUAyjP0kMm4+ z?`9iF=g@w1AKgmxAHQP(-u{QpBbaQt6=&s=!8tNX-9-Hz?;^-mUrJ3SugaF&f3W?f zrJ#m*)bq}M*Y?Hs+up#ljlUuI2s3y}XfMrEj$*+iON~+aFituN4*`um2kgF8AFGx+ z_PWk^64^0qrMsWo?#UJ&DUFCARfP7m@t){A{0om*UW++`lCSc>|oTbnw1Z}ooY^UGc8$A5`mf3I9sf2BJg!bi&0$h(Xm(*&O+e6(xI$CnnC`#J2aQ3?U5 znC_Y5h^u^Ex!Ezxvw^=Y3=n?ti-qmd3S|veNIZn+_A6B_=}vekc;}ho$gG-WC!DA| z)sxF^V`q3s&wjR*_)PI5oOHfMZ+v2&=N)DlVbp8SG72+J`&(bjs2C3E?V1$24;Cps z^K^H%a+ErZE~o2-`x1L!td7Mo1C0TeDV7%|Mpu*Wh4NCQXc8-g#r%J4k>@k_QTm2f zr>|*4y^i=?@nd}=Obb;Hl$HW2tdOt5-lw_NBLw7^2uA`#p=0~{ud%D7^G6oyNEHaShLFQ!R2<z?Tk>YM0Jt3Oal=p5+|d&afSS>XKR zN^t-27-2R9RZ_wepX%KetI7CnX3c*-ozZjvkJ_t|sg` zK2@j`3Wa@QS8!8lfDM9u$|9Ph-)rmkM?O)K?NG@F>yOg#;*GwVxqyR?+LA9WnvEroE+-F~jIu2$~4ouTIvr2r}b5ZWiawb%EXf`4WLV#5=;iccyNb zag1rY(WVR2%w-lbKh;aMb99|`cFh8&kerI%lh$(|-RE6@T_@a!JV`9e9|Bjf;i@*e zP~%kNK>dGe2e}4~kh@8{MU~h8G-=xN+F_as)f%!r`b8?@Vm#6A?rze7$!~-PVsSk6S0Y09ot4#s&n;;rVCyU<0PmKFoQF4ytHg)mEGZlORs{4LUXT1oo*}J7 z6xsr`f%8Bqu5{OTw;$V(OX7;SPh3rYqrge^kUe+~g{XgM%k+;8h59`05p_K#fQeEs z(R9-4v~SgOR3T&#S|oMk-+1o2AG@b`MzQy}Ore9Eh&`u0@OZH2i3jwWyWhJ)=;ilK($8|a17j6E?^6Y$9Pwyu~^G<$8pA9 z>UiyTat+15;xl14*WHurs&IAnbmM%5dg3L~A>NWya~s!?`J_6(1>(0U=zy%n?@%|HLE27wkG`dTymp8hW9+JAhSuECtkJYk zyXnsai%?)c--?a!M0h@Up2M^EPU3d?J2sDw)!fs$boI1qrUAJfy&{)LoH$W@Cv+43 z6L|58+y!e*wNuCGLX7c7gQ1J2J~ai6l@*YrOv7Aw3micPiswDkozol#ob^3czP|WY z{3v|j9(m@wm$@H$ws9>W7QczLq#~)l>`_wC4fqYB1DQcg!Ji{N!7siMo9#|#RSZE7R!IX{r~hP0RsxZH-HPpV>?=CH^AS zKsJAkJ?h!#iD0eVdj6D{B>%u_)4$cCwwZ3H=AcR>wxC~RGYFQph%Vu%kSFw&a^)Ua zFqO?j>V_Nsz>}+MY8$x&eJ&4?OO*Q9M*J=IUYQ~Kvp%i}X9L$UPX}n(BQaG#`B0X1 zEA9~X6Fgm>Eb`)L$qGiuqVfeDfG;O(#CF0D-;E3f{=zwSw5N*)f|gC=rtydQmwbD8 zDscr&fhT7J;Mu|?4W&D#6Sa#piOdz%ZdFqzQhifBQ{9}g()Ea!NHY*EXgMuZd>h!M zTsjX#RQZeLQT^2ewCl92x~A$Ku>mcW+kj5eH8E1m655L=ByVL9#*#ajW7^Yt%rH;q zt$s|NLruzQ8AC)g1Rsg*RT>3l>K;{wpGO9Q`NC{&5j%vJT|?Z%*+YDD@rLLYX7PD!iN^ueQV*!DE{L6^cxf3pCYzD# zs2W!h69^l=8|#G30>^||TzfW{z07XmI`c`qTIdbYVwFttMx;INAg8M~sY^7mTBtwN zEtwcqP1SkTIOYYjnCYb&OkIY4p8@U*pSe^vo^8rS@LPq$Qmpb58%vF0%$nAkf6Qol z9I+S;RyKgWQkA${j1@D)XfRWW#jcaNs(qS{y45;Lt7m$UU(o|fs8S8NfX>9Op-q)n zVo$EnZFXnEQ{&fsd-0}7ikJB2+$gAS+i?H!zl8TnORI+Li z15^bxOU}ZVBkO=2PMJ31e7GIlQvRQiChb!`VG&fJYK!`UdIW<(%+5g@D;Hs|vP#dz z^J0WF9UN6AVgHf4RNk7i+RxgFFd75MH1wO&PicTGN5ipEs73iJUg2hVF1tr~R9qR~ zLyQxB#DDyD?j8G{-Os(_flw&^CuzYa@Li5VwxJ)fQ+N(O4fnw`$VQ+QLwOyS&Gv@% zTO40cSS5TFW{an#@$wZU06#|lpyQZA^#Dz_`WJIYl}A5?{h$u%mG@l|K3u2Bdv4eh9W1mC63Qn8pTPLVEy=gL%U z9NAHIOigQRXy2)8s|tx?lvma&t&kyTChCW}WVeX$#jr9+_6+AN!br#<(V|vJ=c2d> zu8>27aFLNNNlic*D3|LX>(GnXT>K&)g%_Z9WgqAu&f{lrEx8jAizWOR;hq3{QQ}W& zhkOswL)%59ie0bL7E7?79gFMa=|~y?QO{_I!e7zvk~IqB^^&x zp<&1sr6bY|J%wf4=zS5tkYpHN!z3)U5R3l71ZY|2~sHT+oNp12cKD)+I)bhj6)7Qr&*q}}XM7*wx!^A@gK^LfBKI>=j`@-peNeTJDQ5ODiK-I18y!p!p|8^) z>8bR1>K$H5p-U+rdOSUs*3m|4HL(b*ixdMv z+$((N@A5`?e_g2(gd#^V11VGCswhoQ?}n~W8q0%0vUF04lVZR;xh^sl`$G88 zMXCZOn|Y|ZMYSY&bUl30WFV4@5NL1YxI7U~4V>l>Za$n@h!;+aC6HO)Lv^-{U&qrz zrtnPMCIy0LV2s>RX@~Siqp<|+H#P)ok6u(h0Z!Z{Jb>3e;_C{xpvRVo`QmQr7%(Vl zND7ufY@&+jk17lE74rQII*W>@eo_9kKOIlKB-;}`v1$mMK$lVkUxDR^3$?^)Qh;0^ z*^B)m?oyBGm-H{H9@!XghHh74(5#OnGss+qQm_de97J@^M zAil^DBoWC%LXm^=B54lnCr;-+a0z^t@Br$KtD;?KA-v|l^4$azfBlKMe?q^48zNs_36`5}6_uVfHM2lk6uRVLd)WiM+lCrhm*^f zVBh2&7tBk%O8hA5q?cldI8V?D24S1fO4Lg)q*0(8?3c$V-H;|Ik1oMhU?e6XyOlf; zBT-@pfe@O&s@ovW5>v$vl1(ZBU6cak9=4ERs9v-IDi43v9@)l$`R`aSsxcc4#^mdJ4ABytqVQr63tr4m^4ROhGjt%N$zLqXDC zvAcLp=pzggUPBg+hFr85Q1VrIrV@ihp!sNjY%ul@%|SLQ4lrA4Ep8CH2(yLHu>XHj z6vTOw59HAiN(FKT>qjKRczHn!^k>K(PpB9wjH*lZq*_ql;0)0LJRil7M)E0XmH1Cc zfHR_*A_jKIqme0CHzI{RMO~nNlgo&Ym=(ROY?8l#!Js*u|Kb3ytVH@_?Fch9m#(YQ zsDkOW5!aCuKAP5u1_hPtoOzHsY!#S)f7%jDl zW8`CM8hwpkLeumr%Ac~6EUBX$>{}P-fN|rY$y%8_;9%{x^ z*fI1vvJB23$4jS0Nq8yz5u(LyVx`zadMix=1L3tEWDTYvCXx%NV3@@k(l6mU3&?k5 z0+~$SB|DR|iH7(klvVo4MN*#FM{F%_7rRTN!8>^w(gEY~t|UTRASRrR{`V z;VA4043{FII_o47A_FTum$+YQ2ws71a)G>E>5I_lNz{Rsp$pLy$V|m5|B_N*kA#CU zHWKPGZ)u)XDs2aI~ki^NR1di`OE0u9F z159uY^PompB5elw@+zbm_6gUK_uvmg{==tZ0C}Rkl+E&ea2#^k1bMJ>9%+tc;^WE9 zlnKUfF!hD#i2p+~ktxU^WHb_uY=b`2f_vgCVTiC9^5AnA|1+en(m!#j*aTX)AD(_Z zka~e%V65zr4=eo;9@&BZKtH1+(2dAcB|>&cxD+eagLV8SF-vSF9hac~0RPF?6&9I@ zy~h>83Hw2>s54YIDvR7fP9{f@W68$kCj#Jqu!U$b+}kM7MtUk<7CG^%bPWJ`6H)`a zjDII)l2ggo|F6AW$a5uGj*^pL_OXKv@(kq`QV)B9*Cks~3DjXKjod>-;>l(RlPG z+8Sp0@k$ff2_yVrT4;2v^jGf1S5ckNQ)KY3ZrKWC?W5^a{ zT{4Q~iG9Qe{601U%~#gQUBC>f1U@?gJ}Vh`!5H$!*5XHrNHUzU?s>KB}Z;6 zmxDCmC7+hpDUXnF>@3a_961xBd^i|52Y zVr^-ylm#dJD!@zmy&@r_c#D96pXHBeIFUL~lG4`->zg#qtOlm08G6@$y0C1!Be4;~$8- zWKD`E`;fH>8}JkE(iZ8av>W1)6F-T+AQpGP z%y=01$VX&=KZ|W)h44bUA;n6el1Id$g}0?>a1+##FUyfiJhbdL(i++| z3iUw#fp0y@bQ!`YCP{kHgN} zSm_DG`db)l-JosJ@M$Sff4&3l}3d&qy>n677uUA&rp{$|QLucqjFge5Ej{ zw{%F#l{$j!po=WY70_osSOYwi$Rq}l>&Q7U!feE2Vkfbh7)R76Ug6$&O-zOUQtrwR z0SZn@2c!%*8Qe!64;eiT6|f|{4bh0Wjb~sfFaxJ5KFS`sx7=A?538v!&@u^)f>*$I zgJI+zAg<#_unREGR=_9=MN*WW%1U_`xFk{11Mw^5?2WJzvrE5Vr8ya{bg;Bva>AM1 zO!&M+xt;P(>59BV0?}@0H2M{(jSN?2$wxo|-0v>1+Fc|)lu$4RdwpbRlvAM|{s?{c7}2ARVJ-dw z2|a73Ca&6W;IX;Ls)0Sdr;xrU-Z)|XKZ`+=_~@(Dj^*B{~-L{|r*4#5z= z@hSL6tPHLsMM;zkL1$17pIjF_02}2s$_eB-8jr2Sf8k&7q4+_FvN$9|siVZn%jEg; zJs1~-%6Fs?b;5kPk4S?^s6`BiS#Kwrk1R*pA_nBO(q8#bz6Wg5Fj$3$KtDZ{WT^TW zkPhSPfOJv%4YTw?ppxgwx$;oufA#83So;K_YN)EM$RK$4Te#n^rT-ujA4sg!2C4*1 z-UR2pX~d0Gp;_2%d;swXa@=p?1~HfDNCXl%k%wQ$+v1n7@8~aR*>~929S^*K3iJm* z!Et$)avZsXp2P;?SMZZ~9efFP0KJW5D$SL5@(y{c{9bOOR4TuapJ*ob95Uz`VhthS zjqt(P8ko-(A|2rO03^Vx_sg$=9;}r*Nxh^^5Qiq%^A80c>AUnuN|bo154a7i@7BKi=1goLN#&+zrQ72o*(+ViWDA$!ZKK~qo*ECdb^2i41I*dg13Md52;S3(6> zya~OCA5TuXvPnIK{9is0djH>Do8I6D$BJ!_PBKd=lMb994N-w2Iz6F`AM4qT1$Y10I zdL3JY1H3iN3}tK)^{Z*q!JFc7bfDFkj2DN+42! zd_)gmeV{GT#6ak;=>O>{{5F0RpN%)ild&$?7KpM>ic@YWKLvB4$8LkVa=Lt5If-mQ z2Vgl^UECjkhJ|8d(4$D9GFWlTuOO4j@*IeW-$)#K2GxN+Jdw%=5=Gq^3wDY!&IvXa7U6;_U(ln@_B7b){LPvi#)MsT&V374FmN&^hK9Y!aC%0zAQ^A%R zKMG?_XCJGw)&o|@U-c6p8xQY)9+orP{X)z>vu3t49s#OHsK{*eYAqDz6`Sadi0o47 zuD@yj=!57;loxFy53{1~(LK@i2*y9N(yvX+Pq-*R2)aibo7oL)9b+Ue} z|JGFcbTV^Ap+D=D{jy!MCO*^avb%Gon_kdV*r;DrDjKOrr8OP>8T#xmzL=P8wg&8a z*Z8)$THHJy46Y^dyYZBGBtMUhm&N(`jFT+Sl6|V@P!DT+GH_kA9$X`cS8{Y~^oy>?`#K`cWD+q;q^DN0 zGxWilHifrzzVSE1qf#UTb-vEf2Xs5?dtA8SBfOrgU*|3TpI$E<3X`R!9?*NEC+Hxj zG)+H~=5*PCp$_rQ0oe{~!LAPi)dSSn#CTEsAv$phGx_T%f*sUNdz)cDGsVuNKQ@7T z7K3d%>g<$1<$2cAF2*b4$y^KJ#cR;3%c;q1?)*NyCJpsNtp|5ci)KW_qh3){a9!2? zI#2IoC3R&q$ZF6#lC30;P=z^onn!(3=qTS4kr(wjt)VNVBKKXuu}>zxl|03l`8}w- zSEM%Yc2@lBGe6a~x)EixEo7h?&-eoH@3M#C{fT5>k2nh~n^@nc@s@Zo4#BGUcwCmX zziYo)Tc77Y6N#a)b zTX{9FOLedJd&Bp|3z#!g}!K`0)NX}lTW?rNjbb9Hfrhj&U;&XYgwHm*UK}+tg)}PURK{4u}h!voP3bgmycO@ z2W_kuWxQNr>iX5Edq+?5raX10H-(`yM0K=&tA%_`&|6R{m1I3mK~40^LT}+mVZPF+ z?b-2bW zYpo6ZahM5fgSVu{Gw>z{Ypz}w)r%TLiIGMpb+<0jG1~gC6_}No{%Pm(pNW-+Z!}fW0@coaGjnE z)xvR*wc_+M%WlPASP8DJaV|40f8xkgd@eo}m!Ju{*emvxRq#}L#CdNJo(kjO#{nUU zi7As_G>bI_`}6MKamyY>t(>t=Omc0=mp*uty?AaR*vc_yw7(b^#ECP$=|X2W0?xbk%m6*fzs*Up-t zyYPs9#2wtrYL9_wkUeKh>@q$6zy4~tQ6|YD`JWWXDtg2Bp-(7*6*9al=q7s}`?izW zez!E$kvdaH;C~*LzWj5ncf;c$k(j*$vO=3lPl|D;^2okFKvRtlSJBGb4OYXN+hCN{ zX{M5azS@6}E3Mze|m-ChOwB2bHxW6V-;c(k%LZ92mvv-FM@`cpbaryxn z`3<<%Q1PRvv1BcgHPQ)p@L?vY&gj7$8%uB5Xk}13SA9|_EyLs!Vv;NK@RD-Lu`96B z5OAfFl_$I{$Xd!axs84|j8CkhJERv`n}ffT$jmq2yHF_$V8`-S9RCCZ+yJfyM5?Y; zw_4P6HyeYpDz-cQd0*?remi|ElWy3JGmo!%(k=c06HY5o)rPqrx5;oodG_`o$Uf(E zW0NH608ThIgKiC6|1x~nR;%h6$)=m_!>ith9{iP=Yno-F3KM;-o4*p`kS_1bE_h^| zG?Xv!>aXH24FlI;GVLdC7q)Z4uu95kJME>dIAvHZo#lL($I2>(qaf?*cx}w7wNL@U z%7E)GyN?cc4_9Nla4L1Q6%E>yz1+-HR2ygL>Ci892sgvm&%wew;gBvwy@_?OOfvTn z=Z9I$yzS7I$+ApJYl@Di=PuMY!POrgzY8UJh-|A*Uu}nO?Cp-TzSvgsyLEh`mtx|- z6ne-zvX0+>R;tU#WbIXUb`1GH940&W*96>v-6^JH-mgi_unVLWCk=D(cWxqP+4vyW zz_~+MS0Wi$*&4F{53$m=)}GwzN$zLEACb55(Y}S4)dEu*x!8vP@wS(z{LQ~6o73pe z9k}}?GB+O;G0Kl|YO@8$WVT$Edtt8e@bXyA(C*qqE5nPQ;1bn9>9^%m(pf_;^T~Fm z+q>cRGN|nfWb!23znPLIH_19`;1WAKfhWF9>@MOoZ3?yI4f%oIP+QBRtfopMIUZg| zK~#XXbKvp>-%5_R2U9KL^?-Hd?|XW3r4X-l*kuJwd?WQZ-ai6aP1e$r9c+$Qxd+Xi JOPN#f3kU%dm~-sjUT_@pz`mn@P#ctt7T^`& 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/8.wav b/examples/ffva/filesystem_support/english_usa/8.wav new file mode 100644 index 0000000000000000000000000000000000000000..a1a5c4256b528c931d534d9a1df891008d57762e GIT binary patch literal 31710 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 literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/english_usa/9.wav b/examples/ffva/filesystem_support/english_usa/9.wav new file mode 100644 index 0000000000000000000000000000000000000000..bb034482c31f42077ee9c1317b689e3836078ef8 GIT binary patch literal 24618 zcmW)n1$Y!m(|~()UE}Tvfe?bbySuv{?(T57`yq#OxVt++5(32CJsa7q?@0gk{m(p` z-OSETcXe0SRJ~O_tZ%Pg!BY?#-E~yYSqqj2Y7s&N9Gg4AhYbjkNQ-7nT|QNujL--C zAAX82<0tU<9=?O`;s^x$3Qd+jX~qkY%~LonP@&*iq=E(N6-QEAG(NcpsVOEe7b|~ zqkHHHdW1fr_vi!4Kv}2)j#5+&e``@XibIdl8TjQe)BsiChj;~!!Uf_1v7g8bPlWYC zFTq_f@%j7@K7}vj1-_9mR5&Q42_E7C@rme)CxJxuxC`2YUZ5)EMf4`d6Dx?d#2#WF zaRAjc__rbGXGfJDtu3)BI%0XbHnz33+Tfo#Z~=te9jZWHN* zA311a=V(+k+b(csavFsT3J0oEl)2*lw z5)twEinvr5!FS~PJC{3t*@xJXJCXPL#t|O?R;aJsjc;-{k8Ke ze^T^B4Tu8b1i6+PM@KL+wwApvX&{|1y(YaST`zqnDPU8WJUWSbN?s+7priPRST5`o z<_JrKe}pfBQ)nZu5U+?=LF25#1|d*L;y3Y~cy~UGpT%eLdxgc~7(5;gA|{b5sM&No zCXwmE{>>g`53+Mv8M}~qK|3fnDu`r=TzpZSC3x{k&Zmwi_CGd_?Sgft^{CZi-ESLi zAM04?yw9nHsp2i1j~G%)xzINH8H~VdmXQQW0wgYyBz8KR&h%q0(W!9HP_hXTjmF{a z;%^~ZxX!ob)3`_61@1Kb&Ej}&B>#+W3hSt+_)#2;EAeg=N<1P$$b;k$QX~T?kt_in z?@oRqMi6OeAdcE`8X0iN27=~WE*N2y^1-`K9;n)65aZ+tR; zkKfNPtn&dxhIViXay!i7&+__yRWLmS`PVqCY4X6~QYF?9L;!6YS?guuB6_AJh@eK~GUM zQBJ6-1vJHek)+7V6yH@E4Wn7C-ltqG!%RLgi|=A>WSm{St#VHJpt9j*3(BWg=2qV{ zzO$4&mI@!yeTrspNKQ)!$o9(q$j-=BN|UNoGgz0PE7!Hwt<+R1VkGm38s{leZbtQ+(5-R_xb+cHUDaf5YCl65H)ehF*(tpr-X`8DjD2L12Nk_8b^aCPKEaM98 zr!1oJ@0#0H1r@z3xC%GJjN03#BKsrp7(GwkSYy}!aa-i!@446`!EKF;r*?<(Z|P3@ zExO46w5OTp8zQydBJj%Tjd>{H9 z@ss#!y<~1bG)}33xa+)YDmFxvT`iR6ZOajJ{>gt)ys$F0F3C0oFJwJb>-5Ln4}10S z`RKFUr_3wZ<3E?#nmh6dj0zoh23aQ5Junnk>@443-n^nyl~{e=c)@bcv0Erdq4Y)e zqO_;HlY&>=RHmt-HQ~A=E@rpgo?U!A{UZJ5`84pDsy)LlceJXUlr`|zkl4`o(l^WA zK8OuXPAu+WVd?j}c>k=1S8exvv%flco;No*7p-Yme&gHk`G&vNhT`&E7}Y@gjL zEil=bSeX=(_B%VS_^+A|_L0;MRkC}nzfYLCL9d2;Bdf#j1PA(ca^I`&&BhCBEW@jR zl#I!nmpM3XeCoNh;aP_YUR3lp%@VFiTIhCrr3X9=nH%;xG%0AL-+7O}v<`_3*I66a z4XAukGOO@sepF%4(%33V{Uv(@UdN~vEi{jHYxVB>t6G)%gM6c8JRMFn5zlbrVI7UL z|7Y*x*y=3h{uajI1SBEL$!XL(N={p;E7V_98QF&<$TLJ8%0vB#Kg0_%gDfVRp<7}p zAM5OD?_-%`dR)K8G~S}OJ+QauD#ZijdNxwtOZ8K0aZT_#6>u#yHhg1v%h10A{5{7j z|8*o54oirCzxLJYmlt0?eY5!6*(_y!8`?|l@3PM`Ea*g|<1H4pi|hEOQ)2t?&3lJu zyL=X;rQ3hac$@HK_tVdBtAC75Us5u`IGBIWELP@gYITR*Jp53Drp>t4ecEu%21ict zc_Djh{ZO(i<8srmrmcU2(d$Q@zYoVNzR59Aj`U+gZEG+ZM8?;jO5hk)4D3 zyYZ4;+z(?~)y)cRRccM3X{+^~t%>zmy}D*t<>HFphUw;Rc&*}zdq6;Vh$HBL&tTm! zHkj9&PFH(XFRas9M{t#V0XM@LYCmK-Z0c-UX{{EvNKWgXcm?|n_Ic)Nk`E#FIzmjx zYAb5rn!=so#Afi zc%=?*@U?@cx1*1-N6)s?LpRC;jgi@3<9mN*J`egyHN+q2tT&~}1SZXlV&9-)TI=KBp7!E|mNP>Fc zNyJljjVjSa?yl>ubve)Ac%IwZ@QEb*JW_c{!g!FOVNq z$~B4lv2N#GXthliuS{??d5`zm=`q#iwDziMmFx>O+tHaM zTAeqSOjJeli^|v4zSIwDH*(;}Udx(}^{dldmbPSeF$VTCb;Vv#b*sdy)U{NXGc11a zyU;Hc1wYxIzJEOSFgLk5bgp~P;E*79zdr%}Ba@muYSi36O5GUkH+QcZUYb#oQ5sYH zD?2nL_fKrn-SnAx2g<^Y!#Ei`SQqTuI`l$=9*uGu_ln7C(xOpB=rP~Ht}0o&)1|tk zXhCjl>a4iD4;deK|13zi=cMIpE0);)Vq5CId)^N4Y|uHzwN+~Cx)x&Nr6CjDhsmDf zD#rnfSmR#mld~?haZ>xFPN^$1U*?6Cb+2!Tda7Ex_w;V#Gsm}8VE3>S5d%WYeQI@K za*kFJGxse~*WY3ObnMDJ5a>6N-pnjJcsyNCB@pJiV6ToBy((KX@LszQ|;0+IiaC8;!x1+ zAiv-n9$8LP>YdnisSWA}TN0AKHQ~k%clVmZk>G2 zHi&In*sM>~@t_}W2Ng{jJE0Ky`W5-!zedNkNz;|Yl!T?;`?@#2xcHMeTzZdq$>o!G zU0Z~HYM2uBzG+t5`VL)MR0Vy}3}f3cYG#kv)i|TDG_~#THE}Cr4}VPlv?2ag=I4t2 z78`$#Y%c%cwlmb$Y<7pHonN*$G|y^yKX{+tVV?<}FXhcm({sKgd#3dLQyjbF?br7S zaYxeU<>~VG7nD~H=JqHXd({Sx3=e9Q7~|dgZo8_Mrbx5j2G7Bs^=|!?YN1)p#ggvD zvr5JlN9S!z4@l{h-m_p|)hDCQ)ZF~q=^@d&1bA+Cd#A0|PV{)?pB5@Xt?Jj+sttz9M^z^b3k`b=@>*Y0YuiG{Ouh$^A{njn)85hLxmI|; z3|tb@Jh)+?=ojug*L#E47teH;K5Vq{VBVi>zsj5D#TB_fOb?H~*pNQTm}uyh{4};% zMhkS&ufTtpdL`9F;U2KCNw4OfjT1tf1t|UGKG7bPnn$dU80tv1H+O!utBm%NcDY0I zB;}rUQ|k1UT}s8O88(%e%U!Yc;AhKUyWe#4lf{eI$;0|>f%Sp0E{~|y{5RBBc0l1u zGma%@lO5v{G|n-l&aI~0JP;pc-Pl)RkvX`|-8NRzQqM^%Ox?d2FWT(Q|UzMP019dj9g6k6IW4)nB$Cf#BpsQmYIoS zs8nV;>&gC+ep7GK_fcSH=E1gI&d+wma*G84p-b;y6YL-rH%R4{<~~S#?gC zF6|~cDF2~t>PEY+QZZ}>9jjD(xVaCMw6Z&`=g|^&FS>3TTp3UyGwe4EsO($by*#yS zSt(n(w!Ck(q<)L_vU9sg5x1#zvibTV&qB{*Zs*;$`pkf}z0hZudzAYo_nG?B@>?`b zrIH`;b%)B#8`F)AjT37>S1W5e)c#es!-!37Z1Wtqo$*|h5G|VVCt?=0hwjC+XMH7K zB;DB>%7^Sk)w3S*L}>@wiwLGv(n$GhX&qc)m+X=@lb9K;#7kNsIm0$(!l}h*vv>{X z6Y0p|IAJ8}imdH0FC^8I<+sZhn<&1IV_$8XO1@^f-Aj-=2bpJ>-rIBWB61~8<_KXi zH9>Yw)|Z)0dNMl{5gJ-0mliQ!rA<_6YA@wp$tn78x(mBlqL<{-)x-kaQ1lURi(|1_ zwDE5oXKZck$xc7s;COC%Xqs$o#Pt{UIs02jSyJqcg$-hN;WV!lTYv|544o#F^c`A9 zzQMu7b6UyXpc4@N>a4J!N%eb@LNVJA2W(B%8 z^^?k>C$P`h&JwLOKysXRQYTmo^z9d20u-Pe2!IRKki*Wwn4r`n1N zF%*b}gzwHLa}v(r9K{uJ1}>OCDlEqD@N99Ta7pwfV#$}}Y2pY(P^;+`^df2*d4N1l zJ)zgpx2Q$bRq7SRk=KYKbccwd?oh4C6r3-n;GRT7VlqB0oD-t)CNvFAgR_V53m{c4 zC`odx4T&neT{y%w=GJfwx5Xj1Z?i48^|5WSCR#RHPFfr5E>#Mll%o z$0sp?hM>u41R9L?p%PRF6yZMb)ok<$oknYb$4&!=Fa=2GaNr5UfY5G1mw}L;L92n( z-bW_fNV*Cp^i25pC5%ZNv)H_(hB=sQvp?}7IE0~;+tTH+GoV0D$_803S_ zVHJ)OGsHsiyLeh0AvP4TP$B#jo`H@e3Vp;>F$6RCgP14|$05)wBdnh)G>oVrx{!ME zC2^9-Afm~}K%M#%ZHTGF7@`V#+XDFVW{^pOo&taFh!z8JPQb_T4P1)T@g^LMMNx&D z0b{u!`iU=vTcDY8(H&UbL*XrGdRK9nI9og`n#38PfmOIMFcp%RM?5Dg2{qZ098K;b zpOGKQb7V&{hxka`BUTbx;xuZEazHb?VON-g@4#-Rh!e&0;ybZatQ4JMLp%n^&URqt z@mRoqs6CANIMfqGAtfq=RsS>GNkz1S<1>tNAtH!KBAn0>g}{V9qF3l2Gz)0B3(_DK?t2^Wh4$wHG3@|% z8iSYN^>{U2gO}j_pa+kD>OaKyq3vXxfMa32cH$%WG4TH5@SDZ(|0Cf1|KZd485VI1 zG!!&qJeq`dqqji2CBUz2$boWD0TPiLk%&fuj_m(`dHm6Lya68oxz^&LxDSkc6i7A( zPr-}e%yBp_gDv_5xq~{)B0F#`9=n2eNKh%R`2TYnIGc@2;M*XGnJDl~43JUqM}d%4 zkU*v)5_N^~bVq(D3_dr7BLpbN{PA5Bm&(xXr~(-f}IBO29!b$JV;cn@^qA-)LG zp8}rt5aj!g?FBQYdN&uhzi$2Z6jDP$Ovne`h?9h&gP+Qe=U@KcR1lSO)f} z9rl2=&;ZTF6X3lOw6q(=LeCV?gRwBvgVAW{#ZQp-JAMJJ9s${p!TVjXT~*MZ*D$t) zAXxz9VKmV1PN*F)yjCD*D99BJ_sfOs3Wdy|*Tt}gav=8-hco`)TK-Re{1Jw;J}`EH z@XO)QV+VFYo-kr*kc)W<*_vwjem-bv9zF+@@jbNm3Lk<@jR10Ff*e6`bpuoa{da+T ze8X#?52NAtUvNJdg|_G>4oCICNpFFq99E!MTn1}Ogim*29NoZDY=f~p0@rrJdf<`6 zkt^s*JxW6J#KULV@f5TXbZ@1YijwdT^h?ad7@n|EL`Sg^ktMz*K7(DE zjEZna(pS6)T0e(+iZy&FTFX#)Dc1qjGlNApJ`fF}AD{)?|5iybbBo&|Hpb1^rQ&)= zXJQ>Ql<>9vLR;Bx#7swP;XJ#S8tPcXHI)2dHdwykD5f9X(RLq&(>IAJd?7bodP%q= zP7w;}A$XwhjyNM;qyqT%XfOE>o+w7}qHMJ-nqJ|=%xJJJaYQF)f{d{dGM!1358~gD z8IEKWDvPl{KyCQ7%GK5jXul9IZRi|?O1X|Qt#dl|Cem;$k>{Ky`OETN@zdfdPcS`^ z&f__K* zp_!~XzZ);)zB4oHMD58(z-yTu49mUktZ5fVO~e$ErbQ(-yD;4`?D*otTj zt4~3g#UH2%?nJ5}>)Hk_qHlAb(NJ;?=+$~Ih5Z-&4L$moOy{=~QlSx+Glv{UsAEDX zNc)(~=kB73q@SpyHu8GPhp0y1cs0`zEN3;3AzriaGhmKNiD>aQ(Zji(P!TVAxj3CT zPf%!-b04vW2^0Skdg2RgXF)>!%R{mPsfjE;jhav1;e+rp)S4bH3>2Q?$Fx6}EUp&i zOo_8toPw{h_nnOBL0HMZ2q*WF*nsAj1Xi>)Q6a*E z3Dx3(^FF}TWsmd309l5o3)ZUO|oK~;7N|5 z{HzmXk=7&fqxQ|BAE_jbXa_%4EF#s+d*PhJMRFMJ#^)T@Dd~3Geddg9 zrkrt%pi}G)x(QX_=!;L06~r-nG(8p}%I@rsQi%RkTVXuWfjceT?ZAW=ZF7!BQN%}T zki9kg$T^n{;y&=9k_TKZ`NX-AvLYSz)BI6#n`HU5gq`!0y?4A}yIc24D;)!*P3_^7 z5gBX=>On?#S;M;Xt{JsmWn=6fM7>O5ZYBFz_uM1e&|JT{_OdjS3UqdpD9qb5i8b?d zD;@j!3w#$zYx02YHE~&d0JgBd(?+aCEy%NC8$O90?95OKb%P`%VL%r!#ktG!?K|ly zTzAQCek?bMj3+K(KmHU7rY_m+vg`Z={*JS!yb;yfWS|C#_1c6w13AfI)n@YLT#7xG z`Ju2{_tBS2=La6OCXjSx?<-W1Pg5^tShsKQJQQ?z#lEqi(ihtRtE!;?|%rGTwQF zsdmszKGu=_oeFX+bwm6l^3DeA3(Dxc;5^4jsWF0^Fc3%4&B;^FzM@Ep#6G03HzCtm zjJw&7AU{Tpm+|XyIcWPgu(~(-rT8!5HnWU)x-c>%?1x8-9T9itFIoZQxC;#zBNT z)Qym0d&mHVf3KAgpO5CQ(%GNJ`|G~L0AxdjyJ0-k|-!r9h@9_-dd97&8sBgA*; zEuKnvVsD&AI7BDfB(g+0J_}jZ1pF0!7d{aRJb`EnncP)qD{4!0knE5P$y1pR3P@zXBw-3Op< z=oJ2jr=qoJ2yP0IQ6zo>Ug>Y}zpB8#1D89?MBbUt)yC%6Sbf@*YQR0tL9^WA`4$aCSezm z3VrAU)_guO3EJI_M-kE3f&ay|NQ&BnZx{$YxD3AW9~k@3_z~RqCHRJmz=OOF{`(0` z5zX*+)D6Ezk3iaJ^a{HZ$Ho6y`-v!DJOchI1tYi%Yfv1X4cAcM@jk<&!TX*AUguMs zh3*3#sKtBH1hf&n*+{4lS^`x&cklp|2<>LzMkpM;hG?QAc)*+SP!xgdAzpX_RY<8& zTTzHRpssia=!Gj<4gUKYcz*-&P%Ol@Phm#9P#yT+O~DI(3m$ndG!^ZKny1;2PyYkb zjsY(|0cQ0%M57~sN!)>UCZY$pFG__dpa9x$g&2rIs>MuHjsF*QECxUHf06tqd;t0x z2G4PC@R?uW69}SDnA@Hx3&t-4dNmY%1#kQZHllwa&e?>|Ltk^S9DM%&#Ve!HVN9bl zxB#@k1lL7D&-;MaZpW+9RXhSDRza+t0r662#6uj>0(rq){Egegxg%&Mc<7y>XGYNe z185??fu`eWC?2~*OmP<^SOv8Tr*I$WsRVQrxC)H&E|d=~?FJ2+jb`Cc=w~13;XP_s)Uj71Fhc++NTED>p>rqU`FP`RevCZRtCL(4(}K6Sva#0kArzH zfpL!l{aTKe;)bw(UIE+r-dM!(4=e-VBBKY$J>T z3tD;wV!+D~G5@db>;|-*05W}p`1Jx_idq5_$cJ?-gSI|E%#{f9FbfS4CqOkvELuW5 z2VMLu-W0FnGsG0K3mJsAiw)rVx5QM)b4Ma1dI?cni11i^3VOK^g3c&@wKx!Up*E0@ zh@UV|2Z`Tw1ICrEqneO&Kpa;1SB(>3S+HQCRppn@Vijdo>(A`1AqH4)Fl-NUBzSk7_J$2ReX)=iGKK+_=>nk zjwde=&q)Q9OpYa1LtWf<`Vdu&n?jvYF0LakqCNZ%XC*gFI4(#9%xw`W_`||i3 z*9H|352z_*Dpb(LQ9n>EQ9(K(2Av4;HOF`14~pw?C_W`<#m_<)F&wDUSs)ozFcZIk zisYiRgqe&Z<-`sYPn1FBKq%>n#=>*48|F|sek-`}4S5%Fhqx5K5M4YG^7&ibIKc%7W+;Eud4#(vtV08d=EN~I;M-l|Xjr9ns2mu=K>P_j z&|c^>fvA3@4v2d*;8b#=1FZD5A|+(<*Z3*mQ4bWF390i+l(X3 z)J5tqvIq#A3hTu#&`v*CH(tPR;l-4L&t zfeG&bTkr%u0~X1`6XZ1h0#9cKSEfm$ZRBw8C%S;jY9rs5<;Z4j>jmrDZheY%*kLTqIKI0BHxPKrE5E z%Mzuj(n+%K@@C2y)d&?*Jyo7n6v$i1FUm@!?IhjlMDdgDgt2qYHpBah(CLx@GvG0J|*Thcsw8abC-Po1MXGwa@oI8P^FB!ak^7tmEbj%kc%Gh-^uBg__0D>}b};Hj^xn5Rx>OVx`P@>K^e6R|1oE z<;FMa!;{db6?-iB}AuH`AXx-Qel`=ZeL~%HJ>z&swt~1C_Pv- zv|x5YS&^ZE-XXhG_@W+kkbtX{S_ z>paUP(<9^9I-j~h#?9t--=2HTHS!an_~1W{rF^ z(*s>~&a!ldBZF%~T7|X`nGsa) zALeWJyzbUOzeD|AK1R}q_9suHgII|tz_WQa@rf9M9`bIsgt~QAhs%A+2bH^)_bc08 zdb>nbyr%F=!QG+;<*RB+ElY%9bQhVYB1yKH9Y8fF8&YoUU$Xa#w~BwH1IZW8d{dX& z!_~Ppf%Tg$yKTObEZEJc7KR`b_Y3 z^}X#)dAhjftJN}|>>?O!l&QAnY1Q(If#voTTg+Oq!24$j_@y&*54WJmQ4TNst1ig)Yf)!L)2?x*ZKeS^wmjfzX!GMz&0!5ndH zs<~KdFM3kip;~9|X#ZnhYHeVutcx;AE$+O9{;g`|*3EmC9~H1XV1|Da-yvQ%+<&+# zU8=Pjm8WEos4(xU*q)!9aW?gNYHhkl_QqUu-i`dQ!W|_Wt5Qr41&MU9Zk}hnZ)m`P zz;l7U0z(4s`c``exZYCdN*hw&g%rEfbg1@ERd~hZ@;epfhI2-nbp{_w9A;k26zX;Q zH}1LK690yQlAypKDkvlHLSRn7IRC$WQap~kT+~cd*2;ECCrS=58R)+Kl_4-cDy=+e zOY*{$t*NPLu9>?tJu}{?HcRu)dRSO($hV(neriv89`ug%sMidXr7^r@r<(9+<)`sa z_MnyZ!o#{*<(_3_hL#p5H%7Sb*j*n})w^_1>DQ{UmbK`stcP~H%R09*_Z^;#yjFSb z^&I9A?cUh!xyv1Gy?i{m(bl&rH7_DRURizFK7DWA!)P++CNd?@Iq;MoHId zUU(i2Xc(05vq)D@?X()IFB-nrZ{fO-@5qrty2-yXq)1liU-qIdm2=Wd<$^+@Jgv-B zV_icxjfcCZ)^mr4!R@pDpvu9V%yZWwuKh@cl8*o87);%BXAP$bcQ%K+2qy~bG_+b zQ&Y3rI@>{tcd6I1SDHdsz1Jb%KLI#c9lp82@5q$Mng+^C3MMKl9?|e*gI8_lLdj-+!$BHaT%&?*6K1dpPMKy(oJu>A)n@on+^A z(|m`8b&hm~_4f18CD09_-hmV+Fmgq(qKE^OOW%o*>i@z6aDOg?Hwd$>T zg7}!pko8ieC>+YR+NW+4eOCmBg&z(78R8!>&D-H_b$#eksgr5S_vPmeOdj%L*_3F{NuOO zbCa%%Vy4tv{;&G8eyGc7jZ!MXnbzU;bL#tA7jf5d3*spr$Io*(?aMfb=;^D{0@-@0 zKnD_xc$GiL4-$F^%eYW`XLA!{cx`@7YV9#&6Vp6%oVm!%TB#b{@SM+4^6zftLC`& zhThH1?ymIIc=q$)-Sln;Tzk3pcDbsZt%#zf!fabYeRxem#ne)1v5@~?wo6*4#O}X- zCY1bcm~t@lRo*{Er%EKHJ4?Qn$jZl6=a?13Tq=ppV--vlSx#h_MohXl2Bb$ypnEW0;oG=GF8T$mK00Iv0JG;?BtGt4UM(rTHLLNE#u6q z>p#@pt}UqDX6$O-0rHA`Gw~nr98F?(^o+c~bdkEtAIY`yInrnB872;VKUZ0W{Hj8x zSS9-)xxvP=N2I3}(V9{EcCNHrN4JY^zuX6VP4b-)a3<*Q;PN1SV2)p$Pl)$s&t2{l zT*}oeWl^*mdpb{8%8a9H%?6K3wzNs1BiA#hS@x0a%#_s)yl;;a2sOT8HtFrGcY4uM@Z8b?7}Yj!I%~NY5x3^)Ssl&EJ~onoF7s zT0i{)7kAfEm&^K*+9j&Pa-DPqyPP=yJ1Ihd5hrj>9p1J)%OcBR3$`q>o`veP_STNp zd)DQ)y>^8&g}X1Tz}ZkIdxxA(jiPrm1?+OERz6qJTDeR)Qdyx$RIFDL>b_c|ezMyV zk2KFjubbWmpS^x(13m`jhZKiC3+)*)7p&(buO05=T>jB~md7!V&^NBRb!FYGs+h94 zqIvljbFwnGryoo`l44G_B{xm^FJ*q}l(gFP37OrqZ{=DFwv=?Om})?^8e<33N=vTo znKMnOLYFDboR?HdpUYOumnbMzk@~S#r#I*ax%6=p+nhZ>{TIol+57YAP}mtjwR0cOv&ujwyRYcHivi>^0er?A5uw z^N$t&F3v4|Rlc|~#*kB;Rckl)Fi*7Jus?HN7128w1-pp- z!}gMVm8_OZWpiXFWshZ-VLx1%thfBKT&~!r2v8O%GgOpjhBi&7cky;bZd=`cyBXZB zxrMpKxDI!Dt=pzqsaz|&%$lh7#31n_H_Q=jduiTVzp(Cj&1b{%s=q4}Duz`23tr&( zGFjQ8(&$oK$)?hwWgE)RRotz-TD8c~w;ETgY6sTk7`vN{=B3uRwk`IK4uhlCIgC#e z=0JvdD7lSVOPACAnOV#dW*M`dS;=@XJL!wCs_zm%fm#m6^X?RVPwt?0~ zTd676cxlIJw`xynotjaaA?m(hMJ~&_N(88BJB-{!h8tpkXt`=yZcM9nsX1b>Req}Y zQJz&cxXe^4DGM#TR>qaBF0UyUDpXa63{7hCY7ZLQnaa%zt@$=z*rU{fYr)%j79vr9 zJR9QW4NwcR8mQ4!$ldz^{qzOewM#rCr1CBJx7c-lUj57fV@ z)7RRnWz`!D$yGC|$|_G(?x=iF*|F+J)p^5sn7@TJ=`}936KcQIj;k}&?K1||pRbpi zrkZk0z2ONWx6ZWPu?vpT5WQy#5@41Zpig>=rH|6p^mlq0T|kYbu9Gx5k+=iIJ{<28 zKL{;_EZ)p_6S9G6jfW~Af7rz~k-9)5_BhnP-ICWTkm{Xko;pFT({$2o*4)fx&U%4lVR;;>@1V!C3AqN_ruNRwZeAC{k#zml8fO%>f0?uy%TgG?gxkcLb8 zvC&K|wUA7JY_*O5;{0YWu@120noib_F}lW0^ zx7JvDs&2f|qyBY$Gt+rfzA4JQ$1Js+vkbDbwqLgW_D&9sGsF3YyTYFm=8GL5Be3&- zHCQ11D%g=4Os%6{QZ(J1-c85RjhIbLEMsPZ*`Dk?_AQGf6C@`k6%v173=5>&r6;6c zrKGI2Y`AQ`Y__Z~eA3I*GC^7^O@?pINtZ~Yq$bHT$$rT?$tX!9iJ85}E@mUx-^_Bx zjrk50lWpk=sF3SH6_UG2H}W>omvBG@(;m>c_qYY@Xq1Wbg=EmVs~ib@Y>(rM-DdN# z^{{TTd@;wE&zRbpa_TqMd(^)%&NntUx*7e9la2Al(e;V-y-a6eKh0osrTL0wrFFEe zxxLnYz@c)Ua<+upx@Y`pVKZcWgJ7@lS>i9U8`XeTFlEercBv#n8ZR9pOP39of0Z{@ ztW{i8JXQQqWGPA%af*A2ISQ5HzPz*inJh-OQ(7$PEjiC}Ok-vmy$5Ja19BH(0ut;F z?0Py-wH(+ORO@(y?K=$IB9K@>+$Ri#7ulH{PR@h9f9J@1um|uW zxsDu0D#&j@y$Ip}aPYmbZ@C5Jw9oSioEOwy-*@o#0rs;tqqT?iiKVe6!Th&*s5!zc zHJ6#bnO>S6n#xS=%%l#Z zl3CIK*=JdQd5Szrv0QNsjx zIiGlpbimT8fIz+zx`KV!#kc3xyn##NzH;B8s?owl!_LYtz=ozkU7bznCLR@QfI;sB z)-OUvHS%?E5S3N%sJPxSr}leI(8S@vK3~#A0F*$`sEEn|Pip z<0_$E@&jjf3XV8?2YWyJOM9+8+P>5lX!~dzX5VQ~wOzH1v_EpR1y4l^2_;gIYaVZNxg;SY#`zr)y_a5#?7) z-O9{mk4jaA19Prs3facOe=8dpU9CqPkNAi94ta|SkUo|lQGL~QcNe|tz5ny9ahawm zQ^qKVDF-N5D@MrPvuCM)&=a8vXSYwZjk7(oA8`HyHO%hLJo``k2d75-Nu)5pWDixV zb-!JwdK~sz~)pwyRM1X>wpKLe1k7hg-u$;G;KT~qADQM zPew}H?2d)Y%bb;$tEswArrwU9cqRK% zab9y?f8Nd8qpy3hzL&bWyhPFuv?2}mH2lN#A-mvx{BLKrqs)=v93`A1+}RPbck}>6aMvE(xrls z8c)X`GFn;bV)t_R9SY*Z_BVRjw12C&t^aEAca&eyY1eA03y!v)s{U4TGjCFsZ+cxa z|NCM5sqa4Dm&OyxzjNnT23tbJKg<>7E&U*m8D49>Or9IOWIo=$h2HnvhiNONdBjR? zowav;^V&a#aKnsh@45{p(Xzlc-oC+^FN`5Kv#sSpsy&+Cy2g4^&uQb;=M@_1Cwe0} z3#}E~^4QVVe%@w-8ue?=v4T6%nw_Rx;xfuRCTL>Bzfn0cwavS=eAS{^v-XW|gb(nu z=omWAN>pyiIsE&_=Yg;MpL}_k_2ACKn#bQ>?*CXBzcnMg{DJML#M}LN@W#get^ah? zb^Y2kvP*c!4sAL#i;tWc(8{G9TW41p&g6AYe)Z$uPd(pVd9&zU^(UVnlm3)sTrYZE zGuFA5)#`)&wnW@*vZ-Zto20fKTcB2+?}xg#l3v17)3C~;1?@9W zCujX;ex3XEJMn({rF>20Df1{KRXy?=9X_dcChOdHHhkBvpx6Gv<3>e| zeK4xipy}QBwMuEQ#w$WL$lkcTPsYZ$;P{ST-o3eOBDa^io@_#E=e_;~-ryvG+`PX6>F zVN&++svPGqd8MZ@{AhD&$FJR@dntMk@5yvo(E3v&b-*3nQ0k+ZEE}6O>Cchx|9$@Y z(fBF(>q1!A*K@yCrr0JjNqV2a;f-Il&FFD%AR19R+JEe?QH4Wa_I=(dHRg5T0!z?G+saxyr&3kg-O_%SN(|pU*?38SZ=eh9E77II{ z>%F7@hyla;jp(_&gEA&Hu!$}c$?9}@e!qr&eDHkW!*h54y}Rh)>6hx*-pMzL|FPbd zPV?T_uzQ=2-46~JI5ceNvw`MbmpiU%`frHQWeT;&^tw1DwR2p;N5vcQMVpsR-tPSJ z^w)%}{bf1kzbUnz3)mjjtj&mS$^D9l>>u%JWa5Z}LniiJ)!Etnb$GSMRLS1@(yYW- z@tN^vpL2g5soa-w=;7JWJEvcbi)RbH9G8_>{rkkc?DAy5)ZrUO^d9kXSZ@EL9U>ZL zdv280Tbq|GO5PV+{QCKmLl5HbH+YiwYR_jOp+k1ps^Q#r`A-jLFc_0qWTZAw` z_~v@p=}xl)uX%MejBBWSd;IAix0aqWrT)J2-OlpVUTMEx$$P*TSnHQ+?zF!E{O}ettMJ@6J^9JRZ zo@#S5U!8xO{&7i7mh~HUqeG3CF<^Gc@X)H>1NsFIEEv$XUwyAGU1tRza`hxmU&4xh zc+xZ7a%@%V*3@=K`<{-!xanTpo2y?I(upn|0;FzX-@c4s25%aiKF~4zQv1(7Sx#}9 zpDllu#k}eM{AqT#hjZ_G-+P^@JU#JhN>O~pM2io*&+bXH#Fje(UE3wMYait0KeOcy zPrb8^K3a`4|NF(IJgQ`F!R1$rUS57Bzdlq@T$KN@Pt8Qj9;&-O)$x@38K2{=cLl}= zWw-GPnA$4c=Z5D^*8;l@+7ncxFtqXeFB8j8l|C-s`#z+2QrFYh_NYoE3e-tBC1xEkvu`n{$KDwFT`EzyS zZno5Ujc?obgE}ASwmfuv_Y)y@0VeMPXOli$v9Ir6QD3^F=x{#uD(Tgx{GEmR_bwl6 zs!AIFlLxbHjX6%&-GBDl?zPJ6p63wvT&GN92sc?>%{SIftQt|asuQOSsp zai33AWtx7mtVL(7m%hZ#$Hmb-#nZ|AvUiI2O0Vf2Ke!1_G4^wem6}2H6ZtMb)tpd& zv^Jw=Q%#%NHub7GU(6!@devdRyS~K{KfcY+?GoBm1Xi|8Zf3CS&D=33lo!A4@~S@f zKn|4?^1R@`?Dw_hiFFl%M1M5=>DbF{xBE?xwjS%<{G1NjzSos7c1n=-O8p-ur&_O? zb5&l|iC^y39 zgH7#C!%SyQ$#n}GZkfmNzet7Z1}0Q{$S}qBuKj+;jZQ0OeGC9G~v&92JN(5!I% z>`)$4DO6vo`GMpmCmb$o(3w{FRo zQPpoNPgOiAFDM^Zd9gaPHlyJtAF0Z$v*Eqn0Ox8qdyj)2!#tebr@96?2HW^(SI|#o z4S%J# zsyz_Y;c}VuLek5NKZdzb6mI2Ky0qq{9!ZC z_^*Dh_7m$$d#F{S1D|Ui*%;ZdzrJ_<#QN_V>KgA`w(%)qkdjON$c*F;YPM?)x=-5M z(6*VwoF^Q*BKM_Ku}(NDWC#&rPpQ8=T{#TYm=Q`m9CIIg(goyyR2#KiJ|*$u-{J$& zB0iQTD9xar^PVZ>GPG;;jz)vcQk$7JuZ<;!)%w@k5^g`^OyU(*+QmCqt~5NX8(~VW zT~yo3G^TD$!*26J{$Ht4<>@39caGM zaIZeBeo1}Hh94X4ES~%oakny>q%xdlw9eMR88;eN8H0^$4MX$`v{A65BPmn%NRxzR zR?hONF}l$g4CSHbI7_j0fv`_1QXEKgrX}}DlcY20UmAKEXBs;gmmA{sgLQctKW;2D zp8TaU%5wOC!bkE|*8R{B8EgGuJ;l!!LdD_O_ZKMxsCcr64rUUV{m|dyX%Vc3UFu%t zh@2p87ahbBAxp>gh@JEmB7f zpC3UCm%( znRsS6SfktNPV_eN9*CQ9>ILu*%uO$=Y0AwGuPNew;MTDFm@#xI@T|62L3_kC=&KCn`|{EJ0zQ{d6t0QYQU_p=Qqk4i zN!v4R8IHM5cLq{z4Yf_p!2HNae+udNouwns(53jHRK&xMQfO| zOkZ{dI*Q)V)mqB5V&>DiKpVM}bKpW%0ckf@tpzu5q%s>=voq)zA5)X46tV#PZXH{I ze*byyKKG2<4a}Gy>&Xb{M|J=@Emm4DUJ^up3EzqD!1v_;;4S<<;V+l1$Bz- zrMohln7^46rU#Hv8N>=yWQgjo_{)8ycu^I$3QL9aLO(1r|(DtCVYL7NM{VjMcd4yvG7l}=y^nqi^2@MkN%pXrU>X?JcZ z7Y|EM1v7OiyOoKhgMjs!q!dV7#g)Q&zR?RoCx zDIi7U0$GmE*C603vy{Qg7P&(DR^fW7gxa~GZUa-cAowTJ;gGTif1pP)&r-R2fl6-%d@Y#8c5c4R1d6%eROZ;4Mv>J z(wqy|ifd$(z@RMxuO}NZVW<)&kCZlwH-xsr2fmX3#5eGs!WyBM$Vx|`k(VVeRzxKj zoE=$dK)?SOxLiY(UW$WqS5A^Q1J$_|t8br7lxQ$)bJ1_j0h2C|3Lq!JdM&ujLzp<`DOC8J zS$iiGPZ3#293zD6qaafP1eTj@B*MZcXq}~J`?vC6^zAkN&R^Z<{%(@4x$ia#iz=d881o$cN z759K0a1v(~Gt~%n8hE5ZKtC1$Yj<0@tXx9WD#uxAqtXt%*;ruKkD}M@3k>u~aDiV@ zjZ_ubscyh%YDg}%4^es~aKP))b=HHe`xy5qQ91&jcpV7p0HE*pf^A#`9?>XhKV}m` z_n;S`Q~Qi=#JSFo=$h}ry1CA5Ml5{|b;s=_AMw7ox>9)rKE_39p)^VwE^U-vOVhw- zUZ%K%Pdku04c>Sd8BBVCEnG>R1nx3NT?`L>t?X37lwhR~)ZQj2)070|9cF9*xUw68 z&?GoPs{k(B9Zc5@@Ntu{27d+@*i#jOC7i4{f!T3d-XbrN=gSA=hd`xvQdS}U+rUbj zfjDjj&cSvnhw>ybWHm`3>oKFpfD|nzHgpuCr9zJZUek!~dJyYp%1#m{lKk`R+@v`5DTr&VCX4iq3gSx{0d&R9at|_z;-4oH{?swMKM5p zDJ&Pp3*QLwf+9>8cObW=OXKAn83<@_J$F(r;vo>+;6+#pZURp)3&bdo30yXZ{1 zf^N+uGPYRRzT6>hgr-t6OM6qRYQ1!!x?r77cS{?m6>&0gg)3!GF+TKZFgCtZlBDKh zE5XWNLVUHune06w8hz)D(l~jf;*VUP1#JILXzMkq{^|-vl$*gBT;; z6n~StBhtN8GJw2~Mh={ZaSUV@L&Y+R@nt?C3h=O%o}2|I!50{KAMo1$0~Yc+nL#&) z%EfkeEawFUm{cfIChO{SUV1ll@z3d&=sb0)IE(pFbBPmJ4NEcUqy@Mx_0q56HolwH z)$+=mZKf^BmIKyn(0EbBV;F}!*25g+)qz;yYrtA)tG0&XVUgG%iF$t7vE zJWc5gK0zdK&Ib|mveh%nOj#$T3wv-@H5?}rzgdf{`}nCibz-C%sa|#fL#01v=yzZj zs@V=)3+RI`ghFT-bAa}y_mei{1{mrkIEB@q#z;{T5s93@5AO#y|7xfWq|%OvHm5Pa zCdSHCFl(8P%vrkM*SfJUI0h2l;|tBs>+%J;MD_v0tQ%MjvCywgAv!vOj-mfV+m zfKuLV)U)%UHn{-#F@k!m&Qik=|N7zN>Hy-?XYg8z5Hml4w;hcr?heh{OUTcU$X*go z6zU0>Hi=N(xD5pc37$6{)nlE~Lg_0HkS2?T!V#Pd|0Sde|KZ$eI}}BHrGwH;IZO!y zBd3U3L>|CXyOU%vHHN9h%2DMY6dzK6aEXJSWk1DT3BuSr0;&B4xbs8ww}?rp%rr*F zJg3u;cjh6xcR^jU4E4}e#QovQ8|Yzf0rEORDOCC+JA4L*<|NK_*THIgQMacc&%Z!L z6v*tN?dZi|01TsELs!F7eF&uqgJO~OiXZ;BM`=Qg+hR5^BTEE8^>-fhQ@RtDWP)R3 z4^%0mZd3x5Jh0{3$-Go0F;Ef?lEdU>@@M%6y+ z6;b~Jzdr)2)deh`S6}1z8tfWQV8xhlraT!{)@5+9vT+W26B-%^@%>`3joeYatVc%S zk=g!){>^sf1rX$sP;bcvN9lXu%x^<=A&Mj-jx&fdJyEGTLX9GojKKcn02o(&p;B-I zxwAV)Y(bv93^aR@5`;*09=xF+psH*{CK?Y_w2JSom3CiM-2wbVXC?excx22OtH9AwAw>;2CE@UIf+_mlQLhKp^OK!U=&oB2O~En zC}*MJApr+J0@@b`)O+wSEA}&?U|ao)iXk53NTiZ6>iyK;@U6`_3to#VVmT^{H8?ee zLJ%}8l8~*N{-r<-`aUvQG4d!49cEYJjJn#5c#&4v&G-{PR8S!#5NoOj83VoM<drK%aHz1WANYgAkxMtqJ)l@~169ZjX($l? z&NvAaL{apUBBccBt|UPRcNTo_n*2fbg6|%HPI7zLWfwSRFOaXRk>i@<409^RauvF) zJa#l*#0Iwh0g)&XPn3t~?m#z^Pso6OlMN&u&(RB`siU633crEpA!8lhLQK2?R>4iM z#hR|uhzf_m`bfji?t&-w2(Ra{d)SS7E(yM}8^3L099E)Y2t;)=7xp@d8O=n#eFIit zI_|v%ysKqk84QDW1VfX8g+{_7-0u=T>j6Hq5UalsC$;I&!uT6lknOnZ9=vW)SE6R@ zp|%8D!>oLU8c73Y)efh)fr!Zi)fiM|2k@=}><)BLujqiC>JTvc79(R`#xp#JI&B3m zGbMsY;s<5yai~Vdp^6*?ZRakyLJ(sj@RJebTjEP}u*HAaQ(wiEftvU_Rz^H{pZ#Ii zrb?(avS~|HTrOB8Pr*K13vR{F>U1><*C5pULGWBRTn5!u^@iuV;(Kqr>Qy6HOb)sNqfWluHfmkVHS>cKX%2d^&} zJJC__obRA_6OFl^341p&<$lN4C0Oxs@Z*H9TD&XqeUqa3N_@8ko_-Wu*fdxr4P4tp zh#7~eW4O*@u8v|AY{uWLpjP9(t8s0@ed51X)-m|3NU(6ift3hCK6U{E&4Rgl1|PWy z6_agW=V&GLR2Bh)G#7ZD`Cq@rg7uON-)Z7D=3#d0aS0evlMQ>qo}& zg_n-PbM-+y@}8PHXEsA=!vO2d=wMcn@j&zFK-YAUp8&VrMA3m*OGYaWA= z0`iG7czInhzEQZw;A+yG?+?A0aQIRv)er%1~bfoPu2(@u11uq0=uvp7JGxw zYr5kjSTGy+eTH#0Y0Q%sW_{cfZDgi`WNU{<;VH z2Q%^y#-E1kEPj#!dt@T|yuecxBE~j7*K5>CpAik}QN?NSOum@mFzB*G;L}E6%w6$$ ht+0-anD0vbPp3H?7; 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 AmjD0& literal 0 HcmV?d00001 diff --git a/examples/ffva/filesystem_support/mandarin_mainland/10.wav b/examples/ffva/filesystem_support/mandarin_mainland/10.wav new file mode 100644 index 0000000000000000000000000000000000000000..c93c4a6b810ded945adf7f461879aa079b02af5c GIT binary patch literal 18464 zcmZ8|1$Y!!_xBy!-PxV>-8e}|2yVsQofc`aQi>O+xRz4f3KS_&pg@ZkEfm+{?vfB7 z1md1`o03m8RiRcso-UJ$jQNk2GDDG% z%p5l($eI6S-EXD0G#z6dudl z{~JNBNGst}goBiFo)vvc|JS0(q4X0Ck?~3&ITOtj4JtGUP0|u7Yg4X?F31?MY#wPV zUx|z)HKiy$3N_MF&MWyQ;}ojoOem2R2q%OWa^SLda!vX}Py%pBI9PT~=qX%~bNLnd zCL_rV(jS8Jf6@_t%I!vchI|ND3axUkh^>@^=$~BwHw=(0gA<$vKmv?s#rzw z4&r&_ql{8kBh&~DvNCDsU@j>v99&sZ!&wDAq?MdGq=7mEhpKE8na!4sVWCACO+!Bm zg?w{xCRc@2LSP$%4zx}pKa-iie&6aos0=MhN=w?tVt@yXqfiB81P+a$4-2eX8`VQ`K&dkF0o6+AJ;)l3@^L9h6pxB= z1(XCc?5IAfi6Y?$Ljtm)Mkoq31}Y5tgdYL7&*9L5Hh2_{p5cFhm+H7Ou8mdrGdvQ{ z$A3VtAGb$c&}5Vd^pf!4P*&u8rHFiBtilqKl{|R53m-r%3_vwiqovksixo=rqDeM{NhIO~7?ThQI84(N!yMcOZZA$AuBNEGgXvM2}LkGV