//引入所需的AVR库,用于与硬件进行低级交互和中断处理
#include <avr/io.h>
#include <avr/interrupt.h>
#include <LiquidCrystal_I2C.h>
//定义音符
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
// 这些宏定义了引脚的映射关系。每个宏代表了一个特定功能的引脚编号
#define GRAIN_FREQ_CONTROL A0 //用于控制晶粒振荡器的频率,影响音调或音高
#define GRAIN_DECAY_CONTROL A1 //用于控制晶粒振荡器的衰减速度,从而调整声音的持续时间或衰减特性
#define GRAIN2_FREQ_CONTROL A2 //用于控制第二个晶粒振荡器的频率,影响合成音的复杂性或谐波结构
#define GRAIN2_DECAY_CONTROL A3 //用于控制第二个晶粒振荡器的衰减速度,影响声音的持续时间或衰减特性
#define SYNC_CONTROL A4 //用于控制振荡器之间的同步频率
#define PWM_PIN 3
#define LED_BIT 5
#define SW1_PIN 4
#define PWM_PIN_2 2
// 定义变量,用于存储音频合成器的参数和状态
uint16_t s;
uint16_t s2;
uint16_t g1;
uint16_t g2;
uint16_t g3;
uint8_t d;
uint16_t gp;
uint16_t gi;
uint16_t gm;
uint8_t gd;
// 存储了一个用于频率映射的反对数表。它通过将输入的低6位作为索引,从数组中获取对应的数值。
uint16_t antilogTable[] = {
64830, 64132, 63441, 62757, 62081, 61413, 60751, 60097, 59449, 58809, 58176, 57549, 56929, 56316, 55709, 55109,
54515, 53928, 53347, 52773, 52204, 51642, 51085, 50535, 49991, 49452, 48920, 48393, 47871, 47356, 46846, 46341,
45842, 45348, 44859, 44376, 43898, 43425, 42958, 42495, 42037, 41584, 41136, 40693, 40255, 39821, 39392, 38968,
38548, 38133, 37722, 37316, 36914, 36516, 36123, 35734, 35349, 34968, 34591, 34219, 33850, 33486, 33125, 32768
};
uint16_t mapPhaseInc(uint16_t input) {
return (antilogTable[input & 0x3f]) >> (input >> 6);
}
// 存储了一个用于MIDI音符映射的表。它将输入值转换为对应的MIDI音符。
uint16_t midiTable[] = {
17, 18, 19, 20, 22, 23, 24, 26, 27, 29, 31, 32, 34, 36, 38, 41, 43, 46, 48, 51, 54, 58, 61, 65, 69, 73,
77, 82, 86, 92, 97, 103, 109, 115, 122, 129, 137, 145, 154, 163, 173, 183, 194, 206, 218, 231,
244, 259, 274, 291, 308, 326, 346, 366, 388, 411, 435, 461, 489, 518, 549, 581, 616, 652, 691,
732, 776, 822, 871, 923, 978, 1036, 1097, 1163, 1232, 1305, 1383, 1465, 1552, 1644, 1742,
1845, 1955, 2071, 2195, 2325, 2463, 2610, 2765, 2930, 3104, 3288, 3484, 3691, 3910, 4143,
4389, 4650, 4927, 5220, 5530, 5859, 6207, 6577, 6968, 7382, 7821, 8286, 8779, 9301, 9854,
10440, 11060, 11718, 12415, 13153, 13935, 14764, 15642, 16572, 17557, 18601, 19708, 20879,
22121, 23436, 24830, 26306
};
uint16_t mapMidi(uint16_t input) {
return (midiTable[(1023 - input) >> 3]);
}
// 存储了一个用于五声音阶映射的表。它将输入值转换为对应的五声音阶音符。
uint16_t pentatonicTable[54] = {
0, 19, 22, 26, 29, 32, 38, 43, 51, 58, 65, 77, 86, 103, 115, 129, 154, 173, 206, 231, 259, 308, 346,
411, 461, 518, 616, 691, 822, 923, 1036, 1232, 1383, 1644, 1845, 2071, 2463, 2765, 3288,
3691, 4143, 4927, 5530, 6577, 7382, 8286, 9854, 11060, 13153, 14764, 16572, 19708, 22121, 26306
};
uint16_t mapPentatonic(uint16_t input) {
uint8_t value = (1023 - input) / (1024 / 53);
return (pentatonicTable[value]);
}
// 创建 LiquidCrystal_I2C 对象
LiquidCrystal_I2C lcd(0x27,20,4);
const int list[8]={0,262,294,330,349,392,440,494};
void setup() {
//初始化配置,配置了各个引脚的模式(输入或输出),并打开了串口通信。
pinMode(GRAIN_FREQ_CONTROL, INPUT);
pinMode(GRAIN2_DECAY_CONTROL, INPUT);
pinMode(GRAIN_DECAY_CONTROL, INPUT);
pinMode(GRAIN2_FREQ_CONTROL, INPUT);
pinMode(SYNC_CONTROL, INPUT);
pinMode(SW1_PIN, INPUT_PULLUP);
pinMode(PWM_PIN, OUTPUT);
pinMode(PWM_PIN, INPUT);
pinMode(LED_BIT, OUTPUT);
pinMode(PWM_PIN_2, OUTPUT);
pinMode(PWM_PIN_2, INPUT);
pinMode(12, INPUT);
pinMode(11, INPUT);
pinMode(10, INPUT);
pinMode(9, INPUT);
pinMode(8, INPUT);
pinMode(7, INPUT);
pinMode(6, INPUT);
lcd.init();
lcd.backlight();
}
// loop() 函数是一个无限循环
//用于更新音频合成器的参数。它根据按键和模拟输入的状态更新各个参数的值。
void loop() {
//设置一个变量来控制显示屏来显示什么内容
if (digitalRead(SW1_PIN) == HIGH) {
// Stepped pentatonic mapping: D, E, G, A, B
s2 = mapPentatonic(analogRead(SYNC_CONTROL));
noTone(3);
//设置灯光效果
digitalWrite(5,LOW);
//利用控制开关来调节控制模块,选择音乐模块还是调频模块
while(digitalRead(SW1_PIN)){
//电子音乐琴模块,点击音乐按键
if (digitalRead(12) == HIGH) {
tone(PWM_PIN_2,list[1]);
delay(20);
} else if (digitalRead(11) == HIGH) {
tone(PWM_PIN_2,list[2]);
delay(20);
} else if (digitalRead(10) == HIGH) {
tone(PWM_PIN_2,list[3]);
delay(20);
} else if (digitalRead(9) == HIGH) {
tone(PWM_PIN_2,list[4]);
delay(20);
} else if (digitalRead(8) == HIGH) {
tone(PWM_PIN_2,list[5]);
delay(20);
} else if (digitalRead(7) == HIGH) {
tone(PWM_PIN_2,list[6]);
delay(20);
} else if (digitalRead(6) == HIGH) {
tone(PWM_PIN_2,list[7]);
delay(20);
} else {
noTone(PWM_PIN_2);
}
lcd.setCursor(7, 0);
lcd.print("Hello!");
lcd.setCursor(4, 1);
lcd.print("Wokwi project");
lcd.setCursor(6, 2);
lcd.print("ZhangXu");
lcd.setCursor(0, 3);
lcd.print(" MUSIC CODE! ");
}
} else {
// 读数映射为 MIDI 音符的值,使用 s2 变量来控制生成的声音的音高或音调。
s2 = mapMidi(analogRead(SYNC_CONTROL));
tone(PWM_PIN, signal());
lcd.setCursor(7, 0);
lcd.print("Hello!");
lcd.setCursor(4, 1);
lcd.print("Wokwi project");
lcd.setCursor(6, 2);
lcd.print("ZhangXu");
lcd.setCursor(0, 3);
lcd.print("Frequency Modulation");
}
//更新振荡器的相位和衰减
//用于控制振荡器的频率,从而调整生成的声音的音高。
g2 = mapPhaseInc(analogRead(GRAIN_FREQ_CONTROL)) / 2;
//用于控制振荡器的衰减速度,从而调整声音的持续时间或衰减特性。
d = analogRead(GRAIN_DECAY_CONTROL) / 8;
//用于控制第二个振荡器的频率,影响合成音的复杂性或谐波结构。
gi = mapPhaseInc(analogRead(GRAIN2_FREQ_CONTROL)) / 2;
//用于控制第二个振荡器的衰减速度,影响声音的持续时间或衰减特性。
gd = analogRead(GRAIN2_DECAY_CONTROL) / 4;
}
// signal() 函数是音频合成器的核心函数。它根据合成器的参数生成音频信号,并返回输出值。
uint16_t signal()
{ //用于存储临时计算结果和输出结果。
uint8_t value;
uint16_t output;
s += s2;
if (s < s2) {
// 检查是否需要启动下一个颗粒。如果 s 小于 s2,则表示到达了启动下一个颗粒的时机。
g1 = 0;
g3 = 0x7fff;
gp = 0;
gm = 0x7fff;
// 通过异或运算翻转指定引脚(LED_BIT 所在的引脚)的电平状态,从而实现更快的引脚控制,用于控制 LED 灯的闪烁。
PORTD ^= 1 << LED_BIT;
}
// 增加晶粒振荡器的相位
g1 += g2;
gp += gi;
// 将 g1 的高位右移 7 位,并与 0xff 进行按位与运算,得到一个 8 位的值。把相位转换成三角波
value = (g1 >> 7) & 0xff;
//如果 g1 的最高位为 1,将 value 取反。
if (g1 & 0x8000) value = ~value;
//将 value 乘以 g3 的高位右移 8 位的值,得到一个 16 位的样本。
output = value * (g3 >> 8);
//将 gp 的高位右移 7 位,并与 0xff 进行按位与运算,得到第二个颗粒的 8 位值。
value = (gp >> 7) & 0xff;
//如果 gp 的最高位为 1,将 value 取反
if (gp & 0x8000) value = ~value;
output += value * (gm >> 8);
// 使每个样品的晶粒振幅衰减一个因子
g3 -= (g3 >> 8) * d;
gm -= (gm >> 8) * gd;
// Scale output to the available range, clipping if necessary
output >>= 9;
//如果 output 大于 255,则将其限制在 0 到 255 的范围内,避免溢出。
if (output > 255) output = 255;
// 返回最终的输出样本。这个样本将用于 PWM 输出,用于生成音频信号。
return output;
}