I'm working on a project where I want to generate (simple) sound by combining different sine waves. I'm using an arduino mkrZero, as it has I2S interface built in and it seems to have enough processing power for what I want.
I have wired my system exactly like in the tutorial for arduino I2S simpleTone:
And the tutorial code works just fine, and I get a simple square wave tone from the speaker.
Now I have modified the code to generate sine wave, there is a lookup table for the sin function to make it fast enough:
#include <I2S.h>
uint16_t isin16[] = {
0, 1144, 2287, 3430, 4571, 5712, 6850, 7987, 9121, 10252, 11380,
12505, 13625, 14742, 15854, 16962, 18064, 19161, 20251, 21336, 22414,
23486, 24550, 25607, 26655, 27696, 28729, 29752, 30767, 31772, 32768,
33753, 34728, 35693, 36647, 37589, 38521, 39440, 40347, 41243, 42125,
42995, 43851, 44695, 45524, 46340, 47142, 47929, 48702, 49460, 50203,
50930, 51642, 52339, 53019, 53683, 54331, 54962, 55577, 56174, 56755,
57318, 57864, 58392, 58902, 59395, 59869, 60325, 60763, 61182, 61583,
61965, 62327, 62671, 62996, 63302, 63588, 63855, 64103, 64331, 64539,
64728, 64897, 65047, 65176, 65286, 65375, 65445, 65495, 65525, 65535,
}; //0-90
const int sincount = 2;
int freqs[] = {50*360,51*360};
float amps[] ={0.1,0.1};
const int sampleRate = 8000; // sample rate in Hz
short sample = 0;
double t = 0;
double dt = 1.0/(1.0*sampleRate);
short LR[] = {0,0};
void setup() {
Serial.begin(115200);
// start I2S at the sample rate with 16-bits per sample
if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, 16)) {
while (1); // do nothing
}
}
void loop() {
sample = 0;
for(int n= 0;n<sincount;n++)
{
sample += fSin(freqs[n]*t)*amps[n];
}
t += dt;
LR[0] = sample;
LR[1] = sample;
I2S.write(LR[0]);//left channel
I2S.write(LR[1]);//right channel
}
float fSin(long x)
{
boolean pos = true; // positive - keeps an eye on the sign.
if (x < 0)
{
x = -x;
pos = false;
}
if (x >= 360) x %= 360;
if (x > 180)
{
x -= 180;
pos = !pos;
}
if (x > 90) x = 180 - x;
if (pos) return isin16[x]; // = /65535.0
return isin16[x];
}
This also works fine.
BUT!
If I change the code a bit and I write
I2S.write(LR,2);
instead of
I2S.write(LR[0]);//left channel
I2S.write(LR[1]);//right channel
Everything just breaks, the sound from the speaker sounds like a horrible scream
From the I2S library reference:
Description
Writes binary data to the I2S interface. This data is sent as a sample or series of samples.
Syntax
I2S.write(val)
// blocking
I2S.write(buf, len)
// not blockingParameters
val: a value to send as a single sample
buf: an array to send as a series of samples
len: the length of the buffer
Returns
byte write() will return the number of bytes written, though reading that number is optional.
I'd like to use the latter version of the write function, because it is not blocking and I can generate new samples while the previous ones are playing.
Any ideas on how to make the buffered version work correctly as well?
In your code, you declare
LR
as anarray of 2 short
, ashort
in Arduino has size2 bytes
.When you write this:
given
your
short
values should be automatically promoted to fit into theint
type. Now, I am a bit unsure on the size ofint
you are dealing with, because the only I2S.h library I found is for the SAMD board on which anint
has4 bytes
size, but normally on Arduino the anint
has size of2 bytes
. In either case, the value should be taken as is, without problems.When you write this:
given
your
array of 2 short
should be cast toconst void *
.From this code in I2SDoubleBuffer:
it looks like
_doubleBuffer
is not aware of the declared size of a sample (you declared it to be of16 bits
), it merely copiessize
bytes from the input to the internal buffer.Therefore, I guess that what really happens is that when you ask for
2 shorts
to be buffered only2 bytes
are actually copied.Example:
Assume that your
LR
contains the following valuesthen this is what I imagine it happens
Proposed Solution:
Try to ask for
4 bytes
to be copied: