mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-07 20:17:04 +03:00
Compare commits
48 Commits
v0.5.642-v
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d45fb6f8f | ||
|
|
d76001a023 | ||
|
|
7951c59d11 | ||
|
|
64d609cc33 | ||
|
|
6b368a7c02 | ||
|
|
3159edc26c | ||
|
|
86cee80393 | ||
|
|
5e11ccbd87 | ||
|
|
874abb99ed | ||
|
|
3c418097b8 | ||
|
|
c576587de4 | ||
|
|
f06c08cef7 | ||
|
|
6385fe9be4 | ||
|
|
31cb0f5df4 | ||
|
|
2df24e2463 | ||
|
|
c844ce01e4 | ||
|
|
a883bd97ef | ||
|
|
b7932a6407 | ||
|
|
8e8a1ffe3d | ||
|
|
a35b5204d5 | ||
|
|
ee7d2a492c | ||
|
|
46f89026ed | ||
|
|
3c317b0b86 | ||
|
|
8b97e3562d | ||
|
|
57ae93447c | ||
|
|
29a74b9ca8 | ||
|
|
d95d1cd3db | ||
|
|
8947f2fc75 | ||
|
|
b6c296460c | ||
|
|
e25c18a070 | ||
|
|
8e3fde6f6e | ||
|
|
26d3d99738 | ||
|
|
ff5fb3a7d1 | ||
|
|
c3bf7f4a04 | ||
|
|
3df6568b6a | ||
|
|
ca97b8045e | ||
|
|
5e8a3fd755 | ||
|
|
92fffb7635 | ||
|
|
5e372860c8 | ||
|
|
36c3f0eb49 | ||
|
|
4ecc9da6d3 | ||
|
|
45e0dd38df | ||
|
|
3922851129 | ||
|
|
f08bde8d48 | ||
|
|
a6b57604d3 | ||
|
|
ca33ff4ba9 | ||
|
|
8b55fa3986 | ||
|
|
e04a631665 |
30
.github/workflows/I2S-4MBFlash.yml
vendored
Normal file
30
.github/workflows/I2S-4MBFlash.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: I2S-4MBFlash
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
branches: [ master-cmake ]
|
||||
pull_request:
|
||||
branches: [ master-cmake ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache build
|
||||
id: cache-build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{github.workspace}}/build
|
||||
key: ${{ runner.os }}-I2S-4MBFlash
|
||||
- name: Build the firmware
|
||||
run: |
|
||||
docker run --rm -v $PWD:/project -w /project espressif/idf:release-v4.0 /bin/bash -c "python3 -m pip install --upgrade pip setuptools wheel && pip3 install protobuf grpcio-tools && cp build-scripts/I2S-4MFlash-sdkconfig.defaults sdkconfig && idf.py build"
|
||||
|
||||
|
||||
20
.github/workflows/cmake.yml
vendored
Normal file
20
.github/workflows/cmake.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test-workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set Target
|
||||
run: echo "TARGET_BUILD_NAME=SqueezeAmp" >> $GITHUB_ENV
|
||||
- name: Get Target
|
||||
run: |
|
||||
echo "Target is ${TARGET_BUILD_NAME}"
|
||||
env
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -67,3 +67,4 @@ libs/
|
||||
/_*
|
||||
sdkconfig
|
||||
squeezelite-esp32-jsonblob.zip
|
||||
*.bak
|
||||
|
||||
203
README.md
203
README.md
@@ -1,11 +1,41 @@
|
||||
# Squeezelite-esp32
|
||||
## Supported Hardware
|
||||
## 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
|
||||
|
||||
- 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 from a Bluetooth device (iPhone, Android)
|
||||
- Stream from an AirPlay controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
|
||||
|
||||
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 (you'll need *one* resistor...)
|
||||
|
||||
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 ...)
|
||||
- IR receiver (no pullup resistor or capacitor needed, just the 38kHz receiver)
|
||||
- Monochrome, GrayScale or Color displays using SPI or I2S (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735 and ST7789).
|
||||
|
||||
Other features include
|
||||
|
||||
- Resampling
|
||||
- 10-bands equalizer
|
||||
- Automatic initial setup using any WiFi device
|
||||
- Full web interface for further configuration/management
|
||||
- Firmware over-the-air update
|
||||
|
||||
## 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.
|
||||
### 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.)
|
||||
|
||||
Please note that when sending to a Bluetooth speaker, then resampling *must* be enabled (using -R) option if you want to send audio with rate other than 44.1kHz. Similarly, when using SPDIF, only 44.1kHz and 48kHz are supported so you might have to enable resampling as well. If you connect a DAC, choice 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.
|
||||
### SqueezeAMP
|
||||
Works with the SqueezeAMP see [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.
|
||||
|
||||
NB: You can use the pre-build binaries SqueezeAMP4MBFlash/SqueezeAMP8MBFlash 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
|
||||
- batt_config: channel=7,scale=20.24
|
||||
- dac_config: model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0
|
||||
@@ -84,8 +114,9 @@ The parameter "dac_controlset" allows definition of simple commands to be sent o
|
||||
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. **Note that all values must be decimal**
|
||||
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 well-known configuration, this is ignored
|
||||
### SPDIF
|
||||
The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF.
|
||||
|
||||
@@ -97,13 +128,15 @@ Leave it blank to disable SPDIF usage, you can also define them at compile time
|
||||
```
|
||||
bck=<gpio>,ws=<gpio>,do=<gpio>
|
||||
```
|
||||
NB: For well-known configuration, this is ignored
|
||||
### Display
|
||||
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
|
||||
```
|
||||
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
|
||||
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[,rotate]]
|
||||
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[,rotate]]
|
||||
```
|
||||
- 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
|
||||
- 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
|
||||
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
|
||||
@@ -156,12 +189,13 @@ Syntax is:
|
||||
```
|
||||
<gpio>=Vcc|GND|amp[:1|0]|ir|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
|
||||
```
|
||||
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for SqueezeAMP 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.
|
||||
### 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
|
||||
```
|
||||
[green=0..100][,red=0..100]
|
||||
```
|
||||
NB: For well-known configuration, this is ignored
|
||||
### 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.
|
||||
|
||||
@@ -278,12 +312,67 @@ There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctr
|
||||
### Battery / ADC
|
||||
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. 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>
|
||||
channel=0..7,scale=<scale>,cells=<2|3>
|
||||
```
|
||||
NB: Set parameter to empty to disable battery reading
|
||||
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
|
||||
## 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".
|
||||
- Once connected, navigate to 192.168.4.1
|
||||
- Wait for the list of access points visible from the device to populate in the web page.
|
||||
- Choose an access point and enter any credential as needed
|
||||
- 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)
|
||||
|
||||
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
|
||||
- Using the list of predefined options, choose the mode in which you want squeezelite to start
|
||||
- Generate the command
|
||||
- Add or change any additional command line option (for example player name, etc)
|
||||
- Activate squeezelite execution: this tells the device to automatiaclly run the command at start
|
||||
- Update the configuration
|
||||
- click on the "start toggle" button. This will force a reboot.
|
||||
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting (you might have to fiddle with it a few times)
|
||||
- You can enable accessto NVS parameters under 'credits'
|
||||
|
||||
## 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.
|
||||
|
||||
## Update Squeezelite
|
||||
- From the firmware tab, click on "Check for Updates"
|
||||
- Look for updated binaries
|
||||
- Select a line
|
||||
- Click on "Flash!"
|
||||
- The system will reboot into recovery mode (if not already in that mode), wipe the squeezelite partition and download/flash the selected version
|
||||
- You can choose a local file or have a local webserver
|
||||
|
||||
## Recovery
|
||||
- From the firmware tab, click on the "Recovery" button. This will reboot the ESP32 into recovery, where additional configuration options are available from the NVS editor
|
||||
|
||||
## Additional configuration notes (from the Web UI)
|
||||
The squeezelite options are very similar to the regular Linux ones. Differences are :
|
||||
|
||||
- the output is -o ["BT -n '<sinkname>' "] | [I2S]
|
||||
- if you've compiled with RESAMPLE option, normal soxr options are available using -R [-u <options>]. Note that anything above LQ or MQ will overload the CPU
|
||||
- if you've used RESAMPLE16, <options> are (b|l|m)[:i], with b = basic linear interpolation, l = 13 taps, m = 21 taps, i = interpolate filter coefficients
|
||||
|
||||
For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and resample everything to 44100 and use 16 bits resample with medium quality, the command line is:
|
||||
|
||||
squeezelite -o "BT -n 'BT <sinkname>'" -b 500:2000 -R -u m -Z 192000 -r "44100-44100"
|
||||
|
||||
See squeezlite command line, but keys options are
|
||||
|
||||
- Z <rate> : tell LMS what is the max sample rate supported before LMS resamples
|
||||
- R (see above)
|
||||
- r "<minrate>-<maxrate>"
|
||||
- C <sec> : set timeout to switch off amp gpio
|
||||
- W : activate WAV and AIFF header parsing
|
||||
# Building everything yourself
|
||||
## Setting up ESP-IDF
|
||||
### Docker
|
||||
You can use docker to build squeezelite-esp32
|
||||
You can use docker to build squeezelite-esp32 (optional)
|
||||
First you need to build the Docker container:
|
||||
```
|
||||
docker build -t esp-idf .
|
||||
@@ -301,9 +390,11 @@ If you want to use a more recent version of gcc and IDF (4.0 stable), move to cm
|
||||
|
||||
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/
|
||||
And then copying the i2s.c patch file from this repo over to the esp-idf folder
|
||||
You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264
|
||||
You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264. As of this writing (08.2020), espressif has patched esp-dsp so this is no more needed
|
||||
|
||||
## Building Squeezelite-esp32
|
||||
Don't forget the to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)
|
||||
### Using make (deprecated)
|
||||
MOST IMPORTANT: create the right default config file
|
||||
- make defconfig
|
||||
(Note: You can also copy over config files from the build-scripts folder to ./sdkconfig)
|
||||
@@ -334,59 +425,30 @@ You can also manually download the recovery & initial boot
|
||||
```
|
||||
python ${IDF_PATH}/components/esptool_py/esptool/esptool.py --chip esp32 --port ${ESPPORT} --baud ${ESPBAUD} --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xd000 ./build/ota_data_initial.bin 0x1000 ./build/bootloader/bootloader.bin 0x10000 ./build/recovery.bin 0x8000 ./build/partitions.bin
|
||||
```
|
||||
|
||||
# Configuration
|
||||
1/ 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".
|
||||
- Once connected, navigate to 192.168.4.1
|
||||
- Wait for the list of access points visible from the device to populate in the web page.
|
||||
- Choose an access point and enter any credential as needed
|
||||
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
|
||||
|
||||
2/ 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.
|
||||
- navigate to the address that was noted in step #1
|
||||
- Using the list of predefined options, hoose the mode in which you want squeezelite to start
|
||||
- Generate the command
|
||||
- Add or change any additional command line option (for example player name, etc)
|
||||
- Activate squeezelite execution: this tells the device to automatiaclly run the command at start
|
||||
- Update the configuration
|
||||
- click on the "start toggle" button. This will force a reboot.
|
||||
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting
|
||||
|
||||
3/ Updating Squeezelite
|
||||
- From the firmware tab, click on "Check for Updates"
|
||||
- Look for updated binaries
|
||||
- Select a line
|
||||
- Click on "Flash!"
|
||||
- The system will reboot into recovery mode (if not already in that mode), wipe the squeezelite partition and download/flash the selected version
|
||||
|
||||
3/ Recovery
|
||||
- From the firmware tab, click on the "Recovery" button. This will reboot the ESP32 into recovery, where additional configuration options are available from the NVS editor
|
||||
|
||||
# Additional command line notes, configured from the http configuration
|
||||
The squeezelite options are very similar to the regular Linux ones. Differences are :
|
||||
|
||||
- the output is -o ["BT -n '<sinkname>' "] | [I2S]
|
||||
- if you've compiled with RESAMPLE option, normal soxr options are available using -R [-u <options>]. Note that anything above LQ or MQ will overload the CPU
|
||||
- if you've used RESAMPLE16, <options> are (b|l|m)[:i], with b = basic linear interpolation, l = 13 taps, m = 21 taps, i = interpolate filter coefficients
|
||||
|
||||
For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and resample everything to 44100 and use 16 bits resample with medium quality, the command line is:
|
||||
|
||||
squeezelite -o "BT -n 'BT <sinkname>'" -b 500:2000 -R -u m -Z 192000 -r "44100-44100"
|
||||
|
||||
See squeezlite command line, but keys options are
|
||||
|
||||
- Z <rate> : tell LMS what is the max sample rate supported before LMS resamples
|
||||
- R (see above)
|
||||
- r "<minrate>-<maxrate>"
|
||||
|
||||
## Additional misc notes to do you build
|
||||
### Using cmake
|
||||
Create you config using 'idf.py menuconfig' then build binaries using 'idf.py all'. It will build the recovery and the application (squeezelite) itself. See the recommended command to upload everything. Otherwise, if you just want to download squeezelite, do
|
||||
```
|
||||
python.exe <idf_path>\components\esptool_py\esptool\esptool.py -p COM<n> -b 921600 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x150000 build\squeezelite.bin
|
||||
```
|
||||
Use 'idf monitor' to monitor the application (see esp-idf documentation)
|
||||
## Additional misc notes to do you build (kitchen sink)
|
||||
- don't forget to set IDF_PATH, ESPPORT and ESPBAUD
|
||||
- When initially cloning the repo, make sure you do it recursively. For example:
|
||||
- git clone --recursive https://github.com/sle118/squeezelite-esp32.git
|
||||
- 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
|
||||
- as of this writing, ESP-IDF has a bug int he way the PLL values are calculated for i2s, so you *must* use the i2s.c file in the patch directory
|
||||
- misc compiler #define
|
||||
- use no resampling or set RESAMPLE (soxr - but overloads CPU) or set RESAMPLE16 for fast fixed 16 bits resampling
|
||||
- use LOOPBACK (mandatory)
|
||||
- use BYTES_PER_FRAME=4 (8 is not fully functionnal)
|
||||
- LINKALL (mandatory)
|
||||
- NO_FAAD unless you want to us faad, which currently overloads the CPU
|
||||
- TREMOR_ONLY (mandatory)
|
||||
### codecs
|
||||
- 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
|
||||
- libfaad does not really support real time, but if you want to try
|
||||
- libfaad does not really support real time, but if you want to try (but using helixaac is a better option)
|
||||
- -O3 -DFIXED_POINT -DSMALL_STACK
|
||||
- change ac_link in configure and case ac_files, remove ''
|
||||
- compiler but in cfft.c and cffti1, must disable optimization using
|
||||
@@ -399,21 +461,4 @@ See squeezlite command line, but keys options are
|
||||
- change ac_files to remove ''
|
||||
- add DEPS_CFLAGS and DEPS_LIBS to avoid pkg-config to be required
|
||||
- stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=32000 and unset VAR_ARRAYS in config.h
|
||||
- better use helixaac
|
||||
- set IDF_PATH=/home/esp-idf
|
||||
- set ESPPORT=COM9
|
||||
- update flash partition size
|
||||
- other compiler #define
|
||||
- use no resampling or set RESAMPLE (soxr) or set RESAMPLE16 for fast fixed 16 bits resampling
|
||||
- use LOOPBACK (mandatory)
|
||||
- use BYTES_PER_FRAME=4 (8 is not fully functionnal)
|
||||
- LINKALL (mandatory)
|
||||
- NO_FAAD unless you want to us faad, which currently overloads the CPU
|
||||
- TREMOR_ONLY (mandatory)
|
||||
- When initially cloning the repo, make sure you do it recursively. For example:
|
||||
- git clone --recursive https://github.com/sle118/squeezelite-esp32.git
|
||||
- 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
|
||||
|
||||
|
||||
|
||||
- libmad has been patched to avoid using a lot of stack and is not provided here. There is an issue with sync detection in 1.15.1b from where the original stack patch was done but since a few fixes have been made wrt sync detection. This 1.15.1b-10 found on debian fixes the issue where mad thinks it has reached sync but has not and so returns a wrong sample rate. It comes at the expense of 8KB (!) of code where a simple check in squeezelite/mad.c that next_frame[0] is 0xff and next_frame[1] & 0xf0 is 0xf0 does the trick ...
|
||||
|
||||
@@ -26,7 +26,7 @@ CONFIG_SPKFAULT_GPIO=-1
|
||||
CONFIG_SPKFAULT_GPIO_LEVEL=0
|
||||
CONFIG_BAT_CHANNEL=-1
|
||||
CONFIG_BAT_SCALE="0"
|
||||
CONFIG_SDIF_NUM=0
|
||||
CONFIG_SPDIF_NUM=0
|
||||
CONFIG_SPDIF_BCK_IO=27
|
||||
CONFIG_SPDIF_WS_IO=26
|
||||
CONFIG_SPDIF_DO_IO=-1
|
||||
@@ -116,10 +116,6 @@ CONFIG_INCLUDE_VORBIS=y
|
||||
CONFIG_INCLUDE_ALAC=y
|
||||
|
||||
CONFIG_A1S=y
|
||||
CONFIG_SDIF_NUM=0
|
||||
CONFIG_SPDIF_BCK_IO=-1
|
||||
CONFIG_SPDIF_WS_IO=-1
|
||||
CONFIG_SPDIF_DO_IO=-1
|
||||
|
||||
CONFIG_A2DP_SINK_NAME="SMSL BT4.2"
|
||||
CONFIG_A2DP_DEV_NAME="Squeezelite"
|
||||
|
||||
@@ -25,13 +25,13 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
|
||||
CONFIG_BAT_CHANNEL=-1
|
||||
CONFIG_BAT_SCALE="0"
|
||||
CONFIG_I2S_NUM=0
|
||||
CONFIG_I2S_BCK_IO=33
|
||||
CONFIG_I2S_WS_IO=25
|
||||
CONFIG_I2S_DO_IO=32
|
||||
CONFIG_I2S_BCK_IO=-1
|
||||
CONFIG_I2S_WS_IO=-1
|
||||
CONFIG_I2S_DO_IO=-1
|
||||
CONFIG_I2S_DI_IO=-1
|
||||
CONFIG_SDIF_NUM=0
|
||||
CONFIG_SPDIF_BCK_IO=33
|
||||
CONFIG_SPDIF_WS_IO=25
|
||||
CONFIG_SPDIF_NUM=0
|
||||
CONFIG_SPDIF_BCK_IO=-1
|
||||
CONFIG_SPDIF_WS_IO=-1
|
||||
CONFIG_SPDIF_DO_IO=-1
|
||||
CONFIG_MUTE_GPIO=-1
|
||||
CONFIG_MUTE_GPIO_LEVEL=-1
|
||||
@@ -121,11 +121,6 @@ CONFIG_INCLUDE_ALAC=y
|
||||
|
||||
CONFIG_BASIC_I2C_BT=y
|
||||
|
||||
CONFIG_SDIF_NUM=0
|
||||
CONFIG_SPDIF_BCK_IO=-1
|
||||
CONFIG_SPDIF_WS_IO=-1
|
||||
CONFIG_SPDIF_DO_IO=-1
|
||||
|
||||
CONFIG_A2DP_SINK_NAME="SMSL BT4.2"
|
||||
CONFIG_A2DP_DEV_NAME="Squeezelite"
|
||||
CONFIG_A2DP_CONTROL_DELAY_MS=500
|
||||
|
||||
@@ -29,7 +29,7 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
|
||||
CONFIG_BAT_CHANNEL=7
|
||||
CONFIG_BAT_SCALE="20.24"
|
||||
CONFIG_I2S_NUM=0
|
||||
CONFIG_SDIF_NUM=0
|
||||
CONFIG_SPDIF_NUM=0
|
||||
CONFIG_SPDIF_CONFIG="bck=33,ws=25,do=15"
|
||||
CONFIG_DAC_CONFIG="model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0"
|
||||
CONFIG_IDF_TARGET_ESP32=y
|
||||
|
||||
@@ -29,7 +29,7 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
|
||||
CONFIG_BAT_CHANNEL=7
|
||||
CONFIG_BAT_SCALE="20.24"
|
||||
CONFIG_I2S_NUM=0
|
||||
CONFIG_SDIF_NUM=0
|
||||
CONFIG_SPDIF_NUM=0
|
||||
CONFIG_SPDIF_CONFIG="bck=33,ws=25,do=15"
|
||||
CONFIG_DAC_CONFIG="model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14"
|
||||
CONFIG_MUTE_GPIO_LEVEL=-1
|
||||
|
||||
Binary file not shown.
@@ -90,7 +90,7 @@ static void Update16( struct GDS_Device* Device ) {
|
||||
for (int i = FirstRow; i <= LastRow; i++) {
|
||||
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
|
||||
optr += ChunkSize;
|
||||
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
|
||||
if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
|
||||
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
|
||||
optr = Private->iRAM;
|
||||
}
|
||||
@@ -157,7 +157,7 @@ static void Update24( struct GDS_Device* Device ) {
|
||||
for (int i = FirstRow; i <= LastRow; i++) {
|
||||
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize);
|
||||
optr += ChunkSize;
|
||||
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
|
||||
if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
|
||||
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
|
||||
optr = Private->iRAM;
|
||||
}
|
||||
|
||||
@@ -35,12 +35,20 @@ static const struct GDS_FontDef *GuessFont( struct GDS_Device *Device, int FontT
|
||||
case GDS_FONT_MEDIUM:
|
||||
default:
|
||||
return &Font_droid_sans_fallback_15x17;
|
||||
#ifdef USE_LARGE_FONTS
|
||||
case GDS_FONT_LARGE:
|
||||
return &Font_droid_sans_fallback_24x28;
|
||||
break;
|
||||
case GDS_FONT_SEGMENT:
|
||||
if (Device->Height == 32) return &Font_Tarable7Seg_16x32;
|
||||
else return &Font_Tarable7Seg_32x64;
|
||||
#else
|
||||
case GDS_FONT_LARGE:
|
||||
case GDS_FONT_SEGMENT:
|
||||
ESP_LOGW(TAG, "large fonts disabled");
|
||||
return &Font_droid_sans_fallback_15x17;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ static bool I2CDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data,
|
||||
bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
|
||||
I2CPortNumber = PortNumber;
|
||||
|
||||
I2CWait = pdMS_TO_TICKS( Speed ? Speed / 4000 : 100 );
|
||||
I2CWait = pdMS_TO_TICKS( Speed ? (250 * 250000) / Speed : 250 );
|
||||
|
||||
if (SDA != -1 && SCL != -1) {
|
||||
i2c_config_t Config = { 0 };
|
||||
|
||||
@@ -33,15 +33,27 @@ static struct {
|
||||
int channel;
|
||||
float sum, avg, scale;
|
||||
int count;
|
||||
int cells;
|
||||
TimerHandle_t timer;
|
||||
} battery;
|
||||
} battery = {
|
||||
.channel = CONFIG_BAT_CHANNEL,
|
||||
.cells = 2,
|
||||
};
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
int battery_value_svc(void) {
|
||||
return battery.avg;
|
||||
return battery.avg;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
uint8_t battery_level_svc(void) {
|
||||
// TODO: this is totally incorrect
|
||||
return battery.avg ? (battery.avg - (3.0 * battery.cells)) / ((4.2 - 3.0) * battery.cells) * 100 : 0;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
@@ -59,29 +71,30 @@ static void battery_callback(TimerHandle_t xTimer) {
|
||||
*
|
||||
*/
|
||||
void battery_svc_init(void) {
|
||||
battery.channel = CONFIG_BAT_CHANNEL;
|
||||
#ifdef CONFIG_BAT_SCALE
|
||||
battery.scale = atof(CONFIG_BAT_SCALE);
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BAT_LOCKED
|
||||
char *nvs_item = config_alloc_get_default(NVS_TYPE_STR, "bat_config", "n", 0);
|
||||
if (nvs_item) {
|
||||
char *p;
|
||||
char *p;
|
||||
#ifndef CONFIG_BAT_LOCKED
|
||||
if ((p = strcasestr(nvs_item, "channel")) != NULL) battery.channel = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "scale")) != NULL) battery.scale = atof(strchr(p, '=') + 1);
|
||||
#endif
|
||||
if ((p = strcasestr(nvs_item, "cells")) != NULL) battery.cells = atof(strchr(p, '=') + 1);
|
||||
free(nvs_item);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (battery.channel != -1) {
|
||||
ESP_LOGI(TAG, "Battery measure channel: %u, scale %f", battery.channel, battery.scale);
|
||||
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(battery.channel, ADC_ATTEN_DB_0);
|
||||
|
||||
|
||||
battery.avg = adc1_get_raw(battery.channel) * battery.scale / 4095.0;
|
||||
battery.timer = xTimerCreate("battery", BATTERY_TIMER / portTICK_RATE_MS, pdTRUE, NULL, battery_callback);
|
||||
xTimerStart(battery.timer, portMAX_DELAY);
|
||||
|
||||
ESP_LOGI(TAG, "Battery measure channel: %u, scale %f, cells %u, avg %.2fV", battery.channel, battery.scale, battery.cells, battery.avg);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "No battery");
|
||||
}
|
||||
|
||||
@@ -17,4 +17,5 @@ extern void (*spkfault_handler_svc)(bool inserted);
|
||||
extern bool spkfault_svc(void);
|
||||
|
||||
extern int battery_value_svc(void);
|
||||
extern uint8_t battery_level_svc(void);
|
||||
|
||||
|
||||
@@ -585,7 +585,7 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
|
||||
int scale = 8 - GDS_GetDepth(display);
|
||||
|
||||
// use "fast" version as we are not beyond screen boundaries
|
||||
if (visu.rotate) {
|
||||
if (rotate) {
|
||||
for (int r = 0; r < width; r++) {
|
||||
for (int c = VU_HEIGHT; --c >= 0;) {
|
||||
GDS_DrawPixelFast(display, c + x, r + y, *data++ >> scale);
|
||||
@@ -594,13 +594,13 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
|
||||
} else {
|
||||
for (int r = 0; r < width; r++) {
|
||||
for (int c = 0; c < VU_HEIGHT; c++) {
|
||||
GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale);
|
||||
GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// use "fast" version as we are not beyond screen boundaries
|
||||
if (visu.rotate) {
|
||||
if (rotate) {
|
||||
for (int r = 0; r < width; r++) {
|
||||
for (int c = VU_HEIGHT; --c >= 0;) {
|
||||
GDS_DrawPixelFast(display, c + x, r + y, grayMap[*data++]);
|
||||
@@ -609,11 +609,10 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
|
||||
} else {
|
||||
for (int r = 0; r < width; r++) {
|
||||
for (int c = 0; c < VU_HEIGHT; c++) {
|
||||
GDS_DrawPixelFast(display, r + x, c + y, grayMap[*data++]);
|
||||
GDS_DrawPixelFast(display, r + x, c + y, grayMap[*data++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// need to manually set dirty flag as DrawPixel does not do it
|
||||
@@ -629,8 +628,9 @@ static void grfe_handler( u8_t *data, int len) {
|
||||
|
||||
scroller.active = false;
|
||||
|
||||
// visu has priority when full screen on small screens
|
||||
if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) {
|
||||
// full screen artwork or for small screen, full screen visu has priority
|
||||
if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) ||
|
||||
(artwork.enable && artwork.x == 0 && artwork.y == 0)) {
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
return;
|
||||
}
|
||||
@@ -752,8 +752,11 @@ static void grfg_handler(u8_t *data, int len) {
|
||||
|
||||
LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
|
||||
|
||||
// on small screen, visu has priority when full screen
|
||||
if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) return;
|
||||
// full screen artwork or for small screen, visu has priority when full screen
|
||||
if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) ||
|
||||
(artwork.enable && artwork.x == 0 && artwork.y == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
@@ -796,7 +799,7 @@ static void grfa_handler(u8_t *data, int len) {
|
||||
|
||||
// when using full screen visualizer on small screen there is a brief overlay
|
||||
artwork.enable = (length != 0);
|
||||
|
||||
|
||||
// just a config or an actual artwork
|
||||
if (length < 32) {
|
||||
if (artwork.enable) {
|
||||
@@ -841,8 +844,10 @@ static void grfa_handler(u8_t *data, int len) {
|
||||
* Update visualization bars
|
||||
*/
|
||||
static void visu_update(void) {
|
||||
// no need to protect against no woning the display as we are playing
|
||||
if (pthread_mutex_trylock(&visu_export.mutex)) return;
|
||||
// no update when artwork is full screen (but no need to protect against not owning the display as we are playing
|
||||
if ((artwork.enable && artwork.x == 0 && artwork.y == 0) || pthread_mutex_trylock(&visu_export.mutex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int mode = visu.mode & ~VISU_ESP32;
|
||||
|
||||
@@ -966,9 +971,13 @@ static void visu_update(void) {
|
||||
}
|
||||
}
|
||||
} else if (displayer.width / 2 > 3 * VU_WIDTH / 4) {
|
||||
int width = visu.rotate ? visu.height : visu.width;
|
||||
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, width / 2, visu.rotate);
|
||||
draw_VU(display, vu_bitmap, visu.bars[1].current, width / 2, visu.row, width / 2, visu.rotate);
|
||||
if (visu.rotate) {
|
||||
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.height / 2, visu.rotate);
|
||||
draw_VU(display, vu_bitmap, visu.bars[1].current, 0, visu.row + visu.height / 2, visu.height / 2, visu.rotate);
|
||||
} else {
|
||||
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.width / 2, visu.rotate);
|
||||
draw_VU(display, vu_bitmap, visu.bars[1].current, visu.width / 2, visu.row, visu.width / 2, visu.rotate);
|
||||
}
|
||||
} else {
|
||||
int level = (visu.bars[0].current + visu.bars[1].current) / 2;
|
||||
draw_VU(display, vu_bitmap, level, 0, visu.row, visu.rotate ? visu.height : visu.width, visu.rotate);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "esp_system.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "monitor.h"
|
||||
|
||||
mutex_type slimp_mutex;
|
||||
|
||||
@@ -60,3 +61,11 @@ u16_t get_RSSI(void) {
|
||||
if (wifidata.primary != 0) return 100 + wifidata.rssi + 30;
|
||||
else return 0xffff;
|
||||
}
|
||||
|
||||
u16_t get_plugged(void) {
|
||||
return jack_inserted_svc() ? PLUG_HEADPHONE : 0;
|
||||
}
|
||||
|
||||
u8_t get_battery(void) {
|
||||
return (battery_level_svc() * 16) / 100;
|
||||
}
|
||||
|
||||
@@ -63,8 +63,13 @@ extern mutex_type slimp_mutex;
|
||||
#define LOCK_P mutex_lock(slimp_mutex)
|
||||
#define UNLOCK_P mutex_unlock(slimp_mutex)
|
||||
|
||||
// must provide or define as 0xffff
|
||||
u16_t get_RSSI(void);
|
||||
// bitmap of plugs status
|
||||
#define PLUG_LINE_IN 0x01
|
||||
#define PLUG_LINE_OUT 0x02
|
||||
#define PLUG_HEADPHONE 0x04
|
||||
u16_t get_RSSI(void); // must provide or define as 0xffff
|
||||
u16_t get_plugged(void); // must provide or define as 0x0
|
||||
u8_t get_battery(void); // must provide 0..15 or define as 0x0
|
||||
|
||||
// to be defined to nothing if you don't want to support these
|
||||
extern struct visu_export_s {
|
||||
|
||||
10
components/squeezelite/external/dac_external.c
vendored
10
components/squeezelite/external/dac_external.c
vendored
@@ -128,12 +128,12 @@ static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val) {
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, val, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
|
||||
ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
@@ -153,15 +153,15 @@ static uint8_t i2c_read_reg(uint8_t reg) {
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
|
||||
ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
|
||||
@@ -31,6 +31,7 @@ sure that using rate_delay would fix that
|
||||
*/
|
||||
|
||||
#include "squeezelite.h"
|
||||
#include "slimproto.h"
|
||||
#include "esp_pthread.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/i2c.h"
|
||||
@@ -82,6 +83,7 @@ const struct adac_s *adac = &dac_external;
|
||||
|
||||
static log_level loglevel;
|
||||
|
||||
static bool (*slimp_handler_chain)(u8_t *data, int len);
|
||||
static bool jack_mutes_amp;
|
||||
static bool running, isI2SStarted;
|
||||
static i2s_config_t i2s_config;
|
||||
@@ -108,6 +110,36 @@ static void (*jack_handler_chain)(bool inserted);
|
||||
|
||||
#define I2C_PORT 0
|
||||
|
||||
/****************************************************************************************
|
||||
* AUDO packet handler
|
||||
*/
|
||||
static bool handler(u8_t *data, int len){
|
||||
bool res = true;
|
||||
|
||||
if (!strncmp((char*) data, "audo", 4)) {
|
||||
struct audo_packet *pkt = (struct audo_packet*) data;
|
||||
// 0 = headphone (internal speakers off), 1 = sub out,
|
||||
// 2 = always on (internal speakers on), 3 = always off
|
||||
|
||||
if (jack_mutes_amp != (pkt->config == 0)) {
|
||||
jack_mutes_amp = pkt->config == 0;
|
||||
config_set_value(NVS_TYPE_STR, "jack_mutes_amp", jack_mutes_amp ? "y" : "n");
|
||||
|
||||
if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
|
||||
else adac->speaker(true);
|
||||
}
|
||||
|
||||
LOG_INFO("got AUDO %02x", pkt->config);
|
||||
} else {
|
||||
res = false;
|
||||
}
|
||||
|
||||
// chain protocol handlers (bitwise or is fine)
|
||||
if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* jack insertion handler
|
||||
*/
|
||||
@@ -164,6 +196,10 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
int silent_do = -1;
|
||||
char *p;
|
||||
esp_err_t res;
|
||||
|
||||
// chain SLIMP handlers
|
||||
slimp_handler_chain = slimp_handler;
|
||||
slimp_handler = handler;
|
||||
|
||||
p = config_alloc_get_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
|
||||
jack_mutes_amp = (strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0);
|
||||
@@ -192,12 +228,17 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
char *dac_config = config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO)
|
||||
",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO)
|
||||
",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL)
|
||||
",mute" STR(CONFIG_MUTE_GPIO));
|
||||
",mute=" STR(CONFIG_MUTE_GPIO));
|
||||
|
||||
i2s_pin_config_t i2s_dac_pin, i2s_spdif_pin;
|
||||
set_i2s_pin(spdif_config, &i2s_spdif_pin);
|
||||
set_i2s_pin(dac_config, &i2s_dac_pin);
|
||||
|
||||
/* BEWARE: i2s. must be patched to set tx_msb_right/rx_msb_right to 1
|
||||
* or SPDIF will not work. These settings are not accessible from
|
||||
* userland and I don't know why
|
||||
*/
|
||||
|
||||
// common I2S initialization
|
||||
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
|
||||
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
@@ -248,7 +289,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
char model[32] = "i2s";
|
||||
if ((p = strcasestr(dac_config, "model")) != NULL) sscanf(p, "%*[^=]=%31[^,]", model);
|
||||
if ((p = strcasestr(dac_config, "mute")) != NULL) {
|
||||
char mute[8];
|
||||
char mute[8] = "";
|
||||
sscanf(p, "%*[^=]=%7[^,]", mute);
|
||||
mute_control.gpio = atoi(mute);
|
||||
if ((p = strchr(mute, ':')) != NULL) mute_control.active = atoi(p + 1);
|
||||
@@ -256,7 +297,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
|
||||
for (int i = 0; adac == &dac_external && dac_set[i]; i++) if (strcasestr(dac_set[i]->model, model)) adac = dac_set[i];
|
||||
res = adac->init(dac_config, I2C_PORT, &i2s_config) ? ESP_OK : ESP_FAIL;
|
||||
|
||||
|
||||
res |= i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
|
||||
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_dac_pin);
|
||||
|
||||
|
||||
@@ -190,6 +190,7 @@ static void sendSTAT(const char *event, u32_t server_timestamp) {
|
||||
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
|
||||
#if EMBEDDED
|
||||
packn(&pkt.signal_strength, get_RSSI());
|
||||
packn(&pkt.voltage, (get_battery() << 4) | get_plugged());
|
||||
#else
|
||||
pkt.signal_strength = 0xffff;
|
||||
#endif
|
||||
@@ -197,7 +198,6 @@ static void sendSTAT(const char *event, u32_t server_timestamp) {
|
||||
packN(&pkt.output_buffer_size, status.output_size);
|
||||
packN(&pkt.output_buffer_fullness, status.output_full);
|
||||
packN(&pkt.elapsed_seconds, ms_played / 1000);
|
||||
// voltage;
|
||||
packN(&pkt.elapsed_milliseconds, ms_played);
|
||||
pkt.server_timestamp = server_timestamp; // keep this is server format - don't unpack/pack
|
||||
// error_code;
|
||||
|
||||
@@ -178,6 +178,12 @@ struct codc_packet {
|
||||
u8_t pcm_endianness;
|
||||
};
|
||||
|
||||
// initially Boom
|
||||
struct audo_packet {
|
||||
char opcode[4];
|
||||
u8_t config;
|
||||
};
|
||||
|
||||
#ifndef SUN
|
||||
#pragma pack(pop)
|
||||
#else
|
||||
|
||||
@@ -42,9 +42,21 @@ static log_level loglevel;
|
||||
static struct buffer buf;
|
||||
struct buffer *streambuf = &buf;
|
||||
|
||||
#define LOCK mutex_lock(streambuf->mutex)
|
||||
#define UNLOCK mutex_unlock(streambuf->mutex)
|
||||
#define LOCK mutex_lock(streambuf->mutex)
|
||||
#define UNLOCK mutex_unlock(streambuf->mutex)
|
||||
|
||||
/*
|
||||
When LMS sends a close/open sequence very quickly, the stream thread might
|
||||
still be waiting in the poll() on the closed socket. It is never recommended
|
||||
to have a thread closing a socket used by another thread but it works, as
|
||||
opposed to an infinite select().
|
||||
In stream_sock() a new socket is created and full OS will allocate a different
|
||||
one but on RTOS and simple IP stack, the same might be re-used and that causes
|
||||
an exception as a thread is already waiting on a newly allocated socket
|
||||
A simple variable that forces stream_sock() to wait until we are out of poll()
|
||||
is enough and much faster than a mutex
|
||||
*/
|
||||
static bool polling;
|
||||
static sockfd fd;
|
||||
|
||||
struct streamstate stream;
|
||||
@@ -195,9 +207,12 @@ static void *stream_thread() {
|
||||
}
|
||||
|
||||
UNLOCK;
|
||||
|
||||
// no mutex needed - we just want to know if we are inside poll()
|
||||
polling = true;
|
||||
|
||||
if (_poll(ssl, &pollinfo, 100)) {
|
||||
|
||||
polling = false;
|
||||
LOCK;
|
||||
|
||||
// check socket has not been closed while in poll
|
||||
@@ -350,7 +365,7 @@ static void *stream_thread() {
|
||||
UNLOCK;
|
||||
|
||||
} else {
|
||||
|
||||
polling = false;
|
||||
LOG_SDEBUG("poll timeout");
|
||||
}
|
||||
}
|
||||
@@ -438,7 +453,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
|
||||
buf_flush(streambuf);
|
||||
|
||||
LOCK;
|
||||
|
||||
|
||||
stream.header_len = header_len;
|
||||
memcpy(stream.header, header, header_len);
|
||||
*(stream.header+header_len) = '\0';
|
||||
@@ -473,6 +488,11 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
|
||||
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
|
||||
struct sockaddr_in addr;
|
||||
|
||||
#if EMBEDDED
|
||||
// wait till we are not polling anymore
|
||||
while (polling && running) { usleep(10000); }
|
||||
#endif
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (sock < 0) {
|
||||
|
||||
@@ -44,7 +44,7 @@ menu "Squeezelite-ESP32"
|
||||
string
|
||||
default "model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0" if SQUEEZEAMP
|
||||
default "model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32" if A1S
|
||||
default "model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22" if TWATCH2020
|
||||
default "model=I2S,bck=26,ws=25,do=33,i2c=53,sda=21,scl=22" if TWATCH2020
|
||||
default ""
|
||||
config SPDIF_CONFIG
|
||||
string
|
||||
@@ -97,17 +97,17 @@ menu "Squeezelite-ESP32"
|
||||
I2S dma channel to use.
|
||||
config I2S_BCK_IO
|
||||
int "I2S Bit clock GPIO number. "
|
||||
default 33
|
||||
default -1
|
||||
help
|
||||
I2S Bit Clock gpio pin to use.
|
||||
config I2S_WS_IO
|
||||
int "I2S Word Select GPIO number. "
|
||||
default 25
|
||||
default -1
|
||||
help
|
||||
I2S Word Select gpio pin to use.
|
||||
config I2S_DO_IO
|
||||
int "I2S Data Output GPIO number. "
|
||||
default 32
|
||||
default -1
|
||||
help
|
||||
I2S data output gpio pin to use.
|
||||
config I2S_DI_IO
|
||||
|
||||
@@ -333,6 +333,9 @@ void register_default_nvs(){
|
||||
ESP_LOGD(TAG,"Registering default value for key %s", "dac_controlset");
|
||||
config_set_default(NVS_TYPE_STR, "dac_controlset", "", 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for key %s", "jack_mutes_amp");
|
||||
config_set_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for key %s", "bat_config");
|
||||
config_set_default(NVS_TYPE_STR, "bat_config", "", 0);
|
||||
|
||||
@@ -410,7 +413,7 @@ void app_main()
|
||||
|
||||
/* start the wifi manager */
|
||||
ESP_LOGD(TAG,"Blinking led");
|
||||
led_blink(LED_GREEN, 250, 250);
|
||||
led_blink_pushed(LED_GREEN, 250, 250);
|
||||
|
||||
if(bypass_wifi_manager){
|
||||
ESP_LOGW(TAG,"\n\nwifi manager is disabled. Please use wifi commands to connect to your wifi access point.\n\n");
|
||||
|
||||
Binary file not shown.
@@ -1,231 +0,0 @@
|
||||
package Plugins::SqueezeESP32::Graphics;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Slim::Display::Squeezebox2);
|
||||
|
||||
use Slim::Utils::Prefs;
|
||||
use Slim::Utils::Log;
|
||||
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
my $log = logger('plugin.squeezeesp32');
|
||||
|
||||
my $VISUALIZER_NONE = 0;
|
||||
my $VISUALIZER_VUMETER = 1;
|
||||
my $VISUALIZER_SPECTRUM_ANALYZER = 2;
|
||||
my $VISUALIZER_WAVEFORM = 3;
|
||||
my $VISUALIZER_VUMETER_ESP32 = 0x11;
|
||||
my $VISUALIZER_SPECTRUM_ANALYZER_ESP32 = 0x12;
|
||||
|
||||
{
|
||||
#__PACKAGE__->mk_accessor('array', 'modes');
|
||||
__PACKAGE__->mk_accessor('rw', 'modes');
|
||||
__PACKAGE__->mk_accessor('rw', qw(vfdmodel));
|
||||
}
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $client = shift;
|
||||
|
||||
my $display = $class->SUPER::new($client);
|
||||
my $cprefs = $prefs->client($client);
|
||||
|
||||
$cprefs->init( {
|
||||
width => 128,
|
||||
small_VU => 15,
|
||||
spectrum => { scale => 25,
|
||||
small => { size => 25, band => 5.33 },
|
||||
full => { band => 8 },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
$display->init_accessor(
|
||||
modes => $display->build_modes,
|
||||
# Only seems to matter for screensaver and update to decide font. Not
|
||||
# any value is acceptable, so use Boom value which seems to be best
|
||||
# compromise
|
||||
vfdmodel => 'graphic-160x32',
|
||||
);
|
||||
|
||||
return $display;
|
||||
}
|
||||
|
||||
=comment
|
||||
sub modes {
|
||||
return \@modes;
|
||||
}
|
||||
=cut
|
||||
|
||||
sub nmodes {
|
||||
return scalar($#{shift->modes()});
|
||||
}
|
||||
|
||||
sub displayWidth {
|
||||
my $display = shift;
|
||||
my $client = $display->client;
|
||||
|
||||
# if we're showing the always-on visualizer & the current buttonmode
|
||||
# hasn't overridden, then use the playing display mode to index
|
||||
# into the display width, otherwise, it's fullscreen.
|
||||
my $mode = 0;
|
||||
|
||||
if ( $display->showVisualizer() && !defined($client->modeParam('visu')) ) {
|
||||
my $cprefs = preferences('server')->client($client);
|
||||
$mode = $cprefs->get('playingDisplayModes')->[ $cprefs->get('playingDisplayMode') ];
|
||||
}
|
||||
|
||||
if ($display->widthOverride) {
|
||||
my $artwork = $prefs->client($client)->get('artwork');
|
||||
if ($artwork->{'enable'} && $artwork->{'y'} < 32 && ($client->isPlaying || $client->isPaused)) {
|
||||
return $artwork->{x} + ($display->modes->[$mode || 0]{_width} || 0);
|
||||
} else {
|
||||
return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0);
|
||||
}
|
||||
} else {
|
||||
return $display->modes->[$mode || 0]{width};
|
||||
}
|
||||
}
|
||||
|
||||
sub brightnessMap {
|
||||
return (0 .. 5);
|
||||
}
|
||||
|
||||
=comment
|
||||
sub bytesPerColumn {
|
||||
return 4;
|
||||
}
|
||||
=cut
|
||||
|
||||
# I don't think LMS renderer handles properly screens other than 32 pixels. It
|
||||
# seems that all we get is a 32 pixel-tall data with anything else padded to 0
|
||||
# i.e. if we try 64 pixels height, bytes 0..3 and 4..7 will contains the same
|
||||
# pattern than the 32 pixels version, where one would have expected bytes 4..7
|
||||
# to be empty
|
||||
sub displayHeight {
|
||||
return 32;
|
||||
}
|
||||
|
||||
sub build_modes {
|
||||
my $client = shift->client;
|
||||
my $cprefs = $prefs->client($client);
|
||||
|
||||
my $artwork = $cprefs->get('artwork');
|
||||
my $disp_width = $cprefs->get('width') || 128;
|
||||
|
||||
# if artwork is in main display, reduce width
|
||||
my $width = ($artwork->{'enable'} && $artwork->{'y'} < 32) ? $artwork->{'x'} : $disp_width;
|
||||
my $width_low = ($artwork->{'enable'} && ($artwork->{'y'} >= 32 || $disp_width - $artwork->{'x'} > 32)) ? $artwork->{'x'} : $disp_width;
|
||||
|
||||
my $small_VU = $cprefs->get('small_VU');
|
||||
my $spectrum = $cprefs->get('spectrum');
|
||||
|
||||
my $small_spectrum_pos = { x => $width - int ($spectrum->{small}->{size} * $width / 100),
|
||||
width => int ($spectrum->{small}->{size} * $width / 100),
|
||||
};
|
||||
my $small_VU_pos = { x => $width - int ($small_VU * $width / 100),
|
||||
width => int ($small_VU * $width / 100),
|
||||
};
|
||||
|
||||
my @modes = (
|
||||
# mode 0
|
||||
{ desc => ['BLANK'],
|
||||
bar => 0, secs => 0, width => $width,
|
||||
params => [$VISUALIZER_NONE] },
|
||||
# mode 1
|
||||
{ desc => ['PROGRESS_BAR'],
|
||||
bar => 1, secs => 0, width => $width,
|
||||
params => [$VISUALIZER_NONE] },
|
||||
# mode 2
|
||||
{ desc => ['ELAPSED'],
|
||||
bar => 0, secs => 1, width => $width,
|
||||
params => [$VISUALIZER_NONE] },
|
||||
# mode 3
|
||||
{ desc => ['ELAPSED', 'AND', 'PROGRESS_BAR'],
|
||||
bar => 1, secs => 1, width => $width,
|
||||
params => [$VISUALIZER_NONE] },
|
||||
# mode 4
|
||||
{ desc => ['REMAINING'],
|
||||
bar => 0, secs => -1, width => $width,
|
||||
params => [$VISUALIZER_NONE] },
|
||||
# mode 5
|
||||
{ desc => ['CLOCK'],
|
||||
bar => 0, secs => 0, width => $width, clock => 1,
|
||||
params => [$VISUALIZER_NONE] },
|
||||
# mode 6
|
||||
{ desc => ['SETUP_SHOWBUFFERFULLNESS'],
|
||||
bar => 0, secs => 0, width => $width, fullness => 1,
|
||||
params => [$VISUALIZER_NONE] },
|
||||
# mode 7
|
||||
{ desc => ['VISUALIZER_VUMETER_SMALL'],
|
||||
bar => 0, secs => 0, width => $width, _width => -$small_VU_pos->{'width'},
|
||||
# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space)
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $small_VU_pos->{'width'}, 32, $small_VU_pos->{'x'}, 0, 2] },
|
||||
# mode 8
|
||||
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'],
|
||||
bar => 0, secs => 0, width => $width, _width => -$small_spectrum_pos->{'width'},
|
||||
# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space, #bars, scale)
|
||||
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $small_spectrum_pos->{width}, 32, $small_spectrum_pos->{'x'}, 0, 2, $small_spectrum_pos->{'width'} / $spectrum->{small}->{band}, $spectrum->{scale}] },
|
||||
# mode 9
|
||||
{ desc => ['VISUALIZER_VUMETER'],
|
||||
bar => 0, secs => 0, width => $width,
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 0] },
|
||||
# mode 10
|
||||
{ desc => ['VISUALIZER_ANALOG_VUMETER'],
|
||||
bar => 0, secs => 0, width => $width,
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 1] },
|
||||
# mode 11
|
||||
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER'],
|
||||
bar => 0, secs => 0, width => $width,
|
||||
# extra parameters (bars)
|
||||
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width_low, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },
|
||||
);
|
||||
|
||||
my @extra = (
|
||||
# mode E1
|
||||
{ desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'],
|
||||
bar => 0, secs => 1, width => $width,
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 0] },
|
||||
# mode E2
|
||||
{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'ELAPSED'],
|
||||
bar => 0, secs => 1, width => $width,
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 1] },
|
||||
# mode E3
|
||||
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'],
|
||||
bar => 0, secs => 1, width => $width,
|
||||
# extra parameters (bars)
|
||||
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width_low, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },
|
||||
# mode E4
|
||||
{ desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'],
|
||||
bar => 0, secs => -1, width => $width,
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 0] },
|
||||
# mode E5
|
||||
{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'REMAINING'],
|
||||
bar => 0, secs => -1, width => $width,
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 1] },
|
||||
# mode E6
|
||||
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'],
|
||||
bar => 0, secs => -1, width => $width,
|
||||
# extra parameters (bars)
|
||||
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width_low, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },
|
||||
# mode E7
|
||||
{ desc => ['VISUALIZER_VUMETER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
|
||||
bar => 1, secs => -1, width => $width,
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 0] },
|
||||
# mode E8
|
||||
{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
|
||||
bar => 1, secs => -1, width => $width,
|
||||
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 1] },
|
||||
# mode E9
|
||||
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
|
||||
bar => 1, secs => -1, width => $width,
|
||||
# extra parameters (bars)
|
||||
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width_low, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },
|
||||
);
|
||||
|
||||
@modes = (@modes, @extra) if $cprefs->get('height') > 32;
|
||||
|
||||
return \@modes;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,104 +0,0 @@
|
||||
[% PROCESS settings/header.html %]
|
||||
|
||||
[% IF prefs.pref_width %]
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %]
|
||||
<!--<input type="text" readonly class="stdedit" name="pref_width" id="width" value="[% prefs.pref_width %]" size="3">-->
|
||||
<input type="hidden" name="pref_width" value="[% prefs.pref_width %]">
|
||||
[% prefs.pref_width %]
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_VU" desc="PLUGIN_SQUEEZEESP32_SMALL_VU_DESC" %]
|
||||
<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_small_VU" id="small_VU" value="[% prefs.pref_small_VU %]" size="3">
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE" desc="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC" %]
|
||||
<input type="number" min="10" max= "50" step="5" class="stdedit" name="pref_spectrum_scale" id="spectrum_scale" value="[% pref_spectrum.scale %]" size="3">
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM" desc="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC" %]
|
||||
[% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE" | string %] 
|
||||
<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_spectrum_small_size" id="spectrum_small_size" value="[% pref_spectrum.small.size %]" size="3">
|
||||
[% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND" | string %] 
|
||||
<input type="text" class="stdedit" name="pref_spectrum_small_band" id="spectrum_small_band" value="[% pref_spectrum.small.band %]" size="3">
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND" desc="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC" %]
|
||||
<input type="text" class="stdedit" name="pref_spectrum_full_band" id="spectrum_full_band" value="[% pref_spectrum.full.band %]" size="3">
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_ARTWORK" desc="PLUGIN_SQUEEZEESP32_ARTWORK_DESC" %]
|
||||
[% "PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE" | string %] 
|
||||
<input type="checkbox" name="pref_artwork_enable" [% IF pref_artwork.enable %] checked [% END %]>
|
||||
[% "PLUGIN_SQUEEZEESP32_ARTWORK_X" | string %] 
|
||||
<input type="text" class="stdedit" name="pref_artwork_x" id="artwork_x" value="[% pref_artwork.x %]" size="2">
|
||||
[% "PLUGIN_SQUEEZEESP32_ARTWORK_Y" | string %] 
|
||||
<input type="text" class="stdedit" name="pref_artwork_y" id="artwork_y" value="[% pref_artwork.y %]" size="2">
|
||||
[% END %]
|
||||
|
||||
<hr>
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_EQUALIZER" desc="" %]
|
||||
<div>[% "PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE" | string %]</div>
|
||||
[% END %]
|
||||
|
||||
<script TYPE="text/javascript">
|
||||
if (Ext) {
|
||||
Ext.onReady(function () {
|
||||
new Ext.util.TaskRunner().start({
|
||||
run: checkEq,
|
||||
interval: 1000
|
||||
});
|
||||
});
|
||||
|
||||
function checkEq() {
|
||||
var eqValues = [];
|
||||
this.lastValues = this.lastValues || [];
|
||||
|
||||
for (var x = 0; x < 10; x++) {
|
||||
eqValues[x] = Ext.get('pref_equalizer.' + x).dom.value || 0;
|
||||
}
|
||||
|
||||
if (eqValues.join() != this.lastValues.join()) {
|
||||
this.lastValues = eqValues;
|
||||
SqueezeJS.Controller.request({
|
||||
params: ['[% playerid %]', ['squeezeesp32', 'seteq', eqValues.join()]]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
[% WRAPPER settingSection %]
|
||||
[% WRAPPER settingGroup title='31Hz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.0" id="pref_equalizer.0" value="[% pref_equalizer.0 %]" size="2"">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='62Hz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.1" id="pref_equalizer.1" value="[% pref_equalizer.1 %]" size="2">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='125Hz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.2" id="pref_equalizer.2" value="[% pref_equalizer.2 %]" size="2">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='250Hz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.3" id="pref_equalizer.3" value="[% pref_equalizer.3 %]" size="2">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='500Hz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.4" id="pref_equalizer.4" value="[% pref_equalizer.4 %]" size="2">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='1kHz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.5" id="pref_equalizer.5" value="[% pref_equalizer.5 %]" size="2">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='2kHz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.6" id="pref_equalizer.6" value="[% pref_equalizer.6 %]" size="2">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='4kHz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.7" id="pref_equalizer.7" value="[% pref_equalizer.7 %]" size="2">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='8kHz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.8" id="pref_equalizer.8" value="[% pref_equalizer.8 %]" size="2">
|
||||
[% END %]
|
||||
[% WRAPPER settingGroup title='16kHz' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.9" id="pref_equalizer.9" value="[% pref_equalizer.9 %]" size="2">
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
||||
[% PROCESS settings/footer.html %]
|
||||
@@ -1,134 +0,0 @@
|
||||
package Plugins::SqueezeESP32::Player;
|
||||
|
||||
use strict;
|
||||
use base qw(Slim::Player::SqueezePlay);
|
||||
|
||||
use Digest::MD5 qw(md5);
|
||||
use List::Util qw(min);
|
||||
|
||||
use Slim::Utils::Log;
|
||||
use Slim::Utils::Prefs;
|
||||
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
my $log = logger('plugin.squeezeesp32');
|
||||
|
||||
sub model { 'squeezeesp32' }
|
||||
sub modelName { 'SqueezeESP32' }
|
||||
sub hasIR { 0 }
|
||||
|
||||
sub init {
|
||||
my $client = shift;
|
||||
$client->SUPER::init(@_);
|
||||
$client->config_artwork();
|
||||
}
|
||||
|
||||
# Allow the player to define it's display width (and probably more)
|
||||
sub playerSettingsFrame {
|
||||
my $client = shift;
|
||||
my $data_ref = shift;
|
||||
|
||||
my $value;
|
||||
my $id = unpack('C', $$data_ref);
|
||||
|
||||
# New SETD command 0xfe for display width & height
|
||||
if ($id == 0xfe) {
|
||||
$value = (unpack('Cn', $$data_ref))[1];
|
||||
if ($value > 100 && $value < 400) {
|
||||
$prefs->client($client)->set('width', $value);
|
||||
|
||||
my $height = (unpack('Cnn', $$data_ref))[2];
|
||||
$prefs->client($client)->set('height', $height || 0);
|
||||
|
||||
$client->display->modes($client->display->build_modes);
|
||||
$client->display->widthOverride(1, $value);
|
||||
$client->update;
|
||||
|
||||
main::INFOLOG && $log->is_info && $log->info("Setting player $value" . "x" . "$height for ", $client->name);
|
||||
}
|
||||
}
|
||||
|
||||
$client->SUPER::playerSettingsFrame($data_ref);
|
||||
}
|
||||
|
||||
sub hasScrolling {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub hasIR {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub update_artwork {
|
||||
my $client = shift;
|
||||
my $cprefs = $prefs->client($client);
|
||||
|
||||
my $artwork = $cprefs->get('artwork') || return;
|
||||
|
||||
return unless $artwork->{'enable'};
|
||||
|
||||
my $s = min($cprefs->get('height') - $artwork->{'y'}, $cprefs->get('width') - $artwork->{'x'});
|
||||
|
||||
my $params = { force => shift || 0 };
|
||||
my $path = 'music/current/cover_' . $s . 'x' . $s . '_o.jpg';
|
||||
my $body = Slim::Web::Graphics::artworkRequest($client, $path, $params, \&send_artwork, undef, HTTP::Response->new);
|
||||
|
||||
send_artwork($client, undef, \$body) if $body;
|
||||
}
|
||||
|
||||
sub send_artwork {
|
||||
my ($client, $params, $dataref) = @_;
|
||||
|
||||
# I'm not sure why we are called so often, so only send when needed
|
||||
my $md5 = md5($$dataref);
|
||||
return if $client->pluginData('artwork_md5') eq $md5 && !$params->{'force'};
|
||||
|
||||
$client->pluginData('artwork', $dataref);
|
||||
$client->pluginData('artwork_md5', $md5);
|
||||
|
||||
my $artwork = $prefs->client($client)->get('artwork') || {};
|
||||
my $length = length $$dataref;
|
||||
my $offset = 0;
|
||||
|
||||
$log->info("got resized artwork (length: ", length $$dataref, ")");
|
||||
|
||||
my $header = pack('Nnn', $length, $artwork->{'x'}, $artwork->{'y'});
|
||||
|
||||
while ($length > 0) {
|
||||
$length = 1280 if $length > 1280;
|
||||
$log->info("sending grfa $length");
|
||||
|
||||
my $data = $header . pack('N', $offset) . substr( $$dataref, 0, $length, '' );
|
||||
|
||||
$client->sendFrame( grfa => \$data );
|
||||
$offset += $length;
|
||||
$length = length $$dataref;
|
||||
}
|
||||
}
|
||||
|
||||
sub clear_artwork {
|
||||
my ($client, $request) = @_;
|
||||
|
||||
my $artwork = $prefs->client($client)->get('artwork');
|
||||
|
||||
if ($artwork && $artwork->{'enable'}) {
|
||||
main::INFOLOG && $log->is_info && $log->info("artwork stop/clear " . $request->getRequestString());
|
||||
$client->pluginData('artwork_md5', '');
|
||||
}
|
||||
}
|
||||
|
||||
sub config_artwork {
|
||||
my ($client) = @_;
|
||||
|
||||
if ( my $artwork = $prefs->client($client)->get('artwork') ) {
|
||||
my $header = pack('Nnn', $artwork->{'enable'}, $artwork->{'x'}, $artwork->{'y'});
|
||||
$client->sendFrame( grfa => \$header );
|
||||
}
|
||||
}
|
||||
|
||||
sub reconnect {
|
||||
my $client = shift;
|
||||
$client->pluginData('artwork_md5', '');
|
||||
$client->SUPER::reconnect(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,90 +0,0 @@
|
||||
package Plugins::SqueezeESP32::PlayerSettings;
|
||||
|
||||
use strict;
|
||||
use base qw(Slim::Web::Settings);
|
||||
use List::Util qw(first);
|
||||
|
||||
use Slim::Utils::Log;
|
||||
use Slim::Utils::Prefs;
|
||||
|
||||
my $sprefs = preferences('server');
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
my $log = logger('plugin.squeezeesp32');
|
||||
|
||||
sub name {
|
||||
return Slim::Web::HTTP::CSRF->protectName('PLUGIN_SQUEEZEESP32_PLAYERSETTINGS');
|
||||
}
|
||||
|
||||
sub needsClient {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub validFor {
|
||||
my ($class, $client) = @_;
|
||||
return $client->model eq 'squeezeesp32';
|
||||
}
|
||||
|
||||
sub page {
|
||||
return Slim::Web::HTTP::CSRF->protectURI('plugins/SqueezeESP32/settings/player.html');
|
||||
}
|
||||
|
||||
sub prefs {
|
||||
my ($class, $client) = @_;
|
||||
my @prefs;
|
||||
push @prefs, qw(width small_VU) if $client->displayWidth;
|
||||
return ($prefs->client($client), @prefs);
|
||||
}
|
||||
|
||||
sub handler {
|
||||
my ($class, $client, $paramRef) = @_;
|
||||
|
||||
my ($cprefs, @prefs) = $class->prefs($client);
|
||||
|
||||
if ($paramRef->{'saveSettings'}) {
|
||||
if ($client->displayWidth) {
|
||||
$cprefs->set('small_VU', $paramRef->{'pref_small_VU'} || 15);
|
||||
my $spectrum = {
|
||||
scale => $paramRef->{'pref_spectrum_scale'} || 25,
|
||||
small => { size => $paramRef->{'pref_spectrum_small_size'} || 25,
|
||||
band => $paramRef->{'pref_spectrum_small_band'} || 5.33 },
|
||||
full => { band => $paramRef->{'pref_spectrum_full_band'} } || 8,
|
||||
};
|
||||
$cprefs->set('spectrum', $spectrum);
|
||||
|
||||
my $artwork = {
|
||||
enable => $paramRef->{'pref_artwork_enable'},
|
||||
x => $paramRef->{'pref_artwork_x'} || 0,
|
||||
y => $paramRef->{'pref_artwork_y'} || 0,
|
||||
};
|
||||
$cprefs->set('artwork', $artwork);
|
||||
$client->display->modes($client->display->build_modes);
|
||||
$client->display->update;
|
||||
|
||||
# force update or disable artwork
|
||||
if ($artwork->{'enable'}) {
|
||||
$client->update_artwork(1);
|
||||
} else {
|
||||
$client->config_artwork();
|
||||
}
|
||||
}
|
||||
|
||||
my $equalizer = $cprefs->get('equalizer');
|
||||
for my $i (0 .. $#{$equalizer}) {
|
||||
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
|
||||
}
|
||||
$cprefs->set('equalizer', $equalizer);
|
||||
}
|
||||
|
||||
if ($client->displayWidth) {
|
||||
# the Settings super class can't handle anything but scalar values
|
||||
# we need to populate the $paramRef for the other prefs manually
|
||||
$paramRef->{'pref_spectrum'} = $cprefs->get('spectrum');
|
||||
$paramRef->{'pref_artwork'} = $cprefs->get('artwork');
|
||||
}
|
||||
|
||||
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer');
|
||||
|
||||
return $class->SUPER::handler($client, $paramRef);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,121 +0,0 @@
|
||||
package Plugins::SqueezeESP32::Plugin;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Slim::Plugin::Base);
|
||||
|
||||
use Slim::Utils::Prefs;
|
||||
use Slim::Utils::Log;
|
||||
use Slim::Web::ImageProxy;
|
||||
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
|
||||
my $log = Slim::Utils::Log->addLogCategory({
|
||||
'category' => 'plugin.squeezeesp32',
|
||||
'defaultLevel' => 'INFO',
|
||||
'description' => 'PLUGIN_SQUEEZEESP32',
|
||||
});
|
||||
|
||||
# migrate 'eq' pref, as that's a reserved word and could cause problems in the future
|
||||
$prefs->migrateClient(1, sub {
|
||||
my ($cprefs, $client) = @_;
|
||||
$cprefs->set('equalizer', $cprefs->get('eq'));
|
||||
$cprefs->remove('eq');
|
||||
1;
|
||||
});
|
||||
|
||||
$prefs->setChange(sub {
|
||||
send_equalizer($_[2]);
|
||||
}, 'equalizer');
|
||||
|
||||
sub initPlugin {
|
||||
my $class = shift;
|
||||
|
||||
if ( main::WEBUI ) {
|
||||
require Plugins::SqueezeESP32::PlayerSettings;
|
||||
Plugins::SqueezeESP32::PlayerSettings->new;
|
||||
|
||||
# require Plugins::SqueezeESP32::Settings;
|
||||
# Plugins::SqueezeESP32::Settings->new;
|
||||
}
|
||||
|
||||
$class->SUPER::initPlugin(@_);
|
||||
Slim::Networking::Slimproto::addPlayerClass($class, 100, 'squeezeesp32', { client => 'Plugins::SqueezeESP32::Player', display => 'Plugins::SqueezeESP32::Graphics' });
|
||||
main::INFOLOG && $log->is_info && $log->info("Added class 100 for SqueezeESP32");
|
||||
|
||||
# register a command to set the EQ - without saving the values! Send params as single comma separated list of values
|
||||
Slim::Control::Request::addDispatch(['squeezeesp32', 'seteq', '_eq'], [1, 0, 0, \&setEQ]);
|
||||
|
||||
# Note for some forgetful know-it-all: we need to wrap the callback to make it unique. Otherwise subscriptions would overwrite each other.
|
||||
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['newmetadata'] ] );
|
||||
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
|
||||
Slim::Control::Request::subscribe( \&onStopClear, [ ['playlist'], ['stop', 'clear'] ]);
|
||||
|
||||
# the custom player class is only initialized if it has a display - thus we need to listen to connect events in order to initializes other player prefs
|
||||
Slim::Control::Request::subscribe( \&onPlayer,[ ['client'], [ 'new', 'reconnect' ] ] );
|
||||
}
|
||||
|
||||
sub onStopClear {
|
||||
my $request = shift;
|
||||
my $client = $request->client || return;
|
||||
|
||||
if ($client->isa('Plugins::SqueezeESP32::Player')) {
|
||||
$client->clear_artwork($request);
|
||||
}
|
||||
}
|
||||
|
||||
sub onPlayer {
|
||||
my $request = shift;
|
||||
my $client = $request->client || return;
|
||||
|
||||
if ($client->model eq 'squeezeesp32') {
|
||||
main::INFOLOG && $log->is_info && $log->info("SqueezeESP player connected: " . $client->id);
|
||||
|
||||
$prefs->client($client)->init( {
|
||||
equalizer => [(0) x 10],
|
||||
} );
|
||||
send_equalizer($client);
|
||||
}
|
||||
}
|
||||
|
||||
sub onNotification {
|
||||
my $request = shift;
|
||||
my $client = $request->client || return;
|
||||
|
||||
if ($client->isa('Plugins::SqueezeESP32::Player')) {
|
||||
$client->update_artwork();
|
||||
}
|
||||
}
|
||||
|
||||
sub setEQ {
|
||||
my $request = shift;
|
||||
|
||||
# check this is the correct command.
|
||||
if ($request->isNotCommand([['squeezeesp32'],['seteq']])) {
|
||||
$request->setStatusBadDispatch();
|
||||
return;
|
||||
}
|
||||
|
||||
# get our parameters
|
||||
my $client = $request->client();
|
||||
my @eqParams = split(/,/, $request->getParam('_eq') || '');
|
||||
|
||||
for (my $x = 0; $x < 10; $x++) {
|
||||
$eqParams[$x] ||= 0;
|
||||
}
|
||||
|
||||
send_equalizer($client, \@eqParams);
|
||||
}
|
||||
|
||||
sub send_equalizer {
|
||||
my ($client, $equalizer) = @_;
|
||||
|
||||
if ($client->model eq 'squeezeesp32') {
|
||||
$equalizer ||= $prefs->client($client)->get('equalizer') || [(0) x 10];
|
||||
my $size = @$equalizer;
|
||||
my $data = pack("c[$size]", @{$equalizer});
|
||||
$client->sendFrame( eqlz => \$data );
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,12 +0,0 @@
|
||||
package Plugins::SqueezeESP32::Text;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Slim::Display::Text);
|
||||
|
||||
# we don't want the special Noritake codes
|
||||
sub vfdmodel {
|
||||
return 'squeezeslave';
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
<extensions>
|
||||
<defaultState>enabled</defaultState>
|
||||
<email>philippe_44@outlook.com</email>
|
||||
<targetApplication>
|
||||
<minVersion>7.9</minVersion>
|
||||
<maxVersion>*</maxVersion>
|
||||
<id>SlimServer</id>
|
||||
</targetApplication>
|
||||
<name>PLUGIN_SQUEEZEESP32</name>
|
||||
<description>PLUGIN_SQUEEZEESP32_DESC</description>
|
||||
<module>Plugins::SqueezeESP32::Plugin</module>
|
||||
<version>0.94</version>
|
||||
<creator>Philippe</creator>
|
||||
</extensions>
|
||||
@@ -1,102 +0,0 @@
|
||||
WELCOME_TO_SQUEEZEESP32
|
||||
DE Willkommen bei SqueezeESP32!
|
||||
EN Welcome to SqueezeESP32
|
||||
|
||||
PLUGIN_SQUEEZEESP32
|
||||
EN SqueezeESP32
|
||||
|
||||
PLUGIN_SQUEEZEESP32_BANNER
|
||||
DE WARNUNG
|
||||
EN WARNING
|
||||
|
||||
PLUGIN_SQUEEZEESP32_BANNER_TEXT
|
||||
DE Sie müssen LMS neu starten, damit diese Einstellungen aktiv werden
|
||||
EN You need to restart LMS for these parameters to be taken into account
|
||||
|
||||
PLUGIN_SQUEEZEESP32_DESC
|
||||
DE Konfiguriert eine neue Player ID (100), um Displays an SqueezeESP32 zu unterstützen
|
||||
EN Adds a new player id (100) to enable display with SqueezeESP32
|
||||
|
||||
PLUGIN_SQUEEZEESP32_PLAYERSETTINGS
|
||||
DE ESP32 Einstellungen
|
||||
EN ESP32 settings
|
||||
|
||||
PLUGIN_SQUEEZEESP32_WIDTH
|
||||
DE Displaybreite
|
||||
EN Screen width
|
||||
|
||||
PLUGIN_SQUEEZEESP32_WIDTH_DESC
|
||||
DE Breite des Displays in Pixeln, wie es vom Player angegeben wird
|
||||
EN Width of the display in pixel as reported by the player
|
||||
|
||||
PLUGIN_SQUEEZEESP32_SMALL_VU
|
||||
DE Kleine VU Grösse
|
||||
EN Small VU size
|
||||
|
||||
PLUGIN_SQUEEZEESP32_SMALL_VU_DESC
|
||||
DE Prozentsatz des Displays, das für den kleinen VU verwendet wird (rechts ausgerichtet)
|
||||
EN % of the display used for small VU (right-justified)
|
||||
|
||||
PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE
|
||||
DE Spektrum-Skalierung
|
||||
EN Spectrum scaling
|
||||
|
||||
PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC
|
||||
DE Prozentsatz des Spektrums, das in der ersten Hälfte des Bildschirms angezeigt wird. Z.B. 50 bedeutet 50% des Spektrums wird auf dem halben Bildschirm angezeigt.
|
||||
DE Aber 25 bedeutet, dass nur 25% des Spektrums auf dem halben Bildschirm angezeigt wird.
|
||||
EN % of Spectrum displayed in first half of the screen. For example, 50 means that 50% of spectrum is displayed in 1/2 of the screen
|
||||
EN But 25 means that only 25% of spectrum is displayed in 1/2 of the screen, so it's a sort of log
|
||||
|
||||
PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM
|
||||
DE Kleines Spektrum
|
||||
EN Small spectrum options
|
||||
|
||||
PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC
|
||||
DE <i>Grösse</i>: Prozentsatz des Displays, das für das kleine Spektrum verwendet wird.
|
||||
DE <br><i>Band-Faktor</i>: die Anzahl Bänder ist die Breite der <b>Spektrumsanzeige</b> dividiert durch diesen Faktor.
|
||||
EN <i>Size</i>: % of the screen used by small spectrum
|
||||
EN <br><i>Band factor</i>: number of bands is the width of the <b>spectrum</b> screen divided by this factor
|
||||
|
||||
PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE
|
||||
DE Grösse
|
||||
EN Size
|
||||
|
||||
PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND
|
||||
DE Band-Faktor
|
||||
EN Band factor
|
||||
|
||||
PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND
|
||||
DE Band-Faktor für ganzes Spektrum
|
||||
EN Full spectrum band factor
|
||||
|
||||
PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC
|
||||
DE Die Anzahl Bänder ist die Breite der Anzeige dividiert durch diesen Faktor.
|
||||
EN The number of bands is the width of the screen divided by this factor
|
||||
|
||||
PLUGIN_SQUEEZEESP32_ARTWORK
|
||||
DE Plattenhüllen
|
||||
EN Artwork
|
||||
|
||||
PLUGIN_SQUEEZEESP32_ARTWORK_DESC
|
||||
DE Wenn die Y Position kleiner als 32 ist, dann werden Plattenhüllen auf der rechten Seite angezeigt, und x definiert die Startposition.
|
||||
DE Plattenhüllen werden auf Displays mit weniger als 16 Graustufen in sehr geringer Qualität angezeigt.
|
||||
EN When Y position is less than 32, then artwork is displayed at the right of the main screen and x defines the starting position
|
||||
EN Using artwork on less than 16-levels grayscale display if really poor quality
|
||||
|
||||
PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE
|
||||
DE Aktivieren
|
||||
EN Enable
|
||||
|
||||
PLUGIN_SQUEEZEESP32_ARTWORK_X
|
||||
EN X
|
||||
|
||||
PLUGIN_SQUEEZEESP32_ARTWORK_Y
|
||||
EN Y
|
||||
|
||||
PLUGIN_SQUEEZEESP32_EQUALIZER
|
||||
DE Parametrischer Equalizer
|
||||
EN Parametric equalizer
|
||||
|
||||
PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE
|
||||
DE Bitte speichern Sie die Equalizer Einstellungen, falls das Gerät diese dauerhaft verwenden soll. Ansonsten werden sie beim nächsten Start zurückgesetzt.
|
||||
EN Don't forget to save the Equalizer settings if you want them to stick. Otherwise they'll be reset next time you restart the device.
|
||||
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
<extensions>
|
||||
<plugins>
|
||||
<plugin version="0.94" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
|
||||
<plugin version="0.353" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
|
||||
<link>https://github.com/sle118/squeezelite-esp32</link>
|
||||
<creator>Philippe</creator>
|
||||
<sha>a9bf10b47d13508ba051e4067cdabc2c283f4824</sha>
|
||||
<sha>357d715715e7bf10f83ad15bc3fd794fc45e9e5a</sha>
|
||||
<email>philippe_44@outlook.com</email>
|
||||
<desc lang="EN">SqueezeESP32 additional player id (100)</desc>
|
||||
<url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>
|
||||
<desc lang="EN">SqueezeESP32 additional player id (100/101)</desc>
|
||||
<url>http://github.com/sle118/squeezelite-esp32/raw/master-cmake/plugin/SqueezeESP32.zip</url>
|
||||
<title lang="EN">SqueezeESP32</title>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
Reference in New Issue
Block a user