Circuit Playground Express volume control

Play with it! Please tell us which board you're using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
ronturcotte
 
Posts: 6
Joined: Fri Jul 27, 2018 9:55 pm

Circuit Playground Express volume control

Post by ronturcotte »

I'd like to be able to adjust the volume on the Circuit Playground Express speaker. I see that there's a "set volume" block in MakeCode, and a music.setVolume command in Javascript. Is there an equivalent command in C++ for the Arduino IDE?

User avatar
adafruit_support_mike
 
Posts: 67485
Joined: Thu Feb 11, 2010 2:51 pm

Re: Circuit Playground Express volume control

Post by adafruit_support_mike »

No. In C++ you have to scale the amplitude of a signal by multiplying the speaker output values by some fraction.

User avatar
ronturcotte
 
Posts: 6
Joined: Fri Jul 27, 2018 9:55 pm

Re: Circuit Playground Express volume control

Post by ronturcotte »

I’m not sure I’m following. Can you give me a simple example of how to do that? For example, how would I play a middle C at full volume vs. at half volume?

Thanks,
Ron

User avatar
adafruit_support_mike
 
Posts: 67485
Joined: Thu Feb 11, 2010 2:51 pm

Re: Circuit Playground Express volume control

Post by adafruit_support_mike »

Concert-pitch middle C is 261.6256Hz, so each cycle of the waveform lasts 3822.25 microseconds.

Audio sampling and playback is usually done at 44.1kHz, with new values arriving every 22.68 microseconds.

Given those values, a middle C would have about 168.5 samples per cycle. If you wanted to be picky, you could build a table with 377 samples that span two cycles of the middle C waveform. For the sake of argument, let's assume the output will be a sine wave.

The SAMD21 has a 10-bit DAC, so the sample values will fall between 0 and 1023. The table would be calculated like so:

Code: Select all

#include <iostream>
#include <math.h>

using namespace std;
int main(int argc, char *argv[]) {
	float step = 4 * atan2( 0, -1 ) / 377;
	
	for ( int i=0 ; i < 377 ; i++ ) {
		float angle = i * step;
		float sample = sin( angle );
		printf ( "%4d%s", 
			int( sample * 512 ),
			( 9 == ( i % 10 ) ) ? ",\n" : ", "
		);
	}
}
and the raw sample values would look like this:

Code: Select all

int middle_C = {
       0,   17,   34,   51,   68,   84,  101,  118,  134,  151,
     167,  183,  199,  214,  230,  245,  260,  274,  289,  303,
     316,  329,  342,  355,  367,  378,  390,  401,  411,  421,
     430,  439,  448,  456,  463,  470,  477,  483,  488,  493,
     497,  501,  504,  507,  509,  510,  511,  511,  511,  511,
     509,  507,  505,  502,  498,  494,  489,  484,  478,  472,
     465,  458,  450,  441,  433,  423,  413,  403,  392,  381,
     370,  358,  345,  333,  319,  306,  292,  278,  263,  249,
     234,  218,  203,  187,  171,  155,  139,  122,  105,   89,
      72,   55,   38,   21,    4,  -12,  -29,  -46,  -63,  -80,
     -97, -114, -130, -147, -163, -179, -195, -211, -226, -241,
    -256, -271, -285, -299, -313, -326, -339, -352, -364, -376,
    -387, -398, -408, -418, -428, -437, -446, -454, -461, -469,
    -475, -481, -487, -492, -496, -500, -503, -506, -508, -510,
    -511, -511, -511, -511, -510, -508, -505, -503, -499, -495,
    -490, -485, -480, -474, -467, -460, -452, -444, -435, -426,
    -416, -406, -395, -384, -373, -361, -348, -336, -323, -309,
    -296, -282, -267, -252, -237, -222, -207, -191, -175, -159,
    -143, -126, -110,  -93,  -76,  -59,  -42,  -25,   -8,    8,
      25,   42,   59,   76,   93,  110,  126,  143,  159,  175,
     191,  207,  222,  237,  252,  267,  282,  296,  309,  323,
     336,  348,  361,  373,  384,  395,  406,  416,  426,  435,
     444,  452,  460,  467,  474,  480,  485,  490,  495,  499,
     503,  505,  508,  510,  511,  511,  511,  511,  510,  508,
     506,  503,  500,  496,  492,  487,  481,  475,  469,  461,
     454,  446,  437,  428,  418,  408,  398,  387,  376,  364,
     352,  339,  326,  313,  299,  285,  271,  256,  241,  226,
     211,  195,  179,  163,  147,  130,  114,   97,   80,   63,
      46,   29,   12,   -4,  -21,  -38,  -55,  -72,  -89, -105,
    -122, -139, -155, -171, -187, -203, -218, -234, -249, -263,
    -278, -292, -306, -319, -333, -345, -358, -370, -381, -392,
    -403, -413, -423, -433, -441, -450, -458, -465, -472, -478,
    -484, -489, -494, -498, -502, -505, -507, -509, -511, -511,
    -511, -511, -510, -509, -507, -504, -501, -497, -493, -488,
    -483, -477, -470, -463, -456, -448, -439, -430, -421, -411,
    -401, -390, -378, -367, -355, -342, -329, -316, -303, -289,
    -274, -260, -245, -230, -214, -199, -183, -167, -151, -134,
    -118, -101,  -84,  -68,  -51,  -34,  -17, 
};
The samples have positive and negative values in the range -512 to +512, which will make it easier to change the volume later. The DAC only takes values between 0 and 1023, which we get by adding 512 to the raw sample values.

To play a sine wave at full volume, you'd use a loop something like this:

Code: Select all

    for ( int i=0 ; i < ( CYCLES / 2 ) ; i++ ) {
        analogWrite( A0, 512 + middle_C[ i ] );
        delayMicroseconds( ( i % 3 ) ? 23 : 22 );
    }
The conditional in delayMicroseconds() makes the average interval 22.66 microseconds long, which is close enough to the 44.1kHz sampling rate for most purposes.

Reducing audio volume by half means reducing the audio power by half, but power is the product of voltage and current. Reducing the voltage by half automatically reduces the current through a resistance by half, so the power is 1/2 x 1/2 or 1/4 of the previous value. To get half volume, you need to drop the volume and current to about 71% of their original values (0.71 x 0.71 = 0.504).

Using that to adjust the volume of the sine wave would look like this:

Code: Select all

    for ( int i=0 ; i < ( CYCLES / 2 ) ; i++ ) {
        analogWrite( A0, 512 + int( 0.71 * (float)middle_C[ i ] ) );
        delayMicroseconds( ( i % 3 ) ? 23 : 22 );
    }
Multiplying the raw sample values reduces the amplitude of the wave, but lets us leave the wave centered around 512 like the full-volume version.

Now, the code above leans toward being mathematically intense for the sake of getting the sample values and timing right. You could simplify the lookup table, use a lower sampling rate to make things rely less on microsecond timing, and round the numbers to get a convenient waveform that's close to middle C without being perfect.

The larger ideas would carry over though: find the amplitude of a sine wave at some number of uniformly-spaced intervals, keep the raw sample values centered around zero, multiply the raw sample values by some constant to change the volume, and add an offset of 512 to bring all the samples up into a range the DAC will accept.

User avatar
ronturcotte
 
Posts: 6
Joined: Fri Jul 27, 2018 9:55 pm

Re: Circuit Playground Express volume control

Post by ronturcotte »

That’s incredibly helpful. Thanks for taking the time to put together a very thorough explanation!

User avatar
foustja
 
Posts: 6
Joined: Thu Oct 05, 2017 3:51 pm

Re: Circuit Playground Express volume control

Post by foustja »

Does any anybody know if there are any simple and straightforward functions/object methods that can be used in Arduino to the volume for the CPX - something along the lines of "CircuitPlayground.setSpeakerVolume(int)"? I'm embarrassed to say that the previous response involving C++ code and scaling the amplitude of the signal is a little over my head.

User avatar
adafruit_support_mike
 
Posts: 67485
Joined: Thu Feb 11, 2010 2:51 pm

Re: Circuit Playground Express volume control

Post by adafruit_support_mike »

No, I'm afraid there's no such function.
foustja wrote: I'm embarrassed to say that the previous response involving C++ code and scaling the amplitude of the signal is a little over my head.
No embarrassment necessary.. it's pretty down-and-dirty stuff. I'd have to bust out the Fourier transforms to get any more abstruse with it. ;--)

The MakeCode environment does have a music.setVolume command though.

Locked
Please be positive and constructive with your questions and comments.

Return to “Circuit Playground Classic, Circuit Playground Express, Circuit Playground Bluefruit”