Audio Arduino-如何在压电蜂鸣器上同时产生两个或多个音调?
我的高中电子课决定买一些arduino uno套件,我必须说它们非常酷。够了,现在在课堂上我们正在试验压电蜂鸣器(看起来像)。我们学习了如何使用压电蜂鸣器创作歌曲。我们的老师告诉我们要“有创造力”。还有什么比使用凯蒂·佩里的“焰火”更具创造性的方法呢 利用一些创造性的自由,我找到了这首歌的一首很好的钢琴曲(链接)。现在我是一名钢琴演奏者(我学习了AP音乐理论),我遇到的问题是,我只能演奏一个音符,只有压电蜂鸣器。有没有可能在压电蜂鸣器上播放这首歌,让它听起来像是在钢琴上播放的(或者至少接近钢琴)。我的意思是,低音和高音谱号在蜂鸣器上同时播放Audio Arduino-如何在压电蜂鸣器上同时产生两个或多个音调?,audio,arduino,frequency,Audio,Arduino,Frequency,我的高中电子课决定买一些arduino uno套件,我必须说它们非常酷。够了,现在在课堂上我们正在试验压电蜂鸣器(看起来像)。我们学习了如何使用压电蜂鸣器创作歌曲。我们的老师告诉我们要“有创造力”。还有什么比使用凯蒂·佩里的“焰火”更具创造性的方法呢 利用一些创造性的自由,我找到了这首歌的一首很好的钢琴曲(链接)。现在我是一名钢琴演奏者(我学习了AP音乐理论),我遇到的问题是,我只能演奏一个音符,只有压电蜂鸣器。有没有可能在压电蜂鸣器上播放这首歌,让它听起来像是在钢琴上播放的(或者至少接近钢琴)
我知道这涉及到相移和增加音符的频率,但如何将其转化为压电蜂鸣器的代码?如果您能发布一些示例代码,将不胜感激。如果没有,请你尽可能清楚地解释一下。我不是编程高手,但也不是初学者。这个第三方音调库可以在多个管脚上同时播放方波:
您可以在多个引脚和单个扬声器之间连接电阻,以获得一个扬声器的所有音调。Arduinos仅提供数字输出:输出为on(+5V)或off(0V)。
tone()
说你想要一个100Hz的音调。100Hz表示输出每1/100秒或10ms重复一次。因此,tone(PIN,100)
将设置每5毫秒调用一次定时器中断。第一次调用中断时,它将输出设置为低,并返回到程序正在执行的任何操作。下次调用时,它会将输出设置为高。因此,每5毫秒,输出就会发生变化,在50%的占空比下会出现一个方波,这意味着输出的开启时间正好是一半
这一切都很好,但大多数音频波形不是方波。如果您想要同时播放两个方波音调,甚至想要控制单个方波音调的音量,您需要能够输出比“开”和“关”更多的值
好消息是,有一个技巧你可以使用称为脉冲宽度调制(通常缩写为PWM)。这个想法是,您可能只能将输出设置为两个值中的一个,但您可以非常快地设置。人类可以听到高达20kHz的音频。如果你的输出速度比这快,比如200kHz(在Arduino的能力范围内,它的时钟频率为16MHz),你就听不到单独的输出转换,而是更长时间内的平均值
想象一下使用音调()
生成200kHz音调。它太高了,听不见,但平均值介于开和关之间(50%占空比,记得吗?)。现在我们有三个可能的输出值:开、关和半开。这足以让我们同时播放两个方波:
高质量音频需要更多的值。CD存储16位音频,这意味着有65536个可能的值。虽然我们不会从Arduino中获得CD质量的音频,但我们可以通过选择50%以外的占空比来获得更多的输出值。事实上,Arduino已经为我们提供了硬件
满足analogWrite()
。这使得使用Arduino内置PWM硬件的输出电平发生变化。坏消息是PWM频率通常为500Hz,这对于LED调光来说是不错的,但是对于音频来说太低了。所以我们必须自己编写硬件寄存器
有更多的信息,这里有一个关于如何在Arduino上实现PWM DAC的示例
我选择了7位分辨率,这意味着输出是一个16MHz/128=125kHz的方波,具有128个可能的占空比
当然,一旦你有了PWM输出工作的乐趣才刚刚开始。
对于多个声音,你不能依靠中断来设置波形的频率,你必须自己拉伸它们。基本的数字信号处理(DSP)知识将非常有用。您将需要紧凑的代码从中断处理程序中生成音频数据,然后需要播放例程在正确的时间触发正确的音符。天空是极限
无论如何,这里有一些代码:
#define PIN 9
/* these magic constants were generated by the following perl script:
#!/usr/bin/perl -lw
my $freq = 16000000/256;
my $A4 = 440;
print int(128*$freq/$A4*exp(-log(2)*$_/12)) for (-9..2);
*/
const uint16_t frtab[] = {
30578, 28861, 27241, 25712,
24269, 22907, 21621, 20408,
19262, 18181, 17161, 16198
};
#define VOICES 4
struct voice {
uint16_t freq;
int16_t frac;
uint8_t octave;
uint8_t off;
int8_t vol;
const uint8_t *waveform;
} voice[VOICES];
#define PITCH 50 /* global pitch adjustment */
/* some waveforms. 16 samples each */
const uint8_t square_50[] = {
0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15
};
const uint8_t square_25[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15
};
const uint8_t square_12[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15,15
};
const uint8_t square_6[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15
};
const uint8_t sawtooth[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15
};
const uint8_t triangle[] = {
0, 2, 4, 6, 8,10,12,14,15,13,11, 9, 7, 5, 3, 1
};
const uint8_t nicebass[] = {
0, 8,14,18,22,23,24,25,26,25,24,23,22,18,14, 8
};
void setup() {
/* TIMER0 is used by the Arduino environment for millis() etc.
So we use TIMER1.
*/
pinMode(PIN, OUTPUT);
/* fast PWM, no prescaler */
TCCR1A = 0x80;
TCCR1B = 0x11;
/* 7-bit precision => 125kHz PWM frequency */
ICR1H = 0;
ICR1L = 0x7f;
/* enable interrupts on TIMER1 overflow */
TIMSK1 = 1;
OCR1AH = 0; /* hi-byte is unused */
for (uint8_t i=0; i<VOICES; i++)
clear_voice(i);
}
void set_voice(uint8_t v, uint8_t note, uint8_t volume, const uint8_t *waveform) {
note += PITCH;
voice[v].octave = note/12;
voice[v].freq = frtab[note%12];
voice[v].frac = 0;
voice[v].off = 0;
voice[v].waveform = waveform;
voice[v].vol = volume;
}
void clear_voice (uint8_t v) {
voice[v].freq = 0;
}
uint8_t s = 0;
ISR(TIMER1_OVF_vect) {
/* Calculate new data every 4 pulses, i.e. at 125/4 = 31.25kHz.
Being interrupted unnecessarily is kinda wasteful, but using another timer is messy.
*/
if (s++ & 3)
return;
int8_t i;
int8_t out = 0;
for (i=0; i<VOICES; i++) {
if (voice[i].freq) {
voice[i].frac -= 128<<voice[i].octave;
if (voice[i].frac < 0) { /* overflow */
voice[i].frac += voice[i].freq;
voice[i].off++;
}
/* warning: vol isn't a real volume control, only for square waves */
out += (voice[i].waveform[voice[i].off & 15]) & voice[i].vol;
}
}
/* out is in the range 0..127. With 4-bit samples this gives us headroom for 8 voices.
Or we could use more than 4-bit samples (see nicebass).
*/
OCR1AL = out;
}
/* tune data */
const uint8_t bass[8][4] = {
{ 12, 19, 23, 24 },
{ 5, 12, 19, 21 },
{ 12, 19, 23, 24 },
{ 5, 12, 19, 21 },
{ 14, 16, 17, 21 },
{ 7, 19, 14, 19 },
{ 14, 16, 17, 21 },
{ 7, 19, 14, 19 }
};
const uint8_t melody[2][8][16] = {
{/* first voice */
{31, 0, 0, 0, 0, 1, 2, 3,31,29,28,29, 0,28,26,24 },
{ 0, 0, 0, 0, 0, 1, 2, 3,53,54,53,54, 0, 1, 2, 3 },
{31, 0, 0, 0, 0, 1, 2, 3,31,29,28,29, 5,28, 5,26 },
{ 5,28,24, 0, 0, 1, 2, 3,53,54,56,54, 0, 1, 2, 3 },
{29, 0, 0, 0, 0, 1, 2, 3,31,29,28,29, 5, 0,28, 5 },
{28, 5, 0,26, 0, 1, 2, 3,54,56,58,56, 0, 1, 2, 3 },
{29, 0, 0, 0, 0, 1, 2, 3,31,29,28,29, 5, 0,28, 5 },
{28, 5, 0,26, 0, 1, 2, 3, 0,19,21,23,24,26,28,29 },
},
{/* second voice */
{24, 0, 0, 0, 0, 1, 2, 3,24,24,24,24, 0,24,24,21 },
{ 0, 0, 0, 0, 0, 1, 2, 3,49,51,49,51, 0, 1, 2, 3 },
{24, 0, 0, 0, 0, 1, 2, 3,24,24,24,24, 5,24, 5,24 },
{ 5,23,21, 0, 0, 1, 2, 3,49,51,53,51, 0, 1, 2, 3 },
{26, 0, 0, 0, 0, 1, 2, 3,24,26,24,24, 5, 0,24, 5 },
{24, 5, 0,24, 0, 0, 0, 0,51,51,54,54, 0, 1, 2, 3 },
{26, 0, 0, 0, 0, 1, 2, 3,24,26,24,24, 5, 0,24, 5 },
{24, 5, 0,23, 0, 1, 2, 3, 0, 5, 0,19,21,23,24,26 },
}
};
void loop() {
uint8_t pos, i, j;
for (pos=0; pos<8; pos++) {
for (i=0; i<16; i++) {
/* melody: voices 0 and 1 */
for (j=0; j<=1; j++) {
uint8_t m = melody[j][pos][i];
if (m>10) {
/* new note */
if (m > 40) /* hack: new note, keep volume */
set_voice(j, m-30, voice[j].vol, square_50);
else /* new note, full volume */
set_voice(j, m, 15, square_50);
} else {
voice[j].vol--; /* fade existing note */
switch(m) { /* apply effect */
case 1: voice[j].waveform = square_25; break;
case 2: voice[j].waveform = square_12; break;
case 3: voice[j].waveform = square_6; break;
case 4: clear_voice(j); break; /* unused */
case 5: voice[j].vol -= 8; break;
}
if (voice[j].vol < 0)
voice[j].vol = 0; /* just in case */
}
}
/* bass: voices 2 and 3 */
set_voice(2, bass[pos][i%4], 31, nicebass);
set_voice(3, bass[pos][0]-12, 15-i, sawtooth);
delay(120); /* time per event */
}
}
}
#定义引脚9
/*这些神奇常量由以下perl脚本生成:
#!/usr/bin/perl-lw
my$freq=16000000/256;
我的$A4=440;
打印(-9..2)的整数(128*$freq/$A4*exp(-log(2)*$\u12));
*/
const uint16\u t frtab[]={
30578, 28861, 27241, 25712,
24269, 22907, 21621, 20408,
19262, 18181, 17161, 16198
};
#定义声音4
结构语音{
uint16_t频率;
国际压裂;
八度音程;
uint8关闭;
国际卷;
常数8_t*波形;
}声音;
#定义节距50/*全局节距调整*/
/*一些波形。各16个样品*/
const uint8_t square_50[]{
0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15
};
const uint8_t square_25[]{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15
};
const uint8_t square_12[]{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15,15
};
const uint8_t square_6[]={
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15
};
常数8_t锯齿[]={
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15
};
常数8_t三角形[]={
0, 2, 4, 6, 8,10,12,14,15,13,11, 9, 7, 5, 3, 1
};
const uint8_t nicebass[]={
0, 8,14,18,22,23,24,25,26,25,24,23,22,18,14, 8
};
无效设置(){
/*计时器0由Arduino环境用于millis()等。
所以我们使用定时器1。
*/
引脚模式(引脚,输出);
/*快速PWM,无预分频器*/
TCCR1A=0x80;
TCCR1B=0x11;
/*7位精度=>125kHz PWM频率*/
ICR1H=0;
ICR1L=0x7f;
/*启用计时器1溢出时的中断*/