Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No pop sounds #523

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ AudioGeneratorRTTTL: Enjoy the pleasures of monophonic, 4-octave ringtones on y
## AudioOutput classes
AudioOutput: Base class for all output drivers. Takes a sample at a time and returns true/false if there is buffer space for it. If it returns false, it is the calling object's (AudioGenerator's) job to keep the data that didn't fit and try again later.

AudioOutputI2S: Interface for any I2S 16-bit DAC. Sends stereo or mono signals out at whatever frequency set. Tested with Adafruit's I2SDAC and a Beyond9032 DAC from eBay. Tested up to 44.1KHz. To use the internal DAC on ESP32, instantiate this class as `AudioOutputI2S(0,1)`, see example `PlayMODFromPROGMEMToDAC` and code in [AudioOutputI2S.cpp](src/AudioOutputI2S.cpp#L29) for details.
AudioOutputI2S: Interface for any I2S 16-bit DAC. Sends stereo or mono signals out at whatever frequency set. Tested with Adafruit's I2SDAC and a Beyond9032 DAC from eBay. Tested up to 44.1KHz. To use the internal DAC on ESP32, instantiate this class as `AudioOutputI2S(0,AudioOutputI2S::INTERNAL_DAC)`, see example `PlayMODFromPROGMEMToDAC` and code in [AudioOutputI2S.cpp](src/AudioOutputI2S.cpp#L29) for details. To use the hardware Pulse Density Modulation (PDM) on ESP32, instantiate this class as `AudioOutputI2S(0,AudioOutputI2S::INTERNAL_PDM)`. For both later cases, default output pins are GPIO25 and GPIO26.

AudioOutputI2SNoDAC: Abuses the I2S interface to play music without a DAC. Turns it into a 32x (or higher) oversampling delta-sigma DAC. Use the schematic below to drive a speaker or headphone from the I2STx pin (i.e. Rx). Note that with this interface, depending on the transistor used, you may need to disconnect the Rx pin from the driver to perform serial uploads. Mono-only output, of course.

Expand Down Expand Up @@ -199,7 +199,8 @@ Use the `AudioOutputI2S*No*DAC` object instead of the `AudioOutputI2S` in your c
ESP8266-GND ------------------+ | +------+ K|
| | | E|
ESP8266-I2SOUT (Rx) -----/\/\/\--+ | \ R|
| +-|
or ESP32 DOUT pin | +-|
|
USB 5V -----------------------------+

You may also want to add a 220uF cap from USB5V to GND just to help filter out any voltage droop during high volume playback.
Expand All @@ -212,12 +213,19 @@ ESP8266-RX(I2S tx) -- Resistor (~1K ohm, not critical) -- 2N3904 Base
ESP8266-GND -- 2N3904 Emitter
USB-5V -- Speaker + Terminal
2N3904-Collector -- Speaker - Terminal

*For ESP32, default output pin is GPIO22. Note that GPIO25 ang GPIO26 are occupied by wclk/bclk and can not be used.
```

*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 `digitalWrite` or `digitalRead` as desired.

### Hardware PDM on ESP32

Hardware PDM outputs 128 * 48Khz pulses regardles of samplerate.
It seems that currently hardware PDM either does not output constant One at maximum sample level, or does not output 3.3V voltage at pulse ( unfortunatelly I not have oscilloscope to test currently) - sound is not as loud as desired. You may consider using software delta-sigma DAC instead.

### 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.

Expand Down Expand Up @@ -258,6 +266,21 @@ The current version allows for using the standard hardware CS (GPIO15) or any ot

![Example of SPIRAM Schematic](examples/StreamMP3FromHTTP_SPIRAM/Schema_Spiram.png)

## Pop sounds (clicks)

Audio wave is centered at the middle level voltage. To output silence, DAC has to output middle voltage (0x8000 sample for 16-bit DAC). PDM or PWM have to output pulses with 50% fill. When sound is not played, pin outputs zero voltage. Thus playback start and stop can cause pop (click) sounds due to voltage change from zero to middle (3.3V/2 average). It might not be a problem with external DAC which continue generating middle voltage when I2S is stopped. There is a problem with internal DAC, PDM on ESP32 and software Delta-sigma. Even if we let I2S to run continuously (which is waste of resources), there would be clicks on each sampling rate adjustments.
To remove pop sounds, AudioOutputI2S and AudioOutputI2SNoDAC can ramp voltage level from zero voltage to actial sound amplitude on playback start and opposite on stop.
```
AudioOutpupI2S->SetRamp(100)
or
AudioOutpupI2SNoDac->SetRamp(100)
```
Parameters is length of ramp in millisecons. Values between 50...100ms are Ok.
Volume will rize from zero to normal on sound start.
After playback, there will be a ramp of specified length to bring voltage down to zero.

*Note: curently works with AudioGeneratorMP3 and AudioGeneratorWAV only*

## Notes for using SD cards and ESP8266Audio on Wemos shields
I've been told the Wemos SD card shield uses GPIO15 as the SD chip select. This needs to be changed because GPIO15 == I2SBCLK, and is driven even if you're using the NoDAC option. Once you move the CS to another pin and update your program it should work fine.

Expand Down
1 change: 1 addition & 0 deletions src/AudioGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class AudioGenerator

protected:
bool running;
bool finishing;
AudioFileSource *file;
AudioOutput *output;
int16_t lastSample[2];
Expand Down
15 changes: 13 additions & 2 deletions src/AudioGeneratorMP3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
AudioGeneratorMP3::AudioGeneratorMP3()
{
running = false;
finishing = false;
file = NULL;
output = NULL;
buff = NULL;
Expand All @@ -34,6 +35,7 @@ AudioGeneratorMP3::AudioGeneratorMP3()
AudioGeneratorMP3::AudioGeneratorMP3(void *space, int size): preallocateSpace(space), preallocateSize(size)
{
running = false;
finishing = false;
file = NULL;
output = NULL;
buff = NULL;
Expand All @@ -48,6 +50,7 @@ AudioGeneratorMP3::AudioGeneratorMP3(void *buff, int buffSize, void *stream, int
preallocateSynthSpace(synth), preallocateSynthSize(synthSize)
{
running = false;
finishing = false;
file = NULL;
output = NULL;
buff = NULL;
Expand Down Expand Up @@ -88,13 +91,14 @@ bool AudioGeneratorMP3::stop()
stream = NULL;

running = false;
finishing = false;
output->stop();
return file->close();
}

bool AudioGeneratorMP3::isRunning()
{
return running;
return running || finishing;
}

enum mad_flow AudioGeneratorMP3::ErrorToFlow()
Expand Down Expand Up @@ -209,6 +213,11 @@ bool AudioGeneratorMP3::GetOneSample(int16_t sample[2])

bool AudioGeneratorMP3::loop()
{
if ( finishing )
{
return output->finish() == false;
}

if (!running) goto done; // Nothing to do here!

// First, try and push in the stored sample. If we can't, then punt and try later
Expand All @@ -221,7 +230,9 @@ bool AudioGeneratorMP3::loop()
if ( (samplePtr >= synth->pcm.length) && (nsCount >= nsCountMax) ) {
retry:
if (Input() == MAD_FLOW_STOP) {
return false;
running = false;
finishing = true;
return true;
}

if (!DecodeNextFrame()) {
Expand Down
25 changes: 20 additions & 5 deletions src/AudioGeneratorWAV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
AudioGeneratorWAV::AudioGeneratorWAV()
{
running = false;
finishing = false;
file = NULL;
output = NULL;
buffSize = 128;
Expand All @@ -42,6 +43,7 @@ bool AudioGeneratorWAV::stop()
{
if (!running) return true;
running = false;
finishing = false;
free(buff);
buff = NULL;
output->stop();
Expand All @@ -50,7 +52,7 @@ bool AudioGeneratorWAV::stop()

bool AudioGeneratorWAV::isRunning()
{
return running;
return running || finishing;
}


Expand All @@ -76,6 +78,19 @@ bool AudioGeneratorWAV::GetBufferedData(int bytes, void *dest)

bool AudioGeneratorWAV::loop()
{
if ( finishing )
{
if ( output->finish() )
{
stop();
return false;
}
else
{
return true;
}
}

if (!running) goto done; // Nothing to do here!

// First, try and push in the stored sample. If we can't, then punt and try later
Expand All @@ -86,18 +101,18 @@ bool AudioGeneratorWAV::loop()
{
if (bitsPerSample == 8) {
uint8_t l, r;
if (!GetBufferedData(1, &l)) stop();
if (!GetBufferedData(1, &l)) finishing = true;
if (channels == 2) {
if (!GetBufferedData(1, &r)) stop();
if (!GetBufferedData(1, &r)) finishing = true;
} else {
r = 0;
}
lastSample[AudioOutput::LEFTCHANNEL] = l;
lastSample[AudioOutput::RIGHTCHANNEL] = r;
} else if (bitsPerSample == 16) {
if (!GetBufferedData(2, &lastSample[AudioOutput::LEFTCHANNEL])) stop();
if (!GetBufferedData(2, &lastSample[AudioOutput::LEFTCHANNEL])) finishing = true;
if (channels == 2) {
if (!GetBufferedData(2, &lastSample[AudioOutput::RIGHTCHANNEL])) stop();
if (!GetBufferedData(2, &lastSample[AudioOutput::RIGHTCHANNEL])) finishing = true;
} else {
lastSample[AudioOutput::RIGHTCHANNEL] = 0;
}
Expand Down
1 change: 1 addition & 0 deletions src/AudioOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class AudioOutput
}
return count;
}
virtual bool finish() { return true; }
virtual bool stop() { return false; }
virtual void flush() { return; }
virtual bool loop() { return true; }
Expand Down
Loading