复古留声机式 arduino 音乐盒-Arduino中文社区 - Powered by Discuz! Archiver

seesea 发表于 2019-4-30 17:42

复古留声机式 arduino 音乐盒



前言
无论是 arduino 还是 51 还是啥单片机或 STEAM 教育,很多的音乐方面的练习就是生硬的机械的刺耳的哔哔声,如果让单片机发出悦耳一点的声音多好啊。比如清脆美妙的音乐盒的声音。
经过多番搜索,功夫不负有心人,找到了一些相关的内容能够让 arduino 发出漂亮的声音。于是打算动手做一个音乐盒。
电子的音乐盒自然想让功能多一点。想到最早的留声机那美丽的外观,就连现在很多音乐盒也用的留声机的外观呢,那么我们的电子音乐盒也做成留声机外观吧。
既然都做成留声机外观了,那不如也做出可以更换唱片的功能呗。其实就是机械的音乐盒也有可以更换唱片的,看上去非常的昂贵哦,也有用纸带的,这两种方式都是通过打孔来制作音乐音符。那么我们发挥电子的优势,把打孔的音符简化为用笔涂黑,使用反射式红外传感器(就是循迹小车的循迹传感器)来检测音符,从而播放自己画出来的唱片的音乐。

材料1.   arduino pro mini 一块2.   360度云台一个3.   360度舵机一个4.   喇叭一个,最好带功放5.   电位器模块一个6.   洞洞板 2 片7.   反射式红外传感器8个8.   2K可调电阻8个9.   支架一套10.铜柱、螺丝若干11.导线等焊接材料12.排针及排母若干13.做装饰喇叭的牛皮纸一张14.做唱片的白纸若干

制作过程

[*]音符传感器的制作
硬件小白,在制作的时候一直打退堂鼓,为了使做出来的外观像个留声机/唱片机,检测输入音符的这个“唱臂”也要做的细一些,不能直接用整块板,切这洞洞板就费了九牛之力,还要切成三片。这也算是平生第一次做的三层板了,还是立体的呢!把走线藏在里面,最后做出来的成品还算过得去,对于非专业的自己,对这个成果已经很满意了。


[*]底座的制作
机械小白,看到论坛有云台硬件申请,看起来刚好像是一个唱片的机构,直接拿来当底座用,装个电位器用来控制转盘的旋转速度,以根据不同的曲风控制乐曲的播放快慢,加一个支架用来装“唱臂”,装喇叭。测试的时候用的 arduino UNO,为了把所有部件都塞进底座,最后组装的时候使用 arduino pro mini。为了连线,焊接了个洞洞板 IO 扩展板,这个其实非常简单,就是把排针往洞洞板上焊上就行了。

[*]大喇叭的制作
留声机最大的特点就是有一个大喇叭,3D 小白,不会建模,就用纸张做一个喽。那天在垃圾桶发现丢弃的一个文件袋,这颜色还真是和留声机的铜喇叭有几分相似,就从垃圾桶里捡出了这个袋子,经过尝试,剪剪粘粘做出了一个大喇叭。当然,这仅仅是一个外观的装饰用的,如果真正的那个喇叭可以调整安装位置的话,这个装饰的喇叭倒是可以用来做个扩音的功能。

[*]组装成品及绘制唱片
唱片根据云台的大小切个圆纸片,根据传感器的大小距离画出八个同心圆,然后用量角器等分圆。分的太细的话,对传感器来说精度和灵敏度要求比较高,最后还是用一个不那么细分的唱片来测试,上面画的是《小星星》的音符,才两句半的乐曲,将就一下吧……这里没有体现出可以同时发出多个音符的特点,实际上可以同时涂三个音哦。把大喇叭装上,拍一张唯美的照片,感觉还过得去啊。
注意传感器阵列那里每两个之间用了个黑纸隔开,以避免邻近的红外光的干扰。
在弄这个传感器的时候突然有一个大胆的想法,直接拿一个条码扫码器来扫描应该也是个好主意,精度可以更好呐!


程序流程

[*]参考大神们的实现,调整和封装了一下音符播放的功能
[*]读取传感器状态,播放对应的音符
[*]读取电位器数据,控制转速

代码
这里附上两个代码,
一个是不需要唱片输入的程序内置音乐的程序,简单起见只固定一首乐曲,大家可以参考格式自己输入喜欢的音乐哦。这里的示例音乐是《千与千寻》的《与我同在》,挑的简单版本,没有很多的伴奏音,只有偶尔的双音,就这样已经让我手工输入了很久,保守估计40分钟吧,需要做一个工具来简化输入啊。网上参考来源的大神用的是 midi 文件导出的方式,我是为了做唱片输入,所以将格式做了调整,两份代码的音乐格式不通用哦。
另一个是使用手绘唱片来输入播放乐曲的代码,不过由于电机声挺大的,其实最终效果不怎么理想,仅仅是实现它,验证了一下想法。
大家可以使用第一个程序来做实验玩,只需要拿个喇叭一头接地一头接 UNO 的 11 引脚就可以开始了。
完整程序大家下载附件,下面展示主程序,头文件比较长影响版面就略过。





[*]内置音乐版本的代码
// ArduinoMusicBoxFixed.ino
// 实现一个 Arduino 音乐盒,内置固定音乐版本
// By seesea, 2019-03-20
//
// 很多的 arduino 示例做音乐播放或电子琴什么的,就只是一个 tone() 哔哔哔的声音,就想着怎样才能让 arduino 播放好听一点的声音呢。
// 在逛一些论坛的时候有人在讨论日本的一个大神做的 avr 程序播放的很好听,但是没有源码。
// 后来多番搜索有了一些收获,下面是几个比较相关的链接可以参考,按相关程度排序:
// 1. arduino 音乐盒播放(页面末有代码下载链接),本程序参考来源:
//   https://www.nutsvolts.com/magazine/article/april2012_Lindley
// 2. arduino wav 声音播放器,arduino 联合创始人的文章,值得一看,上一链接作者的代码我看应该很大程度参考自这里:
//   https://www.elektormagazine.com/files/attachment/331
// 3. makezine 的教学,浅显易懂,推荐学习:
//   https://makezine.com/projects/ma ... no-sound-synthesis/
// 4. 日本大神的音乐盒,主要是原理介绍值得学习:
//   http://elm-chan.org/works/mxb/report.html
// 5. 直接数字合成器,理论讲解很详细:
//   http://www.cs.nott.ac.uk/~pszjm2/?p=674
// 6. 声音采样知识介绍:
//   http://www.quintadicopertina.com ... ng-digitized-audio/
// 7. arduino 声音合成库,功能强大:
//   http://sensorium.github.io/Mozzi

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/atomic.h>
#include "wavetableData.h"
#include "pitchs.h"
#include "songData.h"

#define AUDIO_OUTPUT_PIN      11
#define GENERATOR_COUNT         2
#define SAMPLE_RATE             32000
#define SUSTAIN_LENGTH          128
#define SHIFT_BIT_NUM         8
#define SENSOR_COUNT            8
#define POTENTIOMETER_PIN       A0
#define MOTOR_PWM_PIN         6

const uint16_t WAVETABLE_LENGTH    = sizeof(waveTableData) / sizeof(waveTableData);
const uint16_t ENVELOPTABLE_LENGTH = sizeof(envelopeData) / sizeof(envelopeData);

// 注意,使用8个传感器,如果不是,需要调整相关代码
// 功能按顺序为:高八度标记,音符 7 6 5 4 3 2 1
const uint8_t sensorPins = { 2, 3, 4, 5, 7, 8, 9, 10 };

// 声音合成器结构
// 包括:相位步进,相位累计,包络数据表的索引
// 后续如需扩展多乐器什么的,可以在这里增加乐器波表指针
struct
{
    uint16_t phaseStep;
    uint32_t phaseAccumulator;
    uint16_t envelopeIndex;
} generators;

// 初始化
void systemInit()
{
    // cli();

    pinMode(AUDIO_OUTPUT_PIN, OUTPUT);
    digitalWrite(AUDIO_OUTPUT_PIN, LOW);

    initSensorPins();

    for (int i = 0; i < GENERATOR_COUNT; i++)
    {
      generators.phaseStep      = 0;
      generators.phaseAccumulator = 0;
      generators.envelopeIndex    = 0;
    }

    initTimers();

    // sei();
}

// 配置定时器
// 注意此函数调用前关中断
// 比较底层,直接照搬大神的
// 参考:
//      https://www.nutsvolts.com/magazine/article/april2012_Lindley
//      https://www.elektormagazine.com/files/attachment/331
void initTimers()
{
    /*
    * Timer 1 Configuration
    * Set up Timer 1 to generate interrupt at SAMPLE_RATE.
    */
    // Set CTC mode (Clear Timer on Compare Match)
    // Have to set OCR1A *after*, otherwise it gets reset to 0!
    TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
    TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

    // No prescaler
    TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

    // Set the compare register (OCR1A).
    // OCR1A is a 16-bit register, so we have to do this with
    // interrupts disabled to be safe.
    OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 32000 = 500

    // Enable interrupt when TCNT1 == OCR1A
    TIMSK1 |= _BV(OCIE1A);

    /*
    * Timer 2 Configuration
    * Set up Timer 2 to do pulse width modulation at frequency of 62,500 Hz
    */
    // Set fast PWM mode
    TCCR2A |= _BV(WGM21) | _BV(WGM20);
    TCCR2B &= ~_BV(WGM22);

    // Do non-inverting PWM on pin OC2A. Arduino pin 11
    TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
    TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));

    // No prescaler so PWM frequency is 16000000 / 256 or 62,500 Hz
    TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

    // Set initial pulse width to zero.
    OCR2A = 0;
}

// 定时器 1 的中断处理函数
// 参考:https://www.nutsvolts.com/magazine/article/april2012_Lindley
ISR(TIMER1_COMPA_vect)
{

    int32_tpwmSample = 0;
    uint32_t phase;
    uint32_t waveTableIndex;
    int32_tsampleValue;
    uint8_tenvlopeValue;

    for (uint8_t i = 0; i < GENERATOR_COUNT; i++)
    {
      phase          = generators.phaseAccumulator;      // 取出步进累计值
      waveTableIndex = (phase >> SHIFT_BIT_NUM);            // 去除低位做为波表的索引
      sampleValue    = (int8_t) pgm_read_byte(waveTableData + waveTableIndex);
      envlopeValue   = pgm_read_byte(envelopeData + generators.envelopeIndex);
      pwmSample   += sampleValue * envlopeValue;            // 把波形值和包络相乘做为 pwm 的参数(需去除低位),注意的是这是所有的声音合成器的综合

      // 累加相位步进值
      // 如果到波表的结尾了,则开始增加包络的索引,进行声音大小的改变
      generators.phaseAccumulator += generators.phaseStep;
      if (generators.phaseAccumulator >= ((uint32_t) WAVETABLE_LENGTH) << SHIFT_BIT_NUM)
      {
            generators.phaseAccumulator -= (((uint32_t) SUSTAIN_LENGTH) << SHIFT_BIT_NUM);

            if (generators.envelopeIndex < ENVELOPTABLE_LENGTH - 1)
            {
                generators.envelopeIndex++;
            }
      }
    }

    // 去除低位,留高位,缩小数值到波表的长度内
    pwmSample >>= (SHIFT_BIT_NUM + 2);

    // 加上偏移量避免负数后,设置给定时器 2
    OCR2A = (uint8_t)(127 + pwmSample);
}

// 将音符频率换算为相位步进值
// 计算公式为:2^32 * 频率 / 采样率
// 2^32 来源是相位累加 generator_t.phaseAccumulator 是 32 位的
inline unsigned int hzToPhaseStep(float hz)
{
    return (unsigned int) (hz * 4.096);
}

// 传入音符对应的频率来播放音符
void playPitch(uint16_t pitchFreq)
{
    static int8_t generatorIndex = 0;
    uint16_t step = hzToPhaseStep(pitchFreq);

    ++generatorIndex;
    if (generatorIndex >= GENERATOR_COUNT) {
      generatorIndex = 0;
    }

    // 注意关中断进行设置
    cli();
    generators.phaseStep      = step;
    generators.phaseAccumulator = 0;
    generators.envelopeIndex    = 0;
    sei();
}

// 初始化传感器引脚,设置为内部上拉的输入
void initSensorPins()
{
    for (int i = 0; i < SENSOR_COUNT; ++i)
    {
      pinMode(sensorPins, INPUT_PULLUP);
    }
}

// 目前限制在8个传感器,所以用8位的变量返回值
// 返回值的每一位表示一个传感器的状态
uint8_t getAllSensorsInput()
{
    uint8_t sensorsStatus = 0;

    for (int i = 0; i < SENSOR_COUNT; ++i)
    {
      sensorsStatus <<= 1;

      if (digitalRead(sensorPins) == LOW)
      {
            sensorsStatus |= 0x01;
      }
    }

    return sensorsStatus;
}

void readSensorAndPlayNote()
{
    // 存放至少 16 个音符,这里为两个八度 C 大调音阶,可根据自己的乐曲定制相应的音符
    static const uint16_t noteBox[] = {
      NOTE_C3, NOTE_D3, NOTE_E3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3,
      NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4
    };

    static uint8_t lastSensorStatus = 0;
    uint8_t sensorStatus = getAllSensorsInput();
    uint8_t pitchOffset= 0;

    // 除了高八度标记位外的其它音符位没有变化的时候,认为还没扫描到下一次音符输入,不做任何操作
    if ((0xEF & sensorStatus) == lastSensorStatus)
      return;

    // 高八度
    if ((0x80 & sensorStatus) != 0)
      pitchOffset = 7;

    // 从低到高检查每一位标志来进行音符播放
    sensorStatus &= 0xEF;
    for (uint8_t i = 0; i < SENSOR_COUNT - 1; ++i)
    {
      if (sensorStatus & 0x01)
      {
            playPitch(noteBox);
      }
      sensorStatus >>= 1;
    }

    // 记录旧值用于下次比较
    // 注意:sensorStatus 已经是 sensorStatus & 0xEF 了
    lastSensorStatus = sensorStatus;
}

void playSong(const uint16_t *songAddr)
{   
    int index = 0;
    uint16_t note = 0;
    uint16_t beatTime = 0;
    unsigned long lastTime = millis();

    while (true)
    {
      delay(beatTime);
      /*
      Serial.println(millis());
      Serial.println("-----------");
      if (millis() - lastTime < beatTime)
            continue;
      */
      
      note   = pgm_read_word_near(songAddr + index);
      beatTime = pgm_read_word_near(songAddr + index + 1);

      if (note < 0)
            break;

      playPitch(note);
      index += 2;
      lastTime = millis();
      /*
      Sefrial.println(note);
      Serial.println(beatTime);
      Serial.println(lastTime);
      */
    }
}
void setup()
{
    Serial.begin(115200);
    delay(1000);
    systemInit();
    playSong(songAlwaysWithMe);
}

void loop()
{

}

[*]唱片式音乐盒代码
// ArduinoMusicBox.ino
// 实现一个 Arduino 音乐盒
// By seesea, 2019-03-20
//
// 很多的 arduino 示例做音乐播放或电子琴什么的,就只是一个 tone() 哔哔哔的声音,就想着怎样才能让 arduino 播放好听一点的声音呢。
// 在逛一些论坛的时候有人在讨论日本的一个大神做的 avr 程序播放的很好听,但是没有源码。
// 后来多番搜索有了一些收获,下面是几个比较相关的链接可以参考,按相关程度排序:
// 1. arduino 音乐盒播放(页面末有代码下载链接),本程序参考来源:
//   https://www.nutsvolts.com/magazine/article/april2012_Lindley
// 2. arduino wav 声音播放器,arduino 联合创始人的文章,值得一看,上一链接作者的代码我看应该很大程度参考自这里:
//   https://www.elektormagazine.com/files/attachment/331
// 3. makezine 的教学,浅显易懂,推荐学习:
//   https://makezine.com/projects/ma ... no-sound-synthesis/
// 4. 日本大神的音乐盒,主要是原理介绍值得学习:
//   http://elm-chan.org/works/mxb/report.html
// 5. 直接数字合成器,理论讲解很详细:
//   http://www.cs.nott.ac.uk/~pszjm2/?p=674
// 6. 声音采样知识介绍:
//   http://www.quintadicopertina.com ... ng-digitized-audio/
// 7. arduino 声音合成库,功能强大:
//   http://sensorium.github.io/Mozzi

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/atomic.h>
#include "wavetableData.h"
#include "pitchs.h"

#define AUDIO_OUTPUT_PIN      11
#define GENERATOR_COUNT         3
#define SAMPLE_RATE             32000
#define SUSTAIN_LENGTH          128
#define SHIFT_BIT_NUM         8
#define SENSOR_COUNT            8
#define POTENTIOMETER_PIN       A0
#define MOTOR_PWM_PIN         6

const uint16_t WAVETABLE_LENGTH    = sizeof(waveTableData) / sizeof(waveTableData);
const uint16_t ENVELOPTABLE_LENGTH = sizeof(envelopeData) / sizeof(envelopeData);

// 注意,使用8个传感器,如果不是,需要调整相关代码
// 功能按顺序为:高八度标记,音符 7 6 5 4 3 2 1
const uint8_t sensorPins = { 2, 3, 4, 5, 7, 8, 9, 10 };

// 声音合成器结构
// 包括:相位步进,相位累计,包络数据表的索引
// 后续如需扩展多乐器什么的,可以在这里增加乐器波表指针
struct
{
    uint16_t phaseStep;
    uint32_t phaseAccumulator;
    uint16_t envelopeIndex;
} generators;

// 初始化
void systemInit()
{
    cli();

    pinMode(AUDIO_OUTPUT_PIN, OUTPUT);
    digitalWrite(AUDIO_OUTPUT_PIN, LOW);

    initSensorPins();

    for (int i = 0; i < GENERATOR_COUNT; i++)
    {
      generators.phaseStep      = 0;
      generators.phaseAccumulator = 0;
      generators.envelopeIndex    = 0;
    }

    initTimers();

    sei();
}

// 配置定时器
// 注意此函数调用前关中断
// 比较底层,直接照搬大神的
// 参考:
//      https://www.nutsvolts.com/magazine/article/april2012_Lindley
//      https://www.elektormagazine.com/files/attachment/331
void initTimers()
{
    /*
    * Timer 1 Configuration
    * Set up Timer 1 to generate interrupt at SAMPLE_RATE.
    */
    // Set CTC mode (Clear Timer on Compare Match)
    // Have to set OCR1A *after*, otherwise it gets reset to 0!
    TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
    TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

    // No prescaler
    TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

    // Set the compare register (OCR1A).
    // OCR1A is a 16-bit register, so we have to do this with
    // interrupts disabled to be safe.
    OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 32000 = 500

    // Enable interrupt when TCNT1 == OCR1A
    TIMSK1 |= _BV(OCIE1A);

    /*
    * Timer 2 Configuration
    * Set up Timer 2 to do pulse width modulation at frequency of 62,500 Hz
    */
    // Set fast PWM mode
    TCCR2A |= _BV(WGM21) | _BV(WGM20);
    TCCR2B &= ~_BV(WGM22);

    // Do non-inverting PWM on pin OC2A. Arduino pin 11
    TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
    TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));

    // No prescaler so PWM frequency is 16000000 / 256 or 62,500 Hz
    TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

    // Set initial pulse width to zero.
    OCR2A = 0;
}

// 定时器 1 的中断处理函数
// 参考:https://www.nutsvolts.com/magazine/article/april2012_Lindley
ISR(TIMER1_COMPA_vect)
{

    int32_tpwmSample = 0;
    uint32_t phase;
    uint32_t waveTableIndex;
    int32_tsampleValue;
    uint8_tenvlopeValue;

    for (uint8_t i = 0; i < GENERATOR_COUNT; i++)
    {
      phase          = generators.phaseAccumulator;      // 取出步进累计值
      waveTableIndex = (phase >> SHIFT_BIT_NUM);            // 去除低位做为波表的索引
      sampleValue    = (int8_t) pgm_read_byte(waveTableData + waveTableIndex);
      envlopeValue   = pgm_read_byte(envelopeData + generators.envelopeIndex);
      pwmSample   += sampleValue * envlopeValue;            // 把波形值和包络相乘做为 pwm 的参数(需去除低位),注意的是这是所有的声音合成器的综合

      // 累加相位步进值
      // 如果到波表的结尾了,则开始增加包络的索引,进行声音大小的改变
      generators.phaseAccumulator += generators.phaseStep;
      if (generators.phaseAccumulator >= ((uint32_t) WAVETABLE_LENGTH) << SHIFT_BIT_NUM)
      {
            generators.phaseAccumulator -= (((uint32_t) SUSTAIN_LENGTH) << SHIFT_BIT_NUM);

            if (generators.envelopeIndex < ENVELOPTABLE_LENGTH - 1)
            {
                generators.envelopeIndex++;
            }
      }
    }

    // 去除低位,留高位,缩小数值到波表的长度内
    pwmSample >>= (SHIFT_BIT_NUM + 2);

    // 加上偏移量避免负数后,设置给定时器 2
    OCR2A = (uint8_t)(127 + pwmSample);
}

// 将音符频率换算为相位步进值
// 计算公式为:2^32 * 频率 / 采样率
// 2^32 来源是相位累加 generator_t.phaseAccumulator 是 32 位的
inline unsigned int hzToPhaseStep(float hz)
{
    return (unsigned int) (hz * 4.096);
}

// 传入音符对应的频率来播放音符
void playPitch(uint16_t pitchFreq)
{
    static int8_t generatorIndex = 0;
    uint16_t step = hzToPhaseStep(pitchFreq);

    ++generatorIndex;
    if (generatorIndex >= GENERATOR_COUNT) {
      generatorIndex = 0;
    }

    // 注意关中断进行设置
    cli();
    generators.phaseStep      = step;
    generators.phaseAccumulator = 0;
    generators.envelopeIndex    = 0;
    sei();
}

// 初始化传感器引脚,设置为内部上拉的输入
void initSensorPins()
{
    for (int i = 0; i < SENSOR_COUNT; ++i)
    {
      pinMode(sensorPins, INPUT_PULLUP);
    }
}

// 目前限制在8个传感器,所以用8位的变量返回值
// 返回值的每一位表示一个传感器的状态
uint8_t getAllSensorsInput()
{
    uint8_t sensorsStatus = 0;

    for (int i = 0; i < SENSOR_COUNT; ++i)
    {
      sensorsStatus <<= 1;

      if (digitalRead(sensorPins) == LOW)
      {
            sensorsStatus |= 0x01;
      }
    }

    return sensorsStatus;
}

void readSensorAndPlayNote()
{
    // 存放至少 16 个音符,这里为两个八度 C 大调音阶,可根据自己的乐曲定制相应的音符
    static const uint16_t noteBox[] = {
      NOTE_C3, NOTE_D3, NOTE_E3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3,
      NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4
    };

    static uint8_t lastSensorStatus = 0;
    uint8_t sensorStatus = getAllSensorsInput();
    uint8_t pitchOffset= 0;

    // 除了高八度标记位外的其它音符位没有变化的时候,认为还没扫描到下一次音符输入,不做任何操作
    if ((0xEF & sensorStatus) == lastSensorStatus)
      return;

    // 高八度
    if ((0x80 & sensorStatus) != 0)
      pitchOffset = 7;

    // 从低到高检查每一位标志来进行音符播放
    sensorStatus &= 0xEF;
    for (uint8_t i = 0; i < SENSOR_COUNT - 1; ++i)
    {
      if (sensorStatus & 0x01)
      {
            playPitch(noteBox);
      }
      sensorStatus >>= 1;
    }

    // 记录旧值用于下次比较
    // 注意:sensorStatus 已经是 sensorStatus & 0xEF 了
    lastSensorStatus = sensorStatus;
}

void setup()
{
    systemInit();
    Serial.begin(9600);
}

void loop()
{
    readSensorAndPlayNote();
    analogWrite(MOTOR_PWM_PIN, map(analogRead(POTENTIOMETER_PIN), 0, 1024, 0, 255));
}



演示视频
http://player.youku.com/player.php/sid/XNDE3MjMyODE0NA==/v.swf

参考文档
按与本制作相关程度排序

[*]arduino 音乐盒播放(页面末有代码下载链接),本程序参考来源:
https://www.nutsvolts.com/magazine/article/april2012_Lindley
[*]arduino wav 声音播放器,arduino 联合创始人的文章,值得一看,上一链接作者的代码我看应该很大程度参考自这里:
https://www.elektormagazine.com/files/attachment/331
[*]makezine 的教学,浅显易懂,想了解原理的同学强烈推荐学习:
https://makezine.com/projects/ma ... no-sound-synthesis/
[*]日本大神的音乐播放器,主要是原理介绍值得学习:
http://elm-chan.org/works/mxb/report.html
[*]直接数字合成器,理论讲解很详细:
http://www.cs.nott.ac.uk/~pszjm2/?p=674
[*]声音采样知识介绍:
http://www.quintadicopertina.com ... ng-digitized-audio/
[*]arduino 声音合成库,功能强大:
http://sensorium.github.io/Mozzi


小明来了 发表于 2019-4-30 18:29

哇哦,不错哦,感谢楼主分享

Zoologist 发表于 2019-4-30 20:39

很有意思的设计啊

seesea 发表于 2019-4-30 21:27

小明来了 发表于 2019-4-30 18:29
哇哦,不错哦,感谢楼主分享

谢谢支持,很有趣,也试试吧

seesea 发表于 2019-4-30 21:27

Zoologist 发表于 2019-4-30 20:39
很有意思的设计啊

:D 谢谢支持,咱就搞点小玩意儿,各位大神才是玩大的

mostblack 发表于 2019-5-3 04:23

大佬的代码总是那么牛逼闪闪!

seesea 发表于 2019-5-3 13:29

mostblack 发表于 2019-5-3 04:23
大佬的代码总是那么牛逼闪闪!

不敢装逼不敢闪:$

柯尼赛格 发表于 2019-5-3 21:32

好厉害;P;P

seesea 发表于 2019-5-3 22:13

柯尼赛格 发表于 2019-5-3 21:32
好厉害

过奖过奖,都是大神们做好的,改造一下

kcx 发表于 2019-10-11 17:34

谢谢楼主分享!
页: [1] 2
查看完整版本: 复古留声机式 arduino 音乐盒