Circuit Playground Express volume control
Moderators: adafruit_support_bill, adafruit
Please be positive and constructive with your questions and comments.
- ronturcotte
- Posts: 6
- Joined: Fri Jul 27, 2018 9:55 pm
Circuit Playground Express volume control
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?
- adafruit_support_mike
- Posts: 67485
- Joined: Thu Feb 11, 2010 2:51 pm
Re: Circuit Playground Express volume control
No. In C++ you have to scale the amplitude of a signal by multiplying the speaker output values by some fraction.
- ronturcotte
- Posts: 6
- Joined: Fri Jul 27, 2018 9:55 pm
Re: Circuit Playground Express volume control
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
Thanks,
Ron
- adafruit_support_mike
- Posts: 67485
- Joined: Thu Feb 11, 2010 2:51 pm
Re: Circuit Playground Express volume control
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:
and the raw sample values would look like this:
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:
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:
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.
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" : ", "
);
}
}
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,
};
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 );
}
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 );
}
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.
- ronturcotte
- Posts: 6
- Joined: Fri Jul 27, 2018 9:55 pm
Re: Circuit Playground Express volume control
That’s incredibly helpful. Thanks for taking the time to put together a very thorough explanation!
- foustja
- Posts: 6
- Joined: Thu Oct 05, 2017 3:51 pm
Re: Circuit Playground Express volume control
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.
- adafruit_support_mike
- Posts: 67485
- Joined: Thu Feb 11, 2010 2:51 pm
Re: Circuit Playground Express volume control
No, I'm afraid there's no such function.
The MakeCode environment does have a music.setVolume command though.
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. ;--)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.
The MakeCode environment does have a music.setVolume command though.
Please be positive and constructive with your questions and comments.