Compare commits

..

34 Commits

Author SHA1 Message Date
philippe44
ef692b1b50 more aggressive handling of Spotify loudness war... - release 2023-05-12 21:16:20 +02:00
philippe44
93a2c0969c synchronizing with host versions 2023-05-10 16:45:50 +02:00
philippe44
bc4e56eabc unify Host and Embedded versions 2023-05-10 12:26:43 +02:00
philippe44
fa91879535 more missing stuff 2023-05-07 11:50:46 +02:00
philippe44
2f9b506e9b include typos 2023-05-07 00:28:17 +02:00
philippe44
806cb054ba add missing files, removing un-nedded ones 2023-05-07 00:12:02 +02:00
philippe44
ff663a20b6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-05-06 23:50:32 +02:00
philippe44
8bad480112 new cspot/bell 2023-05-06 23:50:26 +02:00
Sébastien
a38cb55468 Update codeql-analysis.yml [skip actions] 2023-04-26 18:02:51 -04:00
Sébastien
c0a9fd3100 Update codeql-analysis.yml [skip actions] 2023-04-26 17:39:13 -04:00
Sébastien
8cf9c16140 allow manual run - [skip actions] 2023-04-26 13:54:00 -04:00
Sébastien
12147eb570 Exclude bundles form analysis [skip actions] 2023-04-26 13:51:52 -04:00
github-actions
3afb2615c3 Update prebuilt objects [skip actions] 2023-04-19 22:26:14 +00:00
philippe44
e0e7e718ba fix vorbis & opus never ending - release 2023-04-20 00:23:23 +02:00
philippe44
f0527f70ac fix compile error 2023-04-19 12:39:48 +02:00
philippe44
3ecc09b989 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-19 00:43:11 +02:00
philippe44
7dbed7a67b trying to follow cspot... 2023-04-19 00:43:06 +02:00
github-actions
043d73dd6e Update prebuilt objects [skip actions] 2023-04-18 16:23:07 +00:00
philippe44
dc62afd788 backport typo - release 2023-04-18 18:19:28 +02:00
github-actions
d51df83981 Update prebuilt objects [skip actions] 2023-04-17 17:37:39 +00:00
philippe44
f4388a8c0a proper solution to track "plop" - release 2023-04-17 19:34:49 +02:00
philippe44
4ee9878a6f fix no reboot on squeezelite connection loss 2023-04-16 17:16:07 +02:00
Sébastien
025b5d8c75 Update README.md 2023-04-14 20:55:31 -04:00
Sebastien L
0bebaccf57 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-12 14:44:09 -04:00
Sebastien L
7e8313baf3 Merge readme with master 2023-04-12 14:44:02 -04:00
philippe44
ce4cebd994 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-09 10:31:01 -07:00
philippe44
ba81c2ecd5 fix plop when switchign track (still need to fix it when seeking) - release 2023-04-09 10:30:56 -07:00
github-actions
131b1d36b0 Update prebuilt objects [skip actions] 2023-04-09 13:48:46 +00:00
Sebastien L
0d37c270e2 more fixing 2023-04-09 09:43:54 -04:00
Sebastien L
6054affb81 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-09 09:38:05 -04:00
Sebastien L
196a1d179a Attempt to fix build [skip actions] 2023-04-08 12:36:16 -04:00
Sebastien L
7574054e22 Attempt to fix build [skip actions] 2023-04-08 12:34:19 -04:00
Sébastien
cd088d2500 Update Platform_build.yml [skip actions] 2023-04-08 12:23:37 -04:00
github-actions
d16ce964d6 Update prebuilt objects [skip actions] 2023-04-08 14:44:47 +00:00
213 changed files with 8233 additions and 7405 deletions

View File

@@ -13,7 +13,6 @@ on:
description: 'Force a Release build. When not forced, the system will check for release word in the last commit message to trigger a release' description: 'Force a Release build. When not forced, the system will check for release word in the last commit message to trigger a release'
required: true required: true
type: boolean type: boolean
jobs: jobs:
bootstrap: bootstrap:
name: Global setup name: Global setup
@@ -228,5 +227,7 @@ jobs:
update_web_installer: update_web_installer:
name: Update Web Installer After Release name: Update Web Installer After Release
needs: [ bootstrap, build ] needs: [ bootstrap, build ]
if: ${{( always() && !cancelled() ) && needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }} if: ${{ always() && !cancelled() && needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
uses: ./.github/workflows/web_deploy.yml uses: ./.github/workflows/web_deploy.yml
secrets:
WEB_INSTALLER: ${{ secrets.WEB_INSTALLER }}

View File

@@ -19,6 +19,7 @@ on:
branches: [ master-cmake ] branches: [ master-cmake ]
schedule: schedule:
- cron: '19 12 * * 4' - cron: '19 12 * * 4'
workflow_dispatch:
jobs: jobs:
analyze: analyze:
@@ -39,7 +40,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -50,7 +51,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -62,6 +63,13 @@ jobs:
#- run: | #- run: |
# make bootstrap # make bootstrap
# make release # make release
# Exclude specific artifacts from analysis
- name: Exclude Artifacts
run: |
# Exclude components/wifi-manager/webapp/dist/js/index* from analysis
echo 'components/wifi-manager/webapp/dist/js/index*' >> .codeql-exclude-paths
echo 'components/wifi-manager/webapp/dist/js/index*' >> .codeql-exclude-paths.txt
echo 'components/wifi-manager/webapp/dist/index*' >> .codeql-exclude-paths
echo 'components/wifi-manager/webapp/dist/index*' >> .codeql-exclude-paths.txt
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@@ -1,6 +1,9 @@
name: Update Web Installer name: Update Web Installer
on: on:
workflow_call: workflow_call:
secrets:
WEB_INSTALLER:
required: true
workflow_dispatch: workflow_dispatch:
jobs: jobs:
update_web_installer: update_web_installer:

207
README.md
View File

@@ -1,22 +1,23 @@
![Cross-Build](https://github.com/sle118/squeezelite-esp32/workflows/Cross-Build/badge.svg?branch=master-cmake) [![Platform Build](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml/badge.svg)](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml)
![ESP-IDF v4.3.1](https://github.com/sle118/squeezelite-esp32/actions/workflows/esp-idf-v4.3-build.yml/badge.svg?branch=master-v4.3)
# Squeezelite-esp32 # Squeezelite-esp32
## What is this
## What is this?
Squeezelite-esp32 is an audio software suite made to run on espressif's ESP32 wifi (b/g/n) and bluetooth chipset. It offers the following capabilities Squeezelite-esp32 is an audio software suite made to run on espressif's ESP32 wifi (b/g/n) and bluetooth chipset. It offers the following capabilities
- Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices. - Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices.
- Stream directly from a Bluetooth device (iPhone, Android) - Stream from a **Bluetooth** device (iPhone, Android)
- Stream directly from an AirPlay controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only) - Stream from an **AirPlay** controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
- Stream direcly from Spotify using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot) - Stream direcly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)
Depending on the hardware connected to the ESP32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer. Depending on the hardware connected to the ESP32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer.
But squeezelite-esp32 is highly extensible and you can add But squeezelite-esp32 is highly extensible and you can add
- Buttons and Rotary Encoder and map/combine them to various functions (play, pause, volume, next ...) - [Buttons](#buttons) and [Rotary Encoder](#rotary-encoder) and map/combine them to various functions (play, pause, volume, next ...)
- IR receiver (no pullup resistor or capacitor needed, just the 38kHz receiver) - [GPIO expander](#gpio-expanders) (buttons, led and rotary)
- Monochrome, GrayScale or Color displays using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341). - [IR receiver](#infrared) (no pullup resistor or capacitor needed, just the 38kHz receiver)
- Ethernet using a Microchip LAN8720 with RMII interface or Davicom DM9051 over SPI. - [Monochrome, GrayScale or Color displays](#display) using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341).
- [Ethernet](#ethernet-required-unpublished-version-43) using a Microchip LAN8720 with RMII interface or Davicom DM9051/W5500 over SPI.
Other features include Other features include
@@ -51,29 +52,44 @@ The esp32 must run at 240 MHz, with Quad-SPI I/O at 80 MHz and a clock of 40 Mhz
In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommended to limit reported sampling rate to 96k (-Z 96000). Note that some high-speed 24/96k on-line streams might stutter because of TCP/IP stack performances. It is usually due to the fact that the server sends small packets of data and the esp32 cannot receive encoded audio fast enough, regardless of task priority settings (I've tried to tweak that a fair bit). The best option in that case is to let LMS proxy the stream as it will provide larger chunks and a "smoother" stream that can then be handled. In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommended to limit reported sampling rate to 96k (-Z 96000). Note that some high-speed 24/96k on-line streams might stutter because of TCP/IP stack performances. It is usually due to the fact that the server sends small packets of data and the esp32 cannot receive encoded audio fast enough, regardless of task priority settings (I've tried to tweak that a fair bit). The best option in that case is to let LMS proxy the stream as it will provide larger chunks and a "smoother" stream that can then be handled.
Note as well that some codecs consume more CPU than others or have not been optimized as much. I've done my best to tweak these, but that level of optimization includes writing some assembly which is painful. One very demanding codec is AAC when files are encoded with SBR. It allows reconstruction of upper part of spectrum and thus higher sampling rate, but the codec spec is such that this is optional, you can decode simply lower band and accept lower sampling rate - See the AAC_DISABLE_SBR option below. Note as well that some codecs consume more CPU than others or have not been optimized as much. I've done my best to tweak these, but that level of optimization includes writing some assembly which is painful. One very demanding codec is AAC when files are encoded with SBR. It allows reconstruction of upper part of spectrum and thus higher sampling rate, but the codec spec is such that this is optional, you can decode simply lower band and accept lower sampling rate - See the AAC_DISABLE_SBR option below.
## Supported Hardware ## Supported Hardware
Any esp32-based hardware with at least 4MB of flash and 4MB of PSRAM will be capable of running squeezelite-esp32 and there are various boards that include such chip. A few are mentionned below, but any should work. You can find various help & instructions [here](https://forums.slimdevices.com/showthread.php?112697-ANNOUNCE-Squeezelite-ESP32-(dedicated-thread)) Any esp32-based hardware with at least 4MB of flash and 4MB of PSRAM will be capable of running squeezelite-esp32 and there are various boards that include such chip. A few are mentionned below, but any should work. You can find various help & instructions [here](https://forums.slimdevices.com/showthread.php?112697-ANNOUNCE-Squeezelite-ESP32-(dedicated-thread))
**For the sake of clarity, WROOM modules DO NOT work as they don't include PSRAM. Some designs might add it externally, but it's (very) unlikely.** **For the sake of clarity, WROOM modules DO NOT work as they don't include PSRAM. Some designs might add it externally, but it's (very) unlikely.**
### Raw WROVER module ### Raw WROVER module
Per above description, a [WROVER module](https://www.espressif.com/en/products/modules/esp32) is enough to run Squeezelite-esp32, but that requires a bit of tinkering to extend it to have analogue audio or hardware buttons (e.g.) Per above description, a [WROVER module](https://www.espressif.com/en/products/modules/esp32) is enough to run Squeezelite-esp32, but that requires a bit of tinkering to extend it to have analogue audio or hardware buttons (e.g.)
Please note that when sending to a Bluetooth speaker (source), only 44.1 kHz can be used, so you either let LMS do the resampling, but you must make sure it only sends 44.1kHz tracks or enable internal resampling (using -R) option. If you connect a DAC, choice of sample rates will depends on its capabilities. See below for more details. Please note that when sending to a Bluetooth speaker (source), only 44.1 kHz can be used, so you either let LMS do the resampling, but you must make sure it only sends 44.1kHz tracks or enable internal resampling (using -R) option. If you connect a DAC, choice of sample rates will depends on its capabilities. See below for more details.
Most DAC will work out-of-the-box with simply an I2S connection, but some require specific commands to be sent using I2C. See DAC option below to understand how to send these dedicated commands. There is build-in support for TAS575x, TAS5780, TAS5713 and AC101 DAC. Most DAC will work out-of-the-box with simply an I2S connection, but some require specific commands to be sent using I2C. See DAC option below to understand how to send these dedicated commands. There is build-in support for TAS575x, TAS5780, TAS5713 and AC101 DAC.
### SqueezeAMP ### SqueezeAMP
This is the main hardware companion of Squeezelite-esp32 and has been developped together. Details on capabilities can be found [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP). This is the main hardware companion of Squeezelite-esp32 and has been developped together. Details on capabilities can be found [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP).
if you want to rebuild, use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file. If you want to rebuild, use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
- set_GPIO: 12=green,13=red,34=jack,2=spkfault - set_GPIO: `12=green,13=red,34=jack,2=spkfault`
- batt_config: channel=7,scale=20.24 - bat_config: `channel=7,scale=20.24`
- dac_config: model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0 - dac_config: `model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0`
- spdif_config: bck=33,ws=25,do=15 - spdif_config: `bck=33,ws=25,do=15`
### MuseLuxe
This portable battery-powered [speaker](https://raspiaudio.com/produit/esp-muse-luxe) is compatible with squeezelite-esp32 for which there is a dedicated build supplied with every update. If you want to rebuild, use the `squeezelite-esp32-Muse-sdkconfig.defaults` configuration file.
NB: You can use the pre-build binaries Muse4MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
- target: `muse`
- bat_config: `channel=5,scale=7.48,atten=3,cells=1`
- spi_config: `"mosi=15,miso=2,clk=14` *(this one is probably optional)*
- dac_config: `model=I2S,bck=5,ws=25,do=26,di=35,i2c=16,sda=18,scl=23,mck`
- dac_controlset: `{"init":[ {"reg":0,"val":128}, {"reg":0,"val":0}, {"reg":25,"val":4}, {"reg":1,"val":80}, {"reg":2,"val":0}, {"reg":8,"val":0}, {"reg":4,"val":192}, {"reg":0,"val":18}, {"reg":1,"val":0}, {"reg":23,"val":24}, {"reg":24,"val":2}, {"reg":38,"val":9}, {"reg":39,"val":144}, {"reg":42,"val":144}, {"reg":43,"val":128}, {"reg":45,"val":128}, {"reg":27,"val":0}, {"reg":26,"val":0}, {"reg":2,"val":240}, {"reg":2,"val":0}, {"reg":29,"val":28}, {"reg":4,"val":48}, {"reg":25,"val":0}, {"reg":46,"val":33}, {"reg":47,"val":33} ]}`
- actrls_config: buttons
- define a "buttons" variable with: `[{"gpio":32, "pull":true, "debounce":10, "normal":{"pressed":"ACTRLS_VOLDOWN"}}, {"gpio":19, "pull":true, "debounce":40, "normal":{"pressed":"ACTRLS_VOLUP"}}, {"gpio":12, "pull":true, "debounce":40, "long_press":1000, "normal":{"pressed":"ACTRLS_TOGGLE"},"longpress":{"pressed":"ACTRLS_POWER"}}]`
### ESP32-A1S ### ESP32-A1S
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 (see below) Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 with probably two variants - these boards are a mess (see below)
The board shown above has the following IO set The board shown above has the following IO set
- amplifier: GPIO21 - amplifier: GPIO21
@@ -89,23 +105,30 @@ The board shown above has the following IO set
(note that some GPIO need pullups) (note that some GPIO need pullups)
So a possible config would be So a possible config would be
- set_GPIO: 21=amp,22=green:0,39=jack:0 - set_GPIO: `21=amp,22=green:0,39=jack:0`
- a button mapping: - a button mapping:
``` ```json
[{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}] [{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}]
``` ```
for AC101 for **AC101**
- dac_config: model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32 - dac_config: `model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32`
for **ES8388** (it seems that there are variants with same version number - a total mess)
- dac_config: `model=ES8388,bck=5,ws=25,do=26,sda=18,scl=23,i2c=16`
or
- dac_config: `model=ES8388,bck=27,ws=25,do=26,sda=33,scl=32,i2c=16`
for ES8388
- dac_config: model=ES8388,bck=5,ws=25,do=26,sda=18,scl=23,i2c=16
### T-WATCH2020 by LilyGo ### T-WATCH2020 by LilyGo
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its dac (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its dac (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
- dac_config: model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22 - dac_config: `model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22`
- dac_controlset: { "init": [ {"reg":41, "val":128}, {"reg":18, "val":255} ], "poweron": [ {"reg":18, "val":64, "mode":"or"} ], "poweroff": [ {"reg":18, "val":191, "mode":"and"} ] } - dac_controlset:
- spi_config: dc=27,data=19,clk=18 ```json
- display_config: SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip { "init": [ {"reg":41, "val":128}, {"reg":18, "val":255} ], "poweron": [ {"reg":18, "val":64, "mode":"or"} ], "poweroff": [ {"reg":18, "val":191, "mode":"and"} ] }
```
- spi_config: `dc=27,data=19,clk=18`
- display_config: `SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip`
### ESP32-WROVER + I2S DAC ### ESP32-WROVER + I2S DAC
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via: Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
@@ -131,18 +154,23 @@ And the super cool project https://github.com/rochuck/squeeze-amp-too
## Configuration ## Configuration
To access NVS, in the webUI, go to credits and select "shows nvs editor". Go into the NVS editor tab to change NFS parameters. In syntax description below \<\> means a value while \[\] describe optional parameters. To access NVS, in the webUI, go to credits and select "shows nvs editor". Go into the NVS editor tab to change NFS parameters. In syntax description below \<\> means a value while \[\] describe optional parameters.
As mentionned above, there are a few dedicated builds that are provided today: SqueezeAMP and Muse but if you build it yourself, you can also create a build for T-WATCH2020. The default build is a generic firmware named I2S which can be configured through NVS to produce *exactly* the same results than dedicated builds. The difference is that parameters must be entered and can accidently be erased. The GUI provides a great help to load "known config sets" as well.
By design choice, there is no code that is only embedded for a given version, all code is always there. The philosophy is to minimize as much as possible platform-specific code and use of specific `#ifdef` is prohibited, no matter what. So if you want to add your own platfrom, please look **very hard** at the `main\KConfig.projbuild` to see how you can, using parameters below, make your device purely a configuration-based solution. When there is really no other option, look at `targets\<target>` to add your own code. I will not accept PR for code that can avoid creating such dedicated code whenever possible. The NVS "target" will be used to call target-specific code then, but again this is purely runtime, not compile-time.
### I2C ### I2C
The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Default speed is 400000 but some display can do up to 800000 or more. Syntax is The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Default speed is 400000 but some display can do up to 800000 or more. Syntax is
``` ```
sda=<gpio>,scl=<gpio>[,port=0|1][,speed=<speed>] sda=<gpio>,scl=<gpio>[,port=0|1][,speed=<speed>]
``` ```
<strong>Please note that you can not use the same GPIO or port as the DAC</strong> **Please note that you can not use the same GPIO or port as the DAC.**
### SPI ### SPI
The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The NVS parameter "spi_config" set the spi's gpio used for generic purpose (e.g. display). Leave it blank to disable SPI usage. The DC parameter is needed for displays. Syntax is The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The NVS parameter "spi_config" set the spi's gpio used for generic purpose (e.g. display). Leave it blank to disable SPI usage. The DC parameter is needed for displays. Syntax is
``` ```
data|mosi=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1|2][,miso=<gpio>] data|mosi=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1|2][,miso=<gpio>]
``` ```
Default "host" is 1. The "miso" parameter is only used when SPI bus is to be shared with other peripheral (e.g. ethernet, see below), otherwise it can be omitted. Note that "data" can also be named "mosi". Default and only "host" is 1 as others are used already by flash and spiram. The optional "miso" (MasterInSlaveOut) parameter is only used when SPI bus is bi-directional and shared with other peripheral like ethernet, gpio expander. Note that "data" can also be named "mosi" (MasterOutSlaveIn).
### DAC/I2S ### DAC/I2S
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. Syntax is The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. Syntax is
``` ```
@@ -150,17 +178,20 @@ bck=<gpio>,ws=<gpio>,do=<gpio>[,mck][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|A
``` ```
if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). Only GPIO0 can be used as MCLK and be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed. if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). Only GPIO0 can be used as MCLK and be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power on and off using a JSON syntax: So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speakder and headset on and off using a JSON syntax:
```json
{ <command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
<command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
... }
``` ```
{ init: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ], Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff
poweron: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweroff: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ] } This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. The 'val parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
```
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
NB: For specific builds (all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset NB: For specific builds (all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
<strong>Please note that you can not use the same GPIO or port as the I2C</strong> **Please note that you can not use the same GPIO or port as the I2C.**
### SPDIF ### SPDIF
The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF. The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF.
@@ -185,26 +216,26 @@ GPIO ----210ohm-----------||---- coax S/PDIF signal out
| |
Ground -------------------------- coax signal ground Ground -------------------------- coax signal ground
``` ```
### Display ### Display
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is The NVS parameter "display_config" sets the parameters for an optional display. It can be I2C (see [here](#i2c) for shared bus) or SPI (see [here](#spi) for shared bus) Syntax is
``` ```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106] I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789|ILI9341[:16|18][,rotate]][,mode=<mode>] SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735[:x=<offset>][:y=<offset>]|ST7789|ILI9341[:16|18][,rotate]]
``` ```
- back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness - back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness
- reset: some display have a reset pin that is should normally be pulled up if unused - reset: some display have a reset pin that is should normally be pulled up if unused. Most displays require reset and will not initialize well otherwise.
- VFlip and HFlip are optional can be used to change display orientation - VFlip and HFlip are optional can be used to change display orientation
- rotate: for non-square *drivers*, move to portrait mode. Note that *width* and *height* must be inverted then - rotate: for non-square *drivers*, move to portrait mode. Note that *width* and *height* must be inverted then
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz - Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
- mode: Default mode = 0. Some display modules use different transaction line timings. Check the module documentation if a non-standard mode is required. - SH1106 is 128x64 monochrome I2C/SPI [here](https://www.waveshare.com/wiki/1.3inch_OLED_HAT)
- SH1106 is 128x64 monochrome I2C/SPI [here]((https://www.waveshare.com/wiki/1.3inch_OLED_HAT))
- SSD1306 is 128x32 monochrome I2C/SPI [here](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi) - SSD1306 is 128x32 monochrome I2C/SPI [here](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi)
- SSD1322 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice - SSD1322 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
- SSD1326 is 256x32 monochrome or grayscale 16-levels SPI [here](https://www.aliexpress.com/item/32833603664.html?spm=a2g0o.productlist.0.0.2d19776cyQvsBi&algo_pvid=c7a3db92-e019-4095-8a28-dfdf0a087f98&algo_expid=c7a3db92-e019-4095-8a28-dfdf0a087f98-1&btsid=0ab6f81e15955375483301352e4208&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_) - SSD1326 is 256x32 monochrome or grayscale 16-levels SPI [here](https://www.aliexpress.com/item/32833603664.html?spm=a2g0o.productlist.0.0.2d19776cyQvsBi&algo_pvid=c7a3db92-e019-4095-8a28-dfdf0a087f98&algo_expid=c7a3db92-e019-4095-8a28-dfdf0a087f98-1&btsid=0ab6f81e15955375483301352e4208&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_)
- SSD1327 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum - SSD1327 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm) - SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
- SSD1675 is an e-ink paper and is experimental as e-ink is really not suitable for LMS du to its very low refresh rate - SSD1675 is an e-ink paper and is experimental as e-ink is really not suitable for LMS du to its very low refresh rate
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control - ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control. Some have X/Y offsets betwen the driver and the glass (green/black/red models) that can be added using "x" and "y" options (case sensitive!)
- ST7789 is a 240x320 65k (262k not enabled) color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/2inch-lcd-module.htm). It also exist with 240x240 displays. See **rotate** for use in portrait mode - ST7789 is a 240x320 65k (262k not enabled) color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/2inch-lcd-module.htm). It also exist with 240x240 displays. See **rotate** for use in portrait mode
- ILI9341 is another 240x320 65k (262k capable) color SPI. I've not used it much, the driver it has been provided by one external contributor to the project - ILI9341 is another 240x320 65k (262k capable) color SPI. I've not used it much, the driver it has been provided by one external contributor to the project
@@ -212,13 +243,12 @@ You can tweak how the vu-meter and spectrum analyzer are displayed, as well as s
The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is
``` ```
[format=<display_content>][,speed=<speed>][,pause=<pause>] [format=<display_content>][,speed=<speed>][,pause=<pause>][,artwork[:0|1]]
``` ```
- 'speed' is the scrolling speed in ms (default is 33ms) - 'speed' is the scrolling speed in ms (default is 33ms)
- 'pause' is the pause time between scrolls in ms (default is 3600ms) - 'pause' is the pause time between scrolls in ms (default is 3600ms)
- 'format' can contain free text and any of the 3 keywords `%artist%`, `%album%`, `%title%`. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=`%artist% - %title%` and there is no artist in the metadata then only `<title>` will be displayed not ` - <title>`.
- 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only <title> will be displayed not " - <title>". - 'artwork' enables coverart display, if available (does not work for Bluetooth). The optional parameter indicates if the artwork should be resized (1) to fit the available space. Note that the built-in resizer can only do 2,4 and 8 downsizing, so fit is not optimal. The artwork will be placed at the right of the display for landscape displays and underneath the two information lines for others (there is no user option to tweak that).
### Infrared ### Infrared
You can use any IR receiver compatible with NEC protocol (38KHz). Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection. You can use any IR receiver compatible with NEC protocol (38KHz). Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
@@ -234,13 +264,13 @@ The parameter "set_GPIO" is used to assign GPIO to various functions.
GPIO can be set to GND provide or Vcc at boot. This is convenient to power devices that consume less than 40mA from the side connector. Be careful because there is no conflict checks being made wrt which GPIO you're changing, so you might damage your board or create a conflict here. GPIO can be set to GND provide or Vcc at boot. This is convenient to power devices that consume less than 40mA from the side connector. Be careful because there is no conflict checks being made wrt which GPIO you're changing, so you might damage your board or create a conflict here.
The \<amp\> parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through -C \<timeout\> The `<amp>` parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through `-C <timeout>`
If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted. If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
You can set the Green and Red status led as well with their respective active state (:0 or :1) You can set the Green and Red status led as well with their respective active state (:0 or :1)
The \<ir\> parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor The `<ir>` parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor
Syntax is: Syntax is:
@@ -249,12 +279,34 @@ Syntax is:
``` ```
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for well-known configurations where these are forced at runtime. You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for well-known configurations where these are forced at runtime.
**Note that gpio 36 and 39 are input only and cannot use interrupt. When set to jack or speaker fault, a 100ms polling checks their value but that's expensive** **Note that gpio 36 and 39 are input only and cannot use interrupt. When set to jack or speaker fault, a 100ms polling checks their value but that's expensive**
### GPIO expanders
It is possible to add GPIO expanders using I2C or SPI bus. They should mainly be used for buttons but they can support generic-purpose outputs as well. These additional GPIOs can be numbered starting from an arbitrary value (40 and above as esp32 has GPIO 0..39). Then these new "virtual" GPIOs from (e.g) 100 to 115 can be used in [button](#Buttons) configuration, [set_GPIO](#set-gpio) or other config settings.
Each expander can support up to 32 GPIO. To use an expander for buttons, an interrupt must be provided, polling mode is not acceptable. An expander w/o interruption can still be configured, but only output will be usable. Note that the same interrupt can be shared accross expanders, as long as they are using open drain or open collectors (which they probably all do)
The parameter "gpio_exp_config" is a semicolon (;) separated list with following syntax for each expander
```
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>|100][,count=<n>|16][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
```
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version)
- addr: chip i2c/spi address (decimal)
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
- cs (SPI): gpio used for Chip Select
- speed (SPI): speed of the SPI bus for that device (in Hz)
- base: GPIO numbering offset to use everywhere else (default 40)
- count: number of GPIO of expander (default 16 - might be obsolted if model if sufficient to decide)
- intr: real GPIO to use as interrupt.
Note that PWM ("led_brightness" below) is not supported for expanded GPIOs and they cannot be used for high speed or precise timing signals like CS, D/C, Reset and Ready. Buttons, rotary encoder, amplifier control and power are supported. Depending on the actual chipset, pullup or pulldown might be supported so you might have to add external resistors (only MCP23x17 does pullup). The pca8575 is not a great chip, it generate a fair bit of spurious interrupts when used for GPIO out. When using a SPI expander, the bus must be configured using shared [SPI](#SPI) bus
### LED ### LED
See §**set_GPIO** for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is See [set_GPIO](#set-gpio) for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
``` ```
[green=0..100][,red=0..100] [green=0..100][,red=0..100]
``` ```
NB: For well-known configuration, this is ignored NB: For well-known configuration, this is ignored
### Rotary Encoder ### Rotary Encoder
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software. One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
@@ -280,9 +332,10 @@ The SW gpio is optional, you can re-affect it to a pure button if you prefer but
See also the "IMPORTANT NOTE" on the "Buttons" section and remember that when 'lms_ctrls_raw' (see below) is activated, none of these knobonly,volume,longpress options apply, raw button codes (not actions) are simply sent to LMS See also the "IMPORTANT NOTE" on the "Buttons" section and remember that when 'lms_ctrls_raw' (see below) is activated, none of these knobonly,volume,longpress options apply, raw button codes (not actions) are simply sent to LMS
**Note that gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive** **Note that gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive**
### Buttons ### Buttons
Buttons are described using a JSON string with the following syntax Buttons are described using a JSON string with the following syntax
``` ```json
[ [
{"gpio":<num>, {"gpio":<num>,
"type":"BUTTON_LOW | BUTTON_HIGH", "type":"BUTTON_LOW | BUTTON_HIGH",
@@ -311,7 +364,7 @@ Where (all parameters are optionals except gpio)
- "shifted": action to take when a button is pressed/released and shifted (see above/below) - "shifted": action to take when a button is pressed/released and shifted (see above/below)
- "longshifted": action to take when a button is long-pressed/released and shifted (see above/below) - "longshifted": action to take when a button is long-pressed/released and shifted (see above/below)
Where \<action\> is either the name of another configuration to load (remap) or one amongst Where `<action>` is either the name of another configuration to load (remap) or one amongst
``` ```
ACTRLS_NONE, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, ACTRLS_NONE, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY,
@@ -324,7 +377,7 @@ KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
One you've created such a string, use it to fill a new NVS parameter with any name below 16(?) characters. You can have as many of these configs as you can. Then set the config parameter "actrls_config" with the name of your default config One you've created such a string, use it to fill a new NVS parameter with any name below 16(?) characters. You can have as many of these configs as you can. Then set the config parameter "actrls_config" with the name of your default config
For example a config named "buttons" : For example a config named "buttons" :
``` ```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,"normal":{"pressed":"ACTRLS_VOLDOWN"},"longpress":{"pressed":"buttons_remap"}}, [{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,"normal":{"pressed":"ACTRLS_VOLDOWN"},"longpress":{"pressed":"buttons_remap"}},
{"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_TOGGLE"}}] {"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_TOGGLE"}}]
``` ```
@@ -333,7 +386,7 @@ Defines two buttons
- second on GPIO 5, active low. When pressed it triggers a volume up command. If first button is pressed together with this button, then a play/pause toggle command is generated. - second on GPIO 5, active low. When pressed it triggers a volume up command. If first button is pressed together with this button, then a play/pause toggle command is generated.
While the config named "buttons_remap" While the config named "buttons_remap"
``` ```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,"normal":{"pressed":"BCTRLS_DOWN"},"longpress":{"pressed":"buttons"}}, [{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,"normal":{"pressed":"BCTRLS_DOWN"},"longpress":{"pressed":"buttons"}},
{"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"BCTRLS_UP"}}] {"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"BCTRLS_UP"}}]
``` ```
@@ -341,10 +394,14 @@ Defines two buttons
- first on GPIO 4, active low. When pressed, it triggers a navigation down command. When pressed more than 1000ms, it changes the button configuration for the one described above - first on GPIO 4, active low. When pressed, it triggers a navigation down command. When pressed more than 1000ms, it changes the button configuration for the one described above
- second on GPIO 5, active low. When pressed it triggers a navigation up command. That button, in that configuration, has no shift option - second on GPIO 5, active low. When pressed it triggers a navigation up command. That button, in that configuration, has no shift option
Below is a difficult but functional 2-buttons interface for your decoding pleasure Below is a difficult but functional 2-buttons interface for your decoding pleasure:
*buttons* `actrls_config`:
``` ```
buttons
```
`buttons`:
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000, [{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,
"normal":{"pressed":"ACTRLS_VOLDOWN"}, "normal":{"pressed":"ACTRLS_VOLDOWN"},
"longpress":{"pressed":"buttons_remap"}}, "longpress":{"pressed":"buttons_remap"}},
@@ -354,8 +411,8 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
"longpress":{"pressed":"ACTRLS_NEXT"}} "longpress":{"pressed":"ACTRLS_NEXT"}}
] ]
``` ```
*buttons_remap* `buttons_remap`:
``` ```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000, [{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,
"normal":{"pressed":"BCTRLS_DOWN"}, "normal":{"pressed":"BCTRLS_DOWN"},
"longpress":{"pressed":"buttons"}}, "longpress":{"pressed":"buttons"}},
@@ -366,7 +423,7 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
"longshifted":{"pressed":"BCTRLS_LEFT"}} "longshifted":{"pressed":"BCTRLS_LEFT"}}
] ]
``` ```
<strong>IMPORTANT NOTE</strong>: LMS also supports the possibility to send 'raw' button codes. It's a bit complicated, so bear with me. Buttons can either be processed by SqueezeESP32 and mapped to a "function" like play/pause or they can be just sent to LMS as plain (raw) code and the full logic of press/release/longpress is handled by LMS, you don't have any control on that. **IMPORTANT NOTE**: LMS also supports the possibility to send 'raw' button codes. It's a bit complicated, so bear with me. Buttons can either be processed by SqueezeESP32 and mapped to a "function" like play/pause or they can be just sent to LMS as plain (raw) code and the full logic of press/release/longpress is handled by LMS, you don't have any control on that.
The benefit of the "raw" mode is that you can build a player which is as close as possible to a Boom (e.g.) but you can't use the remapping function nor longress or shift logics to do your own mapping when you have a limited set of buttons. In 'raw' mode, all you really need to define is the mapping between the gpio and the button. As far as LMS is concerned, any other option in these JSON payloads does not matter. Now, when you use BT or AirPlay, the full JSON construct described above fully applies, so the shift, longpress, remapping options still work. The benefit of the "raw" mode is that you can build a player which is as close as possible to a Boom (e.g.) but you can't use the remapping function nor longress or shift logics to do your own mapping when you have a limited set of buttons. In 'raw' mode, all you really need to define is the mapping between the gpio and the button. As far as LMS is concerned, any other option in these JSON payloads does not matter. Now, when you use BT or AirPlay, the full JSON construct described above fully applies, so the shift, longpress, remapping options still work.
@@ -375,8 +432,10 @@ The benefit of the "raw" mode is that you can build a player which is as close a
There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctrls_raw" to change that option There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctrls_raw" to change that option
**Note that gpio 36 and 39 are input only and cannot use interrupt. When using them for a button, a 100ms polling is started which is expensive. Long press is also likely to not work very well** **Note that gpio 36 and 39 are input only and cannot use interrupt. When using them for a button, a 100ms polling is started which is expensive. Long press is also likely to not work very well**
### Ethernet (coming soon) ### Ethernet
Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or Davicom DM9051 over SPI like [that](https://www.amazon.com/dp/B08JLFWX9Z). Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or SPI-ethernet bridges like Davicom DM9051 [that](https://www.amazon.com/dp/B08JLFWX9Z) or W5500 like [this](https://www.aliexpress.com/item/32312441357.html).
**Note:** Touch buttons that can be find on some board like the LyraT V4.3 are not supported currently.
#### RMII (LAN8720) #### RMII (LAN8720)
- RMII PHY wiring is fixed and can not be changed - RMII PHY wiring is fixed and can not be changed
@@ -389,6 +448,7 @@ Wired ethernet is supported by esp32 with various options but squeezelite is onl
| GPIO25 | RX0 | EMAC_RXD0 | | GPIO25 | RX0 | EMAC_RXD0 |
| GPIO26 | RX1 | EMAC_RXD1 | | GPIO26 | RX1 | EMAC_RXD1 |
| GPIO27 | CRS_DV | EMAC_RX_DRV | | GPIO27 | CRS_DV | EMAC_RX_DRV |
| GPIO0 | REF_CLK | 50MHz clock |
- SMI (Serial Management Interface) wiring is not fixed and you can change it either in the configuration or using "eth_config" parameter with the following syntax: - SMI (Serial Management Interface) wiring is not fixed and you can change it either in the configuration or using "eth_config" parameter with the following syntax:
``` ```
@@ -398,30 +458,31 @@ Connecting a reset pin for the LAN8720 is optional but recommended to avoid that
- Clock - Clock
The APLL of the esp32 is required for the audio codec, so we **need** a LAN8720 that provides a 50MHz clock. That clock **must** be connected to GPIO0, there is no alternative. This means that if your DAC requires an MCLK, then you are out of luck. It is not possible to have both to work together. There might be some workaround using CLK_OUT2 and GPIO3, but I don't have time for this. The APLL of the esp32 is required for the audio codec, so we **need** a LAN8720 that provides a 50MHz clock. That clock **must** be connected to GPIO0, there is no alternative. This means that if your DAC requires an MCLK, then you are out of luck. It is not possible to have both to work together. There might be some workaround using CLK_OUT2 and GPIO3, but I don't have time for this.
#### SPI (DM9051) #### SPI (DM9051 or W5500)
Ethernet over SPI is supported as well and requires less GPIOs but is obvsiously slower. Another benefit is that the SPI bus can be shared with the display, but it's also possible to have a dedicated SPI interface. The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The "eth_config" parameter syntax becomes: Ethernet over SPI is supported as well and requires less GPIOs but is obvsiously slower. SPI is the shared bus set with [spi_config](#spi). The "eth_config" parameter syntax becomes:
``` ```
model=dm9051,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,host=<-1|1|2>][,rst=<gpio>][,mosi=<gpio>,miso=<gpio>,clk=<gpio>] model=dm9051|w5500,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,rst=<gpio>]
``` ```
- To use the system SPI, shared with display (see spi_config) "host" must be set to -1. Any other value will reserve the SPI interface (careful of conflict with spi_config). The default "host" is 2 to avoid conflicting wiht default "spi_config" settings. - To use the system SPI, shared with display (see spi_config) "host" must be set to -1. Any other value will reserve the SPI interface (careful of conflict with spi_config). The default "host" is 2 to avoid conflicting wiht default "spi_config" settings.
- When not using system SPI, "mosi" for data out, "miso" for data in and "clk" **must** be set - When not using system SPI, "mosi" for data out, "miso" for data in and "clk" **must** be set
- The esp32 has a special I/O multiplexer for faster speed (up to 80 MHz) but that requires using specific GPIOs, which depends on SPI bus (See [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) for more details) - The esp32 has a special I/O multiplexer for faster speed (up to 80 MHz) but that requires using specific GPIOs, which depends on SPI bus (See [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) for more details)
| Pin Name | SPI2 | SPI3 | | Pin Name | SPI1 | SPI2 |
| -------- | ---- | ---- | | -------- | ---- | ---- |
| CS0* | 15 | 5 | | CS | 15 | 5 |
| SCLK | 14 | 18 | | SCLK | 14 | 18 |
| MISO | 12 | 19 | | MISO | 12 | 19 |
| MOSI | 13 | 23 | | MOSI | 13 | 23 |
** THIS IS NOT AVAILABLE YET, SO MORE TO COME ON HOW TO USE WIRED ETHERNET***
### Battery / ADC ### Battery / ADC
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. The "atten" value attenuates the input voltage to the ADC input (the read value maintains a 0-1V rage) where: 0=no attenuation(0..800mV), 1=2.5dB attenuation(0..1.1V), 2=6dB attenuation(0..1.35V), 3=11dB attenuation(0..2.6V). Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. The "atten" value attenuates the input voltage to the ADC input (the read value maintains a 0-1V rage) where: 0=no attenuation(0..800mV), 1=2.5dB attenuation(0..1.1V), 2=6dB attenuation(0..1.35V), 3=11dB attenuation(0..2.6V). Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
``` ```
channel=0..7,scale=<scale>,cells=<2|3>[,atten=<0|1|2|3>] channel=0..7,scale=<scale>,cells=<2|3>[,atten=<0|1|2|3>]
``` ```
NB: Set parameter to empty to disable battery reading. For well-known configuration, this is ignored (except for SqueezeAMP where number of cells is required) NB: Set parameter to empty to disable battery reading. For well-known configuration, this is ignored (except for SqueezeAMP where number of cells is required)
# Configuration # Configuration
## Setup WiFi ## Setup WiFi
- Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite". - Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite".
- Once connected, navigate to 192.168.4.1 - Once connected, navigate to 192.168.4.1
@@ -430,7 +491,6 @@ NB: Set parameter to empty to disable battery reading. For well-known configurat
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward - Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
## Setup squeezelite command line (optional) ## Setup squeezelite command line (optional)
At this point, the device should have disabled its built-in access point and should be connected to a known WiFi network. At this point, the device should have disabled its built-in access point and should be connected to a known WiFi network.
- navigate to the address that was noted in step #1 - navigate to the address that was noted in step #1
- Using the list of predefined options, choose the mode in which you want squeezelite to start - Using the list of predefined options, choose the mode in which you want squeezelite to start
@@ -443,7 +503,6 @@ At this point, the device should have disabled its built-in access point and sho
- You can enable accessto NVS parameters under 'credits' - You can enable accessto NVS parameters under 'credits'
## Monitor ## Monitor
In addition of the esp-idf serial link monitor option, you can also enable a telnet server (see NVS parameters) where you'll have access to a ton of logs of what's happening inside the WROVER. In addition of the esp-idf serial link monitor option, you can also enable a telnet server (see NVS parameters) where you'll have access to a ton of logs of what's happening inside the WROVER.
## Update Squeezelite ## Update Squeezelite
@@ -475,20 +534,23 @@ See squeezlite command line, but keys options are
- r "<minrate>-<maxrate>" - r "<minrate>-<maxrate>"
- C <sec> : set timeout to switch off amp gpio - C <sec> : set timeout to switch off amp gpio
- W : activate WAV and AIFF header parsing - W : activate WAV and AIFF header parsing
# Building everything yourself # Building everything yourself
## Setting up ESP-IDF ## Setting up ESP-IDF
### Docker ### Docker
A simple alternative to building the project's binaries is to leverage the same docker image that is being used on the GitHub Actions to build our releases. The instructions below assume that you have cloned the squeezelite-esp32 code that you want to build locally and that you have opened a command line/bash session in the folder that contains the code. A simple alternative to building the project's binaries is to leverage the same docker image that is being used on the GitHub Actions to build our releases. The instructions below assume that you have cloned the squeezelite-esp32 code that you want to build locally and that you have opened a command line/bash session in the folder that contains the code.
Pull the most recent docker image for the environment: Pull the most recent docker image for the environment:
``` ```
docker pull sle118/squeezelite-esp32-idfv4-master docker pull sle118/squeezelite-esp32-idfv43
``` ```
Then run the container interactively : Then run the container interactively :
``` ```
for windows: for windows:
docker run -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv4-master docker run -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv43
for linux: for linux:
docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-idfv4-master docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-idfv43
``` ```
The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018. The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018.
@@ -511,6 +573,7 @@ Use `idf.py monitor` to monitor the application (see esp-idf documentation)
Note: You can use `idf.py build -DDEPTH=32` to build the 32 bits version and add the `-DVERSION=<your_version>` to add a custom version name (it will be 0.0-<your_version>). If you want to change the whole version string, see squeezelite.h. You can also disable the SBR extension of AAC codecs as it consumes a lot of CPU and might overload the esp32. Use `-DAAC_DISABLE_SBR=1` for that Note: You can use `idf.py build -DDEPTH=32` to build the 32 bits version and add the `-DVERSION=<your_version>` to add a custom version name (it will be 0.0-<your_version>). If you want to change the whole version string, see squeezelite.h. You can also disable the SBR extension of AAC codecs as it consumes a lot of CPU and might overload the esp32. Use `-DAAC_DISABLE_SBR=1` for that
If you have already cloned the repository and you are getting compile errors on one of the submodules (e.g. telnet), run the following git command in the root of the repository location: `git submodule update --init --recursive` If you have already cloned the repository and you are getting compile errors on one of the submodules (e.g. telnet), run the following git command in the root of the repository location: `git submodule update --init --recursive`
### Rebuild codecs (highly recommended to NOT try that) ### Rebuild codecs (highly recommended to NOT try that)
- for codecs libraries, add -mlongcalls if you want to rebuild them, but you should not (use the provided ones in codecs/lib). if you really want to rebuild them, open an issue - for codecs libraries, add -mlongcalls if you want to rebuild them, but you should not (use the provided ones in codecs/lib). if you really want to rebuild them, open an issue
- libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work - libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work

View File

@@ -66,7 +66,7 @@ static void squeezelite_thread(void *arg){
cmd_send_messaging("cfg-audio-tmpl",ret > 1 ? MESSAGING_ERROR : MESSAGING_WARNING,"squeezelite exited with error code %d\n", ret); cmd_send_messaging("cfg-audio-tmpl",ret > 1 ? MESSAGING_ERROR : MESSAGING_WARNING,"squeezelite exited with error code %d\n", ret);
if (ret == 1) { if (ret <= 1) {
int wait = 60; int wait = 60;
wait_for_commit(); wait_for_commit();
cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Rebooting in %d sec\n", wait); cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Rebooting in %d sec\n", wait);

View File

@@ -17,6 +17,8 @@ set(BELL_DISABLE_SINKS ON)
set(BELL_DISABLE_FMT ON) set(BELL_DISABLE_FMT ON)
set(BELL_DISABLE_REGEX ON) set(BELL_DISABLE_REGEX ON)
set(BELL_ONLY_CJSON ON) set(BELL_ONLY_CJSON ON)
set(BELL_DISABLE_MQTT ON)
set(BELL_DISABLE_WEBSERVER ON)
set(CSPOT_TARGET_ESP32 ON) set(CSPOT_TARGET_ESP32 ON)
# because CMake is so broken, the cache set below overrides a normal "set" for the first build # because CMake is so broken, the cache set below overrides a normal "set" for the first build

View File

@@ -16,7 +16,10 @@
#include <stdarg.h> #include <stdarg.h>
#include <ApResolve.h> #include <ApResolve.h>
#include "BellTask.h"
#include "MDNSService.h" #include "MDNSService.h"
#include "TrackPlayer.h"
#include "CSpotContext.h"
#include "SpircHandler.h" #include "SpircHandler.h"
#include "LoginBlob.h" #include "LoginBlob.h"
#include "CentralAudioBuffer.h" #include "CentralAudioBuffer.h"
@@ -31,70 +34,6 @@
static class cspotPlayer *player; static class cspotPlayer *player;
/****************************************************************************************
* Chunk manager class (task)
*/
class chunkManager : public bell::Task {
public:
std::atomic<bool> isRunning = true;
std::atomic<bool> isPaused = true;
chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer, std::function<void()> trackHandler,
std::function<void(const uint8_t*, size_t)> dataHandler);
void teardown();
private:
std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
std::function<void()> trackHandler;
std::function<void(const uint8_t*, size_t)> dataHandler;
std::mutex runningMutex;
void runTask() override;
};
chunkManager::chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer,
std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> dataHandler)
: bell::Task("chunker", 4 * 1024, 0, 0) {
this->centralAudioBuffer = centralAudioBuffer;
this->trackHandler = trackHandler;
this->dataHandler = dataHandler;
startTask();
}
void chunkManager::teardown() {
isRunning = false;
std::scoped_lock lock(runningMutex);
}
void chunkManager::runTask() {
std::scoped_lock lock(runningMutex);
size_t lastHash = 0;
while (isRunning) {
if (isPaused) {
BELL_SLEEP_MS(100);
continue;
}
auto chunk = centralAudioBuffer->readChunk();
if (!chunk || chunk->pcmSize == 0) {
BELL_SLEEP_MS(50);
continue;
}
// receiving first chunk of new track from Spotify server
if (lastHash != chunk->trackHash) {
CSPOT_LOG(info, "hash update %x => %x", lastHash, chunk->trackHash);
lastHash = chunk->trackHash;
trackHandler();
}
dataHandler(chunk->pcmData, chunk->pcmSize);
}
}
/**************************************************************************************** /****************************************************************************************
* Player's main class & task * Player's main class & task
*/ */
@@ -103,20 +42,21 @@ class cspotPlayer : public bell::Task {
private: private:
std::string name; std::string name;
bell::WrappedSemaphore clientConnected; bell::WrappedSemaphore clientConnected;
std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer; std::atomic<bool> isPaused, isConnected;
int startOffset, volume = 0, bitrate = 160; int startOffset, volume = 0, bitrate = 160;
httpd_handle_t serverHandle; httpd_handle_t serverHandle;
int serverPort; int serverPort;
cspot_cmd_cb_t cmdHandler; cspot_cmd_cb_t cmdHandler;
cspot_data_cb_t dataHandler; cspot_data_cb_t dataHandler;
std::string lastTrackId;
std::shared_ptr<cspot::LoginBlob> blob; std::shared_ptr<cspot::LoginBlob> blob;
std::unique_ptr<cspot::SpircHandler> spirc; std::unique_ptr<cspot::SpircHandler> spirc;
std::unique_ptr<chunkManager> chunker;
void eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event); void eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event);
void trackHandler(void); void trackHandler(void);
size_t pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId);
void runTask(); void runTask();
@@ -145,6 +85,17 @@ cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspo
if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160; if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160;
} }
size_t cspotPlayer::pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId) {
if (lastTrackId != trackId) {
CSPOT_LOG(info, "new track started <%s> => <%s>", lastTrackId.c_str(), trackId.data());
lastTrackId = trackId;
trackHandler();
}
dataHandler(pcm, bytes);
return bytes;
}
extern "C" { extern "C" {
static esp_err_t handleGET(httpd_req_t *request) { static esp_err_t handleGET(httpd_req_t *request) {
return player->handleGET(request); return player->handleGET(request);
@@ -223,8 +174,7 @@ esp_err_t cspotPlayer::handlePOST(httpd_req_t *request) {
void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event) { void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event) {
switch (event->eventType) { switch (event->eventType) {
case cspot::SpircHandler::EventType::PLAYBACK_START: { case cspot::SpircHandler::EventType::PLAYBACK_START: {
centralAudioBuffer->clearBuffer(); lastTrackId.clear();
// we are not playing anymore // we are not playing anymore
trackStatus = TRACK_INIT; trackStatus = TRACK_INIT;
// memorize position for when track's beginning will be detected // memorize position for when track's beginning will be detected
@@ -237,13 +187,12 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
break; break;
} }
case cspot::SpircHandler::EventType::PLAY_PAUSE: { case cspot::SpircHandler::EventType::PLAY_PAUSE: {
bool pause = std::get<bool>(event->data); isPaused = std::get<bool>(event->data);
cmdHandler(pause ? CSPOT_PAUSE : CSPOT_PLAY); cmdHandler(isPaused ? CSPOT_PAUSE : CSPOT_PLAY);
chunker->isPaused = pause;
break; break;
} }
case cspot::SpircHandler::EventType::TRACK_INFO: { case cspot::SpircHandler::EventType::TRACK_INFO: {
auto trackInfo = std::get<cspot::CDNTrackStream::TrackInfo>(event->data); auto trackInfo = std::get<cspot::TrackInfo>(event->data);
cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(), cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str()); trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
spirc->updatePositionMs(startOffset); spirc->updatePositionMs(startOffset);
@@ -254,17 +203,14 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
case cspot::SpircHandler::EventType::PREV: case cspot::SpircHandler::EventType::PREV:
case cspot::SpircHandler::EventType::FLUSH: { case cspot::SpircHandler::EventType::FLUSH: {
// FLUSH is sent when there is no next, just clean everything // FLUSH is sent when there is no next, just clean everything
centralAudioBuffer->clearBuffer();
cmdHandler(CSPOT_FLUSH); cmdHandler(CSPOT_FLUSH);
break; break;
} }
case cspot::SpircHandler::EventType::DISC: case cspot::SpircHandler::EventType::DISC:
centralAudioBuffer->clearBuffer();
cmdHandler(CSPOT_DISC); cmdHandler(CSPOT_DISC);
chunker->teardown(); isConnected = false;
break; break;
case cspot::SpircHandler::EventType::SEEK: { case cspot::SpircHandler::EventType::SEEK: {
centralAudioBuffer->clearBuffer();
cmdHandler(CSPOT_SEEK, std::get<int>(event->data)); cmdHandler(CSPOT_SEEK, std::get<int>(event->data));
break; break;
} }
@@ -283,10 +229,9 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
void cspotPlayer::trackHandler(void) { void cspotPlayer::trackHandler(void) {
// this is just informative // this is just informative
auto trackInfo = spirc->getTrackPlayer()->getCurrentTrackInfo();
uint32_t remains; uint32_t remains;
cmdHandler(CSPOT_QUERY_REMAINING, &remains); cmdHandler(CSPOT_QUERY_REMAINING, &remains);
CSPOT_LOG(info, "next track <%s> will play in %d ms", trackInfo.name.c_str(), remains); CSPOT_LOG(info, "next track will play in %d ms", remains);
// inform sink of track beginning // inform sink of track beginning
trackStatus = TRACK_NOTIFY; trackStatus = TRACK_NOTIFY;
@@ -307,7 +252,8 @@ void cspotPlayer::command(cspot_event_t event) {
break; break;
// setPause comes back through cspot::event with PLAY/PAUSE // setPause comes back through cspot::event with PLAY/PAUSE
case CSPOT_TOGGLE: case CSPOT_TOGGLE:
spirc->setPause(!chunker->isPaused); isPaused = !isPaused;
spirc->setPause(isPaused);
break; break;
case CSPOT_STOP: case CSPOT_STOP:
case CSPOT_PAUSE: case CSPOT_PAUSE:
@@ -316,12 +262,11 @@ void cspotPlayer::command(cspot_event_t event) {
case CSPOT_PLAY: case CSPOT_PLAY:
spirc->setPause(false); spirc->setPause(false);
break; break;
// calling spirc->disconnect() might have been logical but it does not /* Calling spirc->disconnect() might have been logical but it does not
// generate any cspot::event, so we need to manually force exiting player * generate any cspot::event */
// loop through chunker which will eventually do the disconnect
case CSPOT_DISC: case CSPOT_DISC:
cmdHandler(CSPOT_DISC); cmdHandler(CSPOT_DISC);
chunker->teardown(); isConnected = false;
break; break;
// spirc->setRemoteVolume does not generate a cspot::event so call cmdHandler // spirc->setRemoteVolume does not generate a cspot::event so call cmdHandler
case CSPOT_VOLUME_UP: case CSPOT_VOLUME_UP:
@@ -369,7 +314,6 @@ void cspotPlayer::runTask() {
CSPOT_LOG(info, "Spotify client connected for %s", name.c_str()); CSPOT_LOG(info, "Spotify client connected for %s", name.c_str());
centralAudioBuffer = std::make_shared<bell::CentralAudioBuffer>(32);
auto ctx = cspot::Context::createFromBlob(blob); auto ctx = cspot::Context::createFromBlob(blob);
if (bitrate == 320) ctx->config.audioFormat = AudioFormat_OGG_VORBIS_320; if (bitrate == 320) ctx->config.audioFormat = AudioFormat_OGG_VORBIS_320;
@@ -382,11 +326,12 @@ void cspotPlayer::runTask() {
// Auth successful // Auth successful
if (token.size() > 0) { if (token.size() > 0) {
spirc = std::make_unique<cspot::SpircHandler>(ctx); spirc = std::make_unique<cspot::SpircHandler>(ctx);
isConnected = true;
// set call back to calculate a hash on trackId // set call back to calculate a hash on trackId
spirc->getTrackPlayer()->setDataCallback( spirc->getTrackPlayer()->setDataCallback(
[this](uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence) { [this](uint8_t* data, size_t bytes, std::string_view trackId) {
return centralAudioBuffer->writePCM(data, bytes, sequence); return pcmWrite(data, bytes, trackId);
}); });
// set event (PLAY, VOLUME...) handler // set event (PLAY, VOLUME...) handler
@@ -398,20 +343,11 @@ void cspotPlayer::runTask() {
// Start handling mercury messages // Start handling mercury messages
ctx->session->startTask(); ctx->session->startTask();
// Create a player, pass the tack handler
chunker = std::make_unique<chunkManager>(centralAudioBuffer,
[this](void) {
return trackHandler();
},
[this](const uint8_t* data, size_t bytes) {
return dataHandler(data, bytes);
});
// set volume at connection // set volume at connection
cmdHandler(CSPOT_VOLUME, volume); cmdHandler(CSPOT_VOLUME, volume);
// exit when player has stopped (received a DISC) // exit when player has stopped (received a DISC)
while (chunker->isRunning) { while (isConnected) {
ctx->session->handlePacket(); ctx->session->handlePacket();
// low-accuracy polling events // low-accuracy polling events
@@ -444,7 +380,6 @@ void cspotPlayer::runTask() {
} }
// we want to release memory ASAP and for sure // we want to release memory ASAP and for sure
centralAudioBuffer.reset();
ctx.reset(); ctx.reset();
token.clear(); token.clear();

View File

@@ -1,2 +1,3 @@
CompileFlags: CompileFlags:
CompilationDatabase: example/build # Search build/ directory for compile_commands.json CompilationDatabase: example/build # Search build/ directory for compile_commands.json

View File

@@ -7,6 +7,8 @@ project(bell)
option(BELL_DISABLE_CODECS "Disable the entire audio codec wrapper" OFF) option(BELL_DISABLE_CODECS "Disable the entire audio codec wrapper" OFF)
option(BELL_CODEC_AAC "Support libhelix-aac codec" ON) option(BELL_CODEC_AAC "Support libhelix-aac codec" ON)
option(BELL_CODEC_MP3 "Support libhelix-mp3 codec" ON) option(BELL_CODEC_MP3 "Support libhelix-mp3 codec" ON)
option(BELL_DISABLE_MQTT "Disable the built-in MQTT wrapper" OFF)
option(BELL_DISABLE_WEBSERVER "Disable the built-in Web server" OFF)
option(BELL_CODEC_VORBIS "Support tremor Vorbis codec" ON) option(BELL_CODEC_VORBIS "Support tremor Vorbis codec" ON)
option(BELL_CODEC_ALAC "Support Apple ALAC codec" ON) option(BELL_CODEC_ALAC "Support Apple ALAC codec" ON)
option(BELL_CODEC_OPUS "Support Opus codec" ON) option(BELL_CODEC_OPUS "Support Opus codec" ON)
@@ -63,13 +65,15 @@ endif()
message(STATUS " Use cJSON only: ${BELL_ONLY_CJSON}") message(STATUS " Use cJSON only: ${BELL_ONLY_CJSON}")
message(STATUS " Disable Fmt: ${BELL_DISABLE_FMT}") message(STATUS " Disable Fmt: ${BELL_DISABLE_FMT}")
message(STATUS " Disable Mqtt: ${BELL_DISABLE_MQTT}")
message(STATUS " Disable Regex: ${BELL_DISABLE_REGEX}") message(STATUS " Disable Regex: ${BELL_DISABLE_REGEX}")
message(STATUS " Disable Web server: ${BELL_DISABLE_WEBSERVER}")
# Include nanoPB library # Include nanoPB library
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/external/nanopb/extra") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/external/nanopb/extra")
find_package(Nanopb REQUIRED) find_package(Nanopb REQUIRED)
message(${NANOPB_INCLUDE_DIRS}) message(${NANOPB_INCLUDE_DIRS})
list(APPEND EXTRA_INCLUDES ${NANOPB_INCLUDE_DIRS}) list(APPEND EXTERNAL_INCLUDES ${NANOPB_INCLUDE_DIRS})
# CMake options # CMake options
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
@@ -84,7 +88,7 @@ set(IO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/io")
set(PLATFORM_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/platform") set(PLATFORM_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/platform")
set(UTILITIES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/utilities") set(UTILITIES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/utilities")
add_definitions("-DUSE_DEFAULT_STDLIB=1") add_definitions("-DUSE_DEFAULT_STDLIB=1 -DTARGET_OS_IPHONE=0")
# Main library sources # Main library sources
file(GLOB SOURCES file(GLOB SOURCES
@@ -93,8 +97,6 @@ file(GLOB SOURCES
"main/io/*.cpp" "main/io/*.c" "main/io/*.cpp" "main/io/*.c"
) )
list(REMOVE_ITEM SOURCES "${IO_DIR}/BellTar.cpp" "${IO_DIR}/BellHTTPServer.cpp")
list(APPEND EXTRA_INCLUDES "main/audio-codec/include") list(APPEND EXTRA_INCLUDES "main/audio-codec/include")
list(APPEND EXTRA_INCLUDES "main/audio-dsp/include") list(APPEND EXTRA_INCLUDES "main/audio-dsp/include")
list(APPEND EXTRA_INCLUDES "main/audio-sinks/include") list(APPEND EXTRA_INCLUDES "main/audio-sinks/include")
@@ -111,7 +113,7 @@ endif()
if(APPLE) if(APPLE)
file(GLOB APPLE_PLATFORM_SOURCES "main/platform/apple/*.cpp" "main/platform/apple/*.c") file(GLOB APPLE_PLATFORM_SOURCES "main/platform/apple/*.cpp" "main/platform/apple/*.c")
list(APPEND SOURCES ${APPLE_PLATFORM_SOURCES}) list(APPEND SOURCES ${APPLE_PLATFORM_SOURCES})
list(APPEND EXTRA_INCLUDES "/usr/local/opt/mbedtls@3/include") list(APPEND EXTERNAL_INCLUDES "/usr/local/opt/mbedtls@3/include")
endif() endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
@@ -122,7 +124,7 @@ endif()
if(WIN32) if(WIN32)
file(GLOB WIN32_PLATFORM_SOURCES "main/platform/win32/*.cpp" "main/platform/win32/*.c") file(GLOB WIN32_PLATFORM_SOURCES "main/platform/win32/*.cpp" "main/platform/win32/*.c")
list(APPEND SOURCES ${WIN32_PLATFORM_SOURCES}) list(APPEND SOURCES ${WIN32_PLATFORM_SOURCES})
list(APPEND EXTRA_INCLUDES "main/platform/win32") list(APPEND EXTERNAL_INCLUDES "main/platform/win32")
endif() endif()
# A hack to make Opus keep quiet # A hack to make Opus keep quiet
@@ -139,7 +141,7 @@ if(ESP_PLATFORM)
else() else()
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_package(MbedTLS REQUIRED) find_package(MbedTLS REQUIRED)
list(APPEND EXTRA_INCLUDES ${MBEDTLS_INCLUDE_DIRS}) list(APPEND EXTERNAL_INCLUDES ${MBEDTLS_INCLUDE_DIRS})
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
list(APPEND EXTRA_LIBS ${MBEDTLS_LIBRARIES} Threads::Threads) list(APPEND EXTRA_LIBS ${MBEDTLS_LIBRARIES} Threads::Threads)
@@ -149,6 +151,14 @@ else()
endif() endif()
endif() endif()
if (NOT BELL_DISABLE_MQTT)
file(GLOB MQTT_SOURCES "external/mqtt/*.c")
list(APPEND SOURCES ${MQTT_SOURCES})
list(APPEND EXTRA_INCLUDES "external/mqtt/include")
else()
list(REMOVE_ITEM SOURCES "${IO_DIR}/BellMQTTClient.cpp")
endif()
if(NOT BELL_DISABLE_CODECS) if(NOT BELL_DISABLE_CODECS)
file(GLOB EXTRA_SOURCES "main/audio-containers/*.cpp" "main/audio-codec/*.cpp" "main/audio-codec/*.c" "main/audio-dsp/*.cpp" "main/audio-dsp/*.c") file(GLOB EXTRA_SOURCES "main/audio-containers/*.cpp" "main/audio-codec/*.cpp" "main/audio-codec/*.c" "main/audio-dsp/*.cpp" "main/audio-dsp/*.c")
@@ -162,7 +172,7 @@ if(NOT BELL_DISABLE_CODECS)
if(BELL_CODEC_AAC) if(BELL_CODEC_AAC)
file(GLOB LIBHELIX_AAC_SOURCES "external/libhelix-aac/*.c") file(GLOB LIBHELIX_AAC_SOURCES "external/libhelix-aac/*.c")
list(APPEND LIBHELIX_SOURCES ${LIBHELIX_AAC_SOURCES}) list(APPEND LIBHELIX_SOURCES ${LIBHELIX_AAC_SOURCES})
list(APPEND EXTRA_INCLUDES "external/libhelix-aac") list(APPEND EXTERNAL_INCLUDES "external/libhelix-aac")
list(APPEND SOURCES "${AUDIO_CODEC_DIR}/AACDecoder.cpp") list(APPEND SOURCES "${AUDIO_CODEC_DIR}/AACDecoder.cpp")
list(APPEND CODEC_FLAGS "-DBELL_CODEC_AAC") list(APPEND CODEC_FLAGS "-DBELL_CODEC_AAC")
endif() endif()
@@ -171,7 +181,7 @@ if(NOT BELL_DISABLE_CODECS)
if(BELL_CODEC_MP3) if(BELL_CODEC_MP3)
file(GLOB LIBHELIX_MP3_SOURCES "external/libhelix-mp3/*.c") file(GLOB LIBHELIX_MP3_SOURCES "external/libhelix-mp3/*.c")
list(APPEND LIBHELIX_SOURCES ${LIBHELIX_MP3_SOURCES}) list(APPEND LIBHELIX_SOURCES ${LIBHELIX_MP3_SOURCES})
list(APPEND EXTRA_INCLUDES "external/libhelix-mp3") list(APPEND EXTERNAL_INCLUDES "external/libhelix-mp3")
list(APPEND SOURCES "${AUDIO_CODEC_DIR}/MP3Decoder.cpp") list(APPEND SOURCES "${AUDIO_CODEC_DIR}/MP3Decoder.cpp")
list(APPEND CODEC_FLAGS "-DBELL_CODEC_MP3") list(APPEND CODEC_FLAGS "-DBELL_CODEC_MP3")
endif() endif()
@@ -230,7 +240,7 @@ else()
file(GLOB TREMOR_SOURCES "external/tremor/*.c") file(GLOB TREMOR_SOURCES "external/tremor/*.c")
list(REMOVE_ITEM TREMOR_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/external/tremor/ivorbisfile_example.c") list(REMOVE_ITEM TREMOR_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/external/tremor/ivorbisfile_example.c")
list(APPEND SOURCES ${TREMOR_SOURCES}) list(APPEND SOURCES ${TREMOR_SOURCES})
list(APPEND EXTRA_INCLUDES "external/tremor") list(APPEND EXTERNAL_INCLUDES "external/tremor")
endif() endif()
if(NOT BELL_DISABLE_SINKS) if(NOT BELL_DISABLE_SINKS)
@@ -247,7 +257,7 @@ if(NOT BELL_DISABLE_SINKS)
# Find ALSA if required, else remove the sink # Find ALSA if required, else remove the sink
if(BELL_SINK_ALSA) if(BELL_SINK_ALSA)
find_package(ALSA REQUIRED) find_package(ALSA REQUIRED)
list(APPEND EXTRA_INCLUDES ${ALSA_INCLUDE_DIRS}) list(APPEND EXTERNAL_INCLUDES ${ALSA_INCLUDE_DIRS})
list(APPEND EXTRA_LIBS ${ALSA_LIBRARIES}) list(APPEND EXTRA_LIBS ${ALSA_LIBRARIES})
else() else()
list(REMOVE_ITEM SINK_SOURCES "${AUDIO_SINKS_DIR}/unix/ALSAAudioSink.cpp") list(REMOVE_ITEM SINK_SOURCES "${AUDIO_SINKS_DIR}/unix/ALSAAudioSink.cpp")
@@ -256,7 +266,7 @@ if(NOT BELL_DISABLE_SINKS)
# Find PortAudio if required, else remove the sink # Find PortAudio if required, else remove the sink
if(BELL_SINK_PORTAUDIO) if(BELL_SINK_PORTAUDIO)
find_package(Portaudio REQUIRED) find_package(Portaudio REQUIRED)
list(APPEND EXTRA_INCLUDES ${PORTAUDIO_INCLUDE_DIRS}) list(APPEND EXTERNAL_INCLUDES ${PORTAUDIO_INCLUDE_DIRS})
list(APPEND EXTRA_LIBS ${PORTAUDIO_LIBRARIES}) list(APPEND EXTRA_LIBS ${PORTAUDIO_LIBRARIES})
else() else()
list(REMOVE_ITEM SINK_SOURCES "${AUDIO_SINKS_DIR}/unix/PortAudioSink.cpp") list(REMOVE_ITEM SINK_SOURCES "${AUDIO_SINKS_DIR}/unix/PortAudioSink.cpp")
@@ -266,6 +276,7 @@ if(NOT BELL_DISABLE_SINKS)
endif() endif()
if(NOT BELL_ONLY_CJSON) if(NOT BELL_ONLY_CJSON)
set(JSON_SystemInclude ON CACHE INTERNAL "")
add_subdirectory(external/nlohmann_json) add_subdirectory(external/nlohmann_json)
list(APPEND EXTRA_LIBS nlohmann_json::nlohmann_json) list(APPEND EXTRA_LIBS nlohmann_json::nlohmann_json)
endif() endif()
@@ -274,22 +285,25 @@ if(BELL_EXTERNAL_CJSON)
list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON}) list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON})
else() else()
list(APPEND SOURCES "external/cJSON/cJSON.c") list(APPEND SOURCES "external/cJSON/cJSON.c")
list(APPEND EXTRA_INCLUDES "external/cJSON") list(APPEND EXTERNAL_INCLUDES "external/cJSON")
endif() endif()
if (NOT BELL_DISABLE_FMT) if (NOT BELL_DISABLE_FMT)
list(APPEND EXTRA_INCLUDES "external/fmt/include") list(APPEND EXTERNAL_INCLUDES "external/fmt/include")
endif() endif()
if(WIN32 OR UNIX) if(WIN32 OR UNIX)
list(APPEND SOURCES "external/mdnssvc/mdns.c" "external/mdnssvc/mdnsd.c") list(APPEND SOURCES "external/mdnssvc/mdns.c" "external/mdnssvc/mdnsd.c")
list(APPEND EXTRA_INCLUDES "external/mdnssvc") list(APPEND EXTERNAL_INCLUDES "external/mdnssvc")
endif() endif()
# file(GLOB CIVET_SRC "external/civetweb/*.c" "external/civetweb/*.inl" "external/civetweb/*.cpp") if(NOT BELL_DISABLE_WEBSERVER)
file(GLOB CIVET_SRC "external/civetweb/*.c" "external/civetweb/*.inl" "external/civetweb/*.cpp")
# list(APPEND SOURCES ${CIVET_SRC}) list(APPEND SOURCES ${CIVET_SRC})
# list(APPEND EXTRA_INCLUDES "external/civetweb/include") list(APPEND EXTRA_INCLUDES "external/civetweb/include")
else()
list(REMOVE_ITEM SOURCES "${IO_DIR}/BellHTTPServer.cpp")
endif()
add_library(bell STATIC ${SOURCES}) add_library(bell STATIC ${SOURCES})
@@ -305,6 +319,7 @@ endif()
# PUBLIC to propagate esp-idf includes to bell dependents # PUBLIC to propagate esp-idf includes to bell dependents
target_link_libraries(bell PUBLIC ${EXTRA_LIBS}) target_link_libraries(bell PUBLIC ${EXTRA_LIBS})
target_include_directories(bell PUBLIC ${EXTRA_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(bell PUBLIC ${EXTRA_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(bell SYSTEM PUBLIC ${EXTERNAL_INCLUDES})
target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC FMT_HEADER_ONLY) target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC FMT_HEADER_ONLY)
if(BELL_DISABLE_CODECS) if(BELL_DISABLE_CODECS)

View File

@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.18)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../ ${CMAKE_CURRENT_BINARY_DIR}/bell) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../ ${CMAKE_CURRENT_BINARY_DIR}/bell)
file(GLOB SOURCES "*.cpp") file(GLOB SOURCES "*.cpp")

View File

@@ -1,26 +1,14 @@
#include <memory.h>
#include <atomic> #include <atomic>
#include <cmath>
#include <fstream>
#include <iostream>
#include <map>
#include <memory> #include <memory>
#include <vector> #include <string>
#include "AudioCodecs.h" #include <type_traits>
#include "AudioContainers.h"
#include "BellHTTPServer.h"
#include "BellTar.h"
#include "BellTask.h" #include "BellTask.h"
#include "CentralAudioBuffer.h" #include "CentralAudioBuffer.h"
#include "Compressor.h"
#include "DecoderGlobals.h"
#include "EncodedAudioStream.h"
#include "HTTPClient.h"
#include "PortAudioSink.h" #include "PortAudioSink.h"
#define DEBUG_LEVEL 4 #include "StreamInfo.h"
#include "X509Bundle.h"
#include "mbedtls/debug.h"
#define DEBUG_LEVEL 4
#include <BellDSP.h> #include <BellDSP.h>
#include <BellLogger.h> #include <BellLogger.h>
@@ -58,13 +46,8 @@ class AudioPlayer : bell::Task {
int main() { int main() {
bell::setDefaultLogger(); bell::setDefaultLogger();
std::fstream file("system.tar", std::ios::in | std::ios::binary);
if (!file.is_open()) {
std::cout << "file not open" << std::endl;
return 1;
}
BellTar::reader reader(file); BELL_LOG(info, "cock", "Published?");
reader.extract_all_files("./dupa2");
return 0; return 0;
} }

View File

@@ -118,13 +118,16 @@
# #
#============================================================================= #=============================================================================
function(NANOPB_GENERATE_CPP SRCS HDRS) function(NANOPB_GENERATE_CPP SRCS HDRS)
cmake_parse_arguments(NANOPB_GENERATE_CPP "" "RELPATH" "" ${ARGN}) cmake_parse_arguments(NANOPB_GENERATE_CPP "" "RELPATH" "" ${ARGN})
if(NOT NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS) if(NOT NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS)
return() return()
endif() endif()
if(MSVC)
set(CUSTOM_COMMAND_PREFIX call)
endif()
if(NANOPB_GENERATE_CPP_APPEND_PATH) if(NANOPB_GENERATE_CPP_APPEND_PATH)
# Create an include path for each file specified # Create an include path for each file specified
foreach(FIL ${NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS}) foreach(FIL ${NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS})
@@ -184,7 +187,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS)
set(GENERATOR_CORE_PYTHON_SRC ${GENERATOR_CORE_PYTHON_SRC} ${output}) set(GENERATOR_CORE_PYTHON_SRC ${GENERATOR_CORE_PYTHON_SRC} ${output})
add_custom_command( add_custom_command(
OUTPUT ${output} OUTPUT ${output}
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} COMMAND ${CUSTOM_COMMAND_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE}
ARGS -I${GENERATOR_PATH}/proto ARGS -I${GENERATOR_PATH}/proto
--python_out=${GENERATOR_CORE_DIR} ${ABS_FIL} --python_out=${GENERATOR_CORE_DIR} ${ABS_FIL}
DEPENDS ${ABS_FIL} DEPENDS ${ABS_FIL}
@@ -276,7 +279,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS)
add_custom_command( add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.c" OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.c"
"${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.h" "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.h"
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} COMMAND ${CUSTOM_COMMAND_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE}
ARGS -I${GENERATOR_PATH} -I${GENERATOR_CORE_DIR} ARGS -I${GENERATOR_PATH} -I${GENERATOR_CORE_DIR}
-I${CMAKE_CURRENT_BINARY_DIR} ${_nanopb_include_path} -I${CMAKE_CURRENT_BINARY_DIR} ${_nanopb_include_path}
--plugin=protoc-gen-nanopb=${NANOPB_GENERATOR_PLUGIN} --plugin=protoc-gen-nanopb=${NANOPB_GENERATOR_PLUGIN}
@@ -293,6 +296,10 @@ function(NANOPB_GENERATE_CPP SRCS HDRS)
set(${SRCS} ${${SRCS}} ${NANOPB_SRCS} PARENT_SCOPE) set(${SRCS} ${${SRCS}} ${NANOPB_SRCS} PARENT_SCOPE)
set(${HDRS} ${${HDRS}} ${NANOPB_HDRS} PARENT_SCOPE) set(${HDRS} ${${HDRS}} ${NANOPB_HDRS} PARENT_SCOPE)
if(MSVC)
unset(CUSTOM_COMMAND_PREFIX)
endif()
endfunction() endfunction()

View File

@@ -1,5 +1,12 @@
#include "AACDecoder.h" #include "AACDecoder.h"
#include <iostream>
#include <stdlib.h> // for free, malloc
#include "CodecType.h" // for bell
namespace bell {
class AudioContainer;
} // namespace bell
using namespace bell; using namespace bell;

View File

@@ -1,27 +1,37 @@
#include "AudioCodecs.h" #include "AudioCodecs.h"
#include <cstring>
#include <iostream> #include <map> // for map, operator!=, map<>::iterator, map<>:...
#include <map> #include <type_traits> // for remove_extent_t
#include "AudioContainer.h" // for AudioContainer
namespace bell {
class BaseCodec;
} // namespace bell
using namespace bell; using namespace bell;
#ifdef BELL_CODEC_AAC #ifdef BELL_CODEC_AAC
#include "AACDecoder.h" #include "AACDecoder.h" // for AACDecoder
static std::shared_ptr<AACDecoder> codecAac; static std::shared_ptr<AACDecoder> codecAac;
#endif #endif
#ifdef BELL_CODEC_MP3 #ifdef BELL_CODEC_MP3
#include "MP3Decoder.h" #include "MP3Decoder.h" // for MP3Decoder
static std::shared_ptr<MP3Decoder> codecMp3; static std::shared_ptr<MP3Decoder> codecMp3;
#endif #endif
#ifdef BELL_CODEC_VORBIS #ifdef BELL_CODEC_VORBIS
#include "VorbisDecoder.h" #include "VorbisDecoder.h" // for VorbisDecoder
static std::shared_ptr<VorbisDecoder> codecVorbis; static std::shared_ptr<VorbisDecoder> codecVorbis;
#endif #endif
#ifdef BELL_CODEC_OPUS #ifdef BELL_CODEC_OPUS
#include "OPUSDecoder.h" #include "OPUSDecoder.h" // for OPUSDecoder
static std::shared_ptr<OPUSDecoder> codecOpus; static std::shared_ptr<OPUSDecoder> codecOpus;
#endif #endif

View File

@@ -1,5 +1,7 @@
#include "BaseCodec.h" #include "BaseCodec.h"
#include <iostream>
#include "AudioContainer.h" // for AudioContainer
#include "CodecType.h" // for bell
using namespace bell; using namespace bell;

View File

@@ -2,7 +2,6 @@
bell::DecodersInstance* bell::decodersInstance; bell::DecodersInstance* bell::decodersInstance;
void bell::createDecoders() void bell::createDecoders() {
{ bell::decodersInstance = new bell::DecodersInstance();
bell::decodersInstance = new bell::DecodersInstance();
} }

View File

@@ -1,5 +1,13 @@
#include "MP3Decoder.h" #include "MP3Decoder.h"
#include <stdlib.h> // for free, malloc
#include "CodecType.h" // for bell
namespace bell {
class AudioContainer;
} // namespace bell
using namespace bell; using namespace bell;
MP3Decoder::MP3Decoder() { MP3Decoder::MP3Decoder() {

View File

@@ -1,5 +1,9 @@
#include "OPUSDecoder.h" #include "OPUSDecoder.h"
#include "opus.h"
#include <stdlib.h> // for free, malloc
#include "CodecType.h" // for bell
#include "opus.h" // for opus_decoder_destroy, opus_decode, opus_decod...
using namespace bell; using namespace bell;

View File

@@ -1,5 +1,13 @@
#include "VorbisDecoder.h" #include "VorbisDecoder.h"
#include "AudioCodecs.h"
#include <stdlib.h> // for free, malloc
#include "CodecType.h" // for bell
#include "config_types.h" // for ogg_int16_t
namespace bell {
class AudioContainer;
} // namespace bell
using namespace bell; using namespace bell;

View File

@@ -1,9 +1,12 @@
#pragma once #pragma once
#include "BaseCodec.h" #include <stdint.h> // for uint8_t, uint32_t, int16_t
#include "aacdec.h"
#include "BaseCodec.h" // for BaseCodec
#include "aacdec.h" // for AACFrameInfo, HAACDecoder
namespace bell { namespace bell {
class AudioContainer;
class AACDecoder : public BaseCodec { class AACDecoder : public BaseCodec {
private: private:

View File

@@ -1,11 +1,12 @@
#pragma once #pragma once
#include <memory> #include <memory> // for shared_ptr
#include "BaseCodec.h"
#include "AudioContainer.h" #include "AudioContainer.h" // for AudioContainer
#include "BaseCodec.h" // for BaseCodec
#include "CodecType.h" // for AudioCodec
namespace bell { namespace bell {
class AudioCodecs { class AudioCodecs {
public: public:
static std::shared_ptr<BaseCodec> getCodec(AudioCodec type); static std::shared_ptr<BaseCodec> getCodec(AudioCodec type);

View File

@@ -1,8 +1,9 @@
#pragma once #pragma once
#include "AudioContainer.h" #include <stdint.h> // for uint32_t, uint8_t
namespace bell { namespace bell {
class AudioContainer;
class BaseCodec { class BaseCodec {
private: private:

View File

@@ -5,48 +5,40 @@
#define AAC_READBUF_SIZE (4 * AAC_MAINBUF_SIZE * AAC_MAX_NCHANS) #define AAC_READBUF_SIZE (4 * AAC_MAINBUF_SIZE * AAC_MAX_NCHANS)
#define MP3_READBUF_SIZE (2 * 1024); #define MP3_READBUF_SIZE (2 * 1024);
#include <stdio.h> #include <stdio.h> // for NULL
#include <stdlib.h>
#include <memory>
#include "aacdec.h"
#include "mp3dec.h"
namespace bell #include "aacdec.h" // for AACFreeDecoder, AACInitDecoder, HAACDecoder
{ #include "mp3dec.h" // for MP3FreeDecoder, MP3InitDecoder, HMP3Decoder
class DecodersInstance
{
public:
DecodersInstance(){};
~DecodersInstance()
{
MP3FreeDecoder(mp3Decoder);
AACFreeDecoder(aacDecoder);
};
HAACDecoder aacDecoder = NULL; namespace bell {
HMP3Decoder mp3Decoder = NULL; class DecodersInstance {
public:
DecodersInstance(){};
~DecodersInstance() {
MP3FreeDecoder(mp3Decoder);
AACFreeDecoder(aacDecoder);
};
void ensureAAC() HAACDecoder aacDecoder = NULL;
{ HMP3Decoder mp3Decoder = NULL;
if (aacDecoder == NULL)
{
aacDecoder = AACInitDecoder();
}
}
void ensureMP3() void ensureAAC() {
{ if (aacDecoder == NULL) {
if (mp3Decoder == NULL) aacDecoder = AACInitDecoder();
{ }
mp3Decoder = MP3InitDecoder(); }
}
}
};
extern bell::DecodersInstance* decodersInstance; void ensureMP3() {
if (mp3Decoder == NULL) {
mp3Decoder = MP3InitDecoder();
}
}
};
void createDecoders(); extern bell::DecodersInstance* decodersInstance;
}
void createDecoders();
} // namespace bell
#endif #endif
#endif #endif

View File

@@ -1,9 +1,13 @@
#pragma once #pragma once
#include "BaseCodec.h" #include <stdint.h> // for uint8_t, uint32_t, int16_t
#include "mp3dec.h"
#include "BaseCodec.h" // for BaseCodec
#include "mp3dec.h" // for HMP3Decoder, MP3FrameInfo
namespace bell { namespace bell {
class AudioContainer;
class MP3Decoder : public BaseCodec { class MP3Decoder : public BaseCodec {
private: private:
HMP3Decoder mp3; HMP3Decoder mp3;

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include "BaseCodec.h" #include <stdint.h> // for uint8_t, uint32_t, int16_t
#include "BaseCodec.h" // for BaseCodec
struct OpusDecoder; struct OpusDecoder;

View File

@@ -1,9 +1,14 @@
#pragma once #pragma once
#include "BaseCodec.h" #include <stdint.h> // for uint8_t, uint32_t, int16_t
#include "ivorbiscodec.h"
#include "BaseCodec.h" // for BaseCodec
#include "ivorbiscodec.h" // for vorbis_comment, vorbis_dsp_state, vorbis_info
#include "ogg.h" // for ogg_packet
namespace bell { namespace bell {
class AudioContainer;
class VorbisDecoder : public BaseCodec { class VorbisDecoder : public BaseCodec {
private: private:
vorbis_info* vi = nullptr; vorbis_info* vi = nullptr;

View File

@@ -1,5 +1,10 @@
#include "AACContainer.h" #include "AACContainer.h"
#include "iostream"
#include <cstring> // for memmove
#include "StreamInfo.h" // for BitWidth, BitWidth::BW_16, SampleRate, Sampl...
#include "aacdec.h" // for AACFindSyncWord
using namespace bell; using namespace bell;
#define SYNC_WORLD_LEN 4 #define SYNC_WORLD_LEN 4

View File

@@ -1,5 +1,16 @@
#include "AudioContainers.h" #include "AudioContainers.h"
#include <string.h> // for memcmp
#include <cstddef> // for byte
#include "AACContainer.h" // for AACContainer
#include "CodecType.h" // for bell
#include "MP3Container.h" // for MP3Container
namespace bell {
class AudioContainer;
} // namespace bell
using namespace bell; using namespace bell;
std::unique_ptr<bell::AudioContainer> AudioContainers::guessAudioContainer( std::unique_ptr<bell::AudioContainer> AudioContainers::guessAudioContainer(
@@ -7,8 +18,7 @@ std::unique_ptr<bell::AudioContainer> AudioContainers::guessAudioContainer(
std::byte tmp[14]; std::byte tmp[14];
istr.read((char*)tmp, sizeof(tmp)); istr.read((char*)tmp, sizeof(tmp));
if (memcmp(tmp, "\xFF\xF1", 2) == 0 || if (memcmp(tmp, "\xFF\xF1", 2) == 0 || memcmp(tmp, "\xFF\xF9", 2) == 0) {
memcmp(tmp, "\xFF\xF9", 2) == 0) {
// AAC found // AAC found
std::cout << "AAC" << std::endl; std::cout << "AAC" << std::endl;
return std::make_unique<bell::AACContainer>(istr); return std::make_unique<bell::AACContainer>(istr);

View File

@@ -1,5 +1,10 @@
#include "MP3Container.h" #include "MP3Container.h"
#include <cstring> // for memmove
#include "StreamInfo.h" // for BitWidth, BitWidth::BW_16, SampleRate, Sampl...
#include "mp3dec.h" // for MP3FindSyncWord
using namespace bell; using namespace bell;
MP3Container::MP3Container(std::istream& istr) : bell::AudioContainer(istr) {} MP3Container::MP3Container(std::istream& istr) : bell::AudioContainer(istr) {}

View File

@@ -1,10 +1,12 @@
#pragma once #pragma once
#include <cstring> #include <stdint.h> // for uint32_t
#include <cstddef> #include <cstddef> // for byte, size_t
#include <vector> #include <istream> // for istream
#include "AudioContainer.h" #include <vector> // for vector
#include "aacdec.h"
#include "AudioContainer.h" // for AudioContainer
#include "CodecType.h" // for AudioCodec, AudioCodec::AAC
namespace bell { namespace bell {
class AACContainer : public AudioContainer { class AACContainer : public AudioContainer {

View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <istream>
#include <cstring> #include <cstring>
#include <istream>
#include "CodecType.h" #include "CodecType.h"
#include "StreamInfo.h" #include "StreamInfo.h"

View File

@@ -1,10 +1,11 @@
#pragma once #pragma once
#include <iostream> #include <iostream> // for istream
#include <memory> #include <memory> // for unique_ptr
#include "AACContainer.h"
#include "AudioContainer.h" namespace bell {
#include "MP3Container.h" class AudioContainer;
} // namespace bell
namespace bell::AudioContainers { namespace bell::AudioContainers {
std::unique_ptr<bell::AudioContainer> guessAudioContainer(std::istream& istr); std::unique_ptr<bell::AudioContainer> guessAudioContainer(std::istream& istr);

View File

@@ -1,10 +1,12 @@
#pragma once #pragma once
#include <cstring> #include <stdint.h> // for uint32_t
#include <cstddef> #include <cstddef> // for byte, size_t
#include <vector> #include <istream> // for istream
#include "AudioContainer.h" #include <vector> // for vector
#include "mp3dec.h"
#include "AudioContainer.h" // for AudioContainer
#include "CodecType.h" // for AudioCodec, AudioCodec::MP3
namespace bell { namespace bell {
class MP3Container : public AudioContainer { class MP3Container : public AudioContainer {

View File

@@ -1,39 +1,44 @@
#include "AudioMixer.h" #include "AudioMixer.h"
#include <mutex> // for scoped_lock
using namespace bell; using namespace bell;
AudioMixer::AudioMixer() { AudioMixer::AudioMixer() {}
}
std::unique_ptr<StreamInfo> AudioMixer::process(std::unique_ptr<StreamInfo> info) { std::unique_ptr<StreamInfo> AudioMixer::process(
std::scoped_lock lock(this->accessMutex); std::unique_ptr<StreamInfo> info) {
if (info->numChannels != from) { std::scoped_lock lock(this->accessMutex);
throw std::runtime_error("AudioMixer: Input channel count does not match configuration"); if (info->numChannels != from) {
} throw std::runtime_error(
info->numChannels = to; "AudioMixer: Input channel count does not match configuration");
}
info->numChannels = to;
for (auto &singleConf : mixerConfig) { for (auto& singleConf : mixerConfig) {
if (singleConf.source.size() == 1) { if (singleConf.source.size() == 1) {
if (singleConf.source[0] == singleConf.destination) { if (singleConf.source[0] == singleConf.destination) {
continue; continue;
} }
// Copy channel // Copy channel
for (int i = 0; i < info->numSamples; i++) { for (int i = 0; i < info->numSamples; i++) {
info->data[singleConf.destination][i] = info->data[singleConf.source[0]][i]; info->data[singleConf.destination][i] =
} info->data[singleConf.source[0]][i];
} else { }
// Mix channels } else {
float sample = 0.0f; // Mix channels
for (int i = 0; i < info->numSamples; i++) { float sample = 0.0f;
sample = 0.0; for (int i = 0; i < info->numSamples; i++) {
for (auto &source : singleConf.source) { sample = 0.0;
sample += info->data[source][i]; for (auto& source : singleConf.source) {
} sample += info->data[source][i];
info->data[singleConf.destination][i] = sample / (float) singleConf.source.size();
}
} }
}
return info; info->data[singleConf.destination][i] =
sample / (float)singleConf.source.size();
}
}
}
return info;
} }

View File

@@ -1,47 +1,53 @@
#include "AudioPipeline.h" #include "AudioPipeline.h"
#include <iostream>
#include "BellLogger.h" #include <type_traits> // for remove_extent_t
#include <utility> // for move
#include "AudioTransform.h" // for AudioTransform
#include "BellLogger.h" // for AbstractLogger, BELL_LOG
#include "TransformConfig.h" // for TransformConfig
using namespace bell; using namespace bell;
AudioPipeline::AudioPipeline() { AudioPipeline::AudioPipeline(){
// this->headroomGainTransform = std::make_shared<Gain>(Channels::LEFT_RIGHT); // this->headroomGainTransform = std::make_shared<Gain>(Channels::LEFT_RIGHT);
// this->transforms.push_back(this->headroomGainTransform); // this->transforms.push_back(this->headroomGainTransform);
}; };
void AudioPipeline::addTransform(std::shared_ptr<AudioTransform> transform) { void AudioPipeline::addTransform(std::shared_ptr<AudioTransform> transform) {
transforms.push_back(transform); transforms.push_back(transform);
recalculateHeadroom(); recalculateHeadroom();
} }
void AudioPipeline::recalculateHeadroom() { void AudioPipeline::recalculateHeadroom() {
float headroom = 0.0f; float headroom = 0.0f;
// Find largest headroom required by any transform down the chain, and apply it // Find largest headroom required by any transform down the chain, and apply it
for (auto transform : transforms) { for (auto transform : transforms) {
if (headroom < transform->calculateHeadroom()) { if (headroom < transform->calculateHeadroom()) {
headroom = transform->calculateHeadroom(); headroom = transform->calculateHeadroom();
}
} }
}
// headroomGainTransform->configure(-headroom); // headroomGainTransform->configure(-headroom);
} }
void AudioPipeline::volumeUpdated(int volume) { void AudioPipeline::volumeUpdated(int volume) {
BELL_LOG(debug, "AudioPipeline", "Requested"); BELL_LOG(debug, "AudioPipeline", "Requested");
std::scoped_lock lock(this->accessMutex); std::scoped_lock lock(this->accessMutex);
for (auto transform : transforms) { for (auto transform : transforms) {
transform->config->currentVolume = volume; transform->config->currentVolume = volume;
transform->reconfigure(); transform->reconfigure();
} }
BELL_LOG(debug, "AudioPipeline", "Volume applied, DSP reconfigured"); BELL_LOG(debug, "AudioPipeline", "Volume applied, DSP reconfigured");
} }
std::unique_ptr<StreamInfo> AudioPipeline::process(std::unique_ptr<StreamInfo> data) { std::unique_ptr<StreamInfo> AudioPipeline::process(
std::scoped_lock lock(this->accessMutex); std::unique_ptr<StreamInfo> data) {
for (auto &transform : transforms) { std::scoped_lock lock(this->accessMutex);
data = transform->process(std::move(data)); for (auto& transform : transforms) {
} data = transform->process(std::move(data));
}
return data; return data;
} }

View File

@@ -1,6 +1,10 @@
#include "BellDSP.h" #include "BellDSP.h"
#include <iostream>
#include "CentralAudioBuffer.h" #include <type_traits> // for remove_extent_t
#include <utility> // for move
#include "AudioPipeline.h" // for CentralAudioBuffer
#include "CentralAudioBuffer.h" // for CentralAudioBuffer
using namespace bell; using namespace bell;

View File

@@ -1,466 +1,439 @@
#include "Biquad.h" #include "Biquad.h"
#include <cmath> // for pow, cosf, sinf, M_PI, sqrtf, tanf, logf, sinh
using namespace bell; using namespace bell;
Biquad::Biquad() Biquad::Biquad() {
{ this->filterType = "biquad";
this->filterType = "biquad";
} }
void Biquad::sampleRateChanged(uint32_t sampleRate) void Biquad::sampleRateChanged(uint32_t sampleRate) {
{ this->sampleRate = sampleRate;
this->sampleRate = sampleRate; //this->configure(this->type, this->currentConfig);
//this->configure(this->type, this->currentConfig);
} }
void Biquad::configure(Type type, std::map<std::string, float> &newConf) void Biquad::configure(Type type, std::map<std::string, float>& newConf) {
{ this->type = type;
this->type = type; this->currentConfig = newConf;
this->currentConfig = newConf;
switch (type) switch (type) {
{
case Type::Free: case Type::Free:
coeffs[0] = newConf["a1"]; coeffs[0] = newConf["a1"];
coeffs[1] = newConf["a2"]; coeffs[1] = newConf["a2"];
coeffs[2] = newConf["b0"]; coeffs[2] = newConf["b0"];
coeffs[3] = newConf["b1"]; coeffs[3] = newConf["b1"];
coeffs[4] = newConf["b2"]; coeffs[4] = newConf["b2"];
break; break;
case Type::Highpass: case Type::Highpass:
highPassCoEffs(newConf["freq"], newConf["q"]); highPassCoEffs(newConf["freq"], newConf["q"]);
break; break;
case Type::HighpassFO: case Type::HighpassFO:
highPassFOCoEffs(newConf["freq"]); highPassFOCoEffs(newConf["freq"]);
break; break;
case Type::Lowpass: case Type::Lowpass:
lowPassCoEffs(newConf["freq"], newConf["q"]); lowPassCoEffs(newConf["freq"], newConf["q"]);
break; break;
case Type::LowpassFO: case Type::LowpassFO:
lowPassFOCoEffs(newConf["freq"]); lowPassFOCoEffs(newConf["freq"]);
break; break;
case Type::Highshelf: case Type::Highshelf:
// check if config has slope key // check if config has slope key
if (newConf.find("slope") != newConf.end()) if (newConf.find("slope") != newConf.end()) {
{ highShelfCoEffsSlope(newConf["freq"], newConf["gain"],
highShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]); newConf["slope"]);
} } else {
else highShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
{ }
highShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); break;
}
break;
case Type::HighshelfFO: case Type::HighshelfFO:
highShelfFOCoEffs(newConf["freq"], newConf["gain"]); highShelfFOCoEffs(newConf["freq"], newConf["gain"]);
break; break;
case Type::Lowshelf: case Type::Lowshelf:
// check if config has slope key // check if config has slope key
if (newConf.find("slope") != newConf.end()) if (newConf.find("slope") != newConf.end()) {
{ lowShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]);
lowShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]); } else {
} lowShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
else }
{ break;
lowShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
}
break;
case Type::LowshelfFO: case Type::LowshelfFO:
lowShelfFOCoEffs(newConf["freq"], newConf["gain"]); lowShelfFOCoEffs(newConf["freq"], newConf["gain"]);
break; break;
case Type::Peaking: case Type::Peaking:
// check if config has bandwidth key // check if config has bandwidth key
if (newConf.find("bandwidth") != newConf.end()) if (newConf.find("bandwidth") != newConf.end()) {
{ peakCoEffsBandwidth(newConf["freq"], newConf["gain"],
peakCoEffsBandwidth(newConf["freq"], newConf["gain"], newConf["bandwidth"]); newConf["bandwidth"]);
} } else {
else peakCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
{ }
peakCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); break;
}
break;
case Type::Notch: case Type::Notch:
if (newConf.find("bandwidth") != newConf.end()) if (newConf.find("bandwidth") != newConf.end()) {
{ notchCoEffsBandwidth(newConf["freq"], newConf["gain"],
notchCoEffsBandwidth(newConf["freq"], newConf["gain"], newConf["bandwidth"]); newConf["bandwidth"]);
} } else {
else notchCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
{ }
notchCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); break;
}
break;
case Type::Bandpass: case Type::Bandpass:
if (newConf.find("bandwidth") != newConf.end()) if (newConf.find("bandwidth") != newConf.end()) {
{ bandPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]);
bandPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); } else {
} bandPassCoEffs(newConf["freq"], newConf["q"]);
else }
{ break;
bandPassCoEffs(newConf["freq"], newConf["q"]);
}
break;
case Type::Allpass: case Type::Allpass:
if (newConf.find("bandwidth") != newConf.end()) if (newConf.find("bandwidth") != newConf.end()) {
{ allPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]);
allPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); } else {
} allPassCoEffs(newConf["freq"], newConf["q"]);
else }
{ break;
allPassCoEffs(newConf["freq"], newConf["q"]);
}
break;
case Type::AllpassFO: case Type::AllpassFO:
allPassFOCoEffs(newConf["freq"]); allPassFOCoEffs(newConf["freq"]);
break; break;
} }
} }
// coefficients for a high pass biquad filter // coefficients for a high pass biquad filter
void Biquad::highPassCoEffs(float f, float q) void Biquad::highPassCoEffs(float f, float q) {
{ float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float c = cosf(w0);
float c = cosf(w0); float s = sinf(w0);
float s = sinf(w0); float alpha = s / (2 * q);
float alpha = s / (2 * q);
float b0 = (1 + c) / 2; float b0 = (1 + c) / 2;
float b1 = -(1 + c); float b1 = -(1 + c);
float b2 = b0; float b2 = b0;
float a0 = 1 + alpha; float a0 = 1 + alpha;
float a1 = -2 * c; float a1 = -2 * c;
float a2 = 1 - alpha; float a2 = 1 - alpha;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
// coefficients for a high pass first order biquad filter // coefficients for a high pass first order biquad filter
void Biquad::highPassFOCoEffs(float f) void Biquad::highPassFOCoEffs(float f) {
{ float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float k = tanf(w0 / 2.0);
float k = tanf(w0 / 2.0);
float alpha = 1.0 + k; float alpha = 1.0 + k;
float b0 = 1.0 / alpha; float b0 = 1.0 / alpha;
float b1 = -1.0 / alpha; float b1 = -1.0 / alpha;
float b2 = 0.0; float b2 = 0.0;
float a0 = 1.0; float a0 = 1.0;
float a1 = -(1.0 - k) / alpha; float a1 = -(1.0 - k) / alpha;
float a2 = 0.0; float a2 = 0.0;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
// coefficients for a low pass biquad filter // coefficients for a low pass biquad filter
void Biquad::lowPassCoEffs(float f, float q) void Biquad::lowPassCoEffs(float f, float q) {
{ float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float c = cosf(w0);
float c = cosf(w0); float s = sinf(w0);
float s = sinf(w0); float alpha = s / (2 * q);
float alpha = s / (2 * q);
float b0 = (1 - c) / 2; float b0 = (1 - c) / 2;
float b1 = 1 - c; float b1 = 1 - c;
float b2 = b0; float b2 = b0;
float a0 = 1 + alpha; float a0 = 1 + alpha;
float a1 = -2 * c; float a1 = -2 * c;
float a2 = 1 - alpha; float a2 = 1 - alpha;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
// coefficients for a low pass first order biquad filter // coefficients for a low pass first order biquad filter
void Biquad::lowPassFOCoEffs(float f) void Biquad::lowPassFOCoEffs(float f) {
{ float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float k = tanf(w0 / 2.0);
float k = tanf(w0 / 2.0);
float alpha = 1.0 + k; float alpha = 1.0 + k;
float b0 = k / alpha; float b0 = k / alpha;
float b1 = k / alpha; float b1 = k / alpha;
float b2 = 0.0; float b2 = 0.0;
float a0 = 1.0; float a0 = 1.0;
float a1 = -(1.0 - k) / alpha; float a1 = -(1.0 - k) / alpha;
float a2 = 0.0; float a2 = 0.0;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
// coefficients for a peak biquad filter // coefficients for a peak biquad filter
void Biquad::peakCoEffs(float f, float gain, float q) void Biquad::peakCoEffs(float f, float gain, float q) {
{ float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float c = cosf(w0);
float c = cosf(w0); float s = sinf(w0);
float s = sinf(w0); float alpha = s / (2 * q);
float alpha = s / (2 * q);
float ampl = std::pow(10.0f, gain / 40.0f); float ampl = std::pow(10.0f, gain / 40.0f);
float b0 = 1.0 + (alpha * ampl); float b0 = 1.0 + (alpha * ampl);
float b1 = -2.0 * c; float b1 = -2.0 * c;
float b2 = 1.0 - (alpha * ampl); float b2 = 1.0 - (alpha * ampl);
float a0 = 1 + (alpha / ampl); float a0 = 1 + (alpha / ampl);
float a1 = -2 * c; float a1 = -2 * c;
float a2 = 1 - (alpha / ampl); float a2 = 1 - (alpha / ampl);
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::peakCoEffsBandwidth(float f, float gain, float bandwidth) void Biquad::peakCoEffsBandwidth(float f, float gain, float bandwidth) {
{ float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float c = cosf(w0);
float c = cosf(w0); float s = sinf(w0);
float s = sinf(w0); float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
float ampl = std::pow(10.0f, gain / 40.0f); float ampl = std::pow(10.0f, gain / 40.0f);
float b0 = 1.0 + (alpha * ampl); float b0 = 1.0 + (alpha * ampl);
float b1 = -2.0 * c; float b1 = -2.0 * c;
float b2 = 1.0 - (alpha * ampl); float b2 = 1.0 - (alpha * ampl);
float a0 = 1 + (alpha / ampl); float a0 = 1 + (alpha / ampl);
float a1 = -2 * c; float a1 = -2 * c;
float a2 = 1 - (alpha / ampl); float a2 = 1 - (alpha / ampl);
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::highShelfCoEffs(float f, float gain, float q) void Biquad::highShelfCoEffs(float f, float gain, float q) {
{ float A = std::pow(10.0f, gain / 40.0f);
float A = std::pow(10.0f, gain / 40.0f); float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float c = cosf(w0);
float c = cosf(w0); float s = sinf(w0);
float s = sinf(w0); float alpha = s / (2 * q);
float alpha = s / (2 * q); float beta = s * sqrtf(A) / q;
float beta = s * sqrtf(A) / q; float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta);
float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c);
float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta);
float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); float a0 = (A + 1.0) - (A - 1.0) * c + beta;
float a0 = (A + 1.0) - (A - 1.0) * c + beta; float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c);
float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); float a2 = (A + 1.0) - (A - 1.0) * c - beta;
float a2 = (A + 1.0) - (A - 1.0) * c - beta;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::highShelfCoEffsSlope(float f, float gain, float slope) void Biquad::highShelfCoEffsSlope(float f, float gain, float slope) {
{ float A = std::pow(10.0f, gain / 40.0f);
float A = std::pow(10.0f, gain / 40.0f); float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float c = cosf(w0);
float c = cosf(w0); float s = sinf(w0);
float s = sinf(w0); float alpha =
float alpha = s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0);
s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); float beta = 2.0 * sqrtf(A) * alpha;
float beta = 2.0 * sqrtf(A) * alpha; float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta);
float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c);
float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta);
float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); float a0 = (A + 1.0) - (A - 1.0) * c + beta;
float a0 = (A + 1.0) - (A - 1.0) * c + beta; float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c);
float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); float a2 = (A + 1.0) - (A - 1.0) * c - beta;
float a2 = (A + 1.0) - (A - 1.0) * c - beta;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::highShelfFOCoEffs(float f, float gain) void Biquad::highShelfFOCoEffs(float f, float gain) {
{ float A = std::pow(10.0f, gain / 40.0f);
float A = std::pow(10.0f, gain / 40.0f); float w0 = 2 * M_PI * f / this->sampleRate;
float w0 = 2 * M_PI * f / this->sampleRate; float tn = tanf(w0 / 2.0);
float tn = tanf(w0 / 2.0);
float b0 = A * tn + std::pow(A, 2); float b0 = A * tn + std::pow(A, 2);
float b1 = A * tn - std::pow(A, 2); float b1 = A * tn - std::pow(A, 2);
float b2 = 0.0; float b2 = 0.0;
float a0 = A * tn + 1.0; float a0 = A * tn + 1.0;
float a1 = A * tn - 1.0; float a1 = A * tn - 1.0;
float a2 = 0.0; float a2 = 0.0;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::lowShelfCoEffs(float f, float gain, float q) { void Biquad::lowShelfCoEffs(float f, float gain, float q) {
float A = std::pow(10.0f, gain / 40.0f); float A = std::pow(10.0f, gain / 40.0f);
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float c = cosf(w0); float c = cosf(w0);
float s = sinf(w0); float s = sinf(w0);
float beta = s * sqrtf(A) / q; float beta = s * sqrtf(A) / q;
float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta);
float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c);
float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta);
float a0 = (A + 1.0) + (A - 1.0) * c + beta; float a0 = (A + 1.0) + (A - 1.0) * c + beta;
float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c);
float a2 = (A + 1.0) + (A - 1.0) * c - beta; float a2 = (A + 1.0) + (A - 1.0) * c - beta;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::lowShelfCoEffsSlope(float f, float gain, float slope) { void Biquad::lowShelfCoEffsSlope(float f, float gain, float slope) {
float A = std::pow(10.0f, gain / 40.0f); float A = std::pow(10.0f, gain / 40.0f);
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float c = cosf(w0); float c = cosf(w0);
float s = sinf(w0); float s = sinf(w0);
float alpha = float alpha =
s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0);
float beta = 2.0 * sqrtf(A) * alpha; float beta = 2.0 * sqrtf(A) * alpha;
float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta);
float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c);
float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta);
float a0 = (A + 1.0) + (A - 1.0) * c + beta; float a0 = (A + 1.0) + (A - 1.0) * c + beta;
float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c);
float a2 = (A + 1.0) + (A - 1.0) * c - beta; float a2 = (A + 1.0) + (A - 1.0) * c - beta;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::lowShelfFOCoEffs(float f, float gain) { void Biquad::lowShelfFOCoEffs(float f, float gain) {
float A = std::pow(10.0f, gain / 40.0f); float A = std::pow(10.0f, gain / 40.0f);
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float tn = tanf(w0 / 2.0); float tn = tanf(w0 / 2.0);
float b0 = std::pow(A, 2) * tn + A; float b0 = std::pow(A, 2) * tn + A;
float b1 = std::pow(A, 2) * tn - A; float b1 = std::pow(A, 2) * tn - A;
float b2 = 0.0; float b2 = 0.0;
float a0 = tn + A; float a0 = tn + A;
float a1 = tn - A; float a1 = tn - A;
float a2 = 0.0; float a2 = 0.0;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::notchCoEffs(float f, float gain, float q) { void Biquad::notchCoEffs(float f, float gain, float q) {
float A = std::pow(10.0f, gain / 40.0f); float A = std::pow(10.0f, gain / 40.0f);
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float c = cosf(w0); float c = cosf(w0);
float s = sinf(w0); float s = sinf(w0);
float alpha = s / (2.0 * q); float alpha = s / (2.0 * q);
float b0 = 1.0; float b0 = 1.0;
float b1 = -2.0 * c; float b1 = -2.0 * c;
float b2 = 1.0; float b2 = 1.0;
float a0 = 1.0 + alpha; float a0 = 1.0 + alpha;
float a1 = -2.0 * c; float a1 = -2.0 * c;
float a2 = 1.0 - alpha; float a2 = 1.0 - alpha;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::notchCoEffsBandwidth(float f, float gain, float bandwidth) { void Biquad::notchCoEffsBandwidth(float f, float gain, float bandwidth) {
float A = std::pow(10.0f, gain / 40.0f); float A = std::pow(10.0f, gain / 40.0f);
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float c = cosf(w0); float c = cosf(w0);
float s = sinf(w0); float s = sinf(w0);
float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
float b0 = 1.0; float b0 = 1.0;
float b1 = -2.0 * c; float b1 = -2.0 * c;
float b2 = 1.0; float b2 = 1.0;
float a0 = 1.0 + alpha; float a0 = 1.0 + alpha;
float a1 = -2.0 * c; float a1 = -2.0 * c;
float a2 = 1.0 - alpha; float a2 = 1.0 - alpha;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::bandPassCoEffs(float f, float q) { void Biquad::bandPassCoEffs(float f, float q) {
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float c = cosf(w0); float c = cosf(w0);
float s = sinf(w0); float s = sinf(w0);
float alpha = s / (2.0 * q); float alpha = s / (2.0 * q);
float b0 = alpha; float b0 = alpha;
float b1 = 0.0; float b1 = 0.0;
float b2 = -alpha; float b2 = -alpha;
float a0 = 1.0 + alpha; float a0 = 1.0 + alpha;
float a1 = -2.0 * c; float a1 = -2.0 * c;
float a2 = 1.0 - alpha; float a2 = 1.0 - alpha;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::bandPassCoEffsBandwidth(float f, float bandwidth) { void Biquad::bandPassCoEffsBandwidth(float f, float bandwidth) {
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float c = cosf(w0); float c = cosf(w0);
float s = sinf(w0); float s = sinf(w0);
float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
float b0 = alpha; float b0 = alpha;
float b1 = 0.0; float b1 = 0.0;
float b2 = -alpha; float b2 = -alpha;
float a0 = 1.0 + alpha; float a0 = 1.0 + alpha;
float a1 = -2.0 * c; float a1 = -2.0 * c;
float a2 = 1.0 - alpha; float a2 = 1.0 - alpha;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::allPassCoEffs(float f, float q) { void Biquad::allPassCoEffs(float f, float q) {
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float c = cosf(w0); float c = cosf(w0);
float s = sinf(w0); float s = sinf(w0);
float alpha = s / (2.0 * q); float alpha = s / (2.0 * q);
float b0 = 1.0 - alpha; float b0 = 1.0 - alpha;
float b1 = -2.0 * c; float b1 = -2.0 * c;
float b2 = 1.0 + alpha; float b2 = 1.0 + alpha;
float a0 = 1.0 + alpha; float a0 = 1.0 + alpha;
float a1 = -2.0 * c; float a1 = -2.0 * c;
float a2 = 1.0 - alpha; float a2 = 1.0 - alpha;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::allPassCoEffsBandwidth(float f, float bandwidth) { void Biquad::allPassCoEffsBandwidth(float f, float bandwidth) {
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float c = cosf(w0); float c = cosf(w0);
float s = sinf(w0); float s = sinf(w0);
float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
float b0 = 1.0 - alpha; float b0 = 1.0 - alpha;
float b1 = -2.0 * c; float b1 = -2.0 * c;
float b2 = 1.0 + alpha; float b2 = 1.0 + alpha;
float a0 = 1.0 + alpha; float a0 = 1.0 + alpha;
float a1 = -2.0 * c; float a1 = -2.0 * c;
float a2 = 1.0 - alpha; float a2 = 1.0 - alpha;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::allPassFOCoEffs(float f) { void Biquad::allPassFOCoEffs(float f) {
float w0 = 2 * M_PI * f / this->sampleRate; float w0 = 2 * M_PI * f / this->sampleRate;
float tn = tanf(w0 / 2.0); float tn = tanf(w0 / 2.0);
float alpha = (tn + 1.0) / (tn - 1.0); float alpha = (tn + 1.0) / (tn - 1.0);
float b0 = 1.0; float b0 = 1.0;
float b1 = alpha; float b1 = alpha;
float b2 = 0.0; float b2 = 0.0;
float a0 = alpha; float a0 = alpha;
float a1 = 1.0; float a1 = 1.0;
float a2 = 0.0; float a2 = 0.0;
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
} }
void Biquad::normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, float b2) void Biquad::normalizeCoEffs(float a0, float a1, float a2, float b0, float b1,
{ float b2) {
coeffs[0] = b0 / a0; coeffs[0] = b0 / a0;
coeffs[1] = b1 / a0; coeffs[1] = b1 / a0;
coeffs[2] = b2 / a0; coeffs[2] = b2 / a0;
coeffs[3] = a1 / a0; coeffs[3] = a1 / a0;
coeffs[4] = a2 / a0; coeffs[4] = a2 / a0;
} }
std::unique_ptr<StreamInfo> Biquad::process(std::unique_ptr<StreamInfo> stream) std::unique_ptr<StreamInfo> Biquad::process(
{ std::unique_ptr<StreamInfo> stream) {
std::scoped_lock lock(accessMutex); std::scoped_lock lock(accessMutex);
auto input = stream->data[this->channel]; auto input = stream->data[this->channel];
auto numSamples = stream->numSamples; auto numSamples = stream->numSamples;
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
dsps_biquad_f32_ae32(input, input, numSamples, coeffs, w); dsps_biquad_f32_ae32(input, input, numSamples, coeffs, w);
#else #else
// Apply the set coefficients // Apply the set coefficients
for (int i = 0; i < numSamples; i++) for (int i = 0; i < numSamples; i++) {
{ float d0 = input[i] - coeffs[3] * w[0] - coeffs[4] * w[1];
float d0 = input[i] - coeffs[3] * w[0] - coeffs[4] * w[1]; input[i] = coeffs[0] * d0 + coeffs[1] * w[0] + coeffs[2] * w[1];
input[i] = coeffs[0] * d0 + coeffs[1] * w[0] + coeffs[2] * w[1]; w[1] = w[0];
w[1] = w[0]; w[0] = d0;
w[0] = d0; }
}
#endif #endif
return stream; return stream;
}; };

View File

@@ -1,109 +1,89 @@
#include "BiquadCombo.h" #include "BiquadCombo.h"
#include <stdio.h> // for printf
#include <cmath> // for sinf, M_PI
#include <utility> // for move
using namespace bell; using namespace bell;
BiquadCombo::BiquadCombo() BiquadCombo::BiquadCombo() {}
{
void BiquadCombo::sampleRateChanged(uint32_t sampleRate) {
for (auto& biquad : biquads) {
biquad->sampleRateChanged(sampleRate);
}
} }
void BiquadCombo::sampleRateChanged(uint32_t sampleRate) std::vector<float> BiquadCombo::calculateBWQ(int order) {
{
for (auto &biquad : biquads) std::vector<float> qValues;
{ for (int n = 0; n < order / 2; n++) {
biquad->sampleRateChanged(sampleRate); float q = 1.0f / (2.0f * sinf(M_PI / order * (((float)n) + 0.5)));
} qValues.push_back(q);
}
if (order % 2 > 0) {
qValues.push_back(-1.0);
}
printf("%d\n", qValues.size());
return qValues;
} }
std::vector<float> BiquadCombo::calculateBWQ(int order) std::vector<float> BiquadCombo::calculateLRQ(int order) {
{ auto qValues = calculateBWQ(order / 2);
std::vector<float> qValues; if (order % 4 > 0) {
for (int n = 0; n < order / 2; n++) qValues.pop_back();
{ qValues.insert(qValues.end(), qValues.begin(), qValues.end());
float q = 1.0f / (2.0f * sinf(M_PI / order * (((float)n) + 0.5))); qValues.push_back(0.5);
qValues.push_back(q); } else {
} qValues.insert(qValues.end(), qValues.begin(), qValues.end());
}
if (order % 2 > 0) return qValues;
{
qValues.push_back(-1.0);
}
printf("%d\n", qValues.size());
return qValues;
} }
std::vector<float> BiquadCombo::calculateLRQ(int order) void BiquadCombo::butterworth(float freq, int order, FilterType type) {
{ std::vector<float> qValues = calculateBWQ(order);
auto qValues = calculateBWQ(order / 2); for (auto& q : qValues) {}
if (order % 4 > 0)
{
qValues.pop_back();
qValues.insert(qValues.end(), qValues.begin(), qValues.end());
qValues.push_back(0.5);
}
else
{
qValues.insert(qValues.end(), qValues.begin(), qValues.end());
}
return qValues;
} }
void BiquadCombo::butterworth(float freq, int order, FilterType type) void BiquadCombo::linkwitzRiley(float freq, int order, FilterType type) {
{ std::vector<float> qValues = calculateLRQ(order);
std::vector<float> qValues = calculateBWQ(order); for (auto& q : qValues) {
for (auto &q : qValues) auto filter = std::make_unique<Biquad>();
{ filter->channel = channel;
}
}
void BiquadCombo::linkwitzRiley(float freq, int order, FilterType type) auto config = std::map<std::string, float>();
{ config["freq"] = freq;
std::vector<float> qValues = calculateLRQ(order); config["q"] = q;
for (auto &q : qValues)
{
auto filter = std::make_unique<Biquad>();
filter->channel = channel;
auto config = std::map<std::string, float>(); if (q >= 0.0) {
config["freq"] = freq; if (type == FilterType::Highpass) {
config["q"] = q; filter->configure(Biquad::Type::Highpass, config);
} else {
if (q >= 0.0) filter->configure(Biquad::Type::Lowpass, config);
{ }
if (type == FilterType::Highpass) } else {
{ if (type == FilterType::Highpass) {
filter->configure(Biquad::Type::Highpass, config); filter->configure(Biquad::Type::HighpassFO, config);
} } else {
else filter->configure(Biquad::Type::LowpassFO, config);
{ }
filter->configure(Biquad::Type::Lowpass, config);
}
}
else
{
if (type == FilterType::Highpass)
{
filter->configure(Biquad::Type::HighpassFO, config);
}
else
{
filter->configure(Biquad::Type::LowpassFO, config);
}
}
this->biquads.push_back(std::move(filter));
}
}
std::unique_ptr<StreamInfo> BiquadCombo::process(std::unique_ptr<StreamInfo> data) {
std::scoped_lock lock(this->accessMutex);
for (auto &transform : this->biquads) {
data = transform->process(std::move(data));
} }
return data; this->biquads.push_back(std::move(filter));
}
}
std::unique_ptr<StreamInfo> BiquadCombo::process(
std::unique_ptr<StreamInfo> data) {
std::scoped_lock lock(this->accessMutex);
for (auto& transform : this->biquads) {
data = transform->process(std::move(data));
}
return data;
} }

View File

@@ -1,5 +1,7 @@
#include "Compressor.h" #include "Compressor.h"
#include <cstdlib> // for abs
using namespace bell; using namespace bell;
float log2f_approx(float X) { float log2f_approx(float X) {
@@ -19,11 +21,11 @@ float log2f_approx(float X) {
Compressor::Compressor() {} Compressor::Compressor() {}
void Compressor::sumChannels(std::unique_ptr<StreamInfo> &data) { void Compressor::sumChannels(std::unique_ptr<StreamInfo>& data) {
tmp.resize(data->numSamples); tmp.resize(data->numSamples);
for (int i = 0; i < data->numSamples; i++) { for (int i = 0; i < data->numSamples; i++) {
float sum = 0.0f; float sum = 0.0f;
for (auto &channel : channels) { for (auto& channel : channels) {
sum += data->data[channel][i]; sum += data->data[channel][i];
} }
tmp[i] = sum; tmp[i] = sum;
@@ -31,7 +33,7 @@ void Compressor::sumChannels(std::unique_ptr<StreamInfo> &data) {
} }
void Compressor::calLoudness() { void Compressor::calLoudness() {
for (auto &value : tmp) { for (auto& value : tmp) {
value = 20 * log10f_fast(std::abs(value) + 1.0e-9f); value = 20 * log10f_fast(std::abs(value) + 1.0e-9f);
if (value >= lastLoudness) { if (value >= lastLoudness) {
value = attack * lastLoudness + (1.0 - attack) * value; value = attack * lastLoudness + (1.0 - attack) * value;
@@ -44,7 +46,7 @@ void Compressor::calLoudness() {
} }
void Compressor::calGain() { void Compressor::calGain() {
for (auto &value : tmp) { for (auto& value : tmp) {
if (value > threshold) { if (value > threshold) {
value = -(value - threshold) * (factor - 1.0) / factor; value = -(value - threshold) * (factor - 1.0) / factor;
} else { } else {
@@ -58,9 +60,9 @@ void Compressor::calGain() {
} }
} }
void Compressor::applyGain(std::unique_ptr<StreamInfo> &data) { void Compressor::applyGain(std::unique_ptr<StreamInfo>& data) {
for (int i = 0; i < data->numSamples; i++) { for (int i = 0; i < data->numSamples; i++) {
for (auto &channel : channels) { for (auto& channel : channels) {
data->data[channel][i] *= tmp[i]; data->data[channel][i] *= tmp[i];
} }
} }

View File

@@ -1,31 +1,29 @@
#include "Gain.h" #include "Gain.h"
#include <cmath> // for pow
#include <string> // for string
using namespace bell; using namespace bell;
Gain::Gain() : AudioTransform() Gain::Gain() : AudioTransform() {
{ this->gainFactor = 1.0f;
this->gainFactor = 1.0f; this->filterType = "gain";
this->filterType = "gain";
} }
void Gain::configure(std::vector<int> channels, float gainDB) void Gain::configure(std::vector<int> channels, float gainDB) {
{ this->channels = channels;
this->channels = channels; this->gainDb = gainDB;
this->gainDb = gainDB; this->gainFactor = std::pow(10.0f, gainDB / 20.0f);
this->gainFactor = std::pow(10.0f, gainDB / 20.0f);
} }
std::unique_ptr<StreamInfo> Gain::process(std::unique_ptr<StreamInfo> data) std::unique_ptr<StreamInfo> Gain::process(std::unique_ptr<StreamInfo> data) {
{ std::scoped_lock lock(this->accessMutex);
std::scoped_lock lock(this->accessMutex); for (int i = 0; i < data->numSamples; i++) {
for (int i = 0; i < data->numSamples; i++) // Apply gain to all channels
{ for (auto& channel : channels) {
// Apply gain to all channels data->data[channel][i] *= gainFactor;
for (auto &channel : channels)
{
data->data[channel][i] *= gainFactor;
}
} }
}
return data; return data;
} }

View File

@@ -1,89 +1,80 @@
#pragma once #pragma once
#include <vector> #include <cJSON.h> // for cJSON_GetObjectItem, cJSON, cJSON_IsArray
#include <algorithm> #include <stddef.h> // for NULL
#include <cJSON.h> #include <algorithm> // for find
#include <cstdint> // for uint8_t
#include <memory> // for unique_ptr
#include <stdexcept> // for invalid_argument
#include <vector> // for vector
#include "AudioTransform.h" #include "AudioTransform.h" // for AudioTransform
#include "StreamInfo.h" // for StreamInfo
namespace bell namespace bell {
{ class AudioMixer : public bell::AudioTransform {
class AudioMixer : public bell::AudioTransform public:
{ enum DownmixMode { DEFAULT };
public:
enum DownmixMode
{
DEFAULT
};
struct MixerConfig struct MixerConfig {
{ std::vector<int> source;
std::vector<int> source; int destination;
int destination; };
};
AudioMixer(); AudioMixer();
~AudioMixer(){}; ~AudioMixer(){};
// Amount of channels in the input // Amount of channels in the input
int from; int from;
// Amount of channels in the output // Amount of channels in the output
int to; int to;
// Configuration of each channels in the mixer // Configuration of each channels in the mixer
std::vector<MixerConfig> mixerConfig; std::vector<MixerConfig> mixerConfig;
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override; std::unique_ptr<StreamInfo> process(
std::unique_ptr<StreamInfo> data) override;
void reconfigure() override void reconfigure() override {}
{
void fromJSON(cJSON* json) {
cJSON* mappedChannels = cJSON_GetObjectItem(json, "mapped_channels");
if (mappedChannels == NULL || !cJSON_IsArray(mappedChannels)) {
throw std::invalid_argument("Mixer configuration invalid");
}
this->mixerConfig = std::vector<MixerConfig>();
cJSON* iterator = NULL;
cJSON_ArrayForEach(iterator, mappedChannels) {
std::vector<int> sources(0);
cJSON* iteratorNested = NULL;
cJSON_ArrayForEach(iteratorNested,
cJSON_GetObjectItem(iterator, "source")) {
sources.push_back(iteratorNested->valueint);
}
int destination = cJSON_GetObjectItem(iterator, "destination")->valueint;
this->mixerConfig.push_back(
MixerConfig{.source = sources, .destination = destination});
}
std::vector<uint8_t> sources(0);
for (auto& config : mixerConfig) {
for (auto& source : config.source) {
if (std::find(sources.begin(), sources.end(), source) ==
sources.end()) {
sources.push_back(source);
} }
}
}
void fromJSON(cJSON *json) this->from = sources.size();
{ this->to = mixerConfig.size();
cJSON *mappedChannels = cJSON_GetObjectItem(json, "mapped_channels"); }
};
if (mappedChannels == NULL || !cJSON_IsArray(mappedChannels)) } // namespace bell
{
throw std::invalid_argument("Mixer configuration invalid");
}
this->mixerConfig = std::vector<MixerConfig>();
cJSON *iterator = NULL;
cJSON_ArrayForEach(iterator, mappedChannels)
{
std::vector<int> sources(0);
cJSON *iteratorNested = NULL;
cJSON_ArrayForEach(iteratorNested, cJSON_GetObjectItem(iterator, "source"))
{
sources.push_back(iteratorNested->valueint);
}
int destination = cJSON_GetObjectItem(iterator, "destination")->valueint;
this->mixerConfig.push_back(MixerConfig{
.source = sources,
.destination = destination
});
}
std::vector<uint8_t> sources(0);
for (auto &config : mixerConfig)
{
for (auto &source : config.source)
{
if (std::find(sources.begin(), sources.end(), source) == sources.end())
{
sources.push_back(source);
}
}
}
this->from = sources.size();
this->to = mixerConfig.size();
}
};
}

View File

@@ -1,28 +1,29 @@
#pragma once #pragma once
#include "AudioTransform.h" #include <memory> // for shared_ptr, unique_ptr
#include "StreamInfo.h" #include <mutex> // for mutex
#include <memory> #include <vector> // for vector
#include "Gain.h"
#include <mutex>
namespace bell #include "StreamInfo.h" // for StreamInfo
{
class AudioPipeline
{
private:
std::shared_ptr<Gain> headroomGainTransform;
public: namespace bell {
AudioPipeline(); class AudioTransform;
~AudioPipeline(){}; class Gain;
std::mutex accessMutex; class AudioPipeline {
std::vector<std::shared_ptr<AudioTransform>> transforms; private:
std::shared_ptr<Gain> headroomGainTransform;
void recalculateHeadroom(); public:
void addTransform(std::shared_ptr<AudioTransform> transform); AudioPipeline();
void volumeUpdated(int volume); ~AudioPipeline(){};
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data);
}; std::mutex accessMutex;
}; // namespace bell std::vector<std::shared_ptr<AudioTransform>> transforms;
void recalculateHeadroom();
void addTransform(std::shared_ptr<AudioTransform> transform);
void volumeUpdated(int volume);
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data);
};
}; // namespace bell

View File

@@ -1,29 +1,28 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <thread>
#include <mutex> #include <mutex>
#include <thread>
#include "StreamInfo.h" #include "StreamInfo.h"
#include "TransformConfig.h" #include "TransformConfig.h"
namespace bell namespace bell {
{ class AudioTransform {
class AudioTransform protected:
{ std::mutex accessMutex;
protected:
std::mutex accessMutex;
public: public:
virtual std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) = 0; virtual std::unique_ptr<StreamInfo> process(
virtual void sampleRateChanged(uint32_t sampleRate){}; std::unique_ptr<StreamInfo> data) = 0;
virtual float calculateHeadroom() { return 0; }; virtual void sampleRateChanged(uint32_t sampleRate){};
virtual float calculateHeadroom() { return 0; };
virtual void reconfigure() {}; virtual void reconfigure(){};
std::string filterType; std::string filterType;
std::unique_ptr<TransformConfig> config; std::unique_ptr<TransformConfig> config;
AudioTransform() = default; AudioTransform() = default;
virtual ~AudioTransform() = default; virtual ~AudioTransform() = default;
};
}; };
}; // namespace bell

View File

@@ -1,34 +1,43 @@
#pragma once #pragma once
#include <memory> #include <stddef.h> // for size_t
#include <mutex> #include <stdint.h> // for uint32_t, uint8_t
#include <vector> #include <functional> // for function
#include "AudioPipeline.h" #include <memory> // for shared_ptr, unique_ptr
#include "CentralAudioBuffer.h" #include <mutex> // for mutex
#include <vector> // for vector
#include "StreamInfo.h" // for BitWidth
namespace bell { namespace bell {
class AudioPipeline;
class CentralAudioBuffer;
#define MAX_INT16 32767 #define MAX_INT16 32767
class BellDSP { class BellDSP {
public: public:
BellDSP(std::shared_ptr<CentralAudioBuffer> centralAudioBuffer); BellDSP(std::shared_ptr<CentralAudioBuffer> centralAudioBuffer);
~BellDSP() {}; ~BellDSP(){};
class AudioEffect { class AudioEffect {
public: public:
AudioEffect() = default; AudioEffect() = default;
~AudioEffect() = default; ~AudioEffect() = default;
size_t duration; size_t duration;
virtual void apply(float* sampleData, size_t samples, size_t relativePosition) = 0; virtual void apply(float* sampleData, size_t samples,
size_t relativePosition) = 0;
}; };
class FadeEffect: public AudioEffect { class FadeEffect : public AudioEffect {
private: private:
std::function<void()> onFinish; std::function<void()> onFinish;
bool isFadeIn; bool isFadeIn;
public:
FadeEffect(size_t duration, bool isFadeIn, std::function<void()> onFinish = nullptr); public:
~FadeEffect() {}; FadeEffect(size_t duration, bool isFadeIn,
std::function<void()> onFinish = nullptr);
~FadeEffect(){};
void apply(float* sampleData, size_t samples, size_t relativePosition); void apply(float* sampleData, size_t samples, size_t relativePosition);
}; };
@@ -38,8 +47,8 @@ class BellDSP {
std::shared_ptr<AudioPipeline> getActivePipeline(); std::shared_ptr<AudioPipeline> getActivePipeline();
size_t process(uint8_t* data, size_t bytes, int channels, size_t process(uint8_t* data, size_t bytes, int channels, uint32_t sampleRate,
uint32_t sampleRate, BitWidth bitWidth); BitWidth bitWidth);
private: private:
std::shared_ptr<AudioPipeline> activePipeline; std::shared_ptr<AudioPipeline> activePipeline;
@@ -48,7 +57,6 @@ class BellDSP {
std::vector<float> dataLeft = std::vector<float>(1024); std::vector<float> dataLeft = std::vector<float>(1024);
std::vector<float> dataRight = std::vector<float>(1024); std::vector<float> dataRight = std::vector<float>(1024);
std::unique_ptr<AudioEffect> underflowEffect = nullptr; std::unique_ptr<AudioEffect> underflowEffect = nullptr;
std::unique_ptr<AudioEffect> startEffect = nullptr; std::unique_ptr<AudioEffect> startEffect = nullptr;
std::unique_ptr<AudioEffect> instantEffect = nullptr; std::unique_ptr<AudioEffect> instantEffect = nullptr;

View File

@@ -1,158 +1,158 @@
#pragma once #pragma once
#include <cmath> #include <stdint.h> // for uint32_t
#include <mutex> #include <map> // for map
#include <map> #include <memory> // for unique_ptr, allocator
#include <unordered_map> #include <mutex> // for scoped_lock
#include <stdexcept> // for invalid_argument
#include <string> // for string, operator<, hash, operator==
#include <unordered_map> // for operator!=, unordered_map, __hash_map_c...
#include <utility> // for pair
#include <vector> // for vector
#include "AudioTransform.h" #include "AudioTransform.h" // for AudioTransform
extern "C" int dsps_biquad_f32_ae32(const float *input, float *output, int len, float *coef, float *w); #include "StreamInfo.h" // for StreamInfo
#include "TransformConfig.h" // for TransformConfig
namespace bell extern "C" int dsps_biquad_f32_ae32(const float* input, float* output, int len,
{ float* coef, float* w);
class Biquad : public bell::AudioTransform
{
public:
Biquad();
~Biquad(){};
enum class Type namespace bell {
{ class Biquad : public bell::AudioTransform {
Free, public:
Highpass, Biquad();
Lowpass, ~Biquad(){};
HighpassFO,
LowpassFO,
Peaking, enum class Type {
Highshelf, Free,
HighshelfFO, Highpass,
Lowshelf, Lowpass,
LowshelfFO, HighpassFO,
Notch, LowpassFO,
Bandpass,
Allpass,
AllpassFO
};
std::map<std::string, float> currentConfig; Peaking,
Highshelf,
HighshelfFO,
Lowshelf,
LowshelfFO,
Notch,
Bandpass,
Allpass,
AllpassFO
};
std::unordered_map<std::string, Type> const strMapType = { std::map<std::string, float> currentConfig;
{"free", Type::Free},
{"highpass", Type::Highpass},
{"lowpass", Type::Lowpass},
{"highpass_fo", Type::HighpassFO},
{"lowpass_fo", Type::LowpassFO},
{"peaking", Type::Peaking},
{"highshelf", Type::Highshelf},
{"highshelf_fo", Type::HighpassFO},
{"lowshelf", Type::Lowshelf},
{"lowshelf_fo", Type::LowpassFO},
{"notch", Type::Notch},
{"bandpass", Type::Bandpass},
{"allpass", Type::Allpass},
{"allpass_fo", Type::AllpassFO},
};
float freq, q, gain; std::unordered_map<std::string, Type> const strMapType = {
int channel; {"free", Type::Free},
Biquad::Type type; {"highpass", Type::Highpass},
{"lowpass", Type::Lowpass},
{"highpass_fo", Type::HighpassFO},
{"lowpass_fo", Type::LowpassFO},
{"peaking", Type::Peaking},
{"highshelf", Type::Highshelf},
{"highshelf_fo", Type::HighpassFO},
{"lowshelf", Type::Lowshelf},
{"lowshelf_fo", Type::LowpassFO},
{"notch", Type::Notch},
{"bandpass", Type::Bandpass},
{"allpass", Type::Allpass},
{"allpass_fo", Type::AllpassFO},
};
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override; float freq, q, gain;
int channel;
Biquad::Type type;
void configure(Type type, std::map<std::string, float> &config); std::unique_ptr<StreamInfo> process(
std::unique_ptr<StreamInfo> data) override;
void sampleRateChanged(uint32_t sampleRate) override; void configure(Type type, std::map<std::string, float>& config);
void reconfigure() override void sampleRateChanged(uint32_t sampleRate) override;
{
std::scoped_lock lock(this->accessMutex);
std::map<std::string, float> biquadConfig;
this->channel = config->getChannels()[0];
float invalid = -0x7C; void reconfigure() override {
std::scoped_lock lock(this->accessMutex);
std::map<std::string, float> biquadConfig;
this->channel = config->getChannels()[0];
auto type = config->getString("biquad_type"); float invalid = -0x7C;
float bandwidth = config->getFloat("bandwidth", false, invalid);
float slope = config->getFloat("slope", false, invalid);
float gain = config->getFloat("gain", false, invalid);
float frequency = config->getFloat("frequency", false, invalid);
float q = config->getFloat("q", false, invalid);
if (currentConfig["bandwidth"] == bandwidth && auto type = config->getString("biquad_type");
currentConfig["slope"] == slope && float bandwidth = config->getFloat("bandwidth", false, invalid);
currentConfig["gain"] == gain && float slope = config->getFloat("slope", false, invalid);
currentConfig["frequency"] == frequency && float gain = config->getFloat("gain", false, invalid);
currentConfig["q"] == q) float frequency = config->getFloat("frequency", false, invalid);
{ float q = config->getFloat("q", false, invalid);
return;
}
if (bandwidth != invalid) if (currentConfig["bandwidth"] == bandwidth &&
biquadConfig["bandwidth"] = bandwidth; currentConfig["slope"] == slope && currentConfig["gain"] == gain &&
if (slope != invalid) currentConfig["frequency"] == frequency && currentConfig["q"] == q) {
biquadConfig["slope"] = slope; return;
if (gain != invalid) }
biquadConfig["gain"] = gain;
if (frequency != invalid)
biquadConfig["freq"] = frequency;
if (q != invalid)
biquadConfig["q"] = q;
if (type == "free") if (bandwidth != invalid)
{ biquadConfig["bandwidth"] = bandwidth;
biquadConfig["a1"] = config->getFloat("a1"); if (slope != invalid)
biquadConfig["a2"] = config->getFloat("a2"); biquadConfig["slope"] = slope;
biquadConfig["b0"] = config->getFloat("b0"); if (gain != invalid)
biquadConfig["b1"] = config->getFloat("b1"); biquadConfig["gain"] = gain;
biquadConfig["b2"] = config->getFloat("b2"); if (frequency != invalid)
} biquadConfig["freq"] = frequency;
if (q != invalid)
biquadConfig["q"] = q;
auto typeElement = strMapType.find(type); if (type == "free") {
if (typeElement != strMapType.end()) biquadConfig["a1"] = config->getFloat("a1");
{ biquadConfig["a2"] = config->getFloat("a2");
this->configure(typeElement->second, biquadConfig); biquadConfig["b0"] = config->getFloat("b0");
} biquadConfig["b1"] = config->getFloat("b1");
else biquadConfig["b2"] = config->getFloat("b2");
{ }
throw std::invalid_argument("No biquad of type " + type);
}
}
private: auto typeElement = strMapType.find(type);
float coeffs[5]; if (typeElement != strMapType.end()) {
float w[2] = {1.0, 1.0}; this->configure(typeElement->second, biquadConfig);
} else {
throw std::invalid_argument("No biquad of type " + type);
}
}
float sampleRate = 44100; private:
float coeffs[5];
float w[2] = {1.0, 1.0};
// Generator methods for different filter types float sampleRate = 44100;
void highPassCoEffs(float f, float q);
void highPassFOCoEffs(float f);
void lowPassCoEffs(float f, float q);
void lowPassFOCoEffs(float f);
void peakCoEffs(float f, float gain, float q); // Generator methods for different filter types
void peakCoEffsBandwidth(float f, float gain, float bandwidth); void highPassCoEffs(float f, float q);
void highPassFOCoEffs(float f);
void lowPassCoEffs(float f, float q);
void lowPassFOCoEffs(float f);
void highShelfCoEffs(float f, float gain, float q); void peakCoEffs(float f, float gain, float q);
void highShelfCoEffsSlope(float f, float gain, float slope); void peakCoEffsBandwidth(float f, float gain, float bandwidth);
void highShelfFOCoEffs(float f, float gain);
void lowShelfCoEffs(float f, float gain, float q); void highShelfCoEffs(float f, float gain, float q);
void lowShelfCoEffsSlope(float f, float gain, float slope); void highShelfCoEffsSlope(float f, float gain, float slope);
void lowShelfFOCoEffs(float f, float gain); void highShelfFOCoEffs(float f, float gain);
void notchCoEffs(float f, float gain, float q); void lowShelfCoEffs(float f, float gain, float q);
void notchCoEffsBandwidth(float f, float gain, float bandwidth); void lowShelfCoEffsSlope(float f, float gain, float slope);
void lowShelfFOCoEffs(float f, float gain);
void bandPassCoEffs(float f, float q); void notchCoEffs(float f, float gain, float q);
void bandPassCoEffsBandwidth(float f, float bandwidth); void notchCoEffsBandwidth(float f, float gain, float bandwidth);
void allPassCoEffs(float f, float q); void bandPassCoEffs(float f, float q);
void allPassCoEffsBandwidth(float f, float bandwidth); void bandPassCoEffsBandwidth(float f, float bandwidth);
void allPassFOCoEffs(float f);
void normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, float b2); void allPassCoEffs(float f, float q);
}; void allPassCoEffsBandwidth(float f, float bandwidth);
void allPassFOCoEffs(float f);
} void normalizeCoEffs(float a0, float a1, float a2, float b0, float b1,
float b2);
};
} // namespace bell

View File

@@ -1,85 +1,71 @@
#pragma once #pragma once
#include <vector> #include <stdint.h> // for uint32_t
#include <memory> #include <map> // for map
#include <cmath> #include <memory> // for unique_ptr, allocator
#include <mutex> #include <mutex> // for scoped_lock
#include <map> #include <stdexcept> // for invalid_argument
#include <string> // for string, operator==, char_traits, basic_...
#include <vector> // for vector
#include "Biquad.h" #include "AudioTransform.h" // for AudioTransform
#include "AudioTransform.h" #include "Biquad.h" // for Biquad
#include "StreamInfo.h" // for StreamInfo
#include "TransformConfig.h" // for TransformConfig
namespace bell namespace bell {
{ class BiquadCombo : public bell::AudioTransform {
class BiquadCombo : public bell::AudioTransform private:
{ std::vector<std::unique_ptr<bell::Biquad>> biquads;
private:
std::vector<std::unique_ptr<bell::Biquad>> biquads;
// Calculates Q values for Nth order Butterworth / Linkwitz-Riley filters // Calculates Q values for Nth order Butterworth / Linkwitz-Riley filters
std::vector<float> calculateBWQ(int order); std::vector<float> calculateBWQ(int order);
std::vector<float> calculateLRQ(int order); std::vector<float> calculateLRQ(int order);
public: public:
BiquadCombo(); BiquadCombo();
~BiquadCombo(){}; ~BiquadCombo(){};
int channel; int channel;
std::map<std::string, float> paramCache = { std::map<std::string, float> paramCache = {{"order", 0.0f},
{"order", 0.0f}, {"frequency", 0.0f}};
{"frequency", 0.0f}
};
enum class FilterType enum class FilterType { Highpass, Lowpass };
{
Highpass,
Lowpass
};
void linkwitzRiley(float freq, int order, FilterType type); void linkwitzRiley(float freq, int order, FilterType type);
void butterworth(float freq, int order, FilterType type); void butterworth(float freq, int order, FilterType type);
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override; std::unique_ptr<StreamInfo> process(
void sampleRateChanged(uint32_t sampleRate) override; std::unique_ptr<StreamInfo> data) override;
void sampleRateChanged(uint32_t sampleRate) override;
void reconfigure() override void reconfigure() override {
{ std::scoped_lock lock(this->accessMutex);
std::scoped_lock lock(this->accessMutex);
float freq = config->getFloat("frequency"); float freq = config->getFloat("frequency");
int order = config->getInt("order"); int order = config->getInt("order");
if (paramCache["frequency"] == freq && paramCache["order"] == order) if (paramCache["frequency"] == freq && paramCache["order"] == order) {
{ return;
return; } else {
} else { paramCache["frequency"] = freq;
paramCache["frequency"] = freq; paramCache["order"] = order;
paramCache["order"] = order; }
}
this->channel = config->getChannels()[0]; this->channel = config->getChannels()[0];
this->biquads = std::vector<std::unique_ptr<bell::Biquad>>(); this->biquads = std::vector<std::unique_ptr<bell::Biquad>>();
auto type = config->getString("combo_type"); auto type = config->getString("combo_type");
if (type == "lr_lowpass") if (type == "lr_lowpass") {
{ this->linkwitzRiley(freq, order, FilterType::Lowpass);
this->linkwitzRiley(freq, order, FilterType::Lowpass); } else if (type == "lr_highpass") {
} this->linkwitzRiley(freq, order, FilterType::Highpass);
else if (type == "lr_highpass") } else if (type == "bw_highpass") {
{ this->butterworth(freq, order, FilterType::Highpass);
this->linkwitzRiley(freq, order, FilterType::Highpass); } else if (type == "bw_lowpass") {
} this->butterworth(freq, order, FilterType::Highpass);
else if (type == "bw_highpass") } else {
{ throw std::invalid_argument("Invalid combo filter type");
this->butterworth(freq, order, FilterType::Highpass); }
} }
else if (type == "bw_lowpass")
{
this->butterworth(freq, order, FilterType::Highpass);
}
else
{
throw std::invalid_argument("Invalid combo filter type");
}
}
};
}; };
}; // namespace bell

View File

@@ -2,10 +2,10 @@
#include <atomic> #include <atomic>
#include <cmath> #include <cmath>
#include <functional>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <functional>
#include "BellUtils.h" #include "BellUtils.h"
#include "CircularBuffer.h" #include "CircularBuffer.h"
@@ -70,8 +70,9 @@ class CentralAudioBuffer {
*/ */
void clearBuffer() { void clearBuffer() {
std::scoped_lock lock(this->dataAccessMutex); std::scoped_lock lock(this->dataAccessMutex);
//size_t exceptSize = currentSampleRate + (sizeof(AudioChunk) - (currentSampleRate % sizeof(AudioChunk)));
audioBuffer->emptyBuffer(); audioBuffer->emptyBuffer();
hasChunk = false;
} }
void emptyCompletely() { void emptyCompletely() {
@@ -105,10 +106,10 @@ class CentralAudioBuffer {
} }
} }
AudioChunk currentChunk = { }; AudioChunk currentChunk = {};
bool hasChunk = false; bool hasChunk = false;
AudioChunk lastReadChunk = { }; AudioChunk lastReadChunk = {};
AudioChunk* readChunk() { AudioChunk* readChunk() {
std::scoped_lock lock(this->dataAccessMutex); std::scoped_lock lock(this->dataAccessMutex);

View File

@@ -1,103 +1,102 @@
#pragma once #pragma once
#include <vector> #include <math.h> // for expf
#include <memory> #include <stdint.h> // for uint32_t
#include <algorithm> #include <map> // for map
#include <cmath> #include <memory> // for unique_ptr
#include <math.h> #include <mutex> // for scoped_lock
#include <iostream> #include <string> // for string, operator<
#include <mutex> #include <vector> // for vector
#include <map>
#include "Biquad.h" #include "AudioTransform.h" // for AudioTransform
#include "AudioTransform.h" #include "StreamInfo.h" // for StreamInfo
#include "TransformConfig.h" // for TransformConfig
#define pow10f(x) expf(2.302585092994046f*x) #define pow10f(x) expf(2.302585092994046f * x)
// This is a fast approximation to log2() // This is a fast approximation to log2()
// Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E;
float log2f_approx(float X); float log2f_approx(float X);
#define log10f_fast(x) (log2f_approx(x)*0.3010299956639812f) #define log10f_fast(x) (log2f_approx(x) * 0.3010299956639812f)
namespace bell namespace bell {
{ class Compressor : public bell::AudioTransform {
class Compressor : public bell::AudioTransform private:
{ std::vector<int> channels;
private: std::vector<float> tmp;
std::vector<int> channels;
std::vector<float> tmp;
std::map<std::string, float> paramCache; std::map<std::string, float> paramCache;
float attack; float attack;
float release; float release;
float threshold; float threshold;
float factor; float factor;
float clipLimit; float clipLimit;
float makeupGain; float makeupGain;
float lastLoudness = -100.0f; float lastLoudness = -100.0f;
float sampleRate = 44100; float sampleRate = 44100;
public: public:
Compressor(); Compressor();
~Compressor(){}; ~Compressor(){};
void configure(std::vector<int> channels, float attack, float release, float threshold, float factor, float makeupGain); void configure(std::vector<int> channels, float attack, float release,
float threshold, float factor, float makeupGain);
void sumChannels(std::unique_ptr<StreamInfo> &data); void sumChannels(std::unique_ptr<StreamInfo>& data);
void calLoudness(); void calLoudness();
void calGain(); void calGain();
void applyGain(std::unique_ptr<StreamInfo> &data); void applyGain(std::unique_ptr<StreamInfo>& data);
void reconfigure() override void reconfigure() override {
{ std::scoped_lock lock(this->accessMutex);
std::scoped_lock lock(this->accessMutex); auto newChannels = config->getChannels();
auto newChannels = config->getChannels();
float newAttack = config->getFloat("attack"); float newAttack = config->getFloat("attack");
float newRelease = config->getFloat("release"); float newRelease = config->getFloat("release");
float newThreshold = config->getFloat("threshold"); float newThreshold = config->getFloat("threshold");
float newFactor = config->getFloat("factor"); float newFactor = config->getFloat("factor");
float newMakeupGain = config->getFloat("makeup_gain"); float newMakeupGain = config->getFloat("makeup_gain");
if (paramCache["attack"] == newAttack && if (paramCache["attack"] == newAttack &&
paramCache["release"] == newRelease && paramCache["release"] == newRelease &&
paramCache["threshold"] == newThreshold && paramCache["threshold"] == newThreshold &&
paramCache["factor"] == newFactor && paramCache["factor"] == newFactor &&
paramCache["makeup_gain"] == newMakeupGain) paramCache["makeup_gain"] == newMakeupGain) {
{ return;
return; } else {
}
else
{
paramCache["attack"] = newAttack; paramCache["attack"] = newAttack;
paramCache["release"] = newRelease; paramCache["release"] = newRelease;
paramCache["threshold"] = newThreshold; paramCache["threshold"] = newThreshold;
paramCache["factor"] = newFactor; paramCache["factor"] = newFactor;
paramCache["makeup_gain"] = newMakeupGain; paramCache["makeup_gain"] = newMakeupGain;
} }
this->configure(newChannels, newAttack, newRelease, newThreshold, newFactor, newMakeupGain); this->configure(newChannels, newAttack, newRelease, newThreshold, newFactor,
} newMakeupGain);
}
// void fromJSON(cJSON* json) override { // void fromJSON(cJSON* json) override {
// // get field channels // // get field channels
// channels = jsonGetChannels(json); // channels = jsonGetChannels(json);
// float attack = jsonGetNumber<float>(json, "attack", false, 0); // float attack = jsonGetNumber<float>(json, "attack", false, 0);
// float release = jsonGetNumber<float>(json, "release", false, 0); // float release = jsonGetNumber<float>(json, "release", false, 0);
// float factor = jsonGetNumber<float>(json, "factor", false, 4); // float factor = jsonGetNumber<float>(json, "factor", false, 4);
// float makeupGain = jsonGetNumber<float>(json, "makeup_gain", false, 0); // float makeupGain = jsonGetNumber<float>(json, "makeup_gain", false, 0);
// float threshold = jsonGetNumber<float>(json, "threshold", false, 0); // float threshold = jsonGetNumber<float>(json, "threshold", false, 0);
// this->configure(attack, release, clipLimit, threshold, factor, makeupGain); // this->configure(attack, release, clipLimit, threshold, factor, makeupGain);
// } // }
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override; std::unique_ptr<StreamInfo> process(
void sampleRateChanged(uint32_t sampleRate) override { this->sampleRate = sampleRate; }; std::unique_ptr<StreamInfo> data) override;
}; void sampleRateChanged(uint32_t sampleRate) override {
this->sampleRate = sampleRate;
};
}; };
}; // namespace bell

View File

@@ -1,40 +1,41 @@
#pragma once #pragma once
#include <cmath> #include <memory> // for unique_ptr
#include <mutex> #include <mutex> // for scoped_lock
#include <iostream> #include <vector> // for vector
#include "AudioTransform.h" #include "AudioTransform.h" // for AudioTransform
#include "StreamInfo.h" // for StreamInfo
#include "TransformConfig.h" // for TransformConfig
namespace bell namespace bell {
{ class Gain : public bell::AudioTransform {
class Gain : public bell::AudioTransform private:
{ float gainFactor = 1.0f;
private:
float gainFactor = 1.0f;
std::vector<int> channels; std::vector<int> channels;
public: public:
Gain(); Gain();
~Gain() {}; ~Gain(){};
float gainDb = 0.0; float gainDb = 0.0;
void configure(std::vector<int> channels, float gainDB); void configure(std::vector<int> channels, float gainDB);
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override; std::unique_ptr<StreamInfo> process(
std::unique_ptr<StreamInfo> data) override;
void reconfigure() override { void reconfigure() override {
std::scoped_lock lock(this->accessMutex); std::scoped_lock lock(this->accessMutex);
float gain = config->getFloat("gain"); float gain = config->getFloat("gain");
this->channels = config->getChannels(); this->channels = config->getChannels();
if (gainDb == gain) { if (gainDb == gain) {
return; return;
} }
this->configure(channels, gain); this->configure(channels, gain);
} }
}; };
} } // namespace bell

View File

@@ -3,108 +3,87 @@
#include "TransformConfig.h" #include "TransformConfig.h"
#include "cJSON.h" #include "cJSON.h"
namespace bell namespace bell {
{ class JSONTransformConfig : public bell::TransformConfig {
class JSONTransformConfig : public bell::TransformConfig private:
{ cJSON* json;
private:
cJSON *json;
public: public:
JSONTransformConfig(cJSON *body) JSONTransformConfig(cJSON* body) { this->json = body; };
{ ~JSONTransformConfig(){};
this->json = body;
};
~JSONTransformConfig(){};
std::string rawGetString(const std::string &field) override std::string rawGetString(const std::string& field) override {
{ cJSON* value = cJSON_GetObjectItem(json, field.c_str());
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
if (value != NULL && cJSON_IsString(value)) if (value != NULL && cJSON_IsString(value)) {
{ return std::string(value->valuestring);
return std::string(value->valuestring); }
}
return "invalid"; return "invalid";
}
std::vector<int> rawGetIntArray(const std::string& field) override {
std::vector<int> result;
cJSON* value = cJSON_GetObjectItem(json, field.c_str());
if (value != NULL && cJSON_IsArray(value)) {
for (int i = 0; i < cJSON_GetArraySize(value); i++) {
cJSON* item = cJSON_GetArrayItem(value, i);
if (item != NULL && cJSON_IsNumber(item)) {
result.push_back(item->valueint);
} }
}
}
std::vector<int> rawGetIntArray(const std::string &field) override return result;
{ }
std::vector<int> result;
cJSON *value = cJSON_GetObjectItem(json, field.c_str()); std::vector<float> rawGetFloatArray(const std::string& field) override {
std::vector<float> result;
if (value != NULL && cJSON_IsArray(value)) cJSON* value = cJSON_GetObjectItem(json, field.c_str());
{
for (int i = 0; i < cJSON_GetArraySize(value); i++)
{
cJSON *item = cJSON_GetArrayItem(value, i);
if (item != NULL && cJSON_IsNumber(item))
{
result.push_back(item->valueint);
}
}
}
return result; if (value != NULL && cJSON_IsArray(value)) {
for (int i = 0; i < cJSON_GetArraySize(value); i++) {
cJSON* item = cJSON_GetArrayItem(value, i);
if (item != NULL && cJSON_IsNumber(item)) {
result.push_back(item->valuedouble);
} }
}
}
std::vector<float> rawGetFloatArray(const std::string &field) override return result;
{ }
std::vector<float> result;
cJSON *value = cJSON_GetObjectItem(json, field.c_str()); int rawGetInt(const std::string& field) override {
cJSON* value = cJSON_GetObjectItem(json, field.c_str());
if (value != NULL && cJSON_IsArray(value)) if (value != NULL && cJSON_IsNumber(value)) {
{ return (int)value->valueint;
for (int i = 0; i < cJSON_GetArraySize(value); i++) }
{
cJSON *item = cJSON_GetArrayItem(value, i);
if (item != NULL && cJSON_IsNumber(item))
{
result.push_back(item->valuedouble);
}
}
}
return result; return invalidInt;
} }
int rawGetInt(const std::string &field) override bool isArray(const std::string& field) override {
{ cJSON* value = cJSON_GetObjectItem(json, field.c_str());
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
if (value != NULL && cJSON_IsNumber(value)) if (value != NULL && cJSON_IsArray(value)) {
{ return true;
return (int)value->valueint; }
}
return invalidInt; return false;
} }
bool isArray(const std::string &field) override float rawGetFloat(const std::string& field) override {
{ cJSON* value = cJSON_GetObjectItem(json, field.c_str());
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
if (value != NULL && cJSON_IsArray(value)) if (value != NULL && cJSON_IsNumber(value)) {
{ return (float)value->valuedouble;
return true; }
}
return false; return invalidInt;
} }
};
float rawGetFloat(const std::string &field) override } // namespace bell
{
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
if (value != NULL && cJSON_IsNumber(value))
{
return (float)value->valuedouble;
}
return invalidInt;
}
};
}

View File

@@ -1,36 +1,28 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <vector>
#include <string> #include <string>
#include <vector>
namespace bell namespace bell {
{ enum class Channels { LEFT, RIGHT, LEFT_RIGHT };
enum class Channels {
LEFT,
RIGHT,
LEFT_RIGHT
};
enum class SampleRate : uint32_t enum class SampleRate : uint32_t {
{ SR_44100 = 44100,
SR_44100 = 44100, SR_48000 = 48000,
SR_48000 = 48000,
};
enum class BitWidth : uint32_t
{
BW_16 = 16,
BW_24 = 24,
BW_32 = 32,
};
typedef struct
{
float** data;
BitWidth bitwidth;
int numChannels;
SampleRate sampleRate;
size_t numSamples;
} StreamInfo;
}; };
enum class BitWidth : uint32_t {
BW_16 = 16,
BW_24 = 24,
BW_32 = 32,
};
typedef struct {
float** data;
BitWidth bitwidth;
int numChannels;
SampleRate sampleRate;
size_t numSamples;
} StreamInfo;
}; // namespace bell

View File

@@ -1,134 +1,112 @@
#pragma once #pragma once
#include <memory>
#include <vector>
#include <string>
#include <variant>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <memory>
#include <string>
#include <variant>
#include <vector>
namespace bell namespace bell {
{ class TransformConfig {
class TransformConfig protected:
{ int invalidInt = -0x7C;
protected: std::string invalidString = "_invalid";
int invalidInt = -0x7C;
std::string invalidString = "_invalid";
public: public:
TransformConfig() = default; TransformConfig() = default;
virtual ~TransformConfig() = default; virtual ~TransformConfig() = default;
int currentVolume = 60; int currentVolume = 60;
virtual std::string rawGetString(const std::string &field) = 0; virtual std::string rawGetString(const std::string& field) = 0;
virtual int rawGetInt(const std::string &field) = 0; virtual int rawGetInt(const std::string& field) = 0;
virtual bool isArray(const std::string &field) = 0; virtual bool isArray(const std::string& field) = 0;
virtual float rawGetFloat(const std::string &field) = 0; virtual float rawGetFloat(const std::string& field) = 0;
virtual std::vector<float> rawGetFloatArray(const std::string &field) = 0; virtual std::vector<float> rawGetFloatArray(const std::string& field) = 0;
virtual std::vector<int> rawGetIntArray(const std::string &field) = 0; virtual std::vector<int> rawGetIntArray(const std::string& field) = 0;
typedef std::variant<int, float, std::string> Value; typedef std::variant<int, float, std::string> Value;
std::map<std::string, std::vector<Value>> rawValues; std::map<std::string, std::vector<Value>> rawValues;
Value getRawValue(const std::string &field) Value getRawValue(const std::string& field) {
{ int index = this->currentVolume * (rawValues[field].size()) / 100;
int index = this->currentVolume * (rawValues[field].size()) / 100; if (index >= rawValues[field].size())
if (index >= rawValues[field].size()) index = rawValues[field].size() - 1;
index = rawValues[field].size() - 1; return rawValues[field][index];
return rawValues[field][index]; }
std::string getString(const std::string& field, bool isRequired = false,
std::string defaultValue = "") {
if (rawValues.count(field) == 0) {
rawValues[field] = std::vector<Value>({Value(rawGetString(field))});
}
auto val = std::get<std::string>(getRawValue(field));
if (val == invalidString) {
if (isRequired)
throw std::invalid_argument("Field " + field + " is required");
else
return defaultValue;
} else
return val;
}
int getInt(const std::string& field, bool isRequired = false,
int defaultValue = 0) {
if (rawValues.count(field) == 0) {
if (isArray(field)) {
rawValues[field] = std::vector<Value>();
for (auto f : rawGetIntArray(field)) {
rawValues[field].push_back(f);
} }
} else {
rawValues[field] = std::vector<Value>({Value(rawGetInt(field))});
}
}
std::string getString(const std::string &field, bool isRequired = false, std::string defaultValue = "") auto val = std::get<int>(getRawValue(field));
{ if (val == invalidInt) {
if (rawValues.count(field) == 0) if (isRequired)
{ throw std::invalid_argument("Field " + field + " is required");
rawValues[field] = std::vector<Value>({Value(rawGetString(field))}); else
} return defaultValue;
auto val = std::get<std::string>(getRawValue(field)); } else
if (val == invalidString) return val;
{ }
if (isRequired)
throw std::invalid_argument("Field " + field + " is required"); float getFloat(const std::string& field, bool isRequired = false,
else float defaultValue = 0) {
return defaultValue; if (rawValues.count(field) == 0) {
} if (isArray(field)) {
else
return val; rawValues[field] = std::vector<Value>();
for (auto f : rawGetFloatArray(field)) {
rawValues[field].push_back(f);
} }
} else {
rawValues[field] = std::vector<Value>({Value(rawGetFloat(field))});
}
}
auto val = std::get<float>(getRawValue(field));
if (val == invalidInt) {
if (isRequired)
throw std::invalid_argument("Field " + field + " is required");
else
return defaultValue;
} else
return val;
}
int getInt(const std::string &field, bool isRequired = false, int defaultValue = 0) std::vector<int> getChannels() {
{ auto channel = getInt("channel", false, invalidInt);
if (rawValues.count(field) == 0)
{
if (isArray(field))
{
rawValues[field] = std::vector<Value>();
for (auto f : rawGetIntArray(field))
{
rawValues[field].push_back(f);
}
}
else
{
rawValues[field] = std::vector<Value>({Value(rawGetInt(field))});
}
}
auto val = std::get<int>(getRawValue(field)); if (channel != invalidInt) {
if (val == invalidInt) return std::vector<int>({channel});
{ }
if (isRequired)
throw std::invalid_argument("Field " + field + " is required");
else
return defaultValue;
}
else
return val;
}
float getFloat(const std::string &field, bool isRequired = false, float defaultValue = 0) return rawGetIntArray("channels");
{ }
if (rawValues.count(field) == 0) };
{ } // namespace bell
if (isArray(field))
{
rawValues[field] = std::vector<Value>();
for (auto f : rawGetFloatArray(field))
{
rawValues[field].push_back(f);
}
}
else
{
rawValues[field] = std::vector<Value>({ Value(rawGetFloat(field)) });
}
}
auto val = std::get<float>(getRawValue(field));
if (val == invalidInt)
{
if (isRequired)
throw std::invalid_argument("Field " + field + " is required");
else
return defaultValue;
}
else
return val;
}
std::vector<int> getChannels()
{
auto channel = getInt("channel", false, invalidInt);
if (channel != invalidInt)
{
return std::vector<int>({channel});
}
return rawGetIntArray("channels");
}
};
}

View File

@@ -2,45 +2,42 @@
#include "driver/i2s.h" #include "driver/i2s.h"
AC101AudioSink::AC101AudioSink() AC101AudioSink::AC101AudioSink() {
{ // Disable software volume control, all handled by ::volumeChanged
// Disable software volume control, all handled by ::volumeChanged softwareVolumeControl = false;
softwareVolumeControl = false;
i2s_config_t i2s_config = { i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX
.sample_rate = 44100, .sample_rate = 44100,
.bits_per_sample = (i2s_bits_per_sample_t)16, .bits_per_sample = (i2s_bits_per_sample_t)16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = 0, //Default interrupt priority .intr_alloc_flags = 0, //Default interrupt priority
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = 512, .dma_buf_len = 512,
.use_apll = true, .use_apll = true,
.tx_desc_auto_clear = true //Auto clear tx descriptor on underflow .tx_desc_auto_clear = true //Auto clear tx descriptor on underflow
}; };
i2s_pin_config_t pin_config = { i2s_pin_config_t pin_config = {
.bck_io_num = 27, .bck_io_num = 27,
.ws_io_num = 26, .ws_io_num = 26,
.data_out_num = 25, .data_out_num = 25,
.data_in_num = -1 //Not used .data_in_num = -1 //Not used
}; };
dac = &dac_a1s; dac = &dac_a1s;
dac->init(0, 0, &i2s_config); dac->init(0, 0, &i2s_config);
dac->speaker(false); dac->speaker(false);
dac->power(ADAC_ON); dac->power(ADAC_ON);
startI2sFeed(); startI2sFeed();
} }
AC101AudioSink::~AC101AudioSink() AC101AudioSink::~AC101AudioSink() {}
{
}
void AC101AudioSink::volumeChanged(uint16_t volume) { void AC101AudioSink::volumeChanged(uint16_t volume) {
dac->volume(volume, volume); dac->volume(volume, volume);
} }

View File

@@ -1,47 +1,45 @@
#include "BufferedAudioSink.h" #include "BufferedAudioSink.h"
#include "driver/i2s.h" #include "driver/i2s.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h" #include "freertos/ringbuf.h"
#include "freertos/task.h"
RingbufHandle_t dataBuffer; RingbufHandle_t dataBuffer;
static void i2sFeed(void *pvParameters) static void i2sFeed(void* pvParameters) {
{ while (true) {
while (true) size_t itemSize;
{ char* item = (char*)xRingbufferReceiveUpTo(dataBuffer, &itemSize,
size_t itemSize; portMAX_DELAY, 512);
char *item = (char *)xRingbufferReceiveUpTo(dataBuffer, &itemSize, portMAX_DELAY, 512); if (item != NULL) {
if (item != NULL) size_t written = 0;
{ while (written < itemSize) {
size_t written = 0; i2s_write((i2s_port_t)0, item, itemSize, &written, portMAX_DELAY);
while (written < itemSize) }
{ vRingbufferReturnItem(dataBuffer, (void*)item);
i2s_write((i2s_port_t)0, item, itemSize, &written, portMAX_DELAY);
}
vRingbufferReturnItem(dataBuffer, (void *)item);
}
} }
}
} }
void BufferedAudioSink::startI2sFeed(size_t buf_size) void BufferedAudioSink::startI2sFeed(size_t buf_size) {
{ dataBuffer = xRingbufferCreate(buf_size, RINGBUF_TYPE_BYTEBUF);
dataBuffer = xRingbufferCreate(buf_size, RINGBUF_TYPE_BYTEBUF); xTaskCreatePinnedToCore(&i2sFeed, "i2sFeed", 4096, NULL, 10, NULL,
xTaskCreatePinnedToCore(&i2sFeed, "i2sFeed", 4096, NULL, 10, NULL, tskNO_AFFINITY); tskNO_AFFINITY);
} }
void BufferedAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) void BufferedAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) {
{ feedPCMFramesInternal(buffer, bytes);
feedPCMFramesInternal(buffer, bytes);
} }
void BufferedAudioSink::feedPCMFramesInternal(const void *pvItem, size_t xItemSize) void BufferedAudioSink::feedPCMFramesInternal(const void* pvItem,
{ size_t xItemSize) {
xRingbufferSend(dataBuffer, pvItem, xItemSize, portMAX_DELAY); xRingbufferSend(dataBuffer, pvItem, xItemSize, portMAX_DELAY);
} }
bool BufferedAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { bool BufferedAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount,
// TODO override this for sinks with custom mclk uint8_t bitDepth) {
i2s_set_clk((i2s_port_t)0, sampleRate, (i2s_bits_per_sample_t)bitDepth, (i2s_channel_t)channelCount); // TODO override this for sinks with custom mclk
return true; i2s_set_clk((i2s_port_t)0, sampleRate, (i2s_bits_per_sample_t)bitDepth,
(i2s_channel_t)channelCount);
return true;
} }

View File

@@ -1,106 +1,101 @@
#include "ES8311AudioSink.h" #include "ES8311AudioSink.h"
extern "C" { extern "C" {
#include "es8311.h" #include "es8311.h"
} }
ES8311AudioSink::ES8311AudioSink() ES8311AudioSink::ES8311AudioSink() {
{ this->softwareVolumeControl = false;
this->softwareVolumeControl = false; esp_err_t ret_val = ESP_OK;
esp_err_t ret_val = ESP_OK; Es8311Config cfg = {
Es8311Config cfg = { .esMode = ES_MODE_SLAVE,
.esMode = ES_MODE_SLAVE, .i2c_port_num = I2C_NUM_0,
.i2c_port_num = I2C_NUM_0, .i2c_cfg =
.i2c_cfg = { {
.mode = I2C_MODE_MASTER, .mode = I2C_MODE_MASTER,
.sda_io_num = 1, .sda_io_num = 1,
.scl_io_num = 2, .scl_io_num = 2,
.sda_pullup_en = GPIO_PULLUP_ENABLE, .sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE,
}, },
.dacOutput = (DacOutput) (DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2), .dacOutput = (DacOutput)(DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 |
.adcInput = ADC_INPUT_LINPUT1_RINPUT1, DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2),
}; .adcInput = ADC_INPUT_LINPUT1_RINPUT1,
cfg.i2c_cfg.master.clk_speed = 100000; };
Es8311Init(&cfg); cfg.i2c_cfg.master.clk_speed = 100000;
Es8311SetBitsPerSample(ES_MODULE_DAC, BIT_LENGTH_16BITS); Es8311Init(&cfg);
Es8311ConfigFmt(ES_MODULE_DAC, ES_I2S_NORMAL); Es8311SetBitsPerSample(ES_MODULE_DAC, BIT_LENGTH_16BITS);
Es8311SetVoiceVolume(60); Es8311ConfigFmt(ES_MODULE_DAC, ES_I2S_NORMAL);
Es8311Start(ES_MODULE_DAC); Es8311SetVoiceVolume(60);
ES8311WriteReg(ES8311_CLK_MANAGER_REG01, 0xbf); Es8311Start(ES_MODULE_DAC);
ES8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x18); ES8311WriteReg(ES8311_CLK_MANAGER_REG01, 0xbf);
ES8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x18);
// .codec_mode = AUDIO_HAL_CODEC_MODE_DECODE, // .codec_mode = AUDIO_HAL_CODEC_MODE_DECODE,
// .i2s_iface = { // .i2s_iface = {
// .mode = AUDIO_HAL_MODE_SLAVE, // .mode = AUDIO_HAL_MODE_SLAVE,
// .fmt = AUDIO_HAL_I2S_NORMAL, // .fmt = AUDIO_HAL_I2S_NORMAL,
// .samples = AUDIO_HAL_44K_SAMPLES, // .samples = AUDIO_HAL_44K_SAMPLES,
// .bits = AUDIO_HAL_BIT_LENGTH_16BITS, // .bits = AUDIO_HAL_BIT_LENGTH_16BITS,
// }, // },
// }; // };
// ret_val |= es8311_codec_init(&cfg); // ret_val |= es8311_codec_init(&cfg);
// ret_val |= es8311_set_bits_per_sample(cfg.i2s_iface.bits); // ret_val |= es8311_set_bits_per_sample(cfg.i2s_iface.bits);
// ret_val |= es8311_config_fmt((es_i2s_fmt_t) cfg.i2s_iface.fmt); // ret_val |= es8311_config_fmt((es_i2s_fmt_t) cfg.i2s_iface.fmt);
// ret_val |= es8311_codec_set_voice_volume(60); // ret_val |= es8311_codec_set_voice_volume(60);
// ret_val |= es8311_codec_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START); // ret_val |= es8311_codec_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START);
// ret_val |= es8311_codec_set_clk(); // ret_val |= es8311_codec_set_clk();
i2s_config_t i2s_config = { i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX
.sample_rate = 44100, .sample_rate = 44100,
.bits_per_sample = (i2s_bits_per_sample_t)16, .bits_per_sample = (i2s_bits_per_sample_t)16,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 2-channels .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 2-channels
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = 0, // Default interrupt priority .intr_alloc_flags = 0, // Default interrupt priority
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = 512, .dma_buf_len = 512,
.use_apll = false, .use_apll = false,
.tx_desc_auto_clear = true, // Auto clear tx descriptor on underflow .tx_desc_auto_clear = true, // Auto clear tx descriptor on underflow
}; };
i2s_pin_config_t pin_config = { i2s_pin_config_t pin_config = {
.mck_io_num = 42, .mck_io_num = 42,
.bck_io_num = 40, .bck_io_num = 40,
.ws_io_num = 41, .ws_io_num = 41,
.data_out_num = 39, .data_out_num = 39,
.data_in_num = -1, .data_in_num = -1,
}; };
int err; int err;
err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
if (err != ESP_OK) if (err != ESP_OK) {
{ ESP_LOGE("OI", "i2s driver installation error: %d", err);
ESP_LOGE("OI", "i2s driver installation error: %d", err); }
}
err = i2s_set_pin((i2s_port_t)0, &pin_config); err = i2s_set_pin((i2s_port_t)0, &pin_config);
if (err != ESP_OK) if (err != ESP_OK) {
{ ESP_LOGE("OI", "i2s set pin error: %d", err);
ESP_LOGE("OI", "i2s set pin error: %d", err); }
}
// PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); // PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
// REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0); // REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0);
// ESP_LOGI("OI", "MCLK output on CLK_OUT1"); // ESP_LOGI("OI", "MCLK output on CLK_OUT1");
startI2sFeed(); startI2sFeed();
} }
void ES8311AudioSink::volumeChanged(uint16_t volume) void ES8311AudioSink::volumeChanged(uint16_t volume) {
{ Es8311SetVoiceVolume(volume);
Es8311SetVoiceVolume(volume);
} }
void ES8311AudioSink::writeReg(uint8_t reg_add, uint8_t data) void ES8311AudioSink::writeReg(uint8_t reg_add, uint8_t data) {}
{
}
void ES8311AudioSink::setSampleRate(uint32_t sampleRate) { void ES8311AudioSink::setSampleRate(uint32_t sampleRate) {
std::cout << "ES8311AudioSink::setSampleRate(" << sampleRate << ")" << std::endl; std::cout << "ES8311AudioSink::setSampleRate(" << sampleRate << ")"
// i2s set sample rate << std::endl;
es8311_Codec_Startup(0, sampleRate); // i2s set sample rate
es8311_Codec_Startup(0, sampleRate);
} }
ES8311AudioSink::~ES8311AudioSink() ES8311AudioSink::~ES8311AudioSink() {}
{
}

View File

@@ -1,150 +1,145 @@
#include "ES8388AudioSink.h" #include "ES8388AudioSink.h"
struct es8388_cmd_s { struct es8388_cmd_s {
uint8_t reg; uint8_t reg;
uint8_t value; uint8_t value;
}; };
ES8388AudioSink::ES8388AudioSink() ES8388AudioSink::ES8388AudioSink() {
{ // configure i2c
// configure i2c i2c_config = {
i2c_config = { .mode = I2C_MODE_MASTER,
.mode = I2C_MODE_MASTER, .sda_io_num = 33,
.sda_io_num = 33, .scl_io_num = 32,
.scl_io_num = 32, .sda_pullup_en = GPIO_PULLUP_ENABLE,
.sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE, };
};
i2c_config.master.clk_speed = 100000; i2c_config.master.clk_speed = 100000;
i2s_config_t i2s_config = { i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX
.sample_rate = 44100, .sample_rate = 44100,
.bits_per_sample = (i2s_bits_per_sample_t)16, .bits_per_sample = (i2s_bits_per_sample_t)16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB,
.intr_alloc_flags = 0, //Default interrupt priority .intr_alloc_flags = 0, //Default interrupt priority
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = 512, .dma_buf_len = 512,
.use_apll = true, .use_apll = true,
.tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow
.fixed_mclk = 256 * 44100 .fixed_mclk = 256 * 44100};
};
i2s_pin_config_t pin_config = { i2s_pin_config_t pin_config = {
.bck_io_num = 27, .bck_io_num = 27,
.ws_io_num = 25, .ws_io_num = 25,
.data_out_num = 26, .data_out_num = 26,
.data_in_num = -1 //Not used .data_in_num = -1 //Not used
}; };
int err; int err;
err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE("OI", "i2s driver installation error: %d", err); ESP_LOGE("OI", "i2s driver installation error: %d", err);
} }
err = i2s_set_pin((i2s_port_t)0, &pin_config); err = i2s_set_pin((i2s_port_t)0, &pin_config);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE("OI", "i2s set pin error: %d", err); ESP_LOGE("OI", "i2s set pin error: %d", err);
} }
err = i2c_param_config(0, &i2c_config); err = i2c_param_config(0, &i2c_config);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE("OI", "i2c param config error: %d", err); ESP_LOGE("OI", "i2c param config error: %d", err);
} }
err = i2c_driver_install(0, I2C_MODE_MASTER, 0, 0, 0); err = i2c_driver_install(0, I2C_MODE_MASTER, 0, 0, 0);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE("OI", "i2c driver installation error: %d", err); ESP_LOGE("OI", "i2c driver installation error: %d", err);
} }
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
err = i2c_master_start(i2c_cmd); err = i2c_master_start(i2c_cmd);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE("OI", "i2c master start error: %d", err); ESP_LOGE("OI", "i2c master start error: %d", err);
} }
/* mute DAC during setup, power up all systems, slave mode */ /* mute DAC during setup, power up all systems, slave mode */
writeReg(ES8388_DACCONTROL3, 0x04); writeReg(ES8388_DACCONTROL3, 0x04);
writeReg(ES8388_CONTROL2, 0x50); writeReg(ES8388_CONTROL2, 0x50);
writeReg(ES8388_CHIPPOWER, 0x00); writeReg(ES8388_CHIPPOWER, 0x00);
writeReg(ES8388_MASTERMODE, 0x00); writeReg(ES8388_MASTERMODE, 0x00);
/* power up DAC and enable LOUT1+2 / ROUT1+2, ADC sample rate = DAC sample rate */ /* power up DAC and enable LOUT1+2 / ROUT1+2, ADC sample rate = DAC sample rate */
writeReg(ES8388_DACPOWER, 0x3e); writeReg(ES8388_DACPOWER, 0x3e);
writeReg(ES8388_CONTROL1, 0x12); writeReg(ES8388_CONTROL1, 0x12);
/* DAC I2S setup: 16 bit word length, I2S format; MCLK / Fs = 256*/ /* DAC I2S setup: 16 bit word length, I2S format; MCLK / Fs = 256*/
writeReg(ES8388_DACCONTROL1, 0x18); writeReg(ES8388_DACCONTROL1, 0x18);
writeReg(ES8388_DACCONTROL2, 0x02); writeReg(ES8388_DACCONTROL2, 0x02);
/* DAC to output route mixer configuration: ADC MIX TO OUTPUT */ /* DAC to output route mixer configuration: ADC MIX TO OUTPUT */
writeReg(ES8388_DACCONTROL16, 0x1B); writeReg(ES8388_DACCONTROL16, 0x1B);
writeReg(ES8388_DACCONTROL17, 0x90); writeReg(ES8388_DACCONTROL17, 0x90);
writeReg(ES8388_DACCONTROL20, 0x90); writeReg(ES8388_DACCONTROL20, 0x90);
/* DAC and ADC use same LRCK, enable MCLK input; output resistance setup */ /* DAC and ADC use same LRCK, enable MCLK input; output resistance setup */
writeReg(ES8388_DACCONTROL21, 0x80); writeReg(ES8388_DACCONTROL21, 0x80);
writeReg(ES8388_DACCONTROL23, 0x00); writeReg(ES8388_DACCONTROL23, 0x00);
/* DAC volume control: 0dB (maximum, unattented) */ /* DAC volume control: 0dB (maximum, unattented) */
writeReg(ES8388_DACCONTROL5, 0x00); writeReg(ES8388_DACCONTROL5, 0x00);
writeReg(ES8388_DACCONTROL4, 0x00); writeReg(ES8388_DACCONTROL4, 0x00);
/* power down ADC while configuring; volume: +9dB for both channels */ /* power down ADC while configuring; volume: +9dB for both channels */
writeReg(ES8388_ADCPOWER, 0xff); writeReg(ES8388_ADCPOWER, 0xff);
writeReg(ES8388_ADCCONTROL1, 0x88); // +24db writeReg(ES8388_ADCCONTROL1, 0x88); // +24db
/* select LINPUT2 / RINPUT2 as ADC input; stereo; 16 bit word length, format right-justified, MCLK / Fs = 256 */ /* select LINPUT2 / RINPUT2 as ADC input; stereo; 16 bit word length, format right-justified, MCLK / Fs = 256 */
writeReg(ES8388_ADCCONTROL2, 0xf0); // 50 writeReg(ES8388_ADCCONTROL2, 0xf0); // 50
writeReg(ES8388_ADCCONTROL3, 0x80); // 00 writeReg(ES8388_ADCCONTROL3, 0x80); // 00
writeReg(ES8388_ADCCONTROL4, 0x0e); writeReg(ES8388_ADCCONTROL4, 0x0e);
writeReg(ES8388_ADCCONTROL5, 0x02); writeReg(ES8388_ADCCONTROL5, 0x02);
/* set ADC volume */ /* set ADC volume */
writeReg(ES8388_ADCCONTROL8, 0x20); writeReg(ES8388_ADCCONTROL8, 0x20);
writeReg(ES8388_ADCCONTROL9, 0x20); writeReg(ES8388_ADCCONTROL9, 0x20);
/* set LOUT1 / ROUT1 volume: 0dB (unattenuated) */ /* set LOUT1 / ROUT1 volume: 0dB (unattenuated) */
writeReg(ES8388_DACCONTROL24, 0x1e); writeReg(ES8388_DACCONTROL24, 0x1e);
writeReg(ES8388_DACCONTROL25, 0x1e); writeReg(ES8388_DACCONTROL25, 0x1e);
/* set LOUT2 / ROUT2 volume: 0dB (unattenuated) */ /* set LOUT2 / ROUT2 volume: 0dB (unattenuated) */
writeReg(ES8388_DACCONTROL26, 0x1e); writeReg(ES8388_DACCONTROL26, 0x1e);
writeReg(ES8388_DACCONTROL27, 0x1e); writeReg(ES8388_DACCONTROL27, 0x1e);
/* power up and enable DAC; power up ADC (no MIC bias) */ /* power up and enable DAC; power up ADC (no MIC bias) */
writeReg(ES8388_DACPOWER, 0x3c); writeReg(ES8388_DACPOWER, 0x3c);
writeReg(ES8388_DACCONTROL3, 0x00); writeReg(ES8388_DACCONTROL3, 0x00);
writeReg(ES8388_ADCPOWER, 0x00); writeReg(ES8388_ADCPOWER, 0x00);
startI2sFeed(); startI2sFeed();
} }
void ES8388AudioSink::writeReg(uint8_t reg_add, uint8_t data) void ES8388AudioSink::writeReg(uint8_t reg_add, uint8_t data) {
{
int res = 0; int res = 0;
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_cmd_handle_t cmd = i2c_cmd_link_create();
res |= i2c_master_start(cmd); res |= i2c_master_start(cmd);
res |= i2c_master_write_byte(cmd, ES8388_ADDR, ACK_CHECK_EN); res |= i2c_master_write_byte(cmd, ES8388_ADDR, ACK_CHECK_EN);
res |= i2c_master_write_byte(cmd, reg_add, ACK_CHECK_EN); res |= i2c_master_write_byte(cmd, reg_add, ACK_CHECK_EN);
res |= i2c_master_write_byte(cmd, data, ACK_CHECK_EN); res |= i2c_master_write_byte(cmd, data, ACK_CHECK_EN);
res |= i2c_master_stop(cmd); res |= i2c_master_stop(cmd);
res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd); i2c_cmd_link_delete(cmd);
if (res != ESP_OK) { if (res != ESP_OK) {
ESP_LOGE("RR", "Unable to write to ES8388: %d", res); ESP_LOGE("RR", "Unable to write to ES8388: %d", res);
}else{ } else {
ESP_LOGE("RR", "register successfull written."); ESP_LOGE("RR", "register successfull written.");
} }
} }
ES8388AudioSink::~ES8388AudioSink() ES8388AudioSink::~ES8388AudioSink() {}
{
}

View File

@@ -2,35 +2,31 @@
#include "driver/i2s.h" #include "driver/i2s.h"
ES9018AudioSink::ES9018AudioSink() ES9018AudioSink::ES9018AudioSink() {
{ i2s_config_t i2s_config = {
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX
.sample_rate = 44100, .sample_rate = 44100,
.bits_per_sample = (i2s_bits_per_sample_t)16, .bits_per_sample = (i2s_bits_per_sample_t)16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB,
.intr_alloc_flags = 0, //Default interrupt priority .intr_alloc_flags = 0, //Default interrupt priority
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = 512, .dma_buf_len = 512,
.use_apll = true, .use_apll = true,
.tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow
.fixed_mclk = 384 * 44100 .fixed_mclk = 384 * 44100};
};
i2s_pin_config_t pin_config = { i2s_pin_config_t pin_config = {
.bck_io_num = 27, .bck_io_num = 27,
.ws_io_num = 32, .ws_io_num = 32,
.data_out_num = 25, .data_out_num = 25,
.data_in_num = -1 //Not used .data_in_num = -1 //Not used
}; };
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
i2s_set_pin((i2s_port_t)0, &pin_config); i2s_set_pin((i2s_port_t)0, &pin_config);
startI2sFeed(); startI2sFeed();
} }
ES9018AudioSink::~ES9018AudioSink() ES9018AudioSink::~ES9018AudioSink() {}
{
}

View File

@@ -1,35 +1,32 @@
#include "InternalAudioSink.h" #include "InternalAudioSink.h"
#include "driver/i2s.h" #include "driver/i2s.h"
InternalAudioSink::InternalAudioSink() InternalAudioSink::InternalAudioSink() {
{ softwareVolumeControl = true;
softwareVolumeControl = true; usign = true;
usign = true; #ifdef I2S_MODE_DAC_BUILT_IN
#ifdef I2S_MODE_DAC_BUILT_IN
i2s_config_t i2s_config = { i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), // Only TX .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX |
.sample_rate = (i2s_bits_per_sample_t)44100, I2S_MODE_DAC_BUILT_IN), // Only TX
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .sample_rate = (i2s_bits_per_sample_t)44100,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.intr_alloc_flags = 0,//ESP_INTR_FLAG_LEVEL1 .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S,
.dma_buf_count = 6, .intr_alloc_flags = 0, //ESP_INTR_FLAG_LEVEL1
.dma_buf_len = 512, .dma_buf_count = 6,
.use_apll = true, .dma_buf_len = 512,
.tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow .use_apll = true,
.fixed_mclk=-1 .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow
}; .fixed_mclk = -1};
//install and start i2s driver //install and start i2s driver
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
//init DAC //init DAC
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
#endif #endif
startI2sFeed(); startI2sFeed();
} }
InternalAudioSink::~InternalAudioSink() InternalAudioSink::~InternalAudioSink() {}
{
}

View File

@@ -2,35 +2,31 @@
#include "driver/i2s.h" #include "driver/i2s.h"
PCM5102AudioSink::PCM5102AudioSink() PCM5102AudioSink::PCM5102AudioSink() {
{ i2s_config_t i2s_config = {
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX
.sample_rate = 44100, .sample_rate = 44100,
.bits_per_sample = (i2s_bits_per_sample_t)16, .bits_per_sample = (i2s_bits_per_sample_t)16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = 0, //Default interrupt priority .intr_alloc_flags = 0, //Default interrupt priority
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = 512, .dma_buf_len = 512,
.use_apll = true, .use_apll = true,
.tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow
.fixed_mclk = 384 * 44100 .fixed_mclk = 384 * 44100};
};
i2s_pin_config_t pin_config = { i2s_pin_config_t pin_config = {
.bck_io_num = 27, .bck_io_num = 27,
.ws_io_num = 32, .ws_io_num = 32,
.data_out_num = 25, .data_out_num = 25,
.data_in_num = -1 //Not used .data_in_num = -1 //Not used
}; };
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
i2s_set_pin((i2s_port_t)0, &pin_config); i2s_set_pin((i2s_port_t)0, &pin_config);
startI2sFeed(); startI2sFeed();
} }
PCM5102AudioSink::~PCM5102AudioSink() PCM5102AudioSink::~PCM5102AudioSink() {}
{
}

View File

@@ -5,124 +5,118 @@
// See http://www.hardwarebook.info/S/PDIF for more info on this protocol // See http://www.hardwarebook.info/S/PDIF for more info on this protocol
// Conversion table to biphase code mark (LSB first, ending in 1) // Conversion table to biphase code mark (LSB first, ending in 1)
static const uint16_t bmc_convert[256] = { static const uint16_t bmc_convert[256] = {
0x3333, 0xb333, 0xd333, 0x5333, 0xcb33, 0x4b33, 0x2b33, 0xab33, 0x3333, 0xb333, 0xd333, 0x5333, 0xcb33, 0x4b33, 0x2b33, 0xab33, 0xcd33,
0xcd33, 0x4d33, 0x2d33, 0xad33, 0x3533, 0xb533, 0xd533, 0x5533, 0x4d33, 0x2d33, 0xad33, 0x3533, 0xb533, 0xd533, 0x5533, 0xccb3, 0x4cb3,
0xccb3, 0x4cb3, 0x2cb3, 0xacb3, 0x34b3, 0xb4b3, 0xd4b3, 0x54b3, 0x2cb3, 0xacb3, 0x34b3, 0xb4b3, 0xd4b3, 0x54b3, 0x32b3, 0xb2b3, 0xd2b3,
0x32b3, 0xb2b3, 0xd2b3, 0x52b3, 0xcab3, 0x4ab3, 0x2ab3, 0xaab3, 0x52b3, 0xcab3, 0x4ab3, 0x2ab3, 0xaab3, 0xccd3, 0x4cd3, 0x2cd3, 0xacd3,
0xccd3, 0x4cd3, 0x2cd3, 0xacd3, 0x34d3, 0xb4d3, 0xd4d3, 0x54d3, 0x34d3, 0xb4d3, 0xd4d3, 0x54d3, 0x32d3, 0xb2d3, 0xd2d3, 0x52d3, 0xcad3,
0x32d3, 0xb2d3, 0xd2d3, 0x52d3, 0xcad3, 0x4ad3, 0x2ad3, 0xaad3, 0x4ad3, 0x2ad3, 0xaad3, 0x3353, 0xb353, 0xd353, 0x5353, 0xcb53, 0x4b53,
0x3353, 0xb353, 0xd353, 0x5353, 0xcb53, 0x4b53, 0x2b53, 0xab53, 0x2b53, 0xab53, 0xcd53, 0x4d53, 0x2d53, 0xad53, 0x3553, 0xb553, 0xd553,
0xcd53, 0x4d53, 0x2d53, 0xad53, 0x3553, 0xb553, 0xd553, 0x5553, 0x5553, 0xcccb, 0x4ccb, 0x2ccb, 0xaccb, 0x34cb, 0xb4cb, 0xd4cb, 0x54cb,
0xcccb, 0x4ccb, 0x2ccb, 0xaccb, 0x34cb, 0xb4cb, 0xd4cb, 0x54cb, 0x32cb, 0xb2cb, 0xd2cb, 0x52cb, 0xcacb, 0x4acb, 0x2acb, 0xaacb, 0x334b,
0x32cb, 0xb2cb, 0xd2cb, 0x52cb, 0xcacb, 0x4acb, 0x2acb, 0xaacb, 0xb34b, 0xd34b, 0x534b, 0xcb4b, 0x4b4b, 0x2b4b, 0xab4b, 0xcd4b, 0x4d4b,
0x334b, 0xb34b, 0xd34b, 0x534b, 0xcb4b, 0x4b4b, 0x2b4b, 0xab4b, 0x2d4b, 0xad4b, 0x354b, 0xb54b, 0xd54b, 0x554b, 0x332b, 0xb32b, 0xd32b,
0xcd4b, 0x4d4b, 0x2d4b, 0xad4b, 0x354b, 0xb54b, 0xd54b, 0x554b, 0x532b, 0xcb2b, 0x4b2b, 0x2b2b, 0xab2b, 0xcd2b, 0x4d2b, 0x2d2b, 0xad2b,
0x332b, 0xb32b, 0xd32b, 0x532b, 0xcb2b, 0x4b2b, 0x2b2b, 0xab2b, 0x352b, 0xb52b, 0xd52b, 0x552b, 0xccab, 0x4cab, 0x2cab, 0xacab, 0x34ab,
0xcd2b, 0x4d2b, 0x2d2b, 0xad2b, 0x352b, 0xb52b, 0xd52b, 0x552b, 0xb4ab, 0xd4ab, 0x54ab, 0x32ab, 0xb2ab, 0xd2ab, 0x52ab, 0xcaab, 0x4aab,
0xccab, 0x4cab, 0x2cab, 0xacab, 0x34ab, 0xb4ab, 0xd4ab, 0x54ab, 0x2aab, 0xaaab, 0xcccd, 0x4ccd, 0x2ccd, 0xaccd, 0x34cd, 0xb4cd, 0xd4cd,
0x32ab, 0xb2ab, 0xd2ab, 0x52ab, 0xcaab, 0x4aab, 0x2aab, 0xaaab, 0x54cd, 0x32cd, 0xb2cd, 0xd2cd, 0x52cd, 0xcacd, 0x4acd, 0x2acd, 0xaacd,
0xcccd, 0x4ccd, 0x2ccd, 0xaccd, 0x34cd, 0xb4cd, 0xd4cd, 0x54cd, 0x334d, 0xb34d, 0xd34d, 0x534d, 0xcb4d, 0x4b4d, 0x2b4d, 0xab4d, 0xcd4d,
0x32cd, 0xb2cd, 0xd2cd, 0x52cd, 0xcacd, 0x4acd, 0x2acd, 0xaacd, 0x4d4d, 0x2d4d, 0xad4d, 0x354d, 0xb54d, 0xd54d, 0x554d, 0x332d, 0xb32d,
0x334d, 0xb34d, 0xd34d, 0x534d, 0xcb4d, 0x4b4d, 0x2b4d, 0xab4d, 0xd32d, 0x532d, 0xcb2d, 0x4b2d, 0x2b2d, 0xab2d, 0xcd2d, 0x4d2d, 0x2d2d,
0xcd4d, 0x4d4d, 0x2d4d, 0xad4d, 0x354d, 0xb54d, 0xd54d, 0x554d, 0xad2d, 0x352d, 0xb52d, 0xd52d, 0x552d, 0xccad, 0x4cad, 0x2cad, 0xacad,
0x332d, 0xb32d, 0xd32d, 0x532d, 0xcb2d, 0x4b2d, 0x2b2d, 0xab2d, 0x34ad, 0xb4ad, 0xd4ad, 0x54ad, 0x32ad, 0xb2ad, 0xd2ad, 0x52ad, 0xcaad,
0xcd2d, 0x4d2d, 0x2d2d, 0xad2d, 0x352d, 0xb52d, 0xd52d, 0x552d, 0x4aad, 0x2aad, 0xaaad, 0x3335, 0xb335, 0xd335, 0x5335, 0xcb35, 0x4b35,
0xccad, 0x4cad, 0x2cad, 0xacad, 0x34ad, 0xb4ad, 0xd4ad, 0x54ad, 0x2b35, 0xab35, 0xcd35, 0x4d35, 0x2d35, 0xad35, 0x3535, 0xb535, 0xd535,
0x32ad, 0xb2ad, 0xd2ad, 0x52ad, 0xcaad, 0x4aad, 0x2aad, 0xaaad, 0x5535, 0xccb5, 0x4cb5, 0x2cb5, 0xacb5, 0x34b5, 0xb4b5, 0xd4b5, 0x54b5,
0x3335, 0xb335, 0xd335, 0x5335, 0xcb35, 0x4b35, 0x2b35, 0xab35, 0x32b5, 0xb2b5, 0xd2b5, 0x52b5, 0xcab5, 0x4ab5, 0x2ab5, 0xaab5, 0xccd5,
0xcd35, 0x4d35, 0x2d35, 0xad35, 0x3535, 0xb535, 0xd535, 0x5535, 0x4cd5, 0x2cd5, 0xacd5, 0x34d5, 0xb4d5, 0xd4d5, 0x54d5, 0x32d5, 0xb2d5,
0xccb5, 0x4cb5, 0x2cb5, 0xacb5, 0x34b5, 0xb4b5, 0xd4b5, 0x54b5, 0xd2d5, 0x52d5, 0xcad5, 0x4ad5, 0x2ad5, 0xaad5, 0x3355, 0xb355, 0xd355,
0x32b5, 0xb2b5, 0xd2b5, 0x52b5, 0xcab5, 0x4ab5, 0x2ab5, 0xaab5, 0x5355, 0xcb55, 0x4b55, 0x2b55, 0xab55, 0xcd55, 0x4d55, 0x2d55, 0xad55,
0xccd5, 0x4cd5, 0x2cd5, 0xacd5, 0x34d5, 0xb4d5, 0xd4d5, 0x54d5, 0x3555, 0xb555, 0xd555, 0x5555,
0x32d5, 0xb2d5, 0xd2d5, 0x52d5, 0xcad5, 0x4ad5, 0x2ad5, 0xaad5,
0x3355, 0xb355, 0xd355, 0x5355, 0xcb55, 0x4b55, 0x2b55, 0xab55,
0xcd55, 0x4d55, 0x2d55, 0xad55, 0x3555, 0xb555, 0xd555, 0x5555,
}; };
#define I2S_BUG_MAGIC (26 * 1000 * 1000) // magic number for avoiding I2S bug #define I2S_BUG_MAGIC (26 * 1000 * 1000) // magic number for avoiding I2S bug
#define BITS_PER_SUBFRAME 64 #define BITS_PER_SUBFRAME 64
#define FRAMES_PER_BLOCK 192 #define FRAMES_PER_BLOCK 192
#define SPDIF_BUF_SIZE (BITS_PER_SUBFRAME/8 * 2 * FRAMES_PER_BLOCK) #define SPDIF_BUF_SIZE (BITS_PER_SUBFRAME / 8 * 2 * FRAMES_PER_BLOCK)
#define SPDIF_BUF_ARRAY_SIZE (SPDIF_BUF_SIZE / sizeof(uint32_t)) #define SPDIF_BUF_ARRAY_SIZE (SPDIF_BUF_SIZE / sizeof(uint32_t))
#define BMC_B 0x33173333 // block start #define BMC_B 0x33173333 // block start
#define BMC_M 0x331d3333 // left ch #define BMC_M 0x331d3333 // left ch
#define BMC_W 0x331b3333 // right ch #define BMC_W 0x331b3333 // right ch
#define BMC_MW_DIF (BMC_M ^ BMC_W) #define BMC_MW_DIF (BMC_M ^ BMC_W)
static uint32_t spdif_buf[SPDIF_BUF_ARRAY_SIZE]; static uint32_t spdif_buf[SPDIF_BUF_ARRAY_SIZE];
static uint32_t *spdif_ptr; static uint32_t* spdif_ptr;
static void spdif_buf_init(void) static void spdif_buf_init(void) {
{ // first bllock has W preamble
// first bllock has W preamble spdif_buf[0] = BMC_B;
spdif_buf[0] = BMC_B;
// all other blocks are alternating M, then W preamble // all other blocks are alternating M, then W preamble
uint32_t bmc_mw = BMC_M; uint32_t bmc_mw = BMC_M;
for (int i = 2; i < SPDIF_BUF_ARRAY_SIZE; i += 2) for (int i = 2; i < SPDIF_BUF_ARRAY_SIZE; i += 2) {
{ spdif_buf[i] = bmc_mw ^= BMC_MW_DIF;
spdif_buf[i] = bmc_mw ^= BMC_MW_DIF; }
}
} }
SPDIFAudioSink::SPDIFAudioSink(uint8_t spdifPin) SPDIFAudioSink::SPDIFAudioSink(uint8_t spdifPin) {
{ // initialize S/PDIF buffer
// initialize S/PDIF buffer spdif_buf_init();
spdif_buf_init(); spdif_ptr = spdif_buf;
spdif_ptr = spdif_buf; this->spdifPin = spdifPin;
this->spdifPin = spdifPin; this->setParams(44100, 16, 2);
this->setParams(44100, 16, 2); startI2sFeed(SPDIF_BUF_SIZE * 16);
startI2sFeed(SPDIF_BUF_SIZE * 16);
} }
bool SPDIFAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { bool SPDIFAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount,
if (bitDepth != 16 || channelCount != 2) // TODO support mono playback and different bit widths uint8_t bitDepth) {
return false; if (bitDepth != 16 ||
int sample_rate = (int)sampleRate * 2; channelCount != 2) // TODO support mono playback and different bit widths
int bclk = sample_rate * 64 * 2; return false;
int mclk = (I2S_BUG_MAGIC / bclk) * bclk; int sample_rate = (int)sampleRate * 2;
int bclk = sample_rate * 64 * 2;
int mclk = (I2S_BUG_MAGIC / bclk) * bclk;
i2s_config_t i2s_config = { i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
.sample_rate = (uint32_t) sample_rate, .sample_rate = (uint32_t)sample_rate,
#else #else
.sample_rate = (int) sample_rate, .sample_rate = (int)sample_rate,
#endif #endif
.bits_per_sample = (i2s_bits_per_sample_t)(bitDepth * 2), .bits_per_sample = (i2s_bits_per_sample_t)(bitDepth * 2),
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S, .communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = 0, .intr_alloc_flags = 0,
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = 512, .dma_buf_len = 512,
.use_apll = true, .use_apll = true,
.tx_desc_auto_clear = true, .tx_desc_auto_clear = true,
.fixed_mclk = mclk, // avoiding I2S bug .fixed_mclk = mclk, // avoiding I2S bug
}; };
i2s_pin_config_t pin_config = { i2s_pin_config_t pin_config = {
.bck_io_num = -1, .bck_io_num = -1,
.ws_io_num = -1, .ws_io_num = -1,
.data_out_num = spdifPin, .data_out_num = spdifPin,
.data_in_num = -1, .data_in_num = -1,
}; };
i2s_driver_uninstall((i2s_port_t)0); i2s_driver_uninstall((i2s_port_t)0);
int err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, nullptr); int err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, nullptr);
i2s_set_pin((i2s_port_t)0, &pin_config); i2s_set_pin((i2s_port_t)0, &pin_config);
return !err; return !err;
} }
SPDIFAudioSink::~SPDIFAudioSink() { SPDIFAudioSink::~SPDIFAudioSink() {
i2s_driver_uninstall((i2s_port_t)0); i2s_driver_uninstall((i2s_port_t)0);
} }
int num_frames = 0; int num_frames = 0;
void SPDIFAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) void SPDIFAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) {
{ for (int i = 0; i < bytes; i += 2) {
for (int i = 0; i < bytes; i += 2) /**
{
/**
* What is this, and why does it work? * What is this, and why does it work?
* *
* Rather than assemble all S/PDIF frames from scratch we want to do the * Rather than assemble all S/PDIF frames from scratch we want to do the
@@ -171,16 +165,16 @@ void SPDIFAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes)
* I did not come up with this, all credit goes to * I did not come up with this, all credit goes to
* github.com/amedes/esp_a2dp_sink_spdif * github.com/amedes/esp_a2dp_sink_spdif
*/ */
uint32_t lo = ((uint32_t)(bmc_convert[buffer[i]]) << 16); uint32_t lo = ((uint32_t)(bmc_convert[buffer[i]]) << 16);
uint32_t hi = (uint32_t)((int16_t)bmc_convert[buffer[i+1]]); uint32_t hi = (uint32_t)((int16_t)bmc_convert[buffer[i + 1]]);
*(spdif_ptr + 1) = ((lo ^ hi) << 1) >> 1; *(spdif_ptr + 1) = ((lo ^ hi) << 1) >> 1;
spdif_ptr += 2; // advance to next audio data spdif_ptr += 2; // advance to next audio data
if (spdif_ptr >= &spdif_buf[SPDIF_BUF_ARRAY_SIZE]) { if (spdif_ptr >= &spdif_buf[SPDIF_BUF_ARRAY_SIZE]) {
feedPCMFramesInternal(spdif_buf, sizeof(spdif_buf)); feedPCMFramesInternal(spdif_buf, sizeof(spdif_buf));
spdif_ptr = spdif_buf; spdif_ptr = spdif_buf;
}
} }
}
} }

View File

@@ -1,117 +1,107 @@
#include "TAS5711AudioSink.h" #include "TAS5711AudioSink.h"
struct tas5711_cmd_s { struct tas5711_cmd_s {
uint8_t reg; uint8_t reg;
uint8_t value; uint8_t value;
}; };
static const struct tas5711_cmd_s tas5711_init_sequence[] = { static const struct tas5711_cmd_s tas5711_init_sequence[] = {
{ 0x00, 0x6c }, // 0x6c - 256 x mclk {0x00, 0x6c}, // 0x6c - 256 x mclk
{ 0x04, 0x03 }, // 0x03 - 16 bit i2s {0x04, 0x03}, // 0x03 - 16 bit i2s
{ 0x05, 0x00 }, // system control 0x00 is audio playback {0x05, 0x00}, // system control 0x00 is audio playback
{ 0x06, 0x00 }, // disable mute {0x06, 0x00}, // disable mute
{ 0x07, 0x50 }, // volume register {0x07, 0x50}, // volume register
{ 0xff, 0xff } {0xff, 0xff}
}; };
i2c_ack_type_t ACK_CHECK_EN = (i2c_ack_type_t)0x1; i2c_ack_type_t ACK_CHECK_EN = (i2c_ack_type_t)0x1;
TAS5711AudioSink::TAS5711AudioSink() TAS5711AudioSink::TAS5711AudioSink() {
{ i2s_config_t i2s_config = {
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX
.sample_rate = 44100, .sample_rate = 44100,
.bits_per_sample = (i2s_bits_per_sample_t)16, .bits_per_sample = (i2s_bits_per_sample_t)16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB,
.intr_alloc_flags = 0, //Default interrupt priority .intr_alloc_flags = 0, //Default interrupt priority
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = 512, .dma_buf_len = 512,
.use_apll = true, .use_apll = true,
.tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow
.fixed_mclk = 256 * 44100 .fixed_mclk = 256 * 44100};
};
i2s_pin_config_t pin_config = {
.bck_io_num = 5,
.ws_io_num = 25,
.data_out_num = 26,
.data_in_num = -1 //Not used
};
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
i2s_set_pin((i2s_port_t)0, &pin_config);
i2s_pin_config_t pin_config = { // configure i2c
.bck_io_num = 5, i2c_config = {
.ws_io_num = 25, .mode = I2C_MODE_MASTER,
.data_out_num = 26, .sda_io_num = 21,
.data_in_num = -1 //Not used .scl_io_num = 23,
}; .sda_pullup_en = GPIO_PULLUP_DISABLE,
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); .scl_pullup_en = GPIO_PULLUP_DISABLE,
i2s_set_pin((i2s_port_t)0, &pin_config); };
// configure i2c i2c_config.master.clk_speed = 250000;
i2c_config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = 21,
.scl_io_num = 23,
.sda_pullup_en = GPIO_PULLUP_DISABLE,
.scl_pullup_en = GPIO_PULLUP_DISABLE,
};
i2c_config.master.clk_speed = 250000; i2c_param_config(i2c_port, &i2c_config);
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
i2c_param_config(i2c_port, &i2c_config); uint8_t data, addr = (0x1b);
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
uint8_t data, addr = (0x1b); i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN);
i2c_master_write_byte(i2c_cmd, 00, ACK_CHECK_EN);
i2c_master_start(i2c_cmd); i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_READ, ACK_CHECK_EN);
i2c_master_write_byte(i2c_cmd, 00, ACK_CHECK_EN); i2c_master_read_byte(i2c_cmd, &data, ACK_CHECK_EN);
i2c_master_start(i2c_cmd); i2c_master_stop(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_READ, ACK_CHECK_EN); int ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_PERIOD_MS);
i2c_master_read_byte(i2c_cmd, &data, ACK_CHECK_EN); i2c_cmd_link_delete(i2c_cmd);
i2c_master_stop(i2c_cmd); if (ret == ESP_OK) {
int ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_PERIOD_MS); ESP_LOGI("RR", "Detected TAS");
i2c_cmd_link_delete(i2c_cmd); } else {
ESP_LOGI("RR", "Unable to detect dac");
}
if (ret == ESP_OK) { writeReg(0x1b, 0x00);
ESP_LOGI("RR", "Detected TAS"); vTaskDelay(100 / portTICK_PERIOD_MS);
}
else {
ESP_LOGI("RR", "Unable to detect dac");
}
writeReg(0x1b, 0x00); for (int i = 0; tas5711_init_sequence[i].reg != 0xff; i++) {
vTaskDelay(100 / portTICK_PERIOD_MS); writeReg(tas5711_init_sequence[i].reg, tas5711_init_sequence[i].value);
vTaskDelay(1 / portTICK_PERIOD_MS);
}
startI2sFeed();
for (int i = 0; tas5711_init_sequence[i].reg != 0xff; i++) {
writeReg(tas5711_init_sequence[i].reg, tas5711_init_sequence[i].value);
vTaskDelay(1 / portTICK_PERIOD_MS);
}
startI2sFeed();
} }
void TAS5711AudioSink::writeReg(uint8_t reg, uint8_t value) void TAS5711AudioSink::writeReg(uint8_t reg, uint8_t value) {
{ i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
i2c_master_start(i2c_cmd); i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (0x1b << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); i2c_master_write_byte(i2c_cmd, (0x1b << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN);
i2c_master_write_byte(i2c_cmd, reg, ACK_CHECK_EN); i2c_master_write_byte(i2c_cmd, reg, ACK_CHECK_EN);
i2c_master_write_byte(i2c_cmd, value, ACK_CHECK_EN); i2c_master_write_byte(i2c_cmd, value, ACK_CHECK_EN);
i2c_master_stop(i2c_cmd);
esp_err_t res =
i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_PERIOD_MS);
i2c_master_stop(i2c_cmd); if (res != ESP_OK) {
esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_PERIOD_MS); ESP_LOGE("RR", "Unable to write to TAS5711");
}
if (res != ESP_OK) { i2c_cmd_link_delete(i2c_cmd);
ESP_LOGE("RR", "Unable to write to TAS5711");
}
i2c_cmd_link_delete(i2c_cmd);
} }
TAS5711AudioSink::~TAS5711AudioSink() TAS5711AudioSink::~TAS5711AudioSink() {}
{
}

View File

@@ -22,16 +22,16 @@
* *
*/ */
#include <string.h> #include "ac101.h"
#include <esp_log.h>
#include <esp_types.h>
#include <esp_system.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/i2c.h> #include <driver/i2c.h>
#include <driver/i2s.h> #include <driver/i2s.h>
#include <esp_log.h>
#include <esp_system.h>
#include <esp_types.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <string.h>
#include "adac.h" #include "adac.h"
#include "ac101.h"
const static char TAG[] = "AC101"; const static char TAG[] = "AC101";
@@ -42,14 +42,13 @@ const static char TAG[] = "AC101";
#define min(a, b) (((a) < (b)) ? (a) : (b)) #define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b))
#define AC_ASSERT(a, format, b, ...) \ #define AC_ASSERT(a, format, b, ...) \
if ((a) != 0) \ if ((a) != 0) { \
{ \ ESP_LOGE(TAG, format, ##__VA_ARGS__); \
ESP_LOGE(TAG, format, ##__VA_ARGS__); \ return b; \
return b; \ }
}
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config); static bool init(int i2c_port_num, int i2s_num, i2s_config_t* config);
static void deinit(void); static void deinit(void);
static void speaker(bool active); static void speaker(bool active);
static void headset(bool active); static void headset(bool active);
@@ -71,356 +70,357 @@ static int i2c_port;
/**************************************************************************************** /****************************************************************************************
* init * init
*/ */
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) static bool init(int i2c_port_num, int i2s_num, i2s_config_t* i2s_config) {
{ esp_err_t res = ESP_OK;
esp_err_t res = ESP_OK;
i2c_port = i2c_port_num; i2c_port = i2c_port_num;
// configure i2c // configure i2c
i2c_config_t i2c_config = { i2c_config_t i2c_config = {
.mode = I2C_MODE_MASTER, .mode = I2C_MODE_MASTER,
.sda_io_num = 33, .sda_io_num = 33,
.sda_pullup_en = GPIO_PULLUP_ENABLE, .sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = 32, .scl_io_num = 32,
.scl_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 250000, .master.clk_speed = 250000,
}; };
i2c_param_config(i2c_port, &i2c_config); i2c_param_config(i2c_port, &i2c_config);
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
res = i2c_read_reg(CHIP_AUDIO_RS); res = i2c_read_reg(CHIP_AUDIO_RS);
if (!res) if (!res) {
{ ESP_LOGW(TAG, "No AC101 detected");
ESP_LOGW(TAG, "No AC101 detected"); i2c_driver_delete(i2c_port);
i2c_driver_delete(i2c_port); return 0;
return 0; }
}
ESP_LOGI(TAG, "AC101 DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num); ESP_LOGI(TAG, "AC101 DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num,
i2c_config.scl_io_num);
res = i2c_write_reg(CHIP_AUDIO_RS, 0x123); res = i2c_write_reg(CHIP_AUDIO_RS, 0x123);
// huh? // huh?
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
// enable the PLL from BCLK source // enable the PLL from BCLK source
i2c_write_reg(PLL_CTRL1, BIN(0000, 0001, 0100, 1111)); // F=1,M=1,PLL,INT=31 (medium) i2c_write_reg(PLL_CTRL1,
i2c_write_reg(PLL_CTRL2, BIN(1000, 0110, 0000, 0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2; BIN(0000, 0001, 0100, 1111)); // F=1,M=1,PLL,INT=31 (medium)
// i2c_write_reg(PLL_CTRL2, BIN(1000,0011,1100,0000)); i2c_write_reg(PLL_CTRL2, BIN(1000, 0110, 0000,
0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2;
// i2c_write_reg(PLL_CTRL2, BIN(1000,0011,1100,0000));
// clocking system // clocking system
i2c_write_reg(SYSCLK_CTRL, BIN(1010, 1010, 0000, 1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK i2c_write_reg(SYSCLK_CTRL, BIN(1010, 1010, 0000,
i2c_write_reg(MOD_CLK_ENA, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC 1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK
i2c_write_reg(MOD_RST_CTRL, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC i2c_write_reg(MOD_CLK_ENA, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC
i2c_write_reg(I2S_SR_CTRL, BIN(0111, 0000, 0000, 0000)); // 44.1kHz i2c_write_reg(MOD_RST_CTRL, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC
i2c_write_reg(I2S_SR_CTRL, BIN(0111, 0000, 0000, 0000)); // 44.1kHz
// analogue config // analogue config
i2c_write_reg(I2S1LCK_CTRL, BIN(1000, 1000, 0101, 0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo i2c_write_reg(I2S1LCK_CTRL,
i2c_write_reg(I2S1_SDOUT_CTRL, BIN(1100, 0000, 0000, 0000)); // I2S1ADC (R&L) BIN(1000, 1000, 0101,
i2c_write_reg(I2S1_SDIN_CTRL, BIN(1100, 0000, 0000, 0000)); // IS21DAC (R&L) 0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo
i2c_write_reg(I2S1_MXR_SRC, BIN(0010, 0010, 0000, 0000)); // ADCL, ADCR i2c_write_reg(I2S1_SDOUT_CTRL, BIN(1100, 0000, 0000, 0000)); // I2S1ADC (R&L)
i2c_write_reg(ADC_SRCBST_CTRL, BIN(0100, 0100, 0100, 0000)); // disable all boost (default) i2c_write_reg(I2S1_SDIN_CTRL, BIN(1100, 0000, 0000, 0000)); // IS21DAC (R&L)
i2c_write_reg(I2S1_MXR_SRC, BIN(0010, 0010, 0000, 0000)); // ADCL, ADCR
i2c_write_reg(ADC_SRCBST_CTRL,
BIN(0100, 0100, 0100, 0000)); // disable all boost (default)
#if ENABLE_ADC #if ENABLE_ADC
i2c_write_reg(ADC_SRC, BIN(0000, 0100, 0000, 1000)); // source=linein(R/L) i2c_write_reg(ADC_SRC, BIN(0000, 0100, 0000, 1000)); // source=linein(R/L)
i2c_write_reg(ADC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable digital ADC i2c_write_reg(ADC_DIG_CTRL,
i2c_write_reg(ADC_ANA_CTRL, BIN(1011, 1011, 0000, 0000)); // enable analogue R/L, 0dB BIN(1000, 0000, 0000, 0000)); // enable digital ADC
i2c_write_reg(ADC_ANA_CTRL,
BIN(1011, 1011, 0000, 0000)); // enable analogue R/L, 0dB
#else #else
i2c_write_reg(ADC_SRC, BIN(0000, 0000, 0000, 0000)); // source=none i2c_write_reg(ADC_SRC, BIN(0000, 0000, 0000, 0000)); // source=none
i2c_write_reg(ADC_DIG_CTRL, BIN(0000, 0000, 0000, 0000)); // disable digital ADC i2c_write_reg(ADC_DIG_CTRL,
i2c_write_reg(ADC_ANA_CTRL, BIN(0011, 0011, 0000, 0000)); // disable analogue R/L, 0dB BIN(0000, 0000, 0000, 0000)); // disable digital ADC
i2c_write_reg(ADC_ANA_CTRL,
BIN(0011, 0011, 0000, 0000)); // disable analogue R/L, 0dB
#endif #endif
//Path Configuration //Path Configuration
i2c_write_reg(DAC_MXR_SRC, BIN(1000, 1000, 0000, 0000)); // DAC from I2S i2c_write_reg(DAC_MXR_SRC, BIN(1000, 1000, 0000, 0000)); // DAC from I2S
i2c_write_reg(DAC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable DAC i2c_write_reg(DAC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable DAC
i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111, 0000, 0000, 0000)); // enable DAC/Analogue (see note on offset removal and PA) i2c_write_reg(
i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111, 1111, 0000, 0000)); // this toggle is needed for headphone PA offset OMIXER_DACA_CTRL,
BIN(1111, 0000, 0000,
0000)); // enable DAC/Analogue (see note on offset removal and PA)
i2c_write_reg(OMIXER_DACA_CTRL,
BIN(1111, 1111, 0000,
0000)); // this toggle is needed for headphone PA offset
#if ENABLE_ADC #if ENABLE_ADC
i2c_write_reg(OMIXER_SR, BIN(0000, 0001, 0000, 0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?) i2c_write_reg(
OMIXER_SR,
BIN(0000, 0001, 0000,
0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?)
#else #else
i2c_write_reg(OMIXER_SR, BIN(0000, 0101, 0000, 1010)); // source=DAC(R/L) and LINEIN(R/L) i2c_write_reg(OMIXER_SR, BIN(0000, 0101, 0000,
1010)); // source=DAC(R/L) and LINEIN(R/L)
#endif #endif
// configure I2S pins & install driver // configure I2S pins & install driver
i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t){.bck_io_num = 27, .ws_io_num = 26, .data_out_num = 25, .data_in_num = -1}; i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t){
res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL); .bck_io_num = 27, .ws_io_num = 26, .data_out_num = 25, .data_in_num = -1};
res |= i2s_set_pin(i2s_num, &i2s_pin_config); res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL);
res |= i2s_set_pin(i2s_num, &i2s_pin_config);
// enable earphone & speaker // enable earphone & speaker
i2c_write_reg(SPKOUT_CTRL, 0x0220); i2c_write_reg(SPKOUT_CTRL, 0x0220);
i2c_write_reg(HPOUT_CTRL, 0xf801); i2c_write_reg(HPOUT_CTRL, 0xf801);
// set gain for speaker and earphone // set gain for speaker and earphone
ac101_set_spk_volume(70); ac101_set_spk_volume(70);
ac101_set_earph_volume(70); ac101_set_earph_volume(70);
ESP_LOGI(TAG, "DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num); ESP_LOGI(TAG, "DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num,
i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
return (res == ESP_OK); return (res == ESP_OK);
} }
/**************************************************************************************** /****************************************************************************************
* init * init
*/ */
static void deinit(void) static void deinit(void) {
{ i2c_driver_delete(i2c_port);
i2c_driver_delete(i2c_port);
} }
/**************************************************************************************** /****************************************************************************************
* change volume * change volume
*/ */
static void volume(unsigned left, unsigned right) static void volume(unsigned left, unsigned right) {
{ ac101_set_earph_volume(left);
ac101_set_earph_volume(left); // nothing at that point, volume is handled by backend
// nothing at that point, volume is handled by backend
} }
/**************************************************************************************** /****************************************************************************************
* power * power
*/ */
static void power(adac_power_e mode) static void power(adac_power_e mode) {
{ switch (mode) {
switch (mode) case ADAC_STANDBY:
{ case ADAC_OFF:
case ADAC_STANDBY: ac101_stop();
case ADAC_OFF: break;
ac101_stop(); case ADAC_ON:
break; ac101_start(AC_MODULE_DAC);
case ADAC_ON: break;
ac101_start(AC_MODULE_DAC); default:
break; ESP_LOGW(TAG, "unknown power command");
default: break;
ESP_LOGW(TAG, "unknown power command"); }
break;
}
} }
/**************************************************************************************** /****************************************************************************************
* speaker * speaker
*/ */
static void speaker(bool active) static void speaker(bool active) {
{ uint16_t value = i2c_read_reg(SPKOUT_CTRL);
uint16_t value = i2c_read_reg(SPKOUT_CTRL); if (active)
if (active) i2c_write_reg(SPKOUT_CTRL, value | SPKOUT_EN);
i2c_write_reg(SPKOUT_CTRL, value | SPKOUT_EN); else
else i2c_write_reg(SPKOUT_CTRL, value & ~SPKOUT_EN);
i2c_write_reg(SPKOUT_CTRL, value & ~SPKOUT_EN);
} }
/**************************************************************************************** /****************************************************************************************
* headset * headset
*/ */
static void headset(bool active) static void headset(bool active) {
{ // there might be aneed to toggle OMIXER_DACA_CTRL 11:8, not sure
// there might be aneed to toggle OMIXER_DACA_CTRL 11:8, not sure uint16_t value = i2c_read_reg(HPOUT_CTRL);
uint16_t value = i2c_read_reg(HPOUT_CTRL); if (active)
if (active) i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN);
i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN); else
else i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN);
i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN);
} }
/**************************************************************************************** /****************************************************************************************
* *
*/ */
static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val) static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val) {
{ i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); esp_err_t ret = 0;
esp_err_t ret = 0; uint8_t send_buff[4];
uint8_t send_buff[4]; send_buff[0] = (AC101_ADDR << 1);
send_buff[0] = (AC101_ADDR << 1); send_buff[1] = reg;
send_buff[1] = reg; send_buff[2] = (val >> 8) & 0xff;
send_buff[2] = (val >> 8) & 0xff; send_buff[3] = val & 0xff;
send_buff[3] = val & 0xff; ret |= i2c_master_start(cmd);
ret |= i2c_master_start(cmd); ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN);
ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN); ret |= i2c_master_stop(cmd);
ret |= i2c_master_stop(cmd); ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS);
ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd);
i2c_cmd_link_delete(cmd); return ret;
return ret;
} }
/**************************************************************************************** /****************************************************************************************
* *
*/ */
static uint16_t i2c_read_reg(uint8_t reg) static uint16_t i2c_read_reg(uint8_t reg) {
{ uint8_t data[2] = {0};
uint8_t data[2] = {0};
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd); i2c_master_start(cmd);
i2c_master_write_byte(cmd, (AC101_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN); i2c_master_write_byte(cmd, (AC101_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
i2c_master_start(cmd); i2c_master_start(cmd);
i2c_master_write_byte(cmd, (AC101_ADDR << 1) | READ_BIT, ACK_CHECK_EN); //check or not i2c_master_write_byte(cmd, (AC101_ADDR << 1) | READ_BIT,
i2c_master_read(cmd, data, 2, ACK_VAL); ACK_CHECK_EN); //check or not
i2c_master_stop(cmd); i2c_master_read(cmd, data, 2, ACK_VAL);
i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); i2c_master_stop(cmd);
i2c_cmd_link_delete(cmd); i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return (data[0] << 8) + data[1]; return (data[0] << 8) + data[1];
; ;
} }
/**************************************************************************************** /****************************************************************************************
* *
*/ */
void set_sample_rate(int rate) void set_sample_rate(int rate) {
{ if (rate == 8000)
if (rate == 8000) rate = SAMPLE_RATE_8000;
rate = SAMPLE_RATE_8000; else if (rate == 11025)
else if (rate == 11025) rate = SAMPLE_RATE_11052;
rate = SAMPLE_RATE_11052; else if (rate == 12000)
else if (rate == 12000) rate = SAMPLE_RATE_12000;
rate = SAMPLE_RATE_12000; else if (rate == 16000)
else if (rate == 16000) rate = SAMPLE_RATE_16000;
rate = SAMPLE_RATE_16000; else if (rate == 22050)
else if (rate == 22050) rate = SAMPLE_RATE_22050;
rate = SAMPLE_RATE_22050; else if (rate == 24000)
else if (rate == 24000) rate = SAMPLE_RATE_24000;
rate = SAMPLE_RATE_24000; else if (rate == 32000)
else if (rate == 32000) rate = SAMPLE_RATE_32000;
rate = SAMPLE_RATE_32000; else if (rate == 44100)
else if (rate == 44100) rate = SAMPLE_RATE_44100;
rate = SAMPLE_RATE_44100; else if (rate == 48000)
else if (rate == 48000) rate = SAMPLE_RATE_48000;
rate = SAMPLE_RATE_48000; else if (rate == 96000)
else if (rate == 96000) rate = SAMPLE_RATE_96000;
rate = SAMPLE_RATE_96000; else if (rate == 192000)
else if (rate == 192000) rate = SAMPLE_RATE_192000;
rate = SAMPLE_RATE_192000; else {
else ESP_LOGW(TAG, "Unknown sample rate %hu", rate);
{ rate = SAMPLE_RATE_44100;
ESP_LOGW(TAG, "Unknown sample rate %hu", rate); }
rate = SAMPLE_RATE_44100; i2c_write_reg(I2S_SR_CTRL, rate);
}
i2c_write_reg(I2S_SR_CTRL, rate);
} }
/**************************************************************************************** /****************************************************************************************
* Get normalized (0..100) speaker volume * Get normalized (0..100) speaker volume
*/ */
static int ac101_get_spk_volume(void) static int ac101_get_spk_volume(void) {
{ return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f;
return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f;
} }
/**************************************************************************************** /****************************************************************************************
* Set normalized (0..100) volume * Set normalized (0..100) volume
*/ */
static void ac101_set_spk_volume(uint8_t volume) static void ac101_set_spk_volume(uint8_t volume) {
{ uint16_t value = min(volume, 100);
uint16_t value = min(volume, 100); value = ((int)value * 0x1f) / 100;
value = ((int)value * 0x1f) / 100; value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f;
value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f; i2c_write_reg(SPKOUT_CTRL, value);
i2c_write_reg(SPKOUT_CTRL, value);
} }
/**************************************************************************************** /****************************************************************************************
* Get normalized (0..100) earphone volume * Get normalized (0..100) earphone volume
*/ */
static int ac101_get_earph_volume(void) static int ac101_get_earph_volume(void) {
{ return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
} }
/**************************************************************************************** /****************************************************************************************
* Set normalized (0..100) earphone volume * Set normalized (0..100) earphone volume
*/ */
static void ac101_set_earph_volume(uint8_t volume) static void ac101_set_earph_volume(uint8_t volume) {
{ uint16_t value = min(volume, 255);
uint16_t value = min(volume, 255); value = (((int)value * 0x3f) / 255) << 4;
value = (((int)value * 0x3f) / 255) << 4; value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4);
value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4); i2c_write_reg(HPOUT_CTRL, value);
i2c_write_reg(HPOUT_CTRL, value);
} }
/**************************************************************************************** /****************************************************************************************
* *
*/ */
static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain, ac_output_mixer_source_t source) static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,
{ ac_output_mixer_source_t source) {
uint16_t regval, temp, clrbit; uint16_t regval, temp, clrbit;
regval = i2c_read_reg(OMIXER_BST1_CTRL); regval = i2c_read_reg(OMIXER_BST1_CTRL);
switch (source) switch (source) {
{ case SRC_MIC1:
case SRC_MIC1: temp = (gain & 0x7) << 6;
temp = (gain & 0x7) << 6; clrbit = ~(0x7 << 6);
clrbit = ~(0x7 << 6); break;
break; case SRC_MIC2:
case SRC_MIC2: temp = (gain & 0x7) << 3;
temp = (gain & 0x7) << 3; clrbit = ~(0x7 << 3);
clrbit = ~(0x7 << 3); break;
break; case SRC_LINEIN:
case SRC_LINEIN: temp = (gain & 0x7);
temp = (gain & 0x7); clrbit = ~0x7;
clrbit = ~0x7; break;
break; default:
default: return;
return; }
} regval &= clrbit;
regval &= clrbit; regval |= temp;
regval |= temp; i2c_write_reg(OMIXER_BST1_CTRL, regval);
i2c_write_reg(OMIXER_BST1_CTRL, regval);
} }
/**************************************************************************************** /****************************************************************************************
* *
*/ */
static void ac101_start(ac_module_t mode) static void ac101_start(ac_module_t mode) {
{ if (mode == AC_MODULE_LINE) {
if (mode == AC_MODULE_LINE) i2c_write_reg(0x51, 0x0408);
{ i2c_write_reg(0x40, 0x8000);
i2c_write_reg(0x51, 0x0408); i2c_write_reg(0x50, 0x3bc0);
i2c_write_reg(0x40, 0x8000); }
i2c_write_reg(0x50, 0x3bc0); if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC ||
} mode == AC_MODULE_LINE) {
if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) // I2S1_SDOUT_CTRL
{ // i2c_write_reg(PLL_CTRL2, 0x8120);
// I2S1_SDOUT_CTRL i2c_write_reg(0x04, 0x800c);
// i2c_write_reg(PLL_CTRL2, 0x8120); i2c_write_reg(0x05, 0x800c);
i2c_write_reg(0x04, 0x800c); // res |= i2c_write_reg(0x06, 0x3000);
i2c_write_reg(0x05, 0x800c); }
// res |= i2c_write_reg(0x06, 0x3000); if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC ||
} mode == AC_MODULE_LINE) {
if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) uint16_t value = i2c_read_reg(PLL_CTRL2);
{ value |= 0x8000;
uint16_t value = i2c_read_reg(PLL_CTRL2); i2c_write_reg(PLL_CTRL2, value);
value |= 0x8000; }
i2c_write_reg(PLL_CTRL2, value);
}
} }
/**************************************************************************************** /****************************************************************************************
* *
*/ */
static void ac101_stop(void) static void ac101_stop(void) {
{ uint16_t value = i2c_read_reg(PLL_CTRL2);
uint16_t value = i2c_read_reg(PLL_CTRL2); value &= ~0x8000;
value &= ~0x8000; i2c_write_reg(PLL_CTRL2, value);
i2c_write_reg(PLL_CTRL2, value);
} }
/**************************************************************************************** /****************************************************************************************
* *
*/ */
static void ac101_deinit(void) static void ac101_deinit(void) {
{ i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset
i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset
} }
/**************************************************************************************** /****************************************************************************************
* Don't know when this one is supposed to be called * Don't know when this one is supposed to be called
*/ */
static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) static void ac101_i2s_config_clock(ac_i2s_clock_t* cfg) {
{ uint16_t regval = 0;
uint16_t regval = 0; regval = i2c_read_reg(I2S1LCK_CTRL);
regval = i2c_read_reg(I2S1LCK_CTRL); regval &= 0xe03f;
regval &= 0xe03f; regval |= (cfg->bclk_div << 9);
regval |= (cfg->bclk_div << 9); regval |= (cfg->lclk_div << 6);
regval |= (cfg->lclk_div << 6); i2c_write_reg(I2S1LCK_CTRL, regval);
i2c_write_reg(I2S1LCK_CTRL, regval);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -5,21 +5,23 @@
#include <cstdlib> #include <cstdlib>
#include <vector> #include <vector>
class AudioSink class AudioSink {
{ public:
public: AudioSink() {}
AudioSink() {} virtual ~AudioSink() {}
virtual ~AudioSink() {} virtual void feedPCMFrames(const uint8_t* buffer, size_t bytes) = 0;
virtual void feedPCMFrames(const uint8_t *buffer, size_t bytes) = 0; virtual void volumeChanged(uint16_t volume) {}
virtual void volumeChanged(uint16_t volume) {} // Return false if the sink doesn't support reconfiguration.
// Return false if the sink doesn't support reconfiguration. virtual bool setParams(uint32_t sampleRate, uint8_t channelCount,
virtual bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { return false; } uint8_t bitDepth) {
// Deprecated. Implement/use setParams() instead. return false;
virtual inline bool setRate(uint16_t sampleRate) { }
return setParams(sampleRate, 2, 16); // Deprecated. Implement/use setParams() instead.
} virtual inline bool setRate(uint16_t sampleRate) {
bool softwareVolumeControl = true; return setParams(sampleRate, 2, 16);
bool usign = false; }
bool softwareVolumeControl = true;
bool usign = false;
}; };
#endif #endif

View File

@@ -1,26 +1,26 @@
#ifndef AC101AUDIOSINK_H #ifndef AC101AUDIOSINK_H
#define AC101AUDIOSINK_H #define AC101AUDIOSINK_H
#include <vector>
#include <iostream>
#include "BufferedAudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include "esp_err.h" #include <sys/unistd.h>
#include "esp_log.h" #include <iostream>
#include <vector>
#include "BufferedAudioSink.h"
#include "ac101.h" #include "ac101.h"
#include "adac.h" #include "adac.h"
#include "esp_err.h"
#include "esp_log.h"
class AC101AudioSink : public BufferedAudioSink class AC101AudioSink : public BufferedAudioSink {
{ public:
public: AC101AudioSink();
AC101AudioSink(); ~AC101AudioSink();
~AC101AudioSink(); void volumeChanged(uint16_t volume);
void volumeChanged(uint16_t volume);
private: private:
adac_s *dac; adac_s* dac;
}; };
#endif #endif

View File

@@ -1,25 +1,27 @@
#ifndef BUFFEREDAUDIOSINK_H #ifndef BUFFEREDAUDIOSINK_H
#define BUFFEREDAUDIOSINK_H #define BUFFEREDAUDIOSINK_H
#include <vector>
#include <iostream>
#include "AudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/unistd.h>
#include <iostream>
#include <vector>
#include "AudioSink.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
class BufferedAudioSink : public AudioSink class BufferedAudioSink : public AudioSink {
{ public:
public: void feedPCMFrames(const uint8_t* buffer, size_t bytes) override;
void feedPCMFrames(const uint8_t *buffer, size_t bytes) override; bool setParams(uint32_t sampleRate, uint8_t channelCount,
bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override; uint8_t bitDepth) override;
protected:
void startI2sFeed(size_t buf_size = 4096 * 8); protected:
void feedPCMFramesInternal(const void *pvItem, size_t xItemSize); void startI2sFeed(size_t buf_size = 4096 * 8);
private: void feedPCMFramesInternal(const void* pvItem, size_t xItemSize);
private:
}; };
#endif #endif

View File

@@ -1,28 +1,28 @@
#ifndef ES8311AUDIOSINK_H #ifndef ES8311AUDIOSINK_H
#define ES8311AUDIOSINK_H #define ES8311AUDIOSINK_H
#include "driver/i2s.h"
#include <vector>
#include <iostream>
#include "BufferedAudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include <iostream>
#include <vector>
#include "BufferedAudioSink.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "driver/i2c.h" #include "driver/i2c.h"
#include <sys/unistd.h> #include "driver/i2s.h"
#include <sys/stat.h>
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
class ES8311AudioSink : public BufferedAudioSink class ES8311AudioSink : public BufferedAudioSink {
{ public:
public: ES8311AudioSink();
ES8311AudioSink(); ~ES8311AudioSink();
~ES8311AudioSink(); void writeReg(uint8_t reg_add, uint8_t data);
void writeReg(uint8_t reg_add, uint8_t data); void volumeChanged(uint16_t volume);
void volumeChanged(uint16_t volume); void setSampleRate(uint32_t sampleRate);
void setSampleRate(uint32_t sampleRate);
private: private:
}; };
#endif #endif

View File

@@ -1,23 +1,22 @@
#ifndef ES8388AUDIOSINK_H #ifndef ES8388AUDIOSINK_H
#define ES8388AUDIOSINK_H #define ES8388AUDIOSINK_H
#include "driver/i2s.h"
#include <driver/i2c.h> #include <driver/i2c.h>
#include <vector> #include <stdint.h>
#include <iostream>
#include "BufferedAudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdint.h>
#include <sys/unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/unistd.h>
#include <iostream>
#include <vector>
#include "BufferedAudioSink.h"
#include "driver/i2s.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
#define ES8388_ADDR 0x20 #define ES8388_ADDR 0x20
#define ACK_CHECK_EN 0x1 #define ACK_CHECK_EN 0x1
/* ES8388 register */ /* ES8388 register */
#define ES8388_CONTROL1 0x00 #define ES8388_CONTROL1 0x00
@@ -78,28 +77,27 @@
#define ES8388_DACCONTROL29 0x33 #define ES8388_DACCONTROL29 0x33
#define ES8388_DACCONTROL30 0x34 #define ES8388_DACCONTROL30 0x34
class ES8388AudioSink : public BufferedAudioSink class ES8388AudioSink : public BufferedAudioSink {
{ public:
public: ES8388AudioSink();
ES8388AudioSink(); ~ES8388AudioSink();
~ES8388AudioSink();
bool begin(int sda = -1, int scl = -1, uint32_t frequency = 400000U); bool begin(int sda = -1, int scl = -1, uint32_t frequency = 400000U);
enum ES8388_OUT enum ES8388_OUT {
{ ES_MAIN, // this is the DAC output volume (both outputs)
ES_MAIN, // this is the DAC output volume (both outputs) ES_OUT1, // this is the additional gain for OUT1
ES_OUT1, // this is the additional gain for OUT1 ES_OUT2 // this is the additional gain for OUT2
ES_OUT2 // this is the additional gain for OUT2 };
};
void mute(const ES8388_OUT out, const bool muted); void mute(const ES8388_OUT out, const bool muted);
void volume(const ES8388_OUT out, const uint8_t vol); void volume(const ES8388_OUT out, const uint8_t vol);
void writeReg(uint8_t reg_add, uint8_t data); void writeReg(uint8_t reg_add, uint8_t data);
private:
i2c_config_t i2c_config; private:
i2c_port_t i2c_port = 0; i2c_config_t i2c_config;
i2c_port_t i2c_port = 0;
}; };
#endif #endif

View File

@@ -1,22 +1,22 @@
#ifndef ES9018AUDIOSINK_H #ifndef ES9018AUDIOSINK_H
#define ES9018AUDIOSINK_H #define ES9018AUDIOSINK_H
#include <vector>
#include <iostream>
#include "BufferedAudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/unistd.h>
#include <iostream>
#include <vector>
#include "BufferedAudioSink.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
class ES9018AudioSink : public BufferedAudioSink class ES9018AudioSink : public BufferedAudioSink {
{ public:
public: ES9018AudioSink();
ES9018AudioSink(); ~ES9018AudioSink();
~ES9018AudioSink();
private: private:
}; };
#endif #endif

View File

@@ -1,22 +1,22 @@
#ifndef INTERNALAUDIOSINK_H #ifndef INTERNALAUDIOSINK_H
#define INTERNALAUDIOSINK_H #define INTERNALAUDIOSINK_H
#include <vector>
#include <iostream>
#include "BufferedAudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/unistd.h>
#include <iostream>
#include <vector>
#include "BufferedAudioSink.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
class InternalAudioSink : public BufferedAudioSink class InternalAudioSink : public BufferedAudioSink {
{ public:
public: InternalAudioSink();
InternalAudioSink(); ~InternalAudioSink();
~InternalAudioSink();
private: private:
}; };
#endif #endif

View File

@@ -1,22 +1,22 @@
#ifndef PCM5102AUDIOSINK_H #ifndef PCM5102AUDIOSINK_H
#define PCM5102AUDIOSINK_H #define PCM5102AUDIOSINK_H
#include <vector>
#include <iostream>
#include "BufferedAudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/unistd.h>
#include <iostream>
#include <vector>
#include "BufferedAudioSink.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
class PCM5102AudioSink : public BufferedAudioSink class PCM5102AudioSink : public BufferedAudioSink {
{ public:
public: PCM5102AudioSink();
PCM5102AudioSink(); ~PCM5102AudioSink();
~PCM5102AudioSink();
private: private:
}; };
#endif #endif

View File

@@ -1,26 +1,28 @@
#ifndef SPDIFAUDIOSINK_H #ifndef SPDIFAUDIOSINK_H
#define SPDIFAUDIOSINK_H #define SPDIFAUDIOSINK_H
#include <vector>
#include <iostream>
#include "BufferedAudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/unistd.h>
#include <iostream>
#include <vector>
#include "BufferedAudioSink.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
class SPDIFAudioSink : public BufferedAudioSink class SPDIFAudioSink : public BufferedAudioSink {
{ private:
private: uint8_t spdifPin;
uint8_t spdifPin;
public: public:
explicit SPDIFAudioSink(uint8_t spdifPin); explicit SPDIFAudioSink(uint8_t spdifPin);
~SPDIFAudioSink() override; ~SPDIFAudioSink() override;
void feedPCMFrames(const uint8_t *buffer, size_t bytes) override; void feedPCMFrames(const uint8_t* buffer, size_t bytes) override;
bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override; bool setParams(uint32_t sampleRate, uint8_t channelCount,
private: uint8_t bitDepth) override;
private:
}; };
#endif #endif

View File

@@ -1,30 +1,28 @@
#ifndef TAS5711AUDIOSINK_H #ifndef TAS5711AUDIOSINK_H
#define TAS5711AUDIOSINK_H #define TAS5711AUDIOSINK_H
#include "driver/i2s.h"
#include <driver/i2c.h> #include <driver/i2c.h>
#include <vector>
#include <iostream>
#include "BufferedAudioSink.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/unistd.h>
#include <iostream>
#include <vector>
#include "BufferedAudioSink.h"
#include "driver/i2s.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
class TAS5711AudioSink : public BufferedAudioSink class TAS5711AudioSink : public BufferedAudioSink {
{ public:
public: TAS5711AudioSink();
TAS5711AudioSink(); ~TAS5711AudioSink();
~TAS5711AudioSink();
void writeReg(uint8_t reg, uint8_t value);
void writeReg(uint8_t reg, uint8_t value); private:
private: i2c_config_t i2c_config;
i2c_config_t i2c_config; i2c_port_t i2c_port = 0;
i2c_port_t i2c_port = 0;
}; };
#endif #endif

View File

@@ -27,150 +27,150 @@
#include "esp_types.h" #include "esp_types.h"
#define AC101_ADDR 0x1a /*!< Device address*/ #define AC101_ADDR 0x1a /*!< Device address*/
#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ #define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */
#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ #define READ_BIT I2C_MASTER_READ /*!< I2C master read */
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ #define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ #define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
#define ACK_VAL 0x0 /*!< I2C ack value */ #define ACK_VAL 0x0 /*!< I2C ack value */
#define NACK_VAL 0x1 /*!< I2C nack value */ #define NACK_VAL 0x1 /*!< I2C nack value */
#define CHIP_AUDIO_RS 0x00 #define CHIP_AUDIO_RS 0x00
#define PLL_CTRL1 0x01 #define PLL_CTRL1 0x01
#define PLL_CTRL2 0x02 #define PLL_CTRL2 0x02
#define SYSCLK_CTRL 0x03 #define SYSCLK_CTRL 0x03
#define MOD_CLK_ENA 0x04 #define MOD_CLK_ENA 0x04
#define MOD_RST_CTRL 0x05 #define MOD_RST_CTRL 0x05
#define I2S_SR_CTRL 0x06 #define I2S_SR_CTRL 0x06
#define I2S1LCK_CTRL 0x10 #define I2S1LCK_CTRL 0x10
#define I2S1_SDOUT_CTRL 0x11 #define I2S1_SDOUT_CTRL 0x11
#define I2S1_SDIN_CTRL 0x12 #define I2S1_SDIN_CTRL 0x12
#define I2S1_MXR_SRC 0x13 #define I2S1_MXR_SRC 0x13
#define I2S1_VOL_CTRL1 0x14 #define I2S1_VOL_CTRL1 0x14
#define I2S1_VOL_CTRL2 0x15 #define I2S1_VOL_CTRL2 0x15
#define I2S1_VOL_CTRL3 0x16 #define I2S1_VOL_CTRL3 0x16
#define I2S1_VOL_CTRL4 0x17 #define I2S1_VOL_CTRL4 0x17
#define I2S1_MXR_GAIN 0x18 #define I2S1_MXR_GAIN 0x18
#define ADC_DIG_CTRL 0x40 #define ADC_DIG_CTRL 0x40
#define ADC_VOL_CTRL 0x41 #define ADC_VOL_CTRL 0x41
#define HMIC_CTRL1 0x44 #define HMIC_CTRL1 0x44
#define HMIC_CTRL2 0x45 #define HMIC_CTRL2 0x45
#define HMIC_STATUS 0x46 #define HMIC_STATUS 0x46
#define DAC_DIG_CTRL 0x48 #define DAC_DIG_CTRL 0x48
#define DAC_VOL_CTRL 0x49 #define DAC_VOL_CTRL 0x49
#define DAC_MXR_SRC 0x4c #define DAC_MXR_SRC 0x4c
#define DAC_MXR_GAIN 0x4d #define DAC_MXR_GAIN 0x4d
#define ADC_ANA_CTRL 0x50 #define ADC_ANA_CTRL 0x50
#define ADC_SRC 0x51 #define ADC_SRC 0x51
#define ADC_SRCBST_CTRL 0x52 #define ADC_SRCBST_CTRL 0x52
#define OMIXER_DACA_CTRL 0x53 #define OMIXER_DACA_CTRL 0x53
#define OMIXER_SR 0x54 #define OMIXER_SR 0x54
#define OMIXER_BST1_CTRL 0x55 #define OMIXER_BST1_CTRL 0x55
#define HPOUT_CTRL 0x56 #define HPOUT_CTRL 0x56
#define SPKOUT_CTRL 0x58 #define SPKOUT_CTRL 0x58
#define AC_DAC_DAPCTRL 0xa0 #define AC_DAC_DAPCTRL 0xa0
#define AC_DAC_DAPHHPFC 0xa1 #define AC_DAC_DAPHHPFC 0xa1
#define AC_DAC_DAPLHPFC 0xa2 #define AC_DAC_DAPLHPFC 0xa2
#define AC_DAC_DAPLHAVC 0xa3 #define AC_DAC_DAPLHAVC 0xa3
#define AC_DAC_DAPLLAVC 0xa4 #define AC_DAC_DAPLLAVC 0xa4
#define AC_DAC_DAPRHAVC 0xa5 #define AC_DAC_DAPRHAVC 0xa5
#define AC_DAC_DAPRLAVC 0xa6 #define AC_DAC_DAPRLAVC 0xa6
#define AC_DAC_DAPHGDEC 0xa7 #define AC_DAC_DAPHGDEC 0xa7
#define AC_DAC_DAPLGDEC 0xa8 #define AC_DAC_DAPLGDEC 0xa8
#define AC_DAC_DAPHGATC 0xa9 #define AC_DAC_DAPHGATC 0xa9
#define AC_DAC_DAPLGATC 0xaa #define AC_DAC_DAPLGATC 0xaa
#define AC_DAC_DAPHETHD 0xab #define AC_DAC_DAPHETHD 0xab
#define AC_DAC_DAPLETHD 0xac #define AC_DAC_DAPLETHD 0xac
#define AC_DAC_DAPHGKPA 0xad #define AC_DAC_DAPHGKPA 0xad
#define AC_DAC_DAPLGKPA 0xae #define AC_DAC_DAPLGKPA 0xae
#define AC_DAC_DAPHGOPA 0xaf #define AC_DAC_DAPHGOPA 0xaf
#define AC_DAC_DAPLGOPA 0xb0 #define AC_DAC_DAPLGOPA 0xb0
#define AC_DAC_DAPOPT 0xb1 #define AC_DAC_DAPOPT 0xb1
#define DAC_DAP_ENA 0xb5 #define DAC_DAP_ENA 0xb5
typedef enum{ typedef enum {
SAMPLE_RATE_8000 = 0x0000, SAMPLE_RATE_8000 = 0x0000,
SAMPLE_RATE_11052 = 0x1000, SAMPLE_RATE_11052 = 0x1000,
SAMPLE_RATE_12000 = 0x2000, SAMPLE_RATE_12000 = 0x2000,
SAMPLE_RATE_16000 = 0x3000, SAMPLE_RATE_16000 = 0x3000,
SAMPLE_RATE_22050 = 0x4000, SAMPLE_RATE_22050 = 0x4000,
SAMPLE_RATE_24000 = 0x5000, SAMPLE_RATE_24000 = 0x5000,
SAMPLE_RATE_32000 = 0x6000, SAMPLE_RATE_32000 = 0x6000,
SAMPLE_RATE_44100 = 0x7000, SAMPLE_RATE_44100 = 0x7000,
SAMPLE_RATE_48000 = 0x8000, SAMPLE_RATE_48000 = 0x8000,
SAMPLE_RATE_96000 = 0x9000, SAMPLE_RATE_96000 = 0x9000,
SAMPLE_RATE_192000 = 0xa000, SAMPLE_RATE_192000 = 0xa000,
} ac_adda_fs_i2s1_t; } ac_adda_fs_i2s1_t;
typedef enum{ typedef enum {
BCLK_DIV_1 = 0x0, BCLK_DIV_1 = 0x0,
BCLK_DIV_2 = 0x1, BCLK_DIV_2 = 0x1,
BCLK_DIV_4 = 0x2, BCLK_DIV_4 = 0x2,
BCLK_DIV_6 = 0x3, BCLK_DIV_6 = 0x3,
BCLK_DIV_8 = 0x4, BCLK_DIV_8 = 0x4,
BCLK_DIV_12 = 0x5, BCLK_DIV_12 = 0x5,
BCLK_DIV_16 = 0x6, BCLK_DIV_16 = 0x6,
BCLK_DIV_24 = 0x7, BCLK_DIV_24 = 0x7,
BCLK_DIV_32 = 0x8, BCLK_DIV_32 = 0x8,
BCLK_DIV_48 = 0x9, BCLK_DIV_48 = 0x9,
BCLK_DIV_64 = 0xa, BCLK_DIV_64 = 0xa,
BCLK_DIV_96 = 0xb, BCLK_DIV_96 = 0xb,
BCLK_DIV_128 = 0xc, BCLK_DIV_128 = 0xc,
BCLK_DIV_192 = 0xd, BCLK_DIV_192 = 0xd,
} ac_i2s1_bclk_div_t; } ac_i2s1_bclk_div_t;
typedef enum{ typedef enum {
LRCK_DIV_16 =0x0, LRCK_DIV_16 = 0x0,
LRCK_DIV_32 =0x1, LRCK_DIV_32 = 0x1,
LRCK_DIV_64 =0x2, LRCK_DIV_64 = 0x2,
LRCK_DIV_128 =0x3, LRCK_DIV_128 = 0x3,
LRCK_DIV_256 =0x4, LRCK_DIV_256 = 0x4,
} ac_i2s1_lrck_div_t; } ac_i2s1_lrck_div_t;
typedef enum { typedef enum {
BIT_LENGTH_8_BITS = 0x00, BIT_LENGTH_8_BITS = 0x00,
BIT_LENGTH_16_BITS = 0x01, BIT_LENGTH_16_BITS = 0x01,
BIT_LENGTH_20_BITS = 0x02, BIT_LENGTH_20_BITS = 0x02,
BIT_LENGTH_24_BITS = 0x03, BIT_LENGTH_24_BITS = 0x03,
} ac_bits_length_t; } ac_bits_length_t;
typedef enum { typedef enum {
AC_MODE_MIN = -1, AC_MODE_MIN = -1,
AC_MODE_SLAVE = 0x00, AC_MODE_SLAVE = 0x00,
AC_MODE_MASTER = 0x01, AC_MODE_MASTER = 0x01,
AC_MODE_MAX, AC_MODE_MAX,
} ac_mode_sm_t; } ac_mode_sm_t;
typedef enum { typedef enum {
AC_MODULE_MIN = -1, AC_MODULE_MIN = -1,
AC_MODULE_ADC = 0x01, AC_MODULE_ADC = 0x01,
AC_MODULE_DAC = 0x02, AC_MODULE_DAC = 0x02,
AC_MODULE_ADC_DAC = 0x03, AC_MODULE_ADC_DAC = 0x03,
AC_MODULE_LINE = 0x04, AC_MODULE_LINE = 0x04,
AC_MODULE_MAX AC_MODULE_MAX
} ac_module_t; } ac_module_t;
typedef enum{ typedef enum {
SRC_MIC1 = 1, SRC_MIC1 = 1,
SRC_MIC2 = 2, SRC_MIC2 = 2,
SRC_LINEIN = 3, SRC_LINEIN = 3,
}ac_output_mixer_source_t; } ac_output_mixer_source_t;
typedef enum { typedef enum {
GAIN_N45DB = 0, GAIN_N45DB = 0,
GAIN_N30DB = 1, GAIN_N30DB = 1,
GAIN_N15DB = 2, GAIN_N15DB = 2,
GAIN_0DB = 3, GAIN_0DB = 3,
GAIN_15DB = 4, GAIN_15DB = 4,
GAIN_30DB = 5, GAIN_30DB = 5,
GAIN_45DB = 6, GAIN_45DB = 6,
GAIN_60DB = 7, GAIN_60DB = 7,
} ac_output_mixer_gain_t; } ac_output_mixer_gain_t;
typedef struct { typedef struct {
ac_i2s1_bclk_div_t bclk_div; /*!< bits clock divide */ ac_i2s1_bclk_div_t bclk_div; /*!< bits clock divide */
ac_i2s1_lrck_div_t lclk_div; /*!< WS clock divide */ ac_i2s1_lrck_div_t lclk_div; /*!< WS clock divide */
} ac_i2s_clock_t; } ac_i2s_clock_t;
#endif #endif

View File

@@ -9,18 +9,18 @@
* *
*/ */
#include "freertos/FreeRTOS.h"
#include "driver/i2s.h" #include "driver/i2s.h"
#include "freertos/FreeRTOS.h"
typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e; typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e;
struct adac_s { struct adac_s {
bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t *config); bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t* config);
void (*deinit)(void); void (*deinit)(void);
void (*power)(adac_power_e mode); void (*power)(adac_power_e mode);
void (*speaker)(bool active); void (*speaker)(bool active);
void (*headset)(bool active); void (*headset)(bool active);
void (*volume)(unsigned left, unsigned right); void (*volume)(unsigned left, unsigned right);
}; };
extern struct adac_s dac_tas57xx; extern struct adac_s dac_tas57xx;

View File

@@ -18,80 +18,80 @@
/* /*
* ES8311_REGISTER NAME_REG_REGISTER ADDRESS * ES8311_REGISTER NAME_REG_REGISTER ADDRESS
*/ */
#define ES8311_RESET_REG00 0x00 /*reset digital,csm,clock manager etc.*/ #define ES8311_RESET_REG00 0x00 /*reset digital,csm,clock manager etc.*/
/* /*
* Clock Scheme Register definition * Clock Scheme Register definition
*/ */
#define ES8311_CLK_MANAGER_REG01 0x01 /* select clk src for mclk, enable clock for codec */ #define ES8311_CLK_MANAGER_REG01 \
#define ES8311_CLK_MANAGER_REG02 0x02 /* clk divider and clk multiplier */ 0x01 /* select clk src for mclk, enable clock for codec */
#define ES8311_CLK_MANAGER_REG03 0x03 /* adc fsmode and osr */ #define ES8311_CLK_MANAGER_REG02 0x02 /* clk divider and clk multiplier */
#define ES8311_CLK_MANAGER_REG04 0x04 /* dac osr */ #define ES8311_CLK_MANAGER_REG03 0x03 /* adc fsmode and osr */
#define ES8311_CLK_MANAGER_REG05 0x05 /* clk divier for adc and dac */ #define ES8311_CLK_MANAGER_REG04 0x04 /* dac osr */
#define ES8311_CLK_MANAGER_REG06 0x06 /* bclk inverter and divider */ #define ES8311_CLK_MANAGER_REG05 0x05 /* clk divier for adc and dac */
#define ES8311_CLK_MANAGER_REG07 0x07 /* tri-state, lrck divider */ #define ES8311_CLK_MANAGER_REG06 0x06 /* bclk inverter and divider */
#define ES8311_CLK_MANAGER_REG08 0x08 /* lrck divider */ #define ES8311_CLK_MANAGER_REG07 0x07 /* tri-state, lrck divider */
#define ES8311_SDPIN_REG09 0x09 /* dac serial digital port */ #define ES8311_CLK_MANAGER_REG08 0x08 /* lrck divider */
#define ES8311_SDPOUT_REG0A 0x0A /* adc serial digital port */ #define ES8311_SDPIN_REG09 0x09 /* dac serial digital port */
#define ES8311_SYSTEM_REG0B 0x0B /* system */ #define ES8311_SDPOUT_REG0A 0x0A /* adc serial digital port */
#define ES8311_SYSTEM_REG0C 0x0C /* system */ #define ES8311_SYSTEM_REG0B 0x0B /* system */
#define ES8311_SYSTEM_REG0D 0x0D /* system, power up/down */ #define ES8311_SYSTEM_REG0C 0x0C /* system */
#define ES8311_SYSTEM_REG0E 0x0E /* system, power up/down */ #define ES8311_SYSTEM_REG0D 0x0D /* system, power up/down */
#define ES8311_SYSTEM_REG0F 0x0F /* system, low power */ #define ES8311_SYSTEM_REG0E 0x0E /* system, power up/down */
#define ES8311_SYSTEM_REG10 0x10 /* system */ #define ES8311_SYSTEM_REG0F 0x0F /* system, low power */
#define ES8311_SYSTEM_REG11 0x11 /* system */ #define ES8311_SYSTEM_REG10 0x10 /* system */
#define ES8311_SYSTEM_REG12 0x12 /* system, Enable DAC */ #define ES8311_SYSTEM_REG11 0x11 /* system */
#define ES8311_SYSTEM_REG13 0x13 /* system */ #define ES8311_SYSTEM_REG12 0x12 /* system, Enable DAC */
#define ES8311_SYSTEM_REG14 0x14 /* system, select DMIC, select analog pga gain */ #define ES8311_SYSTEM_REG13 0x13 /* system */
#define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */ #define ES8311_SYSTEM_REG14 \
#define ES8311_ADC_REG16 0x16 /* ADC */ 0x14 /* system, select DMIC, select analog pga gain */
#define ES8311_ADC_REG17 0x17 /* ADC, volume */ #define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */
#define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */ #define ES8311_ADC_REG16 0x16 /* ADC */
#define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */ #define ES8311_ADC_REG17 0x17 /* ADC, volume */
#define ES8311_ADC_REG1A 0x1A /* ADC, alc automute */ #define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */
#define ES8311_ADC_REG1B 0x1B /* ADC, alc automute, adc hpf s1 */ #define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */
#define ES8311_ADC_REG1C 0x1C /* ADC, equalizer, hpf s2 */ #define ES8311_ADC_REG1A 0x1A /* ADC, alc automute */
#define ES8311_DAC_REG31 0x31 /* DAC, mute */ #define ES8311_ADC_REG1B 0x1B /* ADC, alc automute, adc hpf s1 */
#define ES8311_DAC_REG32 0x32 /* DAC, volume */ #define ES8311_ADC_REG1C 0x1C /* ADC, equalizer, hpf s2 */
#define ES8311_DAC_REG33 0x33 /* DAC, offset */ #define ES8311_DAC_REG31 0x31 /* DAC, mute */
#define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */ #define ES8311_DAC_REG32 0x32 /* DAC, volume */
#define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */ #define ES8311_DAC_REG33 0x33 /* DAC, offset */
#define ES8311_DAC_REG37 0x37 /* DAC, ramprate */ #define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */
#define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */ #define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */
#define ES8311_GP_REG45 0x45 /* GP CONTROL */ #define ES8311_DAC_REG37 0x37 /* DAC, ramprate */
#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ #define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */
#define ES8311_CHD2_REGFE 0xFE /* CHIP ID2 */ #define ES8311_GP_REG45 0x45 /* GP CONTROL */
#define ES8311_CHVER_REGFF 0xFF /* VERSION */ #define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */
#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ #define ES8311_CHD2_REGFE 0xFE /* CHIP ID2 */
#define ES8311_CHVER_REGFF 0xFF /* VERSION */
#define ES8311_MAX_REGISTER 0xFF #define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */
#define ES8311_MAX_REGISTER 0xFF
typedef struct { typedef struct {
ESCodecMode esMode; ESCodecMode esMode;
i2c_port_t i2c_port_num; i2c_port_t i2c_port_num;
i2c_config_t i2c_cfg; i2c_config_t i2c_cfg;
DacOutput dacOutput; DacOutput dacOutput;
AdcInput adcInput; AdcInput adcInput;
} Es8311Config; } Es8311Config;
#define AUDIO_CODEC_ES8311_DEFAULT() \
{ \
.esMode = ES_MODE_SLAVE, \
.i2c_port_num = I2C_NUM_0, \
.i2c_cfg = {.mode = I2C_MODE_MASTER, \
.sda_io_num = IIC_DATA, \
.scl_io_num = IIC_CLK, \
.sda_pullup_en = GPIO_PULLUP_ENABLE, \
.scl_pullup_en = GPIO_PULLUP_ENABLE, \
.master.clk_speed = 100000}, \
.adcInput = ADC_INPUT_LINPUT1_RINPUT1, \
.dacOutput = DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | \
DAC_OUTPUT_ROUT2, \
};
#define AUDIO_CODEC_ES8311_DEFAULT(){ \ int Es8311Init(Es8311Config* cfg);
.esMode = ES_MODE_SLAVE, \
.i2c_port_num = I2C_NUM_0, \
.i2c_cfg = { \
.mode = I2C_MODE_MASTER, \
.sda_io_num = IIC_DATA, \
.scl_io_num = IIC_CLK, \
.sda_pullup_en = GPIO_PULLUP_ENABLE,\
.scl_pullup_en = GPIO_PULLUP_ENABLE,\
.master.clk_speed = 100000\
}, \
.adcInput = ADC_INPUT_LINPUT1_RINPUT1,\
.dacOutput = DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2,\
};
int Es8311Init(Es8311Config *cfg);
void Es8311Uninit(); void Es8311Uninit();
esp_err_t Es8311GetRef(bool flag); esp_err_t Es8311GetRef(bool flag);
esp_err_t Es7243Init(void); esp_err_t Es7243Init(void);
@@ -107,9 +107,9 @@ int Es8311Start(ESCodecModule mode);
int Es8311Stop(ESCodecModule mode); int Es8311Stop(ESCodecModule mode);
int Es8311SetVoiceVolume(int volume); int Es8311SetVoiceVolume(int volume);
int Es8311GetVoiceVolume(int *volume); int Es8311GetVoiceVolume(int* volume);
int Es8311SetVoiceMute(int enable); int Es8311SetVoiceMute(int enable);
int Es8311GetVoiceMute(int *mute); int Es8311GetVoiceMute(int* mute);
int Es8311SetMicGain(MicGain gain); int Es8311SetMicGain(MicGain gain);
int Es8311ConfigAdcInput(AdcInput input); int Es8311ConfigAdcInput(AdcInput input);

View File

@@ -2,165 +2,165 @@
#define __ESCODEC_COMMON_H__ #define __ESCODEC_COMMON_H__
typedef enum BitsLength { typedef enum BitsLength {
BIT_LENGTH_MIN = -1, BIT_LENGTH_MIN = -1,
BIT_LENGTH_16BITS = 0x03, BIT_LENGTH_16BITS = 0x03,
BIT_LENGTH_18BITS = 0x02, BIT_LENGTH_18BITS = 0x02,
BIT_LENGTH_20BITS = 0x01, BIT_LENGTH_20BITS = 0x01,
BIT_LENGTH_24BITS = 0x00, BIT_LENGTH_24BITS = 0x00,
BIT_LENGTH_32BITS = 0x04, BIT_LENGTH_32BITS = 0x04,
BIT_LENGTH_MAX, BIT_LENGTH_MAX,
} BitsLength; } BitsLength;
typedef enum { typedef enum {
SAMPLE_RATE_MIN = -1, SAMPLE_RATE_MIN = -1,
SAMPLE_RATE_16K, SAMPLE_RATE_16K,
SAMPLE_RATE_32K, SAMPLE_RATE_32K,
SAMPLE_RATE_44_1K, SAMPLE_RATE_44_1K,
SAMPLE_RATE_MAX, SAMPLE_RATE_MAX,
} SampleRate; } SampleRate;
typedef enum { typedef enum {
MclkDiv_MIN = -1, MclkDiv_MIN = -1,
MclkDiv_1 = 1, MclkDiv_1 = 1,
MclkDiv_2 = 2, MclkDiv_2 = 2,
MclkDiv_3 = 3, MclkDiv_3 = 3,
MclkDiv_4 = 4, MclkDiv_4 = 4,
MclkDiv_6 = 5, MclkDiv_6 = 5,
MclkDiv_8 = 6, MclkDiv_8 = 6,
MclkDiv_9 = 7, MclkDiv_9 = 7,
MclkDiv_11 = 8, MclkDiv_11 = 8,
MclkDiv_12 = 9, MclkDiv_12 = 9,
MclkDiv_16 = 10, MclkDiv_16 = 10,
MclkDiv_18 = 11, MclkDiv_18 = 11,
MclkDiv_22 = 12, MclkDiv_22 = 12,
MclkDiv_24 = 13, MclkDiv_24 = 13,
MclkDiv_33 = 14, MclkDiv_33 = 14,
MclkDiv_36 = 15, MclkDiv_36 = 15,
MclkDiv_44 = 16, MclkDiv_44 = 16,
MclkDiv_48 = 17, MclkDiv_48 = 17,
MclkDiv_66 = 18, MclkDiv_66 = 18,
MclkDiv_72 = 19, MclkDiv_72 = 19,
MclkDiv_5 = 20, MclkDiv_5 = 20,
MclkDiv_10 = 21, MclkDiv_10 = 21,
MclkDiv_15 = 22, MclkDiv_15 = 22,
MclkDiv_17 = 23, MclkDiv_17 = 23,
MclkDiv_20 = 24, MclkDiv_20 = 24,
MclkDiv_25 = 25, MclkDiv_25 = 25,
MclkDiv_30 = 26, MclkDiv_30 = 26,
MclkDiv_32 = 27, MclkDiv_32 = 27,
MclkDiv_34 = 28, MclkDiv_34 = 28,
MclkDiv_7 = 29, MclkDiv_7 = 29,
MclkDiv_13 = 30, MclkDiv_13 = 30,
MclkDiv_14 = 31, MclkDiv_14 = 31,
MclkDiv_MAX, MclkDiv_MAX,
} SclkDiv; } SclkDiv;
typedef enum { typedef enum {
LclkDiv_MIN = -1, LclkDiv_MIN = -1,
LclkDiv_128 = 0, LclkDiv_128 = 0,
LclkDiv_192 = 1, LclkDiv_192 = 1,
LclkDiv_256 = 2, LclkDiv_256 = 2,
LclkDiv_384 = 3, LclkDiv_384 = 3,
LclkDiv_512 = 4, LclkDiv_512 = 4,
LclkDiv_576 = 5, LclkDiv_576 = 5,
LclkDiv_768 = 6, LclkDiv_768 = 6,
LclkDiv_1024 = 7, LclkDiv_1024 = 7,
LclkDiv_1152 = 8, LclkDiv_1152 = 8,
LclkDiv_1408 = 9, LclkDiv_1408 = 9,
LclkDiv_1536 = 10, LclkDiv_1536 = 10,
LclkDiv_2112 = 11, LclkDiv_2112 = 11,
LclkDiv_2304 = 12, LclkDiv_2304 = 12,
LclkDiv_125 = 16, LclkDiv_125 = 16,
LclkDiv_136 = 17, LclkDiv_136 = 17,
LclkDiv_250 = 18, LclkDiv_250 = 18,
LclkDiv_272 = 19, LclkDiv_272 = 19,
LclkDiv_375 = 20, LclkDiv_375 = 20,
LclkDiv_500 = 21, LclkDiv_500 = 21,
LclkDiv_544 = 22, LclkDiv_544 = 22,
LclkDiv_750 = 23, LclkDiv_750 = 23,
LclkDiv_1000 = 24, LclkDiv_1000 = 24,
LclkDiv_1088 = 25, LclkDiv_1088 = 25,
LclkDiv_1496 = 26, LclkDiv_1496 = 26,
LclkDiv_1500 = 27, LclkDiv_1500 = 27,
LclkDiv_MAX, LclkDiv_MAX,
} LclkDiv; } LclkDiv;
typedef enum { typedef enum {
ADC_INPUT_MIN = -1, ADC_INPUT_MIN = -1,
ADC_INPUT_LINPUT1_RINPUT1 = 0x00, ADC_INPUT_LINPUT1_RINPUT1 = 0x00,
ADC_INPUT_MIC1 = 0x05, ADC_INPUT_MIC1 = 0x05,
ADC_INPUT_MIC2 = 0x06, ADC_INPUT_MIC2 = 0x06,
ADC_INPUT_LINPUT2_RINPUT2 = 0x50, ADC_INPUT_LINPUT2_RINPUT2 = 0x50,
ADC_INPUT_DIFFERENCE = 0xf0, ADC_INPUT_DIFFERENCE = 0xf0,
ADC_INPUT_MAX, ADC_INPUT_MAX,
} AdcInput; } AdcInput;
typedef enum { typedef enum {
DAC_OUTPUT_MIN = -1, DAC_OUTPUT_MIN = -1,
DAC_OUTPUT_LOUT1 = 0x04, DAC_OUTPUT_LOUT1 = 0x04,
DAC_OUTPUT_LOUT2 = 0x08, DAC_OUTPUT_LOUT2 = 0x08,
DAC_OUTPUT_SPK = 0x09, DAC_OUTPUT_SPK = 0x09,
DAC_OUTPUT_ROUT1 = 0x10, DAC_OUTPUT_ROUT1 = 0x10,
DAC_OUTPUT_ROUT2 = 0x20, DAC_OUTPUT_ROUT2 = 0x20,
DAC_OUTPUT_ALL = 0x3c, DAC_OUTPUT_ALL = 0x3c,
DAC_OUTPUT_MAX, DAC_OUTPUT_MAX,
} DacOutput; } DacOutput;
typedef enum { typedef enum {
D2SE_PGA_GAIN_MIN = -1, D2SE_PGA_GAIN_MIN = -1,
D2SE_PGA_GAIN_DIS = 0, D2SE_PGA_GAIN_DIS = 0,
D2SE_PGA_GAIN_EN = 1, D2SE_PGA_GAIN_EN = 1,
D2SE_PGA_GAIN_MAX = 2, D2SE_PGA_GAIN_MAX = 2,
} D2SEPGA; } D2SEPGA;
typedef enum { typedef enum {
MIC_GAIN_MIN = -1, MIC_GAIN_MIN = -1,
MIC_GAIN_0DB = 0, MIC_GAIN_0DB = 0,
MIC_GAIN_3DB = 3, MIC_GAIN_3DB = 3,
MIC_GAIN_6DB = 6, MIC_GAIN_6DB = 6,
MIC_GAIN_9DB = 9, MIC_GAIN_9DB = 9,
MIC_GAIN_12DB = 12, MIC_GAIN_12DB = 12,
MIC_GAIN_15DB = 15, MIC_GAIN_15DB = 15,
MIC_GAIN_18DB = 18, MIC_GAIN_18DB = 18,
MIC_GAIN_21DB = 21, MIC_GAIN_21DB = 21,
MIC_GAIN_24DB = 24, MIC_GAIN_24DB = 24,
#if defined CONFIG_CODEC_CHIP_IS_ES8311 #if defined CONFIG_CODEC_CHIP_IS_ES8311
MIC_GAIN_30DB = 30, MIC_GAIN_30DB = 30,
MIC_GAIN_36DB = 36, MIC_GAIN_36DB = 36,
MIC_GAIN_42DB = 42, MIC_GAIN_42DB = 42,
#endif #endif
MIC_GAIN_MAX, MIC_GAIN_MAX,
} MicGain; } MicGain;
typedef enum { typedef enum {
ES_MODULE_MIN = -1, ES_MODULE_MIN = -1,
ES_MODULE_ADC = 0x01, ES_MODULE_ADC = 0x01,
ES_MODULE_DAC = 0x02, ES_MODULE_DAC = 0x02,
ES_MODULE_ADC_DAC = 0x03, ES_MODULE_ADC_DAC = 0x03,
ES_MODULE_LINE = 0x04, ES_MODULE_LINE = 0x04,
ES_MODULE_MAX ES_MODULE_MAX
} ESCodecModule; } ESCodecModule;
typedef enum { typedef enum {
ES_MODE_MIN = -1, ES_MODE_MIN = -1,
ES_MODE_SLAVE = 0x00, ES_MODE_SLAVE = 0x00,
ES_MODE_MASTER = 0x01, ES_MODE_MASTER = 0x01,
ES_MODE_MAX, ES_MODE_MAX,
} ESCodecMode; } ESCodecMode;
typedef enum { typedef enum {
ES_ = -1, ES_ = -1,
ES_I2S_NORMAL = 0, ES_I2S_NORMAL = 0,
ES_I2S_LEFT = 1, ES_I2S_LEFT = 1,
ES_I2S_RIGHT = 2, ES_I2S_RIGHT = 2,
ES_I2S_DSP = 3, ES_I2S_DSP = 3,
ES_I2S_MAX ES_I2S_MAX
} ESCodecI2SFmt; } ESCodecI2SFmt;
typedef struct { typedef struct {
SclkDiv sclkDiv; SclkDiv sclkDiv;
LclkDiv lclkDiv; LclkDiv lclkDiv;
} ESCodecI2sClock; } ESCodecI2sClock;
#endif //__ESCODEC_COMMON_H__ #endif //__ESCODEC_COMMON_H__

View File

@@ -1,124 +1,106 @@
#pragma once #pragma once
#include <vector> #include <BellTask.h>
#include <fstream>
#include "AudioSink.h"
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include <stdio.h> #include <stdio.h>
#include <BellTask.h>
#include <unistd.h> #include <unistd.h>
#include <fstream>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <vector>
#include "AudioSink.h"
#define PCM_DEVICE "default" #define PCM_DEVICE "default"
template <typename T, int SIZE> template <typename T, int SIZE>
class RingbufferPointer class RingbufferPointer {
{ typedef std::unique_ptr<T> TPointer;
typedef std::unique_ptr<T> TPointer;
public: public:
explicit RingbufferPointer() explicit RingbufferPointer() {
{ // create objects
// create objects for (int i = 0; i < SIZE; i++) {
for (int i = 0; i < SIZE; i++) buf_[i] = std::make_unique<T>();
{ }
buf_[i] = std::make_unique<T>(); }
}
bool push(TPointer& item) {
std::lock_guard<std::mutex> lock(mutex_);
if (full())
return false;
std::swap(buf_[head_], item);
if (full_)
tail_ = (tail_ + 1) % max_size_;
head_ = (head_ + 1) % max_size_;
full_ = head_ == tail_;
return true;
}
bool pop(TPointer& item) {
std::lock_guard<std::mutex> lock(mutex_);
if (empty())
return false;
std::swap(buf_[tail_], item);
full_ = false;
tail_ = (tail_ + 1) % max_size_;
return true;
}
void reset() {
std::lock_guard<std::mutex> lock(mutex_);
head_ = tail_;
full_ = false;
}
bool empty() const { return (!full_ && (head_ == tail_)); }
bool full() const { return full_; }
int capacity() const { return max_size_; }
int size() const {
int size = max_size_;
if (!full_) {
if (head_ >= tail_)
size = head_ - tail_;
else
size = max_size_ + head_ - tail_;
} }
bool push(TPointer &item) return size;
{ }
std::lock_guard<std::mutex> lock(mutex_);
if (full())
return false;
std::swap(buf_[head_], item); private:
TPointer buf_[SIZE];
if (full_) std::mutex mutex_;
tail_ = (tail_ + 1) % max_size_; int head_ = 0;
int tail_ = 0;
head_ = (head_ + 1) % max_size_; const int max_size_ = SIZE;
full_ = head_ == tail_; bool full_ = 0;
return true;
}
bool pop(TPointer &item)
{
std::lock_guard<std::mutex> lock(mutex_);
if (empty())
return false;
std::swap(buf_[tail_], item);
full_ = false;
tail_ = (tail_ + 1) % max_size_;
return true;
}
void reset()
{
std::lock_guard<std::mutex> lock(mutex_);
head_ = tail_;
full_ = false;
}
bool empty() const
{
return (!full_ && (head_ == tail_));
}
bool full() const
{
return full_;
}
int capacity() const
{
return max_size_;
}
int size() const
{
int size = max_size_;
if (!full_)
{
if (head_ >= tail_)
size = head_ - tail_;
else
size = max_size_ + head_ - tail_;
}
return size;
}
private:
TPointer buf_[SIZE];
std::mutex mutex_;
int head_ = 0;
int tail_ = 0;
const int max_size_ = SIZE;
bool full_ = 0;
}; };
class ALSAAudioSink : public AudioSink, public bell::Task class ALSAAudioSink : public AudioSink, public bell::Task {
{ public:
public: ALSAAudioSink();
ALSAAudioSink(); ~ALSAAudioSink();
~ALSAAudioSink(); void feedPCMFrames(const uint8_t* buffer, size_t bytes);
void feedPCMFrames(const uint8_t *buffer, size_t bytes); void runTask();
void runTask();
private: private:
RingbufferPointer<std::vector<uint8_t>, 3> ringbuffer; RingbufferPointer<std::vector<uint8_t>, 3> ringbuffer;
unsigned int pcm; unsigned int pcm;
snd_pcm_t *pcm_handle; snd_pcm_t* pcm_handle;
snd_pcm_hw_params_t *params; snd_pcm_hw_params_t* params;
snd_pcm_uframes_t frames; snd_pcm_uframes_t frames;
int buff_size; int buff_size;
std::vector<uint8_t> buff; std::vector<uint8_t> buff;
}; };

View File

@@ -1,16 +1,17 @@
#pragma once #pragma once
#include <vector> #include <stddef.h> // for size_t
#include <fstream> #include <stdint.h> // for uint8_t
#include "AudioSink.h" #include <fstream> // for ofstream
class NamedPipeAudioSink : public AudioSink #include "AudioSink.h" // for AudioSink
{
public:
NamedPipeAudioSink();
~NamedPipeAudioSink();
void feedPCMFrames(const uint8_t *buffer, size_t bytes);
private: class NamedPipeAudioSink : public AudioSink {
std::ofstream namedPipeFile; public:
NamedPipeAudioSink();
~NamedPipeAudioSink();
void feedPCMFrames(const uint8_t* buffer, size_t bytes);
private:
std::ofstream namedPipeFile;
}; };

View File

@@ -1,101 +1,92 @@
#include "ALSAAudioSink.h" #include "ALSAAudioSink.h"
ALSAAudioSink::ALSAAudioSink() : Task("", 0, 0, 0) ALSAAudioSink::ALSAAudioSink() : Task("", 0, 0, 0) {
{ /* Open the PCM device in playback mode */
/* Open the PCM device in playback mode */ if (pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0) <
if (pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, 0) {
SND_PCM_STREAM_PLAYBACK, 0) < 0) printf("ERROR: Can't open \"%s\" PCM device. %s\n", PCM_DEVICE,
{ snd_strerror(pcm));
printf("ERROR: Can't open \"%s\" PCM device. %s\n", }
PCM_DEVICE, snd_strerror(pcm));
/* Allocate parameters object and fill it with default values*/
snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(pcm_handle, params);
/* Set parameters */
if (pcm = snd_pcm_hw_params_set_access(pcm_handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm));
if (pcm = snd_pcm_hw_params_set_format(pcm_handle, params,
SND_PCM_FORMAT_S16_LE) < 0)
printf("ERROR: Can't set format. %s\n", snd_strerror(pcm));
if (pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, 2) < 0)
printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
unsigned int rate = 44100;
if (pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0)
printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm));
unsigned int periodTime = 800;
int dir = -1;
snd_pcm_hw_params_set_period_time_near(pcm_handle, params, &periodTime, &dir);
/* Write parameters */
if (pcm = snd_pcm_hw_params(pcm_handle, params) < 0)
printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm));
/* Resume information */
printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle));
printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
unsigned int tmp;
snd_pcm_hw_params_get_channels(params, &tmp);
printf("channels: %i ", tmp);
if (tmp == 1)
printf("(mono)\n");
else if (tmp == 2)
printf("(stereo)\n");
snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
printf("period_time = %d\n", tmp);
snd_pcm_hw_params_get_period_size(params, &frames, 0);
this->buff_size = frames * 2 * 2 /* 2 -> sample size */;
printf("required buff_size: %d\n", buff_size);
this->startTask();
}
ALSAAudioSink::~ALSAAudioSink() {
snd_pcm_drain(pcm_handle);
snd_pcm_close(pcm_handle);
}
void ALSAAudioSink::runTask() {
std::unique_ptr<std::vector<uint8_t>> dataPtr;
while (true) {
if (!this->ringbuffer.pop(dataPtr)) {
usleep(100);
continue;
} }
if (pcm = snd_pcm_writei(pcm_handle, dataPtr->data(), this->frames) ==
-EPIPE) {
/* Allocate parameters object and fill it with default values*/ snd_pcm_prepare(pcm_handle);
snd_pcm_hw_params_alloca(&params); } else if (pcm < 0) {
printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm));
snd_pcm_hw_params_any(pcm_handle, params);
/* Set parameters */
if (pcm = snd_pcm_hw_params_set_access(pcm_handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm));
if (pcm = snd_pcm_hw_params_set_format(pcm_handle, params,
SND_PCM_FORMAT_S16_LE) < 0)
printf("ERROR: Can't set format. %s\n", snd_strerror(pcm));
if (pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, 2) < 0)
printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
unsigned int rate = 44100;
if (pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0)
printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm));
unsigned int periodTime = 800;
int dir = -1;
snd_pcm_hw_params_set_period_time_near(pcm_handle, params, &periodTime, &dir);
/* Write parameters */
if (pcm = snd_pcm_hw_params(pcm_handle, params) < 0)
printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm));
/* Resume information */
printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle));
printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
unsigned int tmp;
snd_pcm_hw_params_get_channels(params, &tmp);
printf("channels: %i ", tmp);
if (tmp == 1)
printf("(mono)\n");
else if (tmp == 2)
printf("(stereo)\n");
snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
printf("period_time = %d\n", tmp);
snd_pcm_hw_params_get_period_size(params, &frames, 0);
this->buff_size = frames * 2 * 2 /* 2 -> sample size */;
printf("required buff_size: %d\n", buff_size);
this->startTask();
}
ALSAAudioSink::~ALSAAudioSink()
{
snd_pcm_drain(pcm_handle);
snd_pcm_close(pcm_handle);
}
void ALSAAudioSink::runTask()
{
std::unique_ptr<std::vector<uint8_t>> dataPtr;
while (true)
{
if (!this->ringbuffer.pop(dataPtr))
{
usleep(100);
continue;
}
if (pcm = snd_pcm_writei(pcm_handle, dataPtr->data(), this->frames) == -EPIPE)
{
snd_pcm_prepare(pcm_handle);
}
else if (pcm < 0)
{
printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm));
}
} }
}
} }
void ALSAAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) void ALSAAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) {
{
buff.insert(buff.end(), buffer, buffer + bytes); buff.insert(buff.end(), buffer, buffer + bytes);
while (buff.size() > this->buff_size) while (buff.size() > this->buff_size) {
{ auto ptr = std::make_unique<std::vector<uint8_t>>(
auto ptr = std::make_unique<std::vector<uint8_t>>(this->buff.begin(), this->buff.begin() + this->buff_size); this->buff.begin(), this->buff.begin() + this->buff_size);
this->buff = std::vector<uint8_t>(this->buff.begin() + this->buff_size, this->buff.end()); this->buff = std::vector<uint8_t>(this->buff.begin() + this->buff_size,
while (!this->ringbuffer.push(ptr)) this->buff.end());
{ while (!this->ringbuffer.push(ptr)) {
usleep(100); usleep(100);
}; };
} }
} }

View File

@@ -1,21 +1,19 @@
#include "NamedPipeAudioSink.h" #include "NamedPipeAudioSink.h"
NamedPipeAudioSink::NamedPipeAudioSink() #include <stdio.h> // for printf
{
printf("Start\n");
this->namedPipeFile = std::ofstream("outputFifo", std::ios::binary);
printf("stop\n");
NamedPipeAudioSink::NamedPipeAudioSink() {
printf("Start\n");
this->namedPipeFile = std::ofstream("outputFifo", std::ios::binary);
printf("stop\n");
} }
NamedPipeAudioSink::~NamedPipeAudioSink() NamedPipeAudioSink::~NamedPipeAudioSink() {
{ this->namedPipeFile.close();
this->namedPipeFile.close();
} }
void NamedPipeAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) void NamedPipeAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) {
{ // Write the actual data
// Write the actual data this->namedPipeFile.write((char*)buffer, (long)bytes);
this->namedPipeFile.write((char*)buffer, (long)bytes); this->namedPipeFile.flush();
this->namedPipeFile.flush();
} }

View File

@@ -1,65 +1,57 @@
#include "PortAudioSink.h" #include "PortAudioSink.h"
PortAudioSink::PortAudioSink() PortAudioSink::PortAudioSink() {
{ Pa_Initialize();
Pa_Initialize(); this->setParams(44100, 2, 16);
this->setParams(44100, 2, 16);
} }
bool PortAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { bool PortAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount,
if (stream) { uint8_t bitDepth) {
Pa_StopStream(stream); if (stream) {
}
PaStreamParameters outputParameters;
outputParameters.device = Pa_GetDefaultOutputDevice();
if (outputParameters.device == paNoDevice) {
printf("PortAudio: Default audio device not found!\n");
// exit(0);
}
printf("PortAudio: Default audio device not found!\n");
outputParameters.channelCount = channelCount;
switch (bitDepth) {
case 32:
outputParameters.sampleFormat = paInt32;
break;
case 24:
outputParameters.sampleFormat = paInt24;
break;
case 16:
outputParameters.sampleFormat = paInt16;
break;
case 8:
outputParameters.sampleFormat = paInt8;
break;
default:
outputParameters.sampleFormat = paInt16;
break;
}
outputParameters.suggestedLatency = 0.050;
outputParameters.hostApiSpecificStreamInfo = NULL;
PaError err = Pa_OpenStream(
&stream,
NULL,
&outputParameters,
sampleRate,
4096 / (channelCount * bitDepth / 8),
paClipOff,
NULL, // blocking api
NULL
);
Pa_StartStream(stream);
return !err;
}
PortAudioSink::~PortAudioSink()
{
Pa_StopStream(stream); Pa_StopStream(stream);
Pa_Terminate(); }
PaStreamParameters outputParameters;
outputParameters.device = Pa_GetDefaultOutputDevice();
if (outputParameters.device == paNoDevice) {
printf("PortAudio: Default audio device not found!\n");
// exit(0);
}
printf("PortAudio: Default audio device not found!\n");
outputParameters.channelCount = channelCount;
switch (bitDepth) {
case 32:
outputParameters.sampleFormat = paInt32;
break;
case 24:
outputParameters.sampleFormat = paInt24;
break;
case 16:
outputParameters.sampleFormat = paInt16;
break;
case 8:
outputParameters.sampleFormat = paInt8;
break;
default:
outputParameters.sampleFormat = paInt16;
break;
}
outputParameters.suggestedLatency = 0.050;
outputParameters.hostApiSpecificStreamInfo = NULL;
PaError err = Pa_OpenStream(&stream, NULL, &outputParameters, sampleRate,
4096 / (channelCount * bitDepth / 8), paClipOff,
NULL, // blocking api
NULL);
Pa_StartStream(stream);
return !err;
} }
void PortAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) PortAudioSink::~PortAudioSink() {
{ Pa_StopStream(stream);
Pa_WriteStream(stream, buffer, bytes / 4); Pa_Terminate();
}
void PortAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) {
Pa_WriteStream(stream, buffer, bytes / 4);
} }

View File

@@ -1,8 +1,14 @@
#include "BellHTTPServer.h" #include "BellHTTPServer.h"
#include <mutex>
#include <regex> #include <string.h> // for memcpy
#include "CivetServer.h" #include <cassert> // for assert
#include "civetweb.h" #include <exception> // for exception
#include <mutex> // for scoped_lock
#include <regex> // for sregex_token_iterator, regex
#include "BellLogger.h" // for AbstractLogger, BELL_LOG, bell
#include "CivetServer.h" // for CivetServer, CivetWebSocketHandler
#include "civetweb.h" // for mg_get_request_info, mg_printf, mg_set_user...
using namespace bell; using namespace bell;
@@ -195,7 +201,8 @@ std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse(
return response; return response;
} }
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeEmptyResponse() { std::unique_ptr<BellHTTPServer::HTTPResponse>
BellHTTPServer::makeEmptyResponse() {
auto response = std::make_unique<BellHTTPServer::HTTPResponse>(); auto response = std::make_unique<BellHTTPServer::HTTPResponse>();
return response; return response;
} }
@@ -225,8 +232,10 @@ void BellHTTPServer::registerNotFound(HTTPHandler handler) {
std::unordered_map<std::string, std::string> BellHTTPServer::extractParams( std::unordered_map<std::string, std::string> BellHTTPServer::extractParams(
struct mg_connection* conn) { struct mg_connection* conn) {
void* data = mg_get_user_connection_data(conn);
assert(data != nullptr);
std::unordered_map<std::string, std::string>& params = std::unordered_map<std::string, std::string>& params =
*(std::unordered_map<std::string, std::string>*) *(std::unordered_map<std::string, std::string>*)data;
mg_get_user_connection_data(conn);
return params; return params;
} }

View File

@@ -1,14 +1,18 @@
#include "BellTar.h" #include "BellTar.h"
#include <sys/stat.h>
#include <fstream> #include <sys/stat.h> // for mkdir
using namespace bell::BellTar; using namespace bell::BellTar;
#include <cassert> #include <algorithm> // for min
#include <cstdio> // for sprintf, snprintf and sscanf #include <cassert> // for assert
#include <cstdlib> // for rand #include <cstdint> // for uint8_t
#include <cstring> // for strlen and memset #include <cstdio> // for sprintf, size_t, sscanf, EOF, NULL
#include <ctime> // for time #include <cstdlib> // for rand
#include <cstring> // for memset, strlen
#include <ctime> // for time
#include <fstream> // for ofstream
#include <vector> // for vector
#ifdef _WIN32 #ifdef _WIN32
#include <direct.h> #include <direct.h>
#endif #endif
@@ -59,7 +63,7 @@ void header_set_metadata(tar_header* header) {
std::memset(header, 0, sizeof(tar_header)); std::memset(header, 0, sizeof(tar_header));
std::sprintf(header->magic, "ustar"); std::sprintf(header->magic, "ustar");
std::sprintf(header->mtime, "%011lo", (unsigned long) std::time(NULL)); std::sprintf(header->mtime, "%011lo", (unsigned long)std::time(NULL));
std::sprintf(header->mode, "%07o", 0644); std::sprintf(header->mode, "%07o", 0644);
std::sprintf(header->uname, "unkown"); // ... a bit random std::sprintf(header->uname, "unkown"); // ... a bit random
std::sprintf(header->gname, "users"); std::sprintf(header->gname, "users");
@@ -284,7 +288,11 @@ void reader::extract_all_files(std::string dest_directory) {
auto fileName = get_next_file_name(); auto fileName = get_next_file_name();
// 0 is the normal file type, skip apple's ._ files // 0 is the normal file type, skip apple's ._ files
#if __cplusplus >= 202002L
if (fileType == '0' && !fileName.starts_with("._")) { if (fileType == '0' && !fileName.starts_with("._")) {
#else
if (fileType == '0' && fileName.find("._") != 0) {
#endif
std::string path = dest_directory + "/" + fileName; std::string path = dest_directory + "/" + fileName;
size_t pos = 0; size_t pos = 0;

View File

@@ -1,72 +1,69 @@
#include "BinaryReader.h" #include "BinaryReader.h"
#include <stdlib.h>
#include <stdlib.h> // for size_t
#include <cstdint> // for uint8_t
#include <type_traits> // for remove_extent_t
#include "ByteStream.h" // for ByteStream
bell::BinaryReader::BinaryReader(std::shared_ptr<ByteStream> stream) { bell::BinaryReader::BinaryReader(std::shared_ptr<ByteStream> stream) {
this->stream = stream; this->stream = stream;
} }
size_t bell::BinaryReader::position() { size_t bell::BinaryReader::position() {
return stream->position(); return stream->position();
} }
size_t bell::BinaryReader::size() { size_t bell::BinaryReader::size() {
return stream->size(); return stream->size();
} }
void bell::BinaryReader::close() { void bell::BinaryReader::close() {
stream->close(); stream->close();
} }
void bell::BinaryReader::skip(size_t pos) { void bell::BinaryReader::skip(size_t pos) {
std::vector<uint8_t> b(pos); std::vector<uint8_t> b(pos);
stream->read(&b[0], pos); stream->read(&b[0], pos);
} }
int32_t bell::BinaryReader::readInt() { int32_t bell::BinaryReader::readInt() {
uint8_t b[4]; uint8_t b[4];
if (stream->read((uint8_t *) b,4) != 4) if (stream->read((uint8_t*)b, 4) != 4)
return 0; return 0;
return static_cast<int32_t>( return static_cast<int32_t>((b[3]) | (b[2] << 8) | (b[1] << 16) |
(b[3]) | (b[0] << 24));
(b[2] << 8) |
(b[1] << 16)|
(b[0] << 24) );
} }
int16_t bell::BinaryReader::readShort() { int16_t bell::BinaryReader::readShort() {
uint8_t b[2]; uint8_t b[2];
if (stream->read((uint8_t *) b,2) != 2) if (stream->read((uint8_t*)b, 2) != 2)
return 0; return 0;
return static_cast<int16_t>( return static_cast<int16_t>((b[1]) | (b[0] << 8));
(b[1]) |
(b[0] << 8));
} }
uint32_t bell::BinaryReader::readUInt() { uint32_t bell::BinaryReader::readUInt() {
return readInt() & 0xffffffffL; return readInt() & 0xffffffffL;
} }
uint8_t bell::BinaryReader::readByte() { uint8_t bell::BinaryReader::readByte() {
uint8_t b[1]; uint8_t b[1];
if (stream->read((uint8_t *) b,1) != 1) if (stream->read((uint8_t*)b, 1) != 1)
return 0; return 0;
return b[0]; return b[0];
} }
std::vector<uint8_t> bell::BinaryReader::readBytes(size_t size) { std::vector<uint8_t> bell::BinaryReader::readBytes(size_t size) {
std::vector<uint8_t> data(size); std::vector<uint8_t> data(size);
stream->read(&data[0], size); stream->read(&data[0], size);
return data; return data;
} }
long long bell::BinaryReader::readLong() { long long bell::BinaryReader::readLong() {
long high = readInt(); long high = readInt();
long low = readInt(); long low = readInt();
return static_cast<long long>( return static_cast<long long>(((long long)high << 32) | low);
((long long) high << 32) | low );
} }

View File

@@ -1,5 +1,5 @@
#include <BinaryStream.h> #include <BinaryStream.h>
#include <sstream> #include <stdexcept> // for runtime_error
using namespace bell; using namespace bell;

View File

@@ -1,172 +1,182 @@
#include "BufferedStream.h" #include "BufferedStream.h"
#include <cstring>
BufferedStream::BufferedStream( #include <stdlib.h> // for free, malloc
const std::string &taskName, #include <algorithm> // for min
uint32_t bufferSize, #include <cstdint> // for uint32_t
uint32_t readThreshold, #include <cstring> // for memcpy
uint32_t readSize, #include <type_traits> // for remove_extent_t
uint32_t readyThreshold,
uint32_t notReadyThreshold, BufferedStream::BufferedStream(const std::string& taskName, uint32_t bufferSize,
bool waitForReady) uint32_t readThreshold, uint32_t readSize,
: bell::Task(taskName, 4096, 5, 0) { uint32_t readyThreshold,
this->bufferSize = bufferSize; uint32_t notReadyThreshold, bool waitForReady)
this->readAt = bufferSize - readThreshold; : bell::Task(taskName, 4096, 5, 0) {
this->readSize = readSize; this->bufferSize = bufferSize;
this->readyThreshold = readyThreshold; this->readAt = bufferSize - readThreshold;
this->notReadyThreshold = notReadyThreshold; this->readSize = readSize;
this->waitForReady = waitForReady; this->readyThreshold = readyThreshold;
this->buf = static_cast<uint8_t *>(malloc(bufferSize)); this->notReadyThreshold = notReadyThreshold;
this->bufEnd = buf + bufferSize; this->waitForReady = waitForReady;
reset(); this->buf = static_cast<uint8_t*>(malloc(bufferSize));
this->bufEnd = buf + bufferSize;
reset();
} }
BufferedStream::~BufferedStream() { BufferedStream::~BufferedStream() {
this->close(); this->close();
free(buf); free(buf);
} }
void BufferedStream::close() { void BufferedStream::close() {
this->terminate = true; this->terminate = true;
this->readSem.give(); // force a read operation this->readSem.give(); // force a read operation
const std::lock_guard lock(runningMutex); const std::lock_guard lock(runningMutex);
if (this->source) if (this->source)
this->source->close(); this->source->close();
this->source = nullptr; this->source = nullptr;
} }
void BufferedStream::reset() { void BufferedStream::reset() {
this->bufReadPtr = this->buf; this->bufReadPtr = this->buf;
this->bufWritePtr = this->buf; this->bufWritePtr = this->buf;
this->readTotal = 0; this->readTotal = 0;
this->bufferTotal = 0; this->bufferTotal = 0;
this->readAvailable = 0; this->readAvailable = 0;
this->terminate = false; this->terminate = false;
} }
bool BufferedStream::open(const std::shared_ptr<bell::ByteStream> &stream) { bool BufferedStream::open(const std::shared_ptr<bell::ByteStream>& stream) {
if (this->running) if (this->running)
this->close(); this->close();
reset(); reset();
this->source = stream; this->source = stream;
startTask(); startTask();
return source.get(); return source.get();
} }
bool BufferedStream::open(const StreamReader &newReader, uint32_t initialOffset) { bool BufferedStream::open(const StreamReader& newReader,
if (this->running) uint32_t initialOffset) {
this->close(); if (this->running)
reset(); this->close();
this->reader = newReader; reset();
this->bufferTotal = initialOffset; this->reader = newReader;
startTask(); this->bufferTotal = initialOffset;
return source.get(); startTask();
return source.get();
} }
bool BufferedStream::isReady() const { bool BufferedStream::isReady() const {
return readAvailable >= readyThreshold; return readAvailable >= readyThreshold;
} }
bool BufferedStream::isNotReady() const { bool BufferedStream::isNotReady() const {
return readAvailable < notReadyThreshold; return readAvailable < notReadyThreshold;
} }
size_t BufferedStream::skip(size_t len) { size_t BufferedStream::skip(size_t len) {
return read(nullptr, len); return read(nullptr, len);
} }
size_t BufferedStream::position() { size_t BufferedStream::position() {
return readTotal; return readTotal;
} }
size_t BufferedStream::size() { size_t BufferedStream::size() {
return source->size(); return source->size();
} }
uint32_t BufferedStream::lengthBetween(uint8_t *me, uint8_t *other) { uint32_t BufferedStream::lengthBetween(uint8_t* me, uint8_t* other) {
const std::lock_guard lock(readMutex); const std::lock_guard lock(readMutex);
if (other <= me) { if (other <= me) {
// buf .... other ...... me ........ bufEnd // buf .... other ...... me ........ bufEnd
// buf .... me/other ........ bufEnd // buf .... me/other ........ bufEnd
return bufEnd - me; return bufEnd - me;
} else { } else {
// buf ........ me ........ other .... bufEnd // buf ........ me ........ other .... bufEnd
return other - me; return other - me;
} }
} }
size_t BufferedStream::read(uint8_t *dst, size_t len) { size_t BufferedStream::read(uint8_t* dst, size_t len) {
if (waitForReady && isNotReady()) { if (waitForReady && isNotReady()) {
while ((source || reader) && !isReady()) {} // end waiting after termination while ((source || reader) && !isReady()) {
} } // end waiting after termination
if (!running && !readAvailable) { }
reset(); if (!running && !readAvailable) {
return 0; reset();
} return 0;
uint32_t read = 0; }
uint32_t toReadTotal = std::min(readAvailable.load(), static_cast<uint32_t>(len)); uint32_t read = 0;
while (toReadTotal > 0) { uint32_t toReadTotal =
uint32_t toRead = std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr)); std::min(readAvailable.load(), static_cast<uint32_t>(len));
if (dst) { while (toReadTotal > 0) {
memcpy(dst, bufReadPtr, toRead); uint32_t toRead =
dst += toRead; std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr));
} if (dst) {
readAvailable -= toRead; memcpy(dst, bufReadPtr, toRead);
bufReadPtr += toRead; dst += toRead;
if (bufReadPtr >= bufEnd) }
bufReadPtr = buf; readAvailable -= toRead;
toReadTotal -= toRead; bufReadPtr += toRead;
read += toRead; if (bufReadPtr >= bufEnd)
readTotal += toRead; bufReadPtr = buf;
} toReadTotal -= toRead;
this->readSem.give(); read += toRead;
return read; readTotal += toRead;
}
this->readSem.give();
return read;
} }
void BufferedStream::runTask() { void BufferedStream::runTask() {
const std::lock_guard lock(runningMutex); const std::lock_guard lock(runningMutex);
running = true; running = true;
if (!source && reader) { if (!source && reader) {
// get the initial request on the task's thread // get the initial request on the task's thread
source = reader(this->bufferTotal); source = reader(this->bufferTotal);
} }
while (!terminate) { while (!terminate) {
if (!source) if (!source)
break; break;
if (isReady()) { if (isReady()) {
// buffer ready, wait for any read operations // buffer ready, wait for any read operations
this->readSem.wait(); this->readSem.wait();
} }
if (terminate) if (terminate)
break; break;
if (readAvailable > readAt) if (readAvailable > readAt)
continue; continue;
// here, the buffer needs re-filling // here, the buffer needs re-filling
uint32_t len; uint32_t len;
bool wasReady = isReady(); bool wasReady = isReady();
do { do {
uint32_t toRead = std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr)); uint32_t toRead =
if (!source) { std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr));
len = 0; if (!source) {
break; len = 0;
} break;
len = source->read(bufWritePtr, toRead); }
readAvailable += len; len = source->read(bufWritePtr, toRead);
bufferTotal += len; readAvailable += len;
bufWritePtr += len; bufferTotal += len;
if (bufWritePtr >= bufEnd) // TODO is == enough here? bufWritePtr += len;
bufWritePtr = buf; if (bufWritePtr >= bufEnd) // TODO is == enough here?
} while (len && readSize < bufferSize - readAvailable); // loop until there's no more free space in the buffer bufWritePtr = buf;
if (!len && reader) } while (
source = reader(bufferTotal); len &&
else if (!len) readSize <
terminate = true; bufferSize -
// signal that buffer is ready for reading readAvailable); // loop until there's no more free space in the buffer
if (!wasReady && isReady()) { if (!len && reader)
this->readySem.give(); source = reader(bufferTotal);
} else if (!len)
} terminate = true;
source = nullptr; // signal that buffer is ready for reading
reader = nullptr; if (!wasReady && isReady()) {
running = false; this->readySem.give();
}
}
source = nullptr;
reader = nullptr;
running = false;
} }

View File

@@ -1,89 +1,85 @@
#include "CircularBuffer.h" #include "CircularBuffer.h"
#include <algorithm> // for min
using namespace bell; using namespace bell;
CircularBuffer::CircularBuffer(size_t dataCapacity) CircularBuffer::CircularBuffer(size_t dataCapacity) {
{ this->dataCapacity = dataCapacity;
this->dataCapacity = dataCapacity; buffer = std::vector<uint8_t>(dataCapacity);
buffer = std::vector<uint8_t>(dataCapacity); this->dataSemaphore = std::make_unique<bell::WrappedSemaphore>(5);
this->dataSemaphore = std::make_unique<bell::WrappedSemaphore>(5);
}; };
size_t CircularBuffer::write(const uint8_t *data, size_t bytes) size_t CircularBuffer::write(const uint8_t* data, size_t bytes) {
{ if (bytes == 0)
if (bytes == 0) return 0;
return 0;
std::lock_guard<std::mutex> guard(bufferMutex); std::lock_guard<std::mutex> guard(bufferMutex);
size_t bytesToWrite = std::min(bytes, dataCapacity - dataSize); size_t bytesToWrite = std::min(bytes, dataCapacity - dataSize);
// Write in a single step // Write in a single step
if (bytesToWrite <= dataCapacity - endIndex) if (bytesToWrite <= dataCapacity - endIndex) {
{ memcpy(buffer.data() + endIndex, data, bytesToWrite);
memcpy(buffer.data() + endIndex, data, bytesToWrite); endIndex += bytesToWrite;
endIndex += bytesToWrite; if (endIndex == dataCapacity)
if (endIndex == dataCapacity) endIndex = 0;
endIndex = 0; }
}
// Write in two steps // Write in two steps
else { else {
size_t firstChunkSize = dataCapacity - endIndex; size_t firstChunkSize = dataCapacity - endIndex;
memcpy(buffer.data() + endIndex, data, firstChunkSize); memcpy(buffer.data() + endIndex, data, firstChunkSize);
size_t secondChunkSize = bytesToWrite - firstChunkSize; size_t secondChunkSize = bytesToWrite - firstChunkSize;
memcpy(buffer.data(), data + firstChunkSize, secondChunkSize); memcpy(buffer.data(), data + firstChunkSize, secondChunkSize);
endIndex = secondChunkSize; endIndex = secondChunkSize;
} }
dataSize += bytesToWrite; dataSize += bytesToWrite;
// this->dataSemaphore->give(); // this->dataSemaphore->give();
return bytesToWrite; return bytesToWrite;
} }
void CircularBuffer::emptyBuffer() { void CircularBuffer::emptyBuffer() {
std::lock_guard<std::mutex> guard(bufferMutex); std::lock_guard<std::mutex> guard(bufferMutex);
begIndex = 0; begIndex = 0;
dataSize = 0; dataSize = 0;
endIndex = 0; endIndex = 0;
} }
void CircularBuffer::emptyExcept(size_t sizeToSet) { void CircularBuffer::emptyExcept(size_t sizeToSet) {
std::lock_guard<std::mutex> guard(bufferMutex); std::lock_guard<std::mutex> guard(bufferMutex);
if (sizeToSet > dataSize) if (sizeToSet > dataSize)
sizeToSet = dataSize; sizeToSet = dataSize;
dataSize = sizeToSet; dataSize = sizeToSet;
endIndex = begIndex + sizeToSet; endIndex = begIndex + sizeToSet;
if (endIndex > dataCapacity) { if (endIndex > dataCapacity) {
endIndex -= dataCapacity; endIndex -= dataCapacity;
} }
} }
size_t CircularBuffer::read(uint8_t *data, size_t bytes) size_t CircularBuffer::read(uint8_t* data, size_t bytes) {
{ if (bytes == 0)
if (bytes == 0) return 0;
return 0;
std::lock_guard<std::mutex> guard(bufferMutex); std::lock_guard<std::mutex> guard(bufferMutex);
size_t bytesToRead = std::min(bytes, dataSize); size_t bytesToRead = std::min(bytes, dataSize);
// Read in a single step // Read in a single step
if (bytesToRead <= dataCapacity - begIndex) if (bytesToRead <= dataCapacity - begIndex) {
{ memcpy(data, buffer.data() + begIndex, bytesToRead);
memcpy(data, buffer.data() + begIndex, bytesToRead); begIndex += bytesToRead;
begIndex += bytesToRead; if (begIndex == dataCapacity)
if (begIndex == dataCapacity) begIndex = 0;
begIndex = 0; }
} // Read in two steps
// Read in two steps else {
else size_t firstChunkSize = dataCapacity - begIndex;
{ memcpy(data, buffer.data() + begIndex, firstChunkSize);
size_t firstChunkSize = dataCapacity - begIndex; size_t secondChunkSize = bytesToRead - firstChunkSize;
memcpy(data, buffer.data() + begIndex, firstChunkSize); memcpy(data + firstChunkSize, buffer.data(), secondChunkSize);
size_t secondChunkSize = bytesToRead - firstChunkSize; begIndex = secondChunkSize;
memcpy(data + firstChunkSize, buffer.data(), secondChunkSize); }
begIndex = secondChunkSize;
}
dataSize -= bytesToRead; dataSize -= bytesToRead;
return bytesToRead; return bytesToRead;
} }

View File

@@ -1,6 +1,14 @@
#include "EncodedAudioStream.h" #include "EncodedAudioStream.h"
#include <iostream> #include <string.h> // for memcpy, memmove
#include <stdexcept> // for runtime_error
#include <type_traits> // for remove_extent_t
#include <utility> // for move
#include "BellLogger.h" // for AbstractLogger, BELL_LOG, bell
#include "ByteStream.h" // for ByteStream
#include "DecoderGlobals.h" // for DecodersInstance, decodersInstance, AAC_...
using namespace bell; using namespace bell;
EncodedAudioStream::EncodedAudioStream() { EncodedAudioStream::EncodedAudioStream() {
@@ -171,5 +179,4 @@ void EncodedAudioStream::guessDataFormat() {
} }
} }
void EncodedAudioStream::readFully(uint8_t* dst, size_t nbytes) { void EncodedAudioStream::readFully(uint8_t* dst, size_t nbytes) {}
}

View File

@@ -1,70 +1,61 @@
#include "FileStream.h" #include "FileStream.h"
#include <stdexcept> // for runtime_error
#include "BellLogger.h" // for bell
using namespace bell; using namespace bell;
FileStream::FileStream(const std::string& path, std::string read) FileStream::FileStream(const std::string& path, std::string read) {
{ file = fopen(path.c_str(), "rb");
file = fopen(path.c_str(), "rb"); if (file == NULL) {
if (file == NULL) throw std::runtime_error("Could not open file: " + path);
{ }
throw std::runtime_error("Could not open file: " + path);
}
} }
FileStream::~FileStream() FileStream::~FileStream() {
{ close();
close();
} }
size_t FileStream::read(uint8_t *buf, size_t nbytes) size_t FileStream::read(uint8_t* buf, size_t nbytes) {
{ if (file == NULL) {
if (file == NULL) throw std::runtime_error("Stream is closed");
{ }
throw std::runtime_error("Stream is closed");
}
return fread(buf, 1, nbytes, file); return fread(buf, 1, nbytes, file);
} }
size_t FileStream::skip(size_t nbytes) size_t FileStream::skip(size_t nbytes) {
{ if (file == NULL) {
if (file == NULL) throw std::runtime_error("Stream is closed");
{ }
throw std::runtime_error("Stream is closed");
}
return fseek(file, nbytes, SEEK_CUR); return fseek(file, nbytes, SEEK_CUR);
} }
size_t FileStream::position() size_t FileStream::position() {
{ if (file == NULL) {
if (file == NULL) throw std::runtime_error("Stream is closed");
{ }
throw std::runtime_error("Stream is closed");
}
return ftell(file); return ftell(file);
} }
size_t FileStream::size() size_t FileStream::size() {
{ if (file == NULL) {
if (file == NULL) throw std::runtime_error("Stream is closed");
{ }
throw std::runtime_error("Stream is closed");
}
size_t pos = ftell(file); size_t pos = ftell(file);
fseek(file, 0, SEEK_END); fseek(file, 0, SEEK_END);
size_t size = ftell(file); size_t size = ftell(file);
fseek(file, pos, SEEK_SET); fseek(file, pos, SEEK_SET);
return size; return size;
} }
void FileStream::close() void FileStream::close() {
{ if (file != NULL) {
if (file != NULL) fclose(file);
{ file = NULL;
fclose(file); }
file = NULL;
}
} }

View File

@@ -1,5 +1,14 @@
#include "HTTPClient.h" #include "HTTPClient.h"
#include <string.h> // for memcpy
#include <algorithm> // for transform
#include <cassert> // for assert
#include <cctype> // for tolower
#include <ostream> // for operator<<, basic_ostream
#include <stdexcept> // for runtime_error
#include "BellSocket.h" // for bell
using namespace bell; using namespace bell;
void HTTPClient::Response::connect(const std::string& url) { void HTTPClient::Response::connect(const std::string& url) {

View File

@@ -1,9 +1,17 @@
#include "SocketStream.h" #include "SocketStream.h"
#include <stdint.h> // for uint8_t
#include <cstdio> // for NULL, ssize_t
#include "TCPSocket.h" // for TCPSocket
#include "TLSSocket.h" // for TLSSocket
using namespace bell; using namespace bell;
int SocketBuffer::open(const std::string& hostname, int port, bool isSSL) { int SocketBuffer::open(const std::string& hostname, int port, bool isSSL) {
if (internalSocket != nullptr) { close(); } if (internalSocket != nullptr) {
close();
}
if (isSSL) { if (isSSL) {
internalSocket = std::make_unique<bell::TLSSocket>(); internalSocket = std::make_unique<bell::TLSSocket>();
} else { } else {

View File

@@ -1,5 +1,14 @@
#include "TLSSocket.h" #include "TLSSocket.h"
#include "X509Bundle.h"
#include <mbedtls/ctr_drbg.h> // for mbedtls_ctr_drbg_free, mbedtls_ctr_...
#include <mbedtls/entropy.h> // for mbedtls_entropy_free, mbedtls_entro...
#include <mbedtls/net_sockets.h> // for mbedtls_net_connect, mbedtls_net_free
#include <mbedtls/ssl.h> // for mbedtls_ssl_conf_authmode, mbedtls_...
#include <cstring> // for strlen, NULL
#include <stdexcept> // for runtime_error
#include "BellLogger.h" // for AbstractLogger, BELL_LOG
#include "X509Bundle.h" // for shouldVerify, attach
/** /**
* Platform TLSSocket implementation for the mbedtls * Platform TLSSocket implementation for the mbedtls

View File

@@ -4,40 +4,50 @@ namespace bell {
#ifdef BELL_DISABLE_REGEX #ifdef BELL_DISABLE_REGEX
void URLParser::parse(const char* url, std::vector<std::string>& match) { void URLParser::parse(const char* url, std::vector<std::string>& match) {
match[0] = url; match[0] = url;
char scratch[512]; char scratch[512];
/* Parsing the following (http|https://[host][/path][?query]#hash] as in regex /* Parsing the following (http|https://[host][/path][?query]#hash] as in regex
* below. This needs to be changed if you update that regex */ * below. This needs to be changed if you update that regex */
// get the schema // get the schema
if (sscanf(url, "%[^:]:/", scratch) > 0) match[1] = scratch; if (sscanf(url, "%[^:]:/", scratch) > 0)
match[1] = scratch;
// get the host // get the host
if (sscanf(url, "htt%*[^:]://%512[^/#?]", scratch) > 0) match[2] = scratch; if (sscanf(url, "htt%*[^:]://%512[^/#?]", scratch) > 0)
match[2] = scratch;
// get the path // get the path
url = strstr(url, match[2].c_str()) + match[2].size(); url = strstr(url, match[2].c_str()) + match[2].size();
if (sscanf(url, "/%512[^?]", scratch) > 0) match[3] = scratch; if (sscanf(url, "/%512[^?]", scratch) > 0)
else if (*url && *url != '?' && *url != '#') url++; match[3] = scratch;
else if (*url && *url != '?' && *url != '#')
url++;
// get the query // get the query
if (match[3].size()) url += match[3].size() + 1; if (match[3].size())
if (sscanf(url, "?%512[^#]", scratch) > 0) match[4] = scratch; url += match[3].size() + 1;
if (sscanf(url, "?%512[^#]", scratch) > 0)
match[4] = scratch;
// get the hash // get the hash
if (match[4].size()) url += match[4].size() + 1; if (match[4].size())
if (sscanf(url, "#%512s", scratch) > 0) match[5] = scratch; url += match[4].size() + 1;
if (sscanf(url, "#%512s", scratch) > 0)
match[5] = scratch;
// fix the acquired items // fix the acquired items
match[3] = "/" + match[3]; match[3] = "/" + match[3];
if (match[4].size()) match[4] = "?" + match[4]; if (match[4].size())
match[4] = "?" + match[4];
// need at least schema and host // need at least schema and host
if (match[1].size() == 0 || match[2].size() == 0) match.clear(); if (match[1].size() == 0 || match[2].size() == 0)
match.clear();
} }
#else #else
const std::regex URLParser::urlParseRegex = std::regex( const std::regex URLParser::urlParseRegex = std::regex(
"^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?"); "^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?");
#endif #endif
} } // namespace bell

View File

@@ -1,5 +1,15 @@
#include "X509Bundle.h" #include "X509Bundle.h"
#include <mbedtls/md.h> // for mbedtls_md, mbedtls_md_get_size
#include <mbedtls/pk.h> // for mbedtls_pk_can_do, mbedtls_pk_pa...
#include <mbedtls/ssl.h> // for mbedtls_ssl_conf_ca_chain, mbedt...
#include <mbedtls/x509.h> // for mbedtls_x509_buf, MBEDTLS_ERR_X5...
#include <stdlib.h> // for free, calloc
#include <string.h> // for memcmp, memcpy
#include <stdexcept> // for runtime_error
#include "BellLogger.h" // for AbstractLogger, BELL_LOG
using namespace bell::X509Bundle; using namespace bell::X509Bundle;
static mbedtls_x509_crt s_dummy_crt; static mbedtls_x509_crt s_dummy_crt;
@@ -21,7 +31,8 @@ int bell::X509Bundle::crtCheckCertificate(mbedtls_x509_crt* child,
if ((ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf, if ((ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf,
pub_key_len)) != 0) { pub_key_len)) != 0) {
BELL_LOG(error, TAG, "PK parse failed with error %X", ret); BELL_LOG(error, TAG, "PK parse failed with error 0x%04x, key len = %d", ret,
pub_key_len);
goto cleanup; goto cleanup;
} }
@@ -110,6 +121,8 @@ int bell::X509Bundle::crtVerifyCallback(void* buf, mbedtls_x509_crt* crt,
ret = crtCheckCertificate( ret = crtCheckCertificate(
child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len, child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len,
key_len); key_len);
} else {
BELL_LOG(error, TAG, "Certificate not found in bundle");
} }
if (ret == 0) { if (ret == 0) {
@@ -138,10 +151,13 @@ void bell::X509Bundle::init(const uint8_t* x509_bundle, size_t bundle_size) {
throw std::runtime_error("Unable to allocate memory for bundle"); throw std::runtime_error("Unable to allocate memory for bundle");
} }
bundleBytes.resize(bundle_size);
memcpy(bundleBytes.data(), x509_bundle, bundle_size);
const uint8_t* cur_crt; const uint8_t* cur_crt;
/* This is the maximum region that is allowed to access */ /* This is the maximum region that is allowed to access */
const uint8_t* bundle_end = x509_bundle + bundle_size; const uint8_t* bundle_end = bundleBytes.data() + bundle_size;
cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET; cur_crt = bundleBytes.data() + BUNDLE_HEADER_OFFSET;
for (int i = 0; i < num_certs; i++) { for (int i = 0; i < num_certs; i++) {
crts[i] = cur_crt; crts[i] = cur_crt;

View File

@@ -1,22 +1,18 @@
#pragma once #pragma once
#include <BellLogger.h> #include <BellLogger.h> // for bell
#include <stdlib.h> #include <stdint.h> // for uint8_t
#include <sys/types.h> #include <stdlib.h> // for free, size_t
#include <fstream> #include <functional> // for function
#include <functional> #include <map> // for map
#include <iostream> #include <memory> // for unique_ptr
#include <map> #include <mutex> // for mutex
#include <memory> #include <string> // for string, hash, operator==, operator<
#include <utility> #include <unordered_map> // for unordered_map
#include <optional> #include <utility> // for pair
#include <regex> #include <vector> // for vector
#include <sstream>
#include <string> #include "CivetServer.h" // for CivetServer, CivetHandler
#include <mutex>
#include <unordered_map>
#include "CivetServer.h"
#include "civetweb.h"
using namespace bell; using namespace bell;
namespace bell { namespace bell {
@@ -46,7 +42,9 @@ class BellHTTPServer : public CivetHandler {
} }
} }
}; };
typedef std::function<std::unique_ptr<HTTPResponse>(struct mg_connection* conn)> HTTPHandler; typedef std::function<std::unique_ptr<HTTPResponse>(
struct mg_connection* conn)>
HTTPHandler;
typedef std::function<void(struct mg_connection* conn, WSState)> typedef std::function<void(struct mg_connection* conn, WSState)>
WSStateHandler; WSStateHandler;
typedef std::function<void(struct mg_connection* conn, char*, size_t)> typedef std::function<void(struct mg_connection* conn, char*, size_t)>
@@ -79,7 +77,8 @@ class BellHTTPServer : public CivetHandler {
std::vector<int> getListeningPorts() { return server->getListeningPorts(); }; std::vector<int> getListeningPorts() { return server->getListeningPorts(); };
void close() { server->close(); } void close() { server->close(); }
std::unique_ptr<HTTPResponse> makeJsonResponse(const std::string& json, int status = 200); std::unique_ptr<HTTPResponse> makeJsonResponse(const std::string& json,
int status = 200);
std::unique_ptr<HTTPResponse> makeEmptyResponse(); std::unique_ptr<HTTPResponse> makeEmptyResponse();
void registerNotFound(HTTPHandler handler); void registerNotFound(HTTPHandler handler);
@@ -88,7 +87,8 @@ class BellHTTPServer : public CivetHandler {
void registerWS(const std::string&, WSDataHandler dataHandler, void registerWS(const std::string&, WSDataHandler dataHandler,
WSStateHandler stateHandler); WSStateHandler stateHandler);
static std::unordered_map<std::string, std::string> extractParams(struct mg_connection* conn); static std::unordered_map<std::string, std::string> extractParams(
struct mg_connection* conn);
private: private:
std::unique_ptr<CivetServer> server; std::unique_ptr<CivetServer> server;

View File

@@ -14,6 +14,6 @@ class Socket {
virtual size_t read(uint8_t* buf, size_t len) = 0; virtual size_t read(uint8_t* buf, size_t len) = 0;
virtual bool isOpen() = 0; virtual bool isOpen() = 0;
virtual void close() = 0; virtual void close() = 0;
virtual int getFd() = 0;
}; };
} // namespace bell } // namespace bell

View File

@@ -1,8 +1,7 @@
#pragma once #pragma once
#include <iostream> #include <iostream> // for istream, ostream
#include <string> #include <string> // for string
#include <vector>
namespace bell::BellTar { namespace bell::BellTar {
typedef long long unsigned file_size_t; typedef long long unsigned file_size_t;

View File

@@ -1,31 +1,28 @@
#pragma once #pragma once
#include <stdlib.h> #include <stdint.h> // for uint8_t, int16_t, int32_t, uint32_t
#include <iostream> #include <stdlib.h> // for size_t
#include <fstream> #include <memory> // for shared_ptr
#include <vector> #include <vector> // for vector
#include <cstring>
#include <memory>
#include "ByteStream.h"
namespace bell namespace bell {
{ class ByteStream;
class BinaryReader
{
std::shared_ptr<ByteStream> stream;
size_t currentPos = 0;
public: class BinaryReader {
BinaryReader(std::shared_ptr<ByteStream> stream); std::shared_ptr<ByteStream> stream;
int32_t readInt(); size_t currentPos = 0;
int16_t readShort();
uint32_t readUInt(); public:
long long readLong(); BinaryReader(std::shared_ptr<ByteStream> stream);
void close(); int32_t readInt();
uint8_t readByte(); int16_t readShort();
size_t size(); uint32_t readUInt();
size_t position(); long long readLong();
std::vector<uint8_t> readBytes(size_t); void close();
void skip(size_t); uint8_t readByte();
}; size_t size();
} size_t position();
std::vector<uint8_t> readBytes(size_t);
void skip(size_t);
};
} // namespace bell

View File

@@ -1,10 +1,11 @@
#pragma once #pragma once
#ifndef ESP_PLATFORM #ifndef ESP_PLATFORM
#include <bit> #include <bit> // for endian
#endif #endif
#include <iostream> #include <stdint.h> // for int16_t, int32_t, int64_t, uint16_t, uint32_t
#include <vector> #include <cstddef> // for byte
#include <iostream> // for istream, ostream
namespace bell { namespace bell {
class BinaryStream { class BinaryStream {

Some files were not shown because too many files have changed in this diff Show More