From adbed6b207ef2953c52ea1d1c45eb86edfd2e1b4 Mon Sep 17 00:00:00 2001 From: willem Date: Wed, 4 Jan 2023 14:01:20 +0100 Subject: [PATCH] update to 1.9.7 --- README.md | 24 +- examples/MixerSample/MixerSample.ino | 7 +- .../PlayFLAC-SD-SPDIF/PlayFLAC-SD-SPDIF.ino | 10 +- .../PlayMIDIFromLittleFS.ino | 9 +- .../PlayMIDIFromSPIFFS/PlayMIDIFromSPIFFS.ino | 11 +- .../PlayMODFromPROGMEMToDAC.ino | 6 +- .../PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino | 6 + examples/PlayMP3ToSPDIF/PlayMP3ToSPDIF.ino | 5 + .../PlayOpusFromSPIFFS/PlayOpusFromSPIFFS.ino | 6 + .../PlayWAVFromFunction.ino | 2 +- .../StreamMP3FromHTTP/StreamMP3FromHTTP.ino | 9 +- .../StreamMP3FromHTTPToSPDIF.ino | 148 ++++++ .../StreamMP3FromHTTP_SPIRAM.ino | 8 +- examples/StreamOnHost/AudioOutputNullSlow.h | 1 + examples/StreamOnHost/StreamOnHost.ino | 12 +- examples/TalkingClockI2S/TalkingClockI2S.ino | 10 +- examples/WebRadio/WebRadio.ino | 11 +- examples/WebRadio/web.cpp | 8 +- library.json | 7 +- library.properties | 2 +- src/AudioFileSourceFunction.cpp | 10 +- src/AudioFileSourceFunction.h | 2 +- src/AudioFileSourceICYStream.cpp | 4 + src/AudioFileSourceLittleFS.h | 8 +- src/AudioFileSourceSPIFFS.h | 3 + src/AudioFileSourceSPIRAMBuffer.cpp | 2 +- src/AudioGeneratorFLAC.cpp | 5 +- src/AudioGeneratorMIDI.cpp | 14 +- src/AudioGeneratorMIDI.h | 5 + src/AudioGeneratorMOD.cpp | 84 ++- src/AudioGeneratorMOD.h | 14 +- src/AudioGeneratorRTTTL.cpp | 5 +- src/AudioOutputI2S.cpp | 479 ++++-------------- src/AudioOutputI2S.h | 13 +- src/AudioOutputI2SNoDAC.cpp | 46 +- src/AudioOutputI2SNoDAC.h | 9 + src/AudioOutputSPDIF.cpp | 17 +- src/AudioOutputSPDIF.h | 2 +- src/AudioOutputSPIFFSWAV.cpp | 10 +- src/AudioOutputSPIFFSWAV.h | 3 + src/AudioOutputSTDIO.cpp | 5 + src/AudioOutputULP.cpp | 2 +- src/driver/SinglePinI2SDriver.cpp | 8 +- src/libflac/stream_decoder.c | 16 +- src/libhelix-aac/assembly.h | 2 +- src/libhelix-aac/sbrqmf.c | 5 +- src/libhelix-mp3/RPSL.txt | 2 +- src/libhelix-mp3/assembly.h | 4 +- src/libhelix-mp3/scalfact.c | 2 +- src/libmad/layer3.c | 1 + src/libogg/README.md | 4 +- src/libtinysoundfont/README.ESP8266 | 2 +- src/libtinysoundfont/tsf.h | 4 +- src/opusfile/opusfile.c | 2 +- src/opusfile/opusfile.h | 4 +- src/spiram-fast.h | 8 +- 56 files changed, 596 insertions(+), 512 deletions(-) create mode 100644 examples/StreamMP3FromHTTPToSPDIF/StreamMP3FromHTTPToSPDIF.ino diff --git a/README.md b/README.md index f243db5..a6a29ee 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ESP8266Audio - supports ESP8266 & ESP32 & Raspberry Pi RP2040[![Gitter](https://badges.gitter.im/ESP8266Audio/community.svg)](https://gitter.im/ESP8266Audio/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +# ESP8266Audio - supports ESP8266 & ESP32 & Raspberry Pi RP2040 [![Gitter](https://badges.gitter.im/ESP8266Audio/community.svg)](https://gitter.im/ESP8266Audio/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) Arduino library for parsing and decoding MOD, WAV, MP3, FLAC, MIDI, AAC, and RTTL files and playing them on an I2S DAC or even using a software-simulated delta-sigma DAC with dynamic 32x-128x oversampling. ESP8266 is fully supported and most mature, but ESP32 is also mostly there with built-in DAC as well as external ones. @@ -31,7 +31,9 @@ JohannesMTC has built a similar project especially for model trains: https://git A neat MQTT-driven ESP8266 light-and-sound device (alarm? toy? who can say!) was built by @CosmicMac, available at https://github.com/CosmicMac/ESParkle -A very interesting "linear clock" with a stepper motor, NTP time keeping, and configurable recorded chimes with schematics, 3D printer plans, and source code, is now available http://home.kpn.nl/bderogee1980/projects/linear_clock/linear_clock.html +A very interesting "linear clock" with a stepper motor, NTP time keeping, and configurable recorded chimes with schematics, 3D printer plans, and source code, is now available https://janderogee.com/projects/linear_clock/linear_clock.htm + +Source and instructions for a gorgeous wooden MP3-playing clock, FM radio and a walkie-talkie using the ESP8266 and AVR microcontrollers is available https://github.com/zduka/mp3-player ## Prerequisites First, make sure you are running the 2.6.3/later or GIT head version of the Arduino libraries for ESP8266, or the latest ESP32 SDK from Espressif. @@ -100,7 +102,7 @@ AudioFileSourcePROGMEM: Reads a file from a PROGMEM array. Under UNIX you can AudioFileSourceHTTPStream: Simple implementation of a streaming HTTP reader for ShoutCast-type MP3 streaming. Not yet resilient, and at 44.1khz 128bit stutters due to CPU limitations, but it works more or less. ## AudioFileSourceBuffer - Double buffering, useful for HTTP streams -AudioFileSourceBuffer is an input source that simpy adds an additional RAM buffer of the output of any other AudioFileSource. This is particularly useful for web streaming where you need to have 1-2 packets in memory to ensure hiccup-free playback. +AudioFileSourceBuffer is an input source that simply adds an additional RAM buffer of the output of any other AudioFileSource. This is particularly useful for web streaming where you need to have 1-2 packets in memory to ensure hiccup-free playback. Create your standard input file source, create the buffer with the original source as its input, and pass this buffer object to the generator. ```cpp @@ -158,7 +160,15 @@ AudioOutputSPIFFSWAV: Writes a binary WAV format with headers to a SPIFFS files AudioOutputNull: Just dumps samples to /dev/null. Used for speed testing as it doesn't artificially limit the AudioGenerator output speed since there are no buffers to fill/drain. ## I2S DACs -I've used both the Adafruit [I2S +3W amp DAC](https://www.adafruit.com/product/3006) and a generic PCM5102 based DAC with success. The biggest problems I've seen from users involve pinouts from the ESP8266 for GPIO and hooking up all necessary pins on the DAC board. +I've used both the Adafruit [I2S +3W amp DAC](https://www.adafruit.com/product/3006) and a generic PCM5102 based DAC with success. The biggest problems I've seen from users involve pinouts from the ESP8266 for GPIO and hooking up all necessary pins on the DAC board. The essential pins are: + +I2S pin | Common label* | ESP8266 pin +--------|---------------|------------- +LRC | D4 | GPIO2 +BCLK | D8 | GPIO15 +DIN | RX | GPIO3 + +\* The "common label" column applies to common NodeMCU and D1 Mini development boards. Unfortunately some manufacturers use different mappings so the labels listed here might not apply to your particular model. ### Adafruit I2S DAC This is quite simple and only needs the GND, VIN, LRC, BCLK< and DIN pins to be wired. Be sure to use +5V on the VIN to get the loudest sound. See the [Adafruit example page](https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp) for more info. @@ -171,7 +181,7 @@ I've used several versions of PCM5102 DAC boards purchased from eBay. They've a ### Others -There are many other variants out there, and they should all work reasonably well with this code and the ESP8266. Please be certain you've read the datasheet and are applying proper input voltages, and be sure to tie off any unused inputs to GND or VCC as appropriate. LEaving an input pin floating on any integrated circuit can cause unstable operation as it may pick up noise from the environment (very low input capacitance) and cause havoc with internal IC settings. +There are many other variants out there, and they should all work reasonably well with this code and the ESP8266. Please be certain you've read the datasheet and are applying proper input voltages, and be sure to tie off any unused inputs to GND or VCC as appropriate. Leaving an input pin floating on any integrated circuit can cause unstable operation as it may pick up noise from the environment (very low input capacitance) and cause havoc with internal IC settings. ## Software I2S Delta-Sigma DAC (i.e. playing music with a single transistor and speaker) For the best fidelity, and stereo to boot, spend the money on a real I2S DAC. Adafruit makes a great mono one with amplifier, and you can find stereo unamplified ones on eBay or elsewhere quite cheaply. However, thanks to the software delta-sigma DAC with 32x oversampling (up to 128x if the audio rate is low enough) you can still have pretty good sound! @@ -206,7 +216,7 @@ USB-5V -- Speaker + Terminal *NOTE*: A prior version of this schematic had a direct connection from the ESP8266 to the base of the transistor. While this does provide the maximum amplitude, it also can draw more current from the 8266 than is safe, and can also cause the transistor to overheat. -As of the latest ESP8266Audio release, with the software delta-sigma DAC the LRCLK and BCLK pins *can* be used by an application. Simply use normal `pinMode` and `dicitalWrite` or `digitalRead` as desired. +As of the latest ESP8266Audio release, with the software delta-sigma DAC the LRCLK and BCLK pins *can* be used by an application. Simply use normal `pinMode` and `digitalWrite` or `digitalRead` as desired. ### High pitched buzzing with the 1-T circuit The 1-T amp can _NOT_ drive any sort of amplified speaker. If there is a power or USB input to the speaker, or it has lights or Bluetooth or a battery, it can _NOT_ be used with this circuit. @@ -239,7 +249,7 @@ ESP Pin -------|____|--------+ | Ground ---------------------+ ``` -For ESP8266 with red LED (~1.9Vf drop) you need minimum 150Ohm resistor (12mA max per pin), and output pin is fixed (GPIO3/RX0).On ESP32 it is confgurable with `AudioOutputSPDIF(gpio_num)`. +For ESP8266 with red LED (~1.9Vf drop) you need minimum 150Ohm resistor (12mA max per pin), and output pin is fixed (GPIO3/RX0).On ESP32 it is configurable with `AudioOutputSPDIF(gpio_num)`. ## Using external SPI RAM to increase buffer A class allows you to use a 23lc1024 SPI RAM from Microchip as input buffer. This chip connects to ESP8266 HSPI port and provides a large buffer to help avoid hiccus in playback of web streams. diff --git a/examples/MixerSample/MixerSample.ino b/examples/MixerSample/MixerSample.ino index 7a9a1e2..ab02af0 100644 --- a/examples/MixerSample/MixerSample.ino +++ b/examples/MixerSample/MixerSample.ino @@ -1,5 +1,10 @@ #include -#ifdef ESP32 + +#if defined(ARDUINO_ARCH_RP2040) + #define WIFI_OFF + class __x { public: __x() {}; void mode() {}; }; + __x WiFi; +#elif defined(ESP32) #include #else #include diff --git a/examples/PlayFLAC-SD-SPDIF/PlayFLAC-SD-SPDIF.ino b/examples/PlayFLAC-SD-SPDIF/PlayFLAC-SD-SPDIF.ino index 3c12788..110b046 100644 --- a/examples/PlayFLAC-SD-SPDIF/PlayFLAC-SD-SPDIF.ino +++ b/examples/PlayFLAC-SD-SPDIF/PlayFLAC-SD-SPDIF.ino @@ -1,4 +1,8 @@ #include +#ifdef ARDUINO_ARCH_RP2040 +void setup() {} +void loop() {} +#else #include "AudioFileSourceSD.h" #include "AudioOutputSPDIF.h" #include "AudioGeneratorFLAC.h" @@ -8,7 +12,7 @@ // Espressif Audio Development Framework at: // https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/audio-samples.html // -// On ESP8266 you might need to reencode FLAC files with max '-2' compression level +// On ESP8266 you might need to re-encode FLAC files with max '-2' compression level // (i.e. 1152 maximum block size) or you will run out of memory. FLAC files will be // slightly bigger but you don't loose audio quality with reencoding (lossles codec). @@ -61,8 +65,10 @@ void loop() { } } } else { - Serial.println(F("Playback form SD card done\n")); + Serial.println(F("Playback from SD card done\n")); delay(1000); } } } +#endif + diff --git a/examples/PlayMIDIFromLittleFS/PlayMIDIFromLittleFS.ino b/examples/PlayMIDIFromLittleFS/PlayMIDIFromLittleFS.ino index b4f02e3..ed075bc 100644 --- a/examples/PlayMIDIFromLittleFS/PlayMIDIFromLittleFS.ino +++ b/examples/PlayMIDIFromLittleFS/PlayMIDIFromLittleFS.ino @@ -6,7 +6,13 @@ } void loop() {} #else -#include +#if defined(ARDUINO_ARCH_RP2040) + #define WIFI_OFF + class __x { public: __x() {}; void mode() {}; }; + __x WiFi; +#else + #include +#endif #include #include #include @@ -42,7 +48,6 @@ void loop() { if (midi->isRunning()) { if (!midi->loop()) { - uint32_t e = millis(); midi->stop(); } } else { diff --git a/examples/PlayMIDIFromSPIFFS/PlayMIDIFromSPIFFS.ino b/examples/PlayMIDIFromSPIFFS/PlayMIDIFromSPIFFS.ino index dcfa8f9..38e12d3 100644 --- a/examples/PlayMIDIFromSPIFFS/PlayMIDIFromSPIFFS.ino +++ b/examples/PlayMIDIFromSPIFFS/PlayMIDIFromSPIFFS.ino @@ -1,4 +1,11 @@ #include + +// Do not build on GCC8, GCC8 has a compiler bug + +#if defined(ARDUINO_ARCH_RP2040) || (__GNUC__ == 8) +void setup() {} +void loop() {} +#else #ifdef ESP32 #include #include "SPIFFS.h" @@ -25,6 +32,7 @@ void setup() WiFi.mode(WIFI_OFF); Serial.begin(115200); + SPIFFS.begin(); Serial.println("Starting up...\n"); audioLogger = &Serial; @@ -43,7 +51,6 @@ void loop() { if (midi->isRunning()) { if (!midi->loop()) { - uint32_t e = millis(); midi->stop(); } } else { @@ -52,4 +59,4 @@ void loop() } } - +#endif diff --git a/examples/PlayMODFromPROGMEMToDAC/PlayMODFromPROGMEMToDAC.ino b/examples/PlayMODFromPROGMEMToDAC/PlayMODFromPROGMEMToDAC.ino index 44aac59..07ff11c 100644 --- a/examples/PlayMODFromPROGMEMToDAC/PlayMODFromPROGMEMToDAC.ino +++ b/examples/PlayMODFromPROGMEMToDAC/PlayMODFromPROGMEMToDAC.ino @@ -2,7 +2,11 @@ #include "AudioFileSourcePROGMEM.h" #include "AudioGeneratorMOD.h" #include "AudioOutputI2S.h" -#ifdef ESP32 +#if defined(ARDUINO_ARCH_RP2040) + #define WIFI_OFF + class __x { public: __x() {}; void mode() {}; }; + __x WiFi; +#elif defined(ESP32) #include #else #include diff --git a/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino b/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino index 364a7a8..6d2ff65 100644 --- a/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino +++ b/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino @@ -1,4 +1,9 @@ #include +#if defined(ARDUINO_ARCH_RP2040) +void setup() {} +void loop() {} +#else + #ifdef ESP32 #include #include "SPIFFS.h" @@ -70,3 +75,4 @@ void loop() delay(1000); } } +#endif diff --git a/examples/PlayMP3ToSPDIF/PlayMP3ToSPDIF.ino b/examples/PlayMP3ToSPDIF/PlayMP3ToSPDIF.ino index 02dfb98..8e41036 100644 --- a/examples/PlayMP3ToSPDIF/PlayMP3ToSPDIF.ino +++ b/examples/PlayMP3ToSPDIF/PlayMP3ToSPDIF.ino @@ -1,4 +1,8 @@ #include +#ifdef ARDUINO_ARCH_RP2040 +void setup() {} +void loop() {} +#else #ifdef ESP32 #include "SPIFFS.h" #endif @@ -97,3 +101,4 @@ void loop() delay(1000); } } +#endif diff --git a/examples/PlayOpusFromSPIFFS/PlayOpusFromSPIFFS.ino b/examples/PlayOpusFromSPIFFS/PlayOpusFromSPIFFS.ino index 44db7c4..5b96ec9 100644 --- a/examples/PlayOpusFromSPIFFS/PlayOpusFromSPIFFS.ino +++ b/examples/PlayOpusFromSPIFFS/PlayOpusFromSPIFFS.ino @@ -1,4 +1,8 @@ #include +#ifdef ARDUINO_ARCH_RP2040 +void setup() {} +void loop() {} +#else #ifdef ESP32 #include #include "SPIFFS.h" @@ -39,3 +43,5 @@ void loop() delay(1000); } } + +#endif diff --git a/examples/PlayWAVFromFunction/PlayWAVFromFunction.ino b/examples/PlayWAVFromFunction/PlayWAVFromFunction.ino index fe0cc89..9af90e0 100644 --- a/examples/PlayWAVFromFunction/PlayWAVFromFunction.ino +++ b/examples/PlayWAVFromFunction/PlayWAVFromFunction.ino @@ -44,7 +44,7 @@ void setup() { // param : float (current time [sec] of the song) // return : float (the amplitude of sound which varies from -1.f to +1.f) // - // sound function can be registerd only one or the same number with channels + // sound function can be registered only one or the same number with channels // if the channels > 1 && the number of function == 1, // same function are used to generate the sound in every channel // diff --git a/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino b/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino index 84d7d5b..3ac0ec0 100644 --- a/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino +++ b/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino @@ -1,6 +1,11 @@ #include -#ifdef ESP32 +#if defined(ARDUINO_ARCH_RP2040) +void setup() {} +void loop() {} + +#else +#if defined(ESP32) #include #else #include @@ -104,4 +109,4 @@ void loop() delay(1000); } } - +#endif diff --git a/examples/StreamMP3FromHTTPToSPDIF/StreamMP3FromHTTPToSPDIF.ino b/examples/StreamMP3FromHTTPToSPDIF/StreamMP3FromHTTPToSPDIF.ino new file mode 100644 index 0000000..23b8547 --- /dev/null +++ b/examples/StreamMP3FromHTTPToSPDIF/StreamMP3FromHTTPToSPDIF.ino @@ -0,0 +1,148 @@ +#include +#ifdef ARDUINO_ARCH_RP2040 +void setup() {} +void loop() {} +#else + +#if defined(ESP32) + #include +#else + #include +#endif +#include "AudioFileSourceICYStream.h" +#include "AudioFileSourceBuffer.h" +#include "AudioGeneratorMP3.h" +//#include "AudioOutputI2SNoDAC.h" +#include "AudioOutputSPDIF.h" + +// +// Stream MP3 from HTTP to SPDIF +// + +// To run, set your ESP8266 build to 160MHz, update the SSID info, and upload. + +// Note: +// If using ESP8266 NodeMCU connect LED to RX pin and GND pin + +// Enter your WiFi setup here: +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; + +// Examples URLs +//const char *URL="http://kvbstreams.dyndns.org:8000/wkvi-am"; + +// Italian Rock Radio +const char *URL="http://streamingv2.shoutcast.com/radiofreccia"; + +// Stream URL of Logitech Media Server, aka LMS, Version: 8.2.0 (August 2021) +// const char *URL="http://192.168.1.121:9000/stream.mp3"; + +AudioGeneratorMP3 *mp3; +AudioFileSourceICYStream *file; +AudioFileSourceBuffer *buff; + +// Output device is SPDIF +AudioOutputSPDIF *out; + + +// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc. +void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string) +{ + const char *ptr = reinterpret_cast(cbData); + (void) isUnicode; // Punt this ball for now + // Note that the type and string may be in PROGMEM, so copy them to RAM for printf + char s1[32], s2[64]; + strncpy_P(s1, type, sizeof(s1)); + s1[sizeof(s1)-1]=0; + strncpy_P(s2, string, sizeof(s2)); + s2[sizeof(s2)-1]=0; + Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2); + Serial.flush(); +} + +// Called when there's a warning or error (like a buffer underflow or decode hiccup) +void StatusCallback(void *cbData, int code, const char *string) +{ + const char *ptr = reinterpret_cast(cbData); + // Note that the string may be in PROGMEM, so copy it to RAM for printf + char s1[64]; + strncpy_P(s1, string, sizeof(s1)); + s1[sizeof(s1)-1]=0; + Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1); + Serial.flush(); +} + + +void setup() +{ + Serial.begin(115200); + delay(1000); + Serial.println("Connecting to WiFi"); + + WiFi.disconnect(); + WiFi.softAPdisconnect(true); + WiFi.mode(WIFI_STA); + + WiFi.begin(ssid, password); + + // Try forever + while (WiFi.status() != WL_CONNECTED) { + Serial.println("...Connecting to WiFi"); + delay(1000); + } + Serial.println("Connected"); + + audioLogger = &Serial; + file = new AudioFileSourceICYStream(URL); + + // Commented out for performance issues with high rate MP3 stream + //file->RegisterMetadataCB(MDCallback, (void*)"ICY"); + + buff = new AudioFileSourceBuffer(file, 4096); // Doubled form default 2048 + + // Commented out for performance issues with high rate MP3 stream + //buff->RegisterStatusCB(StatusCallback, (void*)"buffer"); + + // Set SPDIF output + out = new AudioOutputSPDIF(); + mp3 = new AudioGeneratorMP3(); + + // Commented out for performance issues with high rate MP3 stream + //mp3->RegisterStatusCB(StatusCallback, (void*)"mp3"); + + mp3->begin(buff, out); +} + + +void loop() +{ + // Commented out + //static int lastms = 0; + + if (mp3->isRunning()) { + /* Commented out + if (millis()-lastms > 1000) { + lastms = millis(); + Serial.printf("Running for %d ms...\n", lastms); + Serial.flush(); + } + */ + if (!mp3->loop()) { + mp3->stop(); + } + } else { + Serial.printf("MP3 done\n"); + + // Restart ESP when streaming is done or errored + delay(10000); + + ESP.restart(); + } +} + +#endif diff --git a/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino b/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino index 0b9ab09..3a59b15 100644 --- a/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino +++ b/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino @@ -1,5 +1,9 @@ #include -#ifdef ESP32 +#if defined(ARDUINO_ARCH_RP2040) +void setup() {} +void loop() {} +#else +#if defined(ESP32) #include #else #include @@ -101,4 +105,4 @@ void loop() delay(1000); } } - +#endif diff --git a/examples/StreamOnHost/AudioOutputNullSlow.h b/examples/StreamOnHost/AudioOutputNullSlow.h index e85cd9e..3010862 100644 --- a/examples/StreamOnHost/AudioOutputNullSlow.h +++ b/examples/StreamOnHost/AudioOutputNullSlow.h @@ -30,6 +30,7 @@ class AudioOutputNullSlow : public AudioOutput ~AudioOutputNullSlow() {}; virtual bool begin() { samples = 0; startms = millis(); return true; } virtual bool ConsumeSample(int16_t sample[2]) { + (void) sample; // return false (= output buffer full) // sometimes to let the main loop running constexpr int everylog2 = 10; diff --git a/examples/StreamOnHost/StreamOnHost.ino b/examples/StreamOnHost/StreamOnHost.ino index 876562d..90f525b 100644 --- a/examples/StreamOnHost/StreamOnHost.ino +++ b/examples/StreamOnHost/StreamOnHost.ino @@ -1,6 +1,10 @@ #include -#ifdef ESP32 +#if defined(ARDUINO_ARCH_RP2040) +void setup() {} +void loop() {} +#else +#if defined(ESP32) #include #else #include @@ -9,10 +13,10 @@ #include "AudioFileSourceBuffer.h" #include "AudioGeneratorMP3.h" #if AUDIO -#pragma message("Outputting audio") +// #pragma message("Outputting audio") #include "AudioOutputLinuxDSP.h" #else -#pragma message("No audio") +// #pragma message("No audio") #include "AudioOutputNullSlow.h" #endif @@ -115,4 +119,4 @@ void loop() delay(1000); } } - +#endif diff --git a/examples/TalkingClockI2S/TalkingClockI2S.ino b/examples/TalkingClockI2S/TalkingClockI2S.ino index 6a2f731..61d735d 100644 --- a/examples/TalkingClockI2S/TalkingClockI2S.ino +++ b/examples/TalkingClockI2S/TalkingClockI2S.ino @@ -2,7 +2,14 @@ // https://github.com/going-digital/Talkie/blob/master/Talkie/examples/Vocab_US_Clock/Vocab_US_Clock.ino // Released under GPL v2 -#ifdef ESP32 +#include + +#if defined(ARDUINO_ARCH_RP2040) +void setup() {} +void loop() {} +#else + +#if defined(ESP32) #include #else #include @@ -183,3 +190,4 @@ void loop() sayTime(tmstruct.tm_hour, tmstruct.tm_min, talkie); delay(1000); } +#endif diff --git a/examples/WebRadio/WebRadio.ino b/examples/WebRadio/WebRadio.ino index e8a0326..13343ad 100644 --- a/examples/WebRadio/WebRadio.ino +++ b/examples/WebRadio/WebRadio.ino @@ -19,7 +19,15 @@ */ #include -#ifdef ESP32 +#if defined(ARDUINO_ARCH_RP2040) +void setup() {} +void loop() {} +#else + +// ESP8266 server.available() is now server.accept() +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +#if defined(ESP32) #include #else #include @@ -439,3 +447,4 @@ void loop() } } +#endif diff --git a/examples/WebRadio/web.cpp b/examples/WebRadio/web.cpp index 75e6873..505b309 100644 --- a/examples/WebRadio/web.cpp +++ b/examples/WebRadio/web.cpp @@ -19,6 +19,9 @@ */ #include +#if defined(ARDUINO_ARCH_RP2040) +// Nothing here +#else #ifdef ESP32 #include #else @@ -308,7 +311,4 @@ void Read4Int(char *str, byte *p) str += ParseInt(str, &i); p[2] = i; if (*str) str++; str += ParseInt(str, &i); p[3] = i; } - - - - +#endif diff --git a/library.json b/library.json index 1392918..c02e634 100644 --- a/library.json +++ b/library.json @@ -14,10 +14,13 @@ "type": "git", "url": "https://github.com/earlephilhower/ESP8266Audio" }, - "version": "1.9.2", + "version": "1.9.7", "homepage": "https://github.com/earlephilhower/ESP8266Audio", "frameworks": "Arduino", "examples": [ "examples/*/*.ino" - ] + ], + "build": { + "libLDFMode": "deep" + } } diff --git a/library.properties b/library.properties index 111915c..d594ee1 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ESP8266Audio -version=1.9.2 +version=1.9.7 author=Earle F. Philhower, III maintainer=Earle F. Philhower, III sentence=Audio file and I2S sound playing routines for ESP8266, ESP32, and Raspberry Pi Pico RP2040 diff --git a/src/AudioFileSourceFunction.cpp b/src/AudioFileSourceFunction.cpp index 9e86eff..6536985 100644 --- a/src/AudioFileSourceFunction.cpp +++ b/src/AudioFileSourceFunction.cpp @@ -1,6 +1,6 @@ /* AudioFileSourceFunction - Audio ouptut generator which can generate WAV file data from function + Audio output generator which can generate WAV file data from function Copyright (C) 2021 Hideaki Tai @@ -25,14 +25,14 @@ AudioFileSourceFunction::AudioFileSourceFunction(float sec, uint16_t channels, u uint32_t len = uint32_t(sec * (float)bytes_per_sec); // RIFF chunk - strncpy(wav_header.riff.chunk_id, "RIFF", 4); + memcpy(wav_header.riff.chunk_id, "RIFF", 4); wav_header.riff.chunk_size = 4 // size of riff chunk w/o chunk_id and chunk_size + 8 + 16 // size of format chunk + 8 + len; // size of data chunk - strncpy(wav_header.riff.format, "WAVE", 4); + memcpy(wav_header.riff.format, "WAVE", 4); // format chunk - strncpy(wav_header.format.chunk_id, "fmt ", 4); + memcpy(wav_header.format.chunk_id, "fmt ", 4); wav_header.format.chunk_size = 16; wav_header.format.format_tag = 0x0001; // PCM wav_header.format.channels = channels; @@ -42,7 +42,7 @@ AudioFileSourceFunction::AudioFileSourceFunction(float sec, uint16_t channels, u wav_header.format.bits_per_sample = bits_per_sample; // data chunk - strncpy(wav_header.data.chunk_id, "data", 4); + memcpy(wav_header.data.chunk_id, "data", 4); wav_header.data.chunk_size = len; funcs.reserve(channels); diff --git a/src/AudioFileSourceFunction.h b/src/AudioFileSourceFunction.h index 6c41229..94b88d6 100644 --- a/src/AudioFileSourceFunction.h +++ b/src/AudioFileSourceFunction.h @@ -1,6 +1,6 @@ /* AudioFileSourceFunction - Audio ouptut generator which can generate WAV file data from function + Audio output generator which can generate WAV file data from function Copyright (C) 2021 Hideaki Tai diff --git a/src/AudioFileSourceICYStream.cpp b/src/AudioFileSourceICYStream.cpp index 63c84f3..5b1601b 100644 --- a/src/AudioFileSourceICYStream.cpp +++ b/src/AudioFileSourceICYStream.cpp @@ -20,6 +20,9 @@ #if defined(ESP32) || defined(ESP8266) +#ifdef _GNU_SOURCE +#undef _GNU_SOURCE +#endif #define _GNU_SOURCE #include "AudioFileSourceICYStream.h" @@ -47,6 +50,7 @@ bool AudioFileSourceICYStream::open(const char *url) http.addHeader("Icy-MetaData", "1"); http.collectHeaders( hdr, 4 ); http.setReuse(true); + http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); int code = http.GET(); if (code != HTTP_CODE_OK) { http.end(); diff --git a/src/AudioFileSourceLittleFS.h b/src/AudioFileSourceLittleFS.h index 345ae3a..6430e80 100644 --- a/src/AudioFileSourceLittleFS.h +++ b/src/AudioFileSourceLittleFS.h @@ -18,10 +18,8 @@ along with this program. If not, see . */ -#ifndef _AUDIOFILESOURCESPIFFS_H -#define _AUDIOFILESOURCESPIFFS_H - -//#ifndef ESP32 // No LittleFS there, yet +#ifndef _AUDIOFILESOURCELITTLEFS_H +#define _AUDIOFILESOURCELITTLEFS_H #include #include @@ -37,7 +35,5 @@ class AudioFileSourceLittleFS : public AudioFileSourceFS // Others are inherited from base }; -//#endif - #endif diff --git a/src/AudioFileSourceSPIFFS.h b/src/AudioFileSourceSPIFFS.h index 74efefa..910ddc2 100644 --- a/src/AudioFileSourceSPIFFS.h +++ b/src/AudioFileSourceSPIFFS.h @@ -27,6 +27,9 @@ #include "AudioFileSource.h" #include "AudioFileSourceFS.h" +// Yes, I know SPIFFS is deprecated +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + class AudioFileSourceSPIFFS : public AudioFileSourceFS { public: diff --git a/src/AudioFileSourceSPIRAMBuffer.cpp b/src/AudioFileSourceSPIRAMBuffer.cpp index 2c9eb3b..475afa4 100644 --- a/src/AudioFileSourceSPIRAMBuffer.cpp +++ b/src/AudioFileSourceSPIRAMBuffer.cpp @@ -105,7 +105,7 @@ uint32_t AudioFileSourceSPIRAMBuffer::read(void *data, uint32_t len) } // Read up to the entire buffer from RAM - uint32_t toReadFromBuffer = std::min(len, writePtr - readPtr); + uint32_t toReadFromBuffer = std::min(len, (uint32_t)(writePtr - readPtr)); uint8_t *ptr = reinterpret_cast(data); if (toReadFromBuffer > 0) { #ifdef FAKERAM diff --git a/src/AudioGeneratorFLAC.cpp b/src/AudioGeneratorFLAC.cpp index bd6e801..f315133 100644 --- a/src/AudioGeneratorFLAC.cpp +++ b/src/AudioGeneratorFLAC.cpp @@ -62,6 +62,9 @@ bool AudioGeneratorFLAC::begin(AudioFileSource *source, AudioOutput *output) output->begin(); running = true; + lastSample[0] = 0; + lastSample[1] = 0; + channels = 0; return true; } @@ -71,7 +74,7 @@ bool AudioGeneratorFLAC::loop() if (!running) goto done; - if (!output->ConsumeSample(lastSample)) goto done; // Try and send last buffered sample + if (channels && !output->ConsumeSample(lastSample)) goto done; // Try and send last buffered sample do { if (buffPtr == buffLen) { diff --git a/src/AudioGeneratorMIDI.cpp b/src/AudioGeneratorMIDI.cpp index e3be2b2..1bb1cd3 100644 --- a/src/AudioGeneratorMIDI.cpp +++ b/src/AudioGeneratorMIDI.cpp @@ -1,6 +1,6 @@ /* AudioGeneratorMIDI - Audio output generator that plays MIDI files using a SF2 SoundFont + Audio output generator that plays MIDI files using an SF2 SoundFont Copyright (C) 2017 Earle F. Philhower, III @@ -58,6 +58,10 @@ #include "AudioGeneratorMIDI.h" +#if __GNUC__ == 8 +// Do not build, GCC8 has a compiler bug +#else // __GNUC__ == 8 + #pragma GCC optimize ("O3") #define TSF_NO_STDIO @@ -172,7 +176,7 @@ unsigned int AudioGeneratorMIDI::buffer_int32 (int offset) { unsigned long AudioGeneratorMIDI::get_varlen (int *ptr) { /* Get a 1-4 byte variable-length value and adjust the pointer past it. - These are a succession of 7-bit values with a MSB bit of zero marking the end */ + These are a succession of 7-bit values with an MSB bit of zero marking the end */ unsigned long val; int i, byte; @@ -331,7 +335,7 @@ note_off: } -// Open file, parse headers, get ready tio process MIDI +// Open file, parse headers, get ready to process MIDI void AudioGeneratorMIDI::PrepareMIDI(AudioFileSource *src) { MakeStreamFromAFS(src, &afsMIDI); @@ -364,7 +368,7 @@ void AudioGeneratorMIDI::PrepareMIDI(AudioFileSource *src) int AudioGeneratorMIDI::PlayMIDI() { /* Continue processing all tracks, in an order based on the simulated time. - This is not unlike multiway merging used for tape sorting algoritms in the 50's! */ + This is not unlike multiway merging used for tape sorting algorithms in the 50's! */ do { /* while there are still track notes to process */ static struct track_status *trk; @@ -637,3 +641,5 @@ void AudioGeneratorMIDI::MakeStreamFromAFS(AudioFileSource *src, tsf_stream *afs afs->size = &afs_size; } +#endif //__GNUC__ == 8 + diff --git a/src/AudioGeneratorMIDI.h b/src/AudioGeneratorMIDI.h index 8a1ac3f..96b2dd6 100644 --- a/src/AudioGeneratorMIDI.h +++ b/src/AudioGeneratorMIDI.h @@ -21,6 +21,10 @@ #ifndef _AUDIOGENERATORMIDI_H #define _AUDIOGENERATORMIDI_H +#if __GNUC__ == 8 +// Do not build, GCC8 has a compiler bug +#else // __GNUC__ == 8 + #include "AudioGenerator.h" #define TSF_NO_STDIO @@ -176,6 +180,7 @@ class AudioGeneratorMIDI : public AudioGenerator short samplesRendered[256]; }; +#endif //__GNUC__ == 8 #endif diff --git a/src/AudioGeneratorMOD.cpp b/src/AudioGeneratorMOD.cpp index f42154f..2be98ca 100644 --- a/src/AudioGeneratorMOD.cpp +++ b/src/AudioGeneratorMOD.cpp @@ -69,6 +69,11 @@ bool AudioGeneratorMOD::stop() free(FatBuffer.channels[i]); FatBuffer.channels[i] = NULL; } + + if(running || ((file != NULL) && (file->isOpen() == true))) { + output->flush(); //flush I2S output buffer, if the player was actually running before. + } + if (file) file->close(); running = false; output->stop(); @@ -124,7 +129,7 @@ bool AudioGeneratorMOD::begin(AudioFileSource *source, AudioOutput *out) UpdateAmiga(); for (int i = 0; i < CHANNELS; i++) { - FatBuffer.channels[i] = reinterpret_cast(malloc(fatBufferSize)); + FatBuffer.channels[i] = reinterpret_cast(calloc(fatBufferSize, 1)); if (!FatBuffer.channels[i]) { stop(); return false; @@ -232,6 +237,11 @@ bool AudioGeneratorMOD::LoadHeader() Mod.numberOfChannels = (temp[0] - '0') * 10 + temp[1] - '0'; else Mod.numberOfChannels = 4; + + if (Mod.numberOfChannels > CHANNELS) { + audioLogger->printf("\nAudioGeneratorMOD::LoadHeader abort - too many channels (configured: %d, needed: %d)\n", CHANNELS, Mod.numberOfChannels); + return(false); + } return true; } @@ -417,7 +427,7 @@ bool AudioGeneratorMOD::ProcessRow() if (sampleNumber) { Player.lastSampleNumber[channel] = sampleNumber - 1; - if (!(effectParameter == 0xE && effectParameterX == NOTEDELAY)) + if (!(effectNumber == 0xE && effectParameterX == NOTEDELAY)) Player.volume[channel] = Mod.samples[Player.lastSampleNumber[channel]].volume; } @@ -565,7 +575,7 @@ bool AudioGeneratorMOD::ProcessRow() Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel]; if (note != NONOTE) - Mixer.channelSampleOffset[channel] = sampleOffset << DIVIDER; + Mixer.channelSampleOffset[channel] = sampleOffset << FIXED_DIVIDER; if (sampleNumber) Mixer.channelSampleNumber[channel] = Player.lastSampleNumber[channel]; @@ -735,13 +745,14 @@ bool AudioGeneratorMOD::RunPlayer() void AudioGeneratorMOD::GetSample(int16_t sample[2]) { - int16_t sumL; - int16_t sumR; + int32_t sumL; + int32_t sumR; uint8_t channel; uint32_t samplePointer; int8_t current; int8_t next; int16_t out; + int32_t out32; if (!running) return; @@ -757,12 +768,12 @@ void AudioGeneratorMOD::GetSample(int16_t sample[2]) if (!Mixer.channelVolume[channel]) continue; samplePointer = Mixer.sampleBegin[Mixer.channelSampleNumber[channel]] + - (Mixer.channelSampleOffset[channel] >> DIVIDER); + (Mixer.channelSampleOffset[channel] >> FIXED_DIVIDER); if (Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]]) { if (samplePointer >= Mixer.sampleLoopEnd[Mixer.channelSampleNumber[channel]]) { - Mixer.channelSampleOffset[channel] -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]] << DIVIDER; + Mixer.channelSampleOffset[channel] -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]] << FIXED_DIVIDER; samplePointer -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]]; } @@ -779,8 +790,8 @@ void AudioGeneratorMOD::GetSample(int16_t sample[2]) samplePointer >= FatBuffer.samplePointer[channel] + fatBufferSize - 1 || Mixer.channelSampleNumber[channel] != FatBuffer.channelSampleNumber[channel]) { - uint16_t toRead = Mixer.sampleEnd[Mixer.channelSampleNumber[channel]] - samplePointer + 1; - if (toRead > fatBufferSize) toRead = fatBufferSize; + uint32_t toRead = Mixer.sampleEnd[Mixer.channelSampleNumber[channel]] - samplePointer + 1; + if (toRead > (uint32_t)fatBufferSize) toRead = fatBufferSize; if (!file->seek(samplePointer, SEEK_SET)) { stop(); @@ -797,30 +808,55 @@ void AudioGeneratorMOD::GetSample(int16_t sample[2]) current = FatBuffer.channels[channel][(samplePointer - FatBuffer.samplePointer[channel]) /*& (FATBUFFERSIZE - 1)*/]; next = FatBuffer.channels[channel][(samplePointer + 1 - FatBuffer.samplePointer[channel]) /*& (FATBUFFERSIZE - 1)*/]; + + // preserve a few more bits from sample interpolation, by upscaling input values. + // This does (slightly) reduce quantization noise in higher frequencies, typically above 8kHz. + // Actually we could could even gain more bits, I was just not sure if more bits would cause overflows in other conputations. + int16_t current16 = (int16_t) current << 2; + int16_t next16 = (int16_t) next << 2; + + out = current16; - out = current; + // Integer linear interpolation - only works correctly in 16bit + out += (next16 - current16) * (Mixer.channelSampleOffset[channel] & ((1 << FIXED_DIVIDER) - 1)) >> FIXED_DIVIDER; - // Integer linear interpolation - out += (next - current) * (Mixer.channelSampleOffset[channel] & ((1 << DIVIDER) - 1)) >> DIVIDER; - - // Upscale to BITDEPTH - out <<= BITDEPTH - 8; + // Upscale to BITDEPTH, considering the we already gained two bits in the previous step + out32 = (int32_t)out << (BITDEPTH - 10); // Channel volume - out = out * Mixer.channelVolume[channel] >> 6; + out32 = out32 * Mixer.channelVolume[channel] >> 6; // Channel panning - sumL += out * min(128 - Mixer.channelPanning[channel], 64) >> 6; - sumR += out * min(Mixer.channelPanning[channel], 64) >> 6; + sumL += out32 * min(128 - Mixer.channelPanning[channel], 64) >> 6; + sumR += out32 * min(Mixer.channelPanning[channel], 64) >> 6; } - // Downscale to BITDEPTH - sumL /= Mod.numberOfChannels; - sumR /= Mod.numberOfChannels; + // Downscale to BITDEPTH - a bit faster because the compiler can replaced division by constants with proper "right shift" + correct handling of sign bit + if (Mod.numberOfChannels <= 4) { + // up to 4 channels + sumL /= 4; + sumR /= 4; + } else { + if (Mod.numberOfChannels <= 6) { + // 5 or 6 channels - pre-multiply be 1.5, then divide by 8 -> same as division by 6 + sumL = (sumL + (sumL/2)) / 8; + sumR = (sumR + (sumR/2)) / 8; + } else { + // 7,8, or more channels + sumL /= 8; + sumR /= 8; + } + } - // Fill the sound buffer with unsigned values - sample[AudioOutput::LEFTCHANNEL] = sumL + (1 << (BITDEPTH - 1)); - sample[AudioOutput::RIGHTCHANNEL] = sumR + (1 << (BITDEPTH - 1)); + // clip samples to 16bit (with saturation in case of overflow) + if(sumL <= INT16_MIN) sumL = INT16_MIN; + else if (sumL >= INT16_MAX) sumL = INT16_MAX; + if(sumR <= INT16_MIN) sumR = INT16_MIN; + else if (sumR >= INT16_MAX) sumR = INT16_MAX; + + // Fill the sound buffer with signed values + sample[AudioOutput::LEFTCHANNEL] = sumL; + sample[AudioOutput::RIGHTCHANNEL] = sumR; } bool AudioGeneratorMOD::LoadMOD() diff --git a/src/AudioGeneratorMOD.h b/src/AudioGeneratorMOD.h index 87fac18..e2b5b27 100644 --- a/src/AudioGeneratorMOD.h +++ b/src/AudioGeneratorMOD.h @@ -53,19 +53,25 @@ class AudioGeneratorMOD : public AudioGenerator protected: int mixerTick; - enum {BITDEPTH = 15}; + enum {BITDEPTH = 16}; int sampleRate; int fatBufferSize; //(6*1024) // File system buffers per-CHANNEL (i.e. total mem required is 4 * FATBUFFERSIZE) - enum {DIVIDER = 10}; // Fixed-point mantissa used for integer arithmetic + enum {FIXED_DIVIDER = 10}; // Fixed-point mantissa used for integer arithmetic int stereoSeparation; //STEREOSEPARATION = 32; // 0 (max) to 64 (mono) bool usePAL; // Hz = 7093789 / (amigaPeriod * 2) for PAL // Hz = 7159091 / (amigaPeriod * 2) for NTSC int AMIGA; - void UpdateAmiga() { AMIGA = ((usePAL?7159091:7093789) / 2 / sampleRate << DIVIDER); } - + void UpdateAmiga() { AMIGA = ((usePAL?7159091:7093789) / 2 / sampleRate << FIXED_DIVIDER); } + +#ifdef ESP8266 // Not sure if C3/C2 have RAM constraints, maybe add them here? + // support max 4 channels enum {ROWS = 64, SAMPLES = 31, CHANNELS = 4, NONOTE = 0xFFFF, NONOTE8 = 0xff }; +#else + // support max 8 channels + enum {ROWS = 64, SAMPLES = 31, CHANNELS = 8, NONOTE = 0xFFFF, NONOTE8 = 0xff }; +#endif typedef struct Sample { uint16_t length; diff --git a/src/AudioGeneratorRTTTL.cpp b/src/AudioGeneratorRTTTL.cpp index 87825bd..5eb1848 100644 --- a/src/AudioGeneratorRTTTL.cpp +++ b/src/AudioGeneratorRTTTL.cpp @@ -41,7 +41,10 @@ AudioGeneratorRTTTL::~AudioGeneratorRTTTL() bool AudioGeneratorRTTTL::stop() { - if (!running) return true; + if (!file || !output) + { + return false; + } running = false; output->stop(); return file->close(); diff --git a/src/AudioOutputI2S.cpp b/src/AudioOutputI2S.cpp index 17496d5..c4e0c3a 100644 --- a/src/AudioOutputI2S.cpp +++ b/src/AudioOutputI2S.cpp @@ -1,8 +1,7 @@ - /* AudioOutputI2S Base class for I2S interface port - + Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify @@ -22,7 +21,9 @@ #include #ifdef ESP32 #include "driver/i2s.h" -#elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266) +#elif defined(ARDUINO_ARCH_RP2040) || ARDUINO_ESP8266_MAJOR >= 3 + #include +#elif ARDUINO_ESP8266_MAJOR < 3 #include #endif #include "AudioOutputI2S.h" @@ -41,6 +42,7 @@ AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int //set defaults mono = false; + lsb_justified = false; bps = 16; channels = 2; hertz = 44100; @@ -50,37 +52,6 @@ AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int SetGain(1.0); } -bool AudioOutputI2S::SetPinout() -{ - #ifdef ESP32 - if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) - return false; // Not allowed - - i2s_pin_config_t pins = { - .bck_io_num = bclkPin, - .ws_io_num = wclkPin, - .data_out_num = doutPin, - .data_in_num = I2S_PIN_NO_CHANGE}; - i2s_set_pin((i2s_port_t)portNo, &pins); - return true; - #else - (void)bclkPin; - (void)wclkPin; - (void)doutPin; - return false; - #endif -} - -bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout) -{ - bclkPin = bclk; - wclkPin = wclk; - doutPin = dout; - if (i2sOn) - return SetPinout(); - - return true; -} #elif defined(ARDUINO_ARCH_RP2040) AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) { i2sOn = false; @@ -110,6 +81,38 @@ AudioOutputI2S::~AudioOutputI2S() i2sOn = false; } +bool AudioOutputI2S::SetPinout() +{ + #ifdef ESP32 + if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) + return false; // Not allowed + + i2s_pin_config_t pins = { + .mck_io_num = 0, // Unused + .bck_io_num = bclkPin, + .ws_io_num = wclkPin, + .data_out_num = doutPin, + .data_in_num = I2S_PIN_NO_CHANGE}; + i2s_set_pin((i2s_port_t)portNo, &pins); + return true; + #else + (void)bclkPin; + (void)wclkPin; + (void)doutPin; + return false; + #endif +} + +bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout) +{ + bclkPin = bclk; + wclkPin = wclk; + doutPin = dout; + if (i2sOn) + return SetPinout(); + + return true; +} bool AudioOutputI2S::SetRate(int hz) { // TODO - have a list of allowable rates from constructor, check them @@ -121,7 +124,7 @@ bool AudioOutputI2S::SetRate(int hz) #elif defined(ESP8266) i2s_set_rate(AdjustI2SRate(hz)); #elif defined(ARDUINO_ARCH_RP2040) - I2S.setFrequency(hz); + i2s.setFrequency(hz); #endif } return true; @@ -147,6 +150,12 @@ bool AudioOutputI2S::SetOutputModeMono(bool mono) return true; } +bool AudioOutputI2S::SetLsbJustified(bool lsbJustified) +{ + this->lsb_justified = lsbJustified; + return true; +} + bool AudioOutputI2S::begin(bool txDAC) { #ifdef ESP32 @@ -167,17 +176,45 @@ bool AudioOutputI2S::begin(bool txDAC) i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); if (output_mode == INTERNAL_DAC) { +#if CONFIG_IDF_TARGET_ESP32 mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN); +#else + return false; +#endif } else if (output_mode == INTERNAL_PDM) { +#if CONFIG_IDF_TARGET_ESP32 mode = (i2s_mode_t)(mode | I2S_MODE_PDM); +#else + return false; +#endif } - i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + i2s_comm_format_t comm_fmt; if (output_mode == INTERNAL_DAC) { - comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + comm_fmt = (i2s_comm_format_t) I2S_COMM_FORMAT_STAND_MSB; +#else + comm_fmt = (i2s_comm_format_t) I2S_COMM_FORMAT_I2S_MSB; +#endif + } + else if (lsb_justified) + { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + comm_fmt = (i2s_comm_format_t) I2S_COMM_FORMAT_STAND_MSB; +#else + comm_fmt = (i2s_comm_format_t) (I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB); +#endif + } + else + { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + comm_fmt = (i2s_comm_format_t) (I2S_COMM_FORMAT_STAND_I2S); +#else + comm_fmt = (i2s_comm_format_t) (I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); +#endif } i2s_config_t i2s_config_dac = { @@ -188,8 +225,12 @@ bool AudioOutputI2S::begin(bool txDAC) .communication_format = comm_fmt, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority .dma_buf_count = dma_buf_count, - .dma_buf_len = 64, - .use_apll = use_apll // Use audio PLL + .dma_buf_len = 128, + .use_apll = use_apll, // Use audio PLL + .tx_desc_auto_clear = true, // Silence on underflow + .fixed_mclk = 0, // Unused + .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, // Unused + .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT // Use bits per sample }; audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac); if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK) @@ -198,8 +239,12 @@ bool AudioOutputI2S::begin(bool txDAC) } if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) { +#if CONFIG_IDF_TARGET_ESP32 i2s_set_pin((i2s_port_t)portNo, NULL); i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); +#else + return false; +#endif } else { @@ -230,9 +275,9 @@ bool AudioOutputI2S::begin(bool txDAC) #elif defined(ARDUINO_ARCH_RP2040) (void)txDAC; if (!i2sOn) { - I2S.setBCLK(bclkPin); - I2S.setDOUT(doutPin); - I2S.begin(hertz); + i2s.setBCLK(bclkPin); + i2s.setDATA(doutPin); + i2s.begin(hertz); } #endif i2sOn = true; @@ -264,22 +309,24 @@ bool AudioOutputI2S::ConsumeSample(int16_t sample[2]) { int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000; int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000; - s32 = (r << 16) | (l & 0xffff); + s32 = ((r & 0xffff) << 16) | (l & 0xffff); } else { s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); } -// Deprecated. Use i2s_write +//"i2s_write_bytes" has been removed in the ESP32 Arduino 2.0.0, use "i2s_write" instead. // return i2s_write_bytes((i2s_port_t)portNo, (const char *)&s32, sizeof(uint32_t), 0); - size_t bytes_written; - i2s_write((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), &bytes_written, 0); - return bytes_written; + + size_t i2s_bytes_written; + i2s_write((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0); + return i2s_bytes_written; #elif defined(ESP8266) uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true #elif defined(ARDUINO_ARCH_RP2040) - return !!I2S.write((void*)ms, 4); + uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); + return !!i2s.write((int32_t)s32, false); #endif } @@ -287,7 +334,7 @@ void AudioOutputI2S::flush() { #ifdef ESP32 // makes sure that all stored DMA samples are consumed / played - int buffersize = 64 * this->dma_buf_count; + int buffersize = 128 * this->dma_buf_count; int16_t samples[2] = {0x0, 0x0}; for (int i = 0; i < buffersize; i++) { @@ -297,7 +344,7 @@ void AudioOutputI2S::flush() } } #elif defined(ARDUINO_ARCH_RP2040) - I2S.flush(); + i2s.flush(); #endif } @@ -309,338 +356,8 @@ bool AudioOutputI2S::stop() #ifdef ESP32 i2s_zero_dma_buffer((i2s_port_t)portNo); #elif defined(ARDUINO_ARCH_RP2040) - I2S.end(); + i2s.end(); #endif i2sOn = false; return true; } -// /* -// AudioOutputI2S -// Base class for I2S interface port - -// Copyright (C) 2017 Earle F. Philhower, III - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// */ - -// #include -// #ifdef ESP32 -// #include "driver/i2s.h" -// #if defined ESP_ARDUINO_VERSION_VAL -// #if (ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) -// #include "soc/soc_caps.h" -// #define ESP_V2 -// #define I2S_FORMAT (I2S_COMM_FORMAT_STAND_I2S) -// #endif -// #else -// #define I2S_FORMAT (I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB) -// #endif -// #elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266) -// #include -// #endif -// #include "AudioOutputI2S.h" - -// #if defined(ESP32) || defined(ESP8266) -// AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll) -// { -// this->portNo = port; -// this->i2sOn = false; -// this->dma_buf_count = dma_buf_count; -// if (output_mode != EXTERNAL_I2S && output_mode != INTERNAL_DAC && output_mode != INTERNAL_PDM) { -// output_mode = EXTERNAL_I2S; -// } -// this->output_mode = output_mode; -// this->use_apll = use_apll; - -// //set defaults -// mono = false; -// bps = 16; -// channels = 2; -// hertz = 44100; -// bclkPin = 26; -// wclkPin = 25; -// doutPin = 22; -// SetGain(1.0); -// } - -// bool AudioOutputI2S::SetPinout() -// { -// #ifdef ESP32 -// if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) -// return false; // Not allowed - -// i2s_pin_config_t pins = { -// .bck_io_num = bclkPin, -// .ws_io_num = wclkPin, -// .data_out_num = doutPin, -// .data_in_num = I2S_PIN_NO_CHANGE}; -// i2s_set_pin((i2s_port_t)portNo, &pins); -// return true; -// #else -// (void)bclkPin; -// (void)wclkPin; -// (void)doutPin; -// return false; -// #endif -// } - -// bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout) -// { -// bclkPin = bclk; -// wclkPin = wclk; -// doutPin = dout; -// if (i2sOn) -// return SetPinout(); - -// return true; -// } -// #elif defined(ARDUINO_ARCH_RP2040) -// AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) { -// i2sOn = false; -// mono = false; -// bps = 16; -// channels = 2; -// hertz = sampleRate; -// bclkPin = sck; -// doutPin = data; -// SetGain(1.0); -// } -// #endif - -// AudioOutputI2S::~AudioOutputI2S() -// { -// #ifdef ESP32 -// if (i2sOn) { -// audioLogger->printf("UNINSTALL I2S\n"); -// i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver -// } -// #elif defined(ESP8266) -// if (i2sOn) -// i2s_end(); -// #elif defined(ARDUINO_ARCH_RP2040) -// stop(); -// #endif -// i2sOn = false; -// } - -// bool AudioOutputI2S::SetRate(int hz) -// { -// // TODO - have a list of allowable rates from constructor, check them -// this->hertz = hz; -// if (i2sOn) -// { -// #ifdef ESP32 -// i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz)); -// #elif defined(ESP8266) -// i2s_set_rate(AdjustI2SRate(hz)); -// #elif defined(ARDUINO_ARCH_RP2040) -// I2S.setFrequency(hz); -// #endif -// } -// return true; -// } - -// bool AudioOutputI2S::SetBitsPerSample(int bits) -// { -// if ( (bits != 16) && (bits != 8) ) return false; -// this->bps = bits; -// return true; -// } - -// bool AudioOutputI2S::SetChannels(int channels) -// { -// if ( (channels < 1) || (channels > 2) ) return false; -// this->channels = channels; -// return true; -// } - -// bool AudioOutputI2S::SetOutputModeMono(bool mono) -// { -// this->mono = mono; -// return true; -// } - -// bool AudioOutputI2S::begin(bool txDAC) -// { -// #ifdef ESP32 -// if (!i2sOn) -// { -// if (use_apll == APLL_AUTO) -// { -// // don't use audio pll on buggy rev0 chips -// use_apll = APLL_DISABLE; -// esp_chip_info_t out_info; -// esp_chip_info(&out_info); -// if (out_info.revision > 0) -// { -// use_apll = APLL_ENABLE; -// } -// } - -// i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); -// if (output_mode == INTERNAL_DAC) -// { -// #if SOC_I2S_SUPPORTS_DAC -// mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN); -// #endif -// } -// else if (output_mode == INTERNAL_PDM) -// { -// mode = (i2s_mode_t)(mode | I2S_MODE_PDM); -// } - -// i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_FORMAT); -// if (output_mode == INTERNAL_DAC) -// { -// #if SOC_I2S_SUPPORTS_DAC -// comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB; -// #endif -// } - -// i2s_config_t i2s_config_dac = { -// .mode = mode, -// .sample_rate = 44100, -// .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, -// .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, -// .communication_format = comm_fmt, -// .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority -// .dma_buf_count = dma_buf_count, -// .dma_buf_len = 64, -// .use_apll = use_apll // Use audio PLL -// }; -// audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac); -// if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK) -// { -// audioLogger->println("ERROR: Unable to install I2S drives\n"); -// } -// if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) -// { -// #if SOC_I2S_SUPPORTS_DAC -// i2s_set_pin((i2s_port_t)portNo, NULL); -// i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); -// #endif -// } -// else -// { -// SetPinout(); -// } -// i2s_zero_dma_buffer((i2s_port_t)portNo); -// } -// #elif defined(ESP8266) -// (void)dma_buf_count; -// (void)use_apll; -// if (!i2sOn) -// { -// orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U); -// orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U); -// #ifdef I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS -// if (!i2s_rxtxdrive_begin(false, true, false, txDAC)) { -// return false; -// } -// #else -// if (!i2s_rxtx_begin(false, true)) { -// return false; -// } -// if (!txDAC) { -// audioLogger->printf_P(PSTR("I2SNoDAC: esp8266 arduino core should be upgraded to avoid conflicts with SPI\n")); -// } -// #endif -// } -// #elif defined(ARDUINO_ARCH_RP2040) -// (void)txDAC; -// if (!i2sOn) { -// I2S.setBCLK(bclkPin); -// I2S.setDOUT(doutPin); -// I2S.begin(hertz); -// } -// #endif -// i2sOn = true; -// SetRate(hertz); // Default -// return true; -// } - -// bool AudioOutputI2S::ConsumeSample(int16_t sample[2]) -// { - -// //return if we haven't called ::begin yet -// if (!i2sOn) -// return false; - -// int16_t ms[2]; - -// ms[0] = sample[0]; -// ms[1] = sample[1]; -// MakeSampleStereo16( ms ); - -// if (this->mono) { -// // Average the two samples and overwrite -// int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL]; -// ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff; -// } -// #ifdef ESP32 -// uint32_t s32; -// if (output_mode == INTERNAL_DAC) -// { -// int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000; -// int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000; -// s32 = (r << 16) | (l & 0xffff); -// } -// else -// { -// s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); -// } -// // Deprecated. Use i2s_write -// // return i2s_write_bytes((i2s_port_t)portNo, (const char *)&s32, sizeof(uint32_t), 0); -// size_t bytes_written; -// i2s_write((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), &bytes_written, 0); -// return bytes_written; -// #elif defined(ESP8266) -// uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); -// return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true -// #elif defined(ARDUINO_ARCH_RP2040) -// return !!I2S.write((void*)ms, 4); -// #endif -// } - -// void AudioOutputI2S::flush() -// { -// #ifdef ESP32 -// // makes sure that all stored DMA samples are consumed / played -// int buffersize = 64 * this->dma_buf_count; -// int16_t samples[2] = {0x0, 0x0}; -// for (int i = 0; i < buffersize; i++) -// { -// while (!ConsumeSample(samples)) -// { -// delay(10); -// } -// } -// #elif defined(ARDUINO_ARCH_RP2040) -// I2S.flush(); -// #endif -// } - -// bool AudioOutputI2S::stop() -// { -// if (!i2sOn) -// return false; - -// #ifdef ESP32 -// i2s_zero_dma_buffer((i2s_port_t)portNo); -// #elif defined(ARDUINO_ARCH_RP2040) -// I2S.end(); -// #endif -// i2sOn = false; -// return true; -// } \ No newline at end of file diff --git a/src/AudioOutputI2S.h b/src/AudioOutputI2S.h index 90370dc..33e784b 100644 --- a/src/AudioOutputI2S.h +++ b/src/AudioOutputI2S.h @@ -22,17 +22,22 @@ #include "AudioOutput.h" +#if defined(ARDUINO_ARCH_RP2040) +#include +#include +#endif + class AudioOutputI2S : public AudioOutput { public: #if defined(ESP32) || defined(ESP8266) AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE); - bool SetPinout(int bclkPin, int wclkPin, int doutPin); enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 }; enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; #elif defined(ARDUINO_ARCH_RP2040) AudioOutputI2S(long sampleRate = 44100, pin_size_t sck = 26, pin_size_t data = 28); #endif + bool SetPinout(int bclkPin, int wclkPin, int doutPin); virtual ~AudioOutputI2S() override; virtual bool SetRate(int hz) override; virtual bool SetBitsPerSample(int bits) override; @@ -44,6 +49,7 @@ class AudioOutputI2S : public AudioOutput bool begin(bool txDAC); bool SetOutputModeMono(bool mono); // Force mono output no matter the input + bool SetLsbJustified(bool lsbJustified); // Allow supporting non-I2S chips, e.g. PT8211 protected: bool SetPinout(); @@ -51,6 +57,7 @@ class AudioOutputI2S : public AudioOutput uint8_t portNo; int output_mode; bool mono; + int lsb_justified; bool i2sOn; int dma_buf_count; int use_apll; @@ -61,4 +68,8 @@ class AudioOutputI2S : public AudioOutput uint8_t bclkPin; uint8_t wclkPin; uint8_t doutPin; + +#if defined(ARDUINO_ARCH_RP2040) + I2S i2s; +#endif }; diff --git a/src/AudioOutputI2SNoDAC.cpp b/src/AudioOutputI2SNoDAC.cpp index 027d612..a70e672 100644 --- a/src/AudioOutputI2SNoDAC.cpp +++ b/src/AudioOutputI2SNoDAC.cpp @@ -1,7 +1,7 @@ /* AudioOutputI2SNoDAC Audio player using SW delta-sigma to generate "analog" on I2S data - + Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify @@ -21,12 +21,33 @@ #include #ifdef ESP32 #include "driver/i2s.h" -#elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266) +#elif defined(ARDUINO_ARCH_RP2040) || ARDUINO_ESP8266_MAJOR >= 3 + #include +#elif ARDUINO_ESP8266_MAJOR < 3 #include #endif #include "AudioOutputI2SNoDAC.h" +#if defined(ARDUINO_ARCH_RP2040) +// +// Create an alternate constructor for the RP2040. The AudioOutputI2S has an alternate +// constructor for the RP2040, so the code was passing port to the sampleRate and false to sck. +// +// AudioOutputI2S(long sampleRate = 44100, pin_size_t sck = 26, pin_size_t data = 28); +// +// So this new constructor adds the ability to pass both port and sck to the underlying class, but +// uses the same defaults in the AudioOutputI2S constructor. +// +AudioOutputI2SNoDAC::AudioOutputI2SNoDAC(int port, int sck) : AudioOutputI2S(44100, sck, port) +{ + SetOversampling(32); + lastSamp = 0; + cumErr = 0; +} + +#else + AudioOutputI2SNoDAC::AudioOutputI2SNoDAC(int port) : AudioOutputI2S(port, false) { SetOversampling(32); @@ -36,7 +57,10 @@ AudioOutputI2SNoDAC::AudioOutputI2SNoDAC(int port) : AudioOutputI2S(port, false) WRITE_PERI_REG(PERIPHS_IO_MUX_MTDO_U, orig_bck); WRITE_PERI_REG(PERIPHS_IO_MUX_GPIO2_U, orig_ws); #endif + } +#endif + AudioOutputI2SNoDAC::~AudioOutputI2SNoDAC() { @@ -66,7 +90,7 @@ void AudioOutputI2SNoDAC::DeltaSigma(int16_t sample[2], uint32_t dsBuff[8]) for (int j = 0; j < oversample32; j++) { uint32_t bits = 0; // The bits we convert the sample into, MSB to go on the wire first - + for (int i = 32; i > 0; i--) { bits = bits << 1; if (cumErr < 0) { @@ -95,12 +119,11 @@ bool AudioOutputI2SNoDAC::ConsumeSample(int16_t sample[2]) // Either send complete pulse stream or nothing #ifdef ESP32 -// Deprecated. Use i2s_write -// if (!i2s_write_bytes((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), 0)) - size_t bytes_written; - i2s_write((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), &bytes_written, 0); - if (!bytes_written) + size_t i2s_bytes_written; + i2s_write((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), &i2s_bytes_written, 0); + if (!i2s_bytes_written){ return false; + } #elif defined(ESP8266) if (!i2s_write_sample_nb(dsBuff[0])) return false; // No room at the inn // At this point we've sent in first of possibly 8 32-bits, need to send @@ -108,10 +131,9 @@ bool AudioOutputI2SNoDAC::ConsumeSample(int16_t sample[2]) for (int i = 32; i < oversample; i+=32) i2s_write_sample( dsBuff[i / 32]); #elif defined(ARDUINO_ARCH_RP2040) - int16_t *p = (int16_t *) dsBuff; - for (int i = 0; i < oversample / 16; i++) { - I2S.write(*(p++)); + for (int i = 0; i < oversample / 32; i++) { + i2s.write((int32_t)dsBuff[i], true); } #endif return true; -} \ No newline at end of file +} diff --git a/src/AudioOutputI2SNoDAC.h b/src/AudioOutputI2SNoDAC.h index 2a63225..de47595 100644 --- a/src/AudioOutputI2SNoDAC.h +++ b/src/AudioOutputI2SNoDAC.h @@ -25,7 +25,16 @@ class AudioOutputI2SNoDAC : public AudioOutputI2S { public: +// +// Define a different constructor for the RP2040, as this class calls the constructor +// of the AudioOutputI2S which has an alternate constructor for the RP2040 +// +#if defined(ARDUINO_ARCH_RP2040) + AudioOutputI2SNoDAC(int port = 28,int sck = 26); +#else AudioOutputI2SNoDAC(int port = 0); +#endif + virtual ~AudioOutputI2SNoDAC() override; virtual bool begin() override { return AudioOutputI2S::begin(false); } virtual bool ConsumeSample(int16_t sample[2]) override; diff --git a/src/AudioOutputSPDIF.cpp b/src/AudioOutputSPDIF.cpp index 53483d4..1d1ec2f 100644 --- a/src/AudioOutputSPDIF.cpp +++ b/src/AudioOutputSPDIF.cpp @@ -3,7 +3,7 @@ S/PDIF output via I2S - Needs transciever from CMOS level to either optical or coaxial interface + Needs transceiver from CMOS level to either optical or coaxial interface See: https://www.epanorama.net/documents/audio/spdif.html Original idea and sources: @@ -94,11 +94,19 @@ AudioOutputSPDIF::AudioOutputSPDIF(int dout_pin, int port, int dma_buf_count) .sample_rate = 88200, // 2 x sampling_rate .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // 32bit words .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // Right than left - .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), +#else + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), +#endif .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority .dma_buf_count = dma_buf_count, .dma_buf_len = DMA_BUF_SIZE_DEFAULT, // bigger buffers, reduces interrupts - .use_apll = true // Audio PLL is needed for low clock jitter + .use_apll = true, // Audio PLL is needed for low clock jitter + .tx_desc_auto_clear = true, // Silence on underflow + .fixed_mclk = 0, // Unused + .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, // Unused + .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT // Use bits per sample }; if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_spdif, 0, NULL) != ESP_OK) { audioLogger->println(F("ERROR: Unable to install I2S drivers")); @@ -143,6 +151,7 @@ bool AudioOutputSPDIF::SetPinout(int bclk, int wclk, int dout) { #if defined(ESP32) i2s_pin_config_t pins = { + .mck_io_num = 0, // unused .bck_io_num = bclk, .ws_io_num = wclk, .data_out_num = dout, @@ -265,7 +274,7 @@ bool AudioOutputSPDIF::ConsumeSample(int16_t sample[2]) #if defined(ESP32) // Assume DMA buffers are multiples of 16 bytes. Either we write all bytes or none. - uint32_t bytes_written; + size_t bytes_written; esp_err_t ret = i2s_write((i2s_port_t)portNo, (const char*)&buf, 8 * channels, &bytes_written, 0); // If we didn't write all bytes, return false early and do not increment frame_num if ((ret != ESP_OK) || (bytes_written != (8 * channels))) return false; diff --git a/src/AudioOutputSPDIF.h b/src/AudioOutputSPDIF.h index 5da160b..7cb194d 100644 --- a/src/AudioOutputSPDIF.h +++ b/src/AudioOutputSPDIF.h @@ -3,7 +3,7 @@ S/PDIF output via I2S - Needs transciever from CMOS level to either optical or coaxial interface + Needs transceiver from CMOS level to either optical or coaxial interface See: https://www.epanorama.net/documents/audio/spdif.html Original idea and sources: diff --git a/src/AudioOutputSPIFFSWAV.cpp b/src/AudioOutputSPIFFSWAV.cpp index 44a5022..075cb4c 100644 --- a/src/AudioOutputSPIFFSWAV.cpp +++ b/src/AudioOutputSPIFFSWAV.cpp @@ -18,12 +18,18 @@ along with this program. If not, see . */ +#if !defined(ARDUINO_ARCH_RP2040) + #include #include #ifdef ESP32 #include "SPIFFS.h" #endif +// Yes, I know SPIFFS is deprecated +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + + #include "AudioOutputSPIFFSWAV.h" static const uint8_t wavHeaderTemplate[] PROGMEM = { // Hardcoded simple WAV header with 0xffffffff lengths all around @@ -110,4 +116,6 @@ bool AudioOutputSPIFFSWAV::stop() f.close(); return true; } - + + +#endif diff --git a/src/AudioOutputSPIFFSWAV.h b/src/AudioOutputSPIFFSWAV.h index 4a2afaf..376a1df 100644 --- a/src/AudioOutputSPIFFSWAV.h +++ b/src/AudioOutputSPIFFSWAV.h @@ -21,6 +21,8 @@ #ifndef _AUDIOOUTPUTSPIFFSWAV_H #define _AUDIOOUTPUTSPIFFSWAV_H +#if !defined(ARDUINO_ARCH_RP2040) + #include #include @@ -43,3 +45,4 @@ class AudioOutputSPIFFSWAV : public AudioOutput #endif +#endif diff --git a/src/AudioOutputSTDIO.cpp b/src/AudioOutputSTDIO.cpp index 33bbd34..f9032bb 100644 --- a/src/AudioOutputSTDIO.cpp +++ b/src/AudioOutputSTDIO.cpp @@ -54,6 +54,11 @@ bool AudioOutputSTDIO::begin() bool AudioOutputSTDIO::ConsumeSample(int16_t sample[2]) { + static int avail = 100; + if (!(--avail)) { + avail = 100; + return false; + } for (int i=0; i. */ -#ifdef ESP32 +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 #include "AudioOutputULP.h" #include diff --git a/src/driver/SinglePinI2SDriver.cpp b/src/driver/SinglePinI2SDriver.cpp index 3d08efa..c2d2280 100644 --- a/src/driver/SinglePinI2SDriver.cpp +++ b/src/driver/SinglePinI2SDriver.cpp @@ -3,7 +3,7 @@ ESP8266Audio I2S Minimal driver Most of this code is taken and reworked from ESP8266 Arduino core, - which itsef is reworked from Espessif's I2S examples. + which itself is reworked from Espessif's I2S examples. Original code is licensed under LGPL 2.1 or above Reasons for rewrite: @@ -196,7 +196,7 @@ void SinglePinI2SDriver::setDividers(uint8_t div1, uint8_t div2) // Ensure dividers fit in bit fields div1 &= I2SBDM; div2 &= I2SCDM; - // trans master(active low), recv master(active_low), !bits mod(==16 bits/chanel), clear clock dividers + // trans master(active low), recv master(active_low), !bits mod(==16 bits/channel), clear clock dividers I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); // I2SRF = Send/recv right channel first // I2SMR = MSB recv/xmit first @@ -249,7 +249,7 @@ void SinglePinI2SDriver::startI2S() I2SFC |= I2SDE; // Enable DMA // I2STXCMM, I2SRXCMM=0 => Dual channel mode, RX/TX CHAN_MOD=0 I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); - // Set dividers to something resonable + // Set dividers to something reasonable currentRate = 0; setRate(44100); // Start I2S peripheral @@ -280,4 +280,4 @@ int SinglePinI2SDriver::getUnderflowCount() // Global instance SinglePinI2SDriver I2SDriver; -#endif // defined(ESP8266) \ No newline at end of file +#endif // defined(ESP8266) diff --git a/src/libflac/stream_decoder.c b/src/libflac/stream_decoder.c index ec172fe..bfbcc54 100644 --- a/src/libflac/stream_decoder.c +++ b/src/libflac/stream_decoder.c @@ -2199,14 +2199,14 @@ FLAC__bool read_frame_header_(FLAC__StreamDecoder *decoder) * Three kinds of things can go wrong when reading the frame header: * 1) We may have sync'ed incorrectly and not landed on a frame header. * If we don't find a sync code, it can end up looking like we read - * a valid but unparseable header, until getting to the frame header + * a valid but unparsable header, until getting to the frame header * CRC. Even then we could get a false positive on the CRC. - * 2) We may have sync'ed correctly but on an unparseable frame (from a + * 2) We may have sync'ed correctly but on an unparsable frame (from a * future encoder). - * 3) We may be on a damaged frame which appears valid but unparseable. + * 3) We may be on a damaged frame which appears valid but unparsable. * * For all these reasons, we try and read a complete frame header as - * long as it seems valid, even if unparseable, up until the frame + * long as it seems valid, even if unparsable, up until the frame * header CRC. */ @@ -2783,7 +2783,7 @@ FLAC__bool read_residual_partitioned_rice_(FLAC__StreamDecoder *decoder, uint32_ if(rice_parameter < pesc) { partitioned_rice_contents->raw_bits[partition] = 0; u = (partition_order == 0 || partition > 0)? partition_samples : partition_samples - predictor_order; - if(!FLAC__bitreader_read_rice_signed_block(decoder->private_->input, residual + sample, u, rice_parameter)) + if(!FLAC__bitreader_read_rice_signed_block(decoder->private_->input, (int *)(residual + sample), u, rice_parameter)) return false; /* read_callback_ sets the state for us */ sample += u; } @@ -2792,7 +2792,7 @@ FLAC__bool read_residual_partitioned_rice_(FLAC__StreamDecoder *decoder, uint32_ return false; /* read_callback_ sets the state for us */ partitioned_rice_contents->raw_bits[partition] = rice_parameter; for(u = (partition_order == 0 || partition > 0)? 0 : predictor_order; u < partition_samples; u++, sample++) { - if(!FLAC__bitreader_read_raw_int32(decoder->private_->input, &i, rice_parameter)) + if(!FLAC__bitreader_read_raw_int32(decoder->private_->input, (FLAC__int32 *)&i, rice_parameter)) return false; /* read_callback_ sets the state for us */ residual[sample] = i; } @@ -2839,7 +2839,7 @@ FLAC__bool read_callback_(FLAC__byte buffer[], size_t *bytes, void *client_data) * FLAC__STREAM_DECODER_UNPARSEABLE_STREAM and increment its * unparseable_frame_count. But there is a remote possibility * that it is properly synced at such a "future-codec frame", - * so to make sure, we wait to see many "unparseable" errors in + * so to make sure, we wait to see many "unparsable" errors in * a row before bailing out. */ if(decoder->private_->is_seeking && decoder->private_->unparseable_frame_count > 20) { @@ -3146,7 +3146,7 @@ FLAC__bool seek_to_absolute_sample_(FLAC__StreamDecoder *decoder, FLAC__uint64 s return false; } /* Now we need to get a frame. First we need to reset our - * unparseable_frame_count; if we get too many unparseable + * unparseable_frame_count; if we get too many unparsable * frames in a row, the read callback will return * FLAC__STREAM_DECODER_READ_STATUS_ABORT, causing * FLAC__stream_decoder_process_single() to return false. diff --git a/src/libhelix-aac/assembly.h b/src/libhelix-aac/assembly.h index 595a8fc..0c17f0c 100644 --- a/src/libhelix-aac/assembly.h +++ b/src/libhelix-aac/assembly.h @@ -558,7 +558,7 @@ static __inline int CLZ(int x) typedef union _U64 { Word64 w64; struct { -#ifdef __XTENSA__ +#if defined(__XTENSA__) || defined (__riscv) unsigned int lo32; signed int hi32; #else diff --git a/src/libhelix-aac/sbrqmf.c b/src/libhelix-aac/sbrqmf.c index 83cf14a..e68bbad 100644 --- a/src/libhelix-aac/sbrqmf.c +++ b/src/libhelix-aac/sbrqmf.c @@ -222,7 +222,8 @@ static void PostMultiply64(int *fft1, int nSampsOut) * Notes: this is carefully written to be efficient on ARM * use the assembly code version in sbrqmfak.s when building for ARM! **************************************************************************************/ -#if (defined (__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(__arm__)) + //TODO - ADD IN .S SOURCES SOMEHOW +#if 0 //(defined (__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(__arm__)) #ifdef __cplusplus extern "C" #endif @@ -395,7 +396,7 @@ int QMFAnalysis(int *inbuf, int *delay, int *XBuf, int fBitsIn, int *delayIdx, i * Notes: this is carefully written to be efficient on ARM * use the assembly code version in sbrqmfsk.s when building for ARM! **************************************************************************************/ -#if (defined (__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(__arm__)) +#if 0 //(defined (__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(__arm__)) #ifdef __cplusplus extern "C" #endif diff --git a/src/libhelix-mp3/RPSL.txt b/src/libhelix-mp3/RPSL.txt index d040a45..94b4458 100644 --- a/src/libhelix-mp3/RPSL.txt +++ b/src/libhelix-mp3/RPSL.txt @@ -201,7 +201,7 @@ and 3, above. 4.2 Compatible Source Licenses. Software modules that have been independently developed without any use of Covered Code and which contain no portion of the Covered Code, Modifications or other Derivative Works, but are used or combined -in any way wtih the Covered Code or any Derivative Work to form a larger +in any way with the Covered Code or any Derivative Work to form a larger Derivative Work, are exempt from the conditions described in Section 4.1 but only to the extent that: the software module, including any software that is linked to, integrated with, or part of the same applications as, the software diff --git a/src/libhelix-mp3/assembly.h b/src/libhelix-mp3/assembly.h index b9e46c5..5ad0f30 100644 --- a/src/libhelix-mp3/assembly.h +++ b/src/libhelix-mp3/assembly.h @@ -40,7 +40,7 @@ * * assembly.h - assembly language functions and prototypes for supported platforms * - * - inline rountines with access to 64-bit multiply results + * - inline routines with access to 64-bit multiply results * - x86 (_WIN32) and ARM (ARM_ADS, _WIN32_WCE) versions included * - some inline functions are mix of asm and C for speed * - some functions are in native asm files, so only the prototype is given here @@ -241,7 +241,7 @@ static __inline int MULSHIFT32(int x, int y) static __inline int FASTABS(int x) { - int t=0; /*Really is not necessary to initialiaze only to avoid warning*/ + int t=0; /*Really is not necessary to initialize only to avoid warning*/ __asm { eor t, x, x, asr #31 diff --git a/src/libhelix-mp3/scalfact.c b/src/libhelix-mp3/scalfact.c index 4937e45..d274b36 100644 --- a/src/libhelix-mp3/scalfact.c +++ b/src/libhelix-mp3/scalfact.c @@ -74,7 +74,7 @@ static const char SFLenTab[16][2] = { * Return: none * * Notes: set order of short blocks to s[band][window] instead of s[window][band] - * so that we index through consectutive memory locations when unpacking + * so that we index through consecutive memory locations when unpacking * (make sure dequantizer follows same convention) * Illegal Intensity Position = 7 (always) for MPEG1 scale factors **************************************************************************************/ diff --git a/src/libmad/layer3.c b/src/libmad/layer3.c index 66ed7c2..bc96433 100644 --- a/src/libmad/layer3.c +++ b/src/libmad/layer3.c @@ -20,6 +20,7 @@ */ #pragma GCC optimize ("O3") +#pragma GCC diagnostic ignored "-Wstrict-aliasing" #include # include "config.h" diff --git a/src/libogg/README.md b/src/libogg/README.md index 63545e2..c3be014 100644 --- a/src/libogg/README.md +++ b/src/libogg/README.md @@ -7,7 +7,7 @@ Ogg project codecs use the Ogg bitstream format to arrange the raw, compressed bitstream into a more robust, useful form. For example, the Ogg bitstream makes seeking, time stamping and error recovery -possible, as well as mixing several sepearate, concurrent media +possible, as well as mixing several separate, concurrent media streams into a single physical bitstream. ## What's here ## @@ -18,7 +18,7 @@ use with Ogg bitstreams. Directory: -- `src` The source for libogg, a BSD-license inplementation of the public domain Ogg bitstream format +- `src` The source for libogg, a BSD-license implementation of the public domain Ogg bitstream format - `include` Library API headers diff --git a/src/libtinysoundfont/README.ESP8266 b/src/libtinysoundfont/README.ESP8266 index e88104d..1009f36 100644 --- a/src/libtinysoundfont/README.ESP8266 +++ b/src/libtinysoundfont/README.ESP8266 @@ -28,7 +28,7 @@ Even with the caching, it was found that SPIFFS, while having great functionality, was horrbly slow. So I wrote a new "faster" ROM filesystem called, surprisingly, FastROMFilesystem. https://github.com/earlephilhower/ESP8266FastROMFS -If you are getting choppy playback, try this new filesytem or using a SD +If you are getting choppy playback, try this new filesystem or using a SD card (not tested by myself, but it'd be hard top be slower than SPIFFS). Simply going from SPIFFS to FastROMFilesystem took my testing of FURELISE.MID and 1MGM.SF2 from 0.5x realtime to 2.5x (i.e. from unusable diff --git a/src/libtinysoundfont/tsf.h b/src/libtinysoundfont/tsf.h index 4ac232d..bdaadae 100644 --- a/src/libtinysoundfont/tsf.h +++ b/src/libtinysoundfont/tsf.h @@ -104,7 +104,7 @@ TSFDEF tsf* tsf_load(struct tsf_stream* stream); // Free the memory related to this tsf instance TSFDEF void tsf_close(tsf* f); -// Stop all playing notes immediatly and reset all channel parameters +// Stop all playing notes immediately and reset all channel parameters TSFDEF void tsf_reset(tsf* f); // Returns the preset index from a bank and preset number, or -1 if it does not exist in the loaded SoundFont @@ -201,7 +201,7 @@ TSFDEF void tsf_channel_set_tuning(tsf* f, int channel, float tuning); TSFDEF void tsf_channel_note_on(tsf* f, int channel, int key, float vel); TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key); TSFDEF void tsf_channel_note_off_all(tsf* f, int channel); //end with sustain and release -TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel); //end immediatly +TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel); //end immediately // Apply a MIDI control change to the channel (not all controllers are supported!) TSFDEF void tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value); diff --git a/src/opusfile/opusfile.c b/src/opusfile/opusfile.c index 7ffe32f..95aab48 100644 --- a/src/opusfile/opusfile.c +++ b/src/opusfile/opusfile.c @@ -1422,7 +1422,7 @@ static int op_open_seekable2_impl(OggOpusFile *_of){ _of->end=sr[0].offset+sr[0].size; if(OP_UNLIKELY(_of->endserialnos,&_of->nserialnos,&_of->cserialnos); free(sr); return ret; diff --git a/src/opusfile/opusfile.h b/src/opusfile/opusfile.h index 7a64572..11cc07a 100644 --- a/src/opusfile/opusfile.h +++ b/src/opusfile/opusfile.h @@ -729,7 +729,7 @@ struct OpusServerInfo{ /**The software used by the origin server (Server). This is NULL if there was no Server header.*/ char *server; - /**The media type of the entity sent to the recepient (Content-Type). + /**The media type of the entity sent to the recipient (Content-Type). This is NULL if there was no Content-Type header.*/ char *content_type; @@ -1436,7 +1436,7 @@ void op_free(OggOpusFile *_of); Some of these functions may be used successfully on the partially open streams returned by op_test_callbacks() or one of the associated convenience functions. - Their documention will indicate so explicitly.*/ + Their documentation will indicate so explicitly.*/ /*@{*/ /**Returns whether or not the stream being read is seekable. diff --git a/src/spiram-fast.h b/src/spiram-fast.h index ce56887..cd75d2d 100644 --- a/src/spiram-fast.h +++ b/src/spiram-fast.h @@ -332,9 +332,9 @@ class ESP8266SPIRAM { } digitalWrite(csPin, HIGH); - pinMode(sck, SPECIAL); - pinMode(miso, SPECIAL); - pinMode(mosi, SPECIAL); + pinMode(sck, OUTPUT); + pinMode(miso, INPUT); + pinMode(mosi, OUTPUT); pinMode(csPin, OUTPUT); // Enable streaming read/write mode @@ -364,4 +364,4 @@ class ESP8266SPIRAM { #endif // ESP32 -#endif \ No newline at end of file +#endif