Основы работы со звуком на Arduino
Узнайте, как создавать тоны и даже целые песни с помощью Arduino.
Авторы: Paul Badger, Alexandre Quessy, Michael Smith, Samantha Lagestee, Dan Thompson
Последняя редакция: 09.09.2023
Примечание
Эта статья была обновлена 28.09.2022 Hannes Siebeneicher.
В этой статье рассматриваются различные подходы к созданию звуков и даже целых песен с помощью Arduino. В 2013 году Brett Hagman создал библиотеку tone(), которая является хорошей отправной точкой для создания различных типов звуков с помощью Arduino. Поскольку примеры в этой статье собраны из Arduino playground и были созданы в основном до 2013 года, многие шаги по-прежнему выполняются вручную, что можно пропустить при использовании библиотеки tone().
Тем не менее, эти примеры по-прежнему актуальны, так как они объясняют некоторые базовые концепции генерации частот тонов, интерполяции и даже предоставляют несколько песен, которые можно попробовать. Если вы хотите увидеть пример простой мелодии с использованием библиотеки tone() и ознакомиться с концепцией внешних файлов звуковых данных, посмотрите этот пример.
Большинство скетчей в этой статье используют пин 8 в качестве выхода для пьезоизлучателя или динамика, что означает, что вам нужно подключить компоненты, как показано ниже, и попробовать различные примеры, загрузив их в свой Arduino. Только пример PCMAudio использует пин 11, поскольку он использует ШИМ (PWM).
Необходимое оборудование
Плата Arduino
пьезоизлучатель или динамик
соединительные провода
Схема подключения
Принципиальная схема
Основы
Чаще всего для создания звуков с помощью Arduino используется пьезоизлучатель. Когда к пьезокерамическому материалу прикладывается напряжение, он начинает быстро вибрировать, что приводит к генерации звуковых волн. Каждая волна имеет связанное с ней свойство, называемое частотой, которое измеряет, сколько циклов происходит каждую секунду. Эта единица циклов называется герцами (Гц). Например, средняя нота До (middle C) на пианино имеет частоту 262 Гц, что означает, что воздух колеблется туда-сюда 262 раза в секунду.
Другим свойством волны является её период, который равен единице, делённой на частоту, и измеряет длину и время волны. Так, для средней ноты До на пианино цикл повторяется каждые 3,8 миллисекунды. В то время как обычный чистый тон представляет собой синусоидальную волну, гораздо проще создать прямоугольную волну с помощью Arduino, включая пин, ожидая определённое время, затем выключая пин и снова ожидая.
Freqout
Следующий пример был создан Paul Badger в 2007 году. Он показывает простую функцию генерации тона, генерирующую прямоугольные волны произвольной частоты и длительности. Программа также включает в себя таблицу поиска верхней октавы и функцию транспонирования.
#include <math.h> // требуется чип Atmega168
#define outpin 8 // аудиовыход на динамик или усилитель
int ptime;
int k, x, dur, freq, t;
int i, j;
float ps; // переменная для процедуры pow pitchShift
float noteval;
// значения нот для двухоктавной шкалы
// делите их на степени двойки для генерации других октав
float A = 14080;
float AS = 14917.2;
float B = 15804.3;
float C = 16744;
float CS = 17739.7;
float D = 18794.5;
float DS = 19912.1;
float E = 21096.2;
float F = 22350.6;
float FS = 23679.6;
float G = 25087.7;
float GS = 26579.5;
float A2 = 28160;
float A2S = 29834.5;
float B2 = 31608.5;
float C2 = 33488.1;
float C2S = 35479.4;
float D2 = 37589.1;
float D2S = 39824.3;
float E2 = 42192.3;
float F2 = 44701.2;
float F2S = 47359.3;
float G2 = 50175.4;
float G2S = 53159;
float A3 = 56320;
//октавы — соответствуют октавам пианино
float oct8 = 4;
float oct7 = 8;
float oct6 = 16;
float oct5 = 32;
float oct4 = 64;
float oct3 = 128;
float oct2 = 256;
float oct1 = 512;
float oct0 = 1024;
//значения ритма
int wh = 1024;
int h = 512;
int dq = 448;
int q = 256;
int qt = 170;
int de = 192;
int e = 128;
int et = 85;
int dsx = 96;
int sx = 64;
int thx = 32;
// мажорная гамма просто для демонстрации, дорабатывайте
float majScale[] = {
A, B, CS, D, E, FS, GS, A2, B2, C2S, D2, E2, F2S, G2S, A3};
void setup() {
Serial.begin(9600);
}
void loop(){
for(i= 0; i<=11; i++){
ps = (float)i / 12; // выбираем новый интервал транспонирования каждый цикл
for(x= 0; x<=15; x++){
noteval = (majScale[x] / oct4) * pow(2,ps); // транспонирует гамму вверх на 12 тонов
// функция pow генерирует транспонирование
// удалите " * pow(2,ps) ", чтобы убрать процедуру транспонирования
dur = 100;
freqout((int)noteval, dur);
delay(10);
}
}
}
void freqout(int freq, int t) // freq в Гц, t в мс
{
int hperiod; //вычисляем 1/2 периода в мкс
long cycles, i;
pinMode(outpin, OUTPUT); // включаем выходной пин
hperiod = (500000 / freq) - 7; // вычитаем 7 мкс для компенсации накладных расходов digitalWrite
cycles = ((long)freq * (long)t) / 1000; // вычисляем циклы
for (i=0; i<= cycles; i++){ // воспроизводим ноту в течение t мс
digitalWrite(outpin, HIGH);
delayMicroseconds(hperiod);
digitalWrite(outpin, LOW);
delayMicroseconds(hperiod - 1); // - 1 для компенсации накладных расходов digitalWrite
}
pinMode(outpin, INPUT); // отключаем пин, чтобы избежать шума от других операций
}
Расширение по длительности
В приведённом ниже примере были сделаны небольшие изменения: в основном массив был изменён, чтобы содержать длительности, а в конец был добавлен sentinel-маркер. Пример выше остаётся, так как он демонстрирует прекрасную простую структуру.
float EIGHTH = 1;
float QUARTER = 2;
float DOTTED_QUARTER =3;
float HALF = 4;
float ETERNITY =-1;
float TEMPO = 150;
float majScale[] = {
A,QUARTER, B,QUARTER, CS,QUARTER, D,QUARTER, E,QUARTER, FS,QUARTER, GS,QUARTER, A2,QUARTER, B2,QUARTER,
C2S,QUARTER, D2,QUARTER, E2,QUARTER, F2S,QUARTER, G2S,QUARTER, A3,QUARTER, REST,ETERNITY
};
float odeToJoy[] = {
F2S,QUARTER, F2S,QUARTER, G2,QUARTER, A3,QUARTER, A3,QUARTER, G2,QUARTER, F2S,QUARTER, E2,QUARTER, D2,QUARTER,
D2,QUARTER, E2,QUARTER, F2S,QUARTER, F2S,DOTTED_QUARTER, E2,EIGHTH, E2,HALF, F2S,QUARTER, F2S,QUARTER, G2,QUARTER,
A3,QUARTER, A3,QUARTER,G2,QUARTER, F2S,QUARTER, E2,QUARTER, D2,QUARTER, D2,QUARTER, E2,QUARTER, F2S,QUARTER, E2,DOTTED_QUARTER,
D2,EIGHTH, D2,HALF, E2,QUARTER, E2,QUARTER, F2S,QUARTER, D2,QUARTER, E2,QUARTER, F2S,EIGHTH, G2,EIGHTH, F2S,QUARTER, D2,QUARTER,
E2,QUARTER, F2S,EIGHTH, G2,EIGHTH, F2S,QUARTER, E2,QUARTER, D2,QUARTER, E2,QUARTER, A,QUARTER, REST,ETERNITY
};
void play(float song[]) {
for(x= 0; x<10000; x=x+2) {
noteval = (song[x] / 64);
dur = TEMPO * song[x+1];
if(dur < 0) {
break;
freqout((int)noteval, dur);
delay(10);
}
}
}
Примеры
Smoothstep
Этот пример сделан Dan Thompson в 2009 году для плавной интерполяции между двумя значениями. Smoothstep — это распространённая формула, используемая для множества разных приложений, таких как анимация и аудио. Этот скетч включает в себя вывод в Serial, чтобы помочь вам визуализировать формулу. Посетите danthompsonsblog.blogspot.com для полного руководства по smoothstep, а также многих других. Для всестороннего обзора интерполяции, а также отличных советов и приёмов посетите эту страницу.
Код
///////////////////////////////////////
// Пример интерполяции Smoothstep //
///////////////////////////////////////
// Dan Thompson 2009
//
// Вдохновлено кодом и обсуждением на этом сайте.
// https://sol.gfxile.net/interpolation/
//
// Используйте этот код на свой страх и риск.
//
// Этот скетч был написан с прицелом на покадровую съёмку timelapse
// с управлением движением. Я постарался сделать его достаточно общим, чтобы
// понять концепцию smoothstep, чтобы можно было адаптировать эту мощную
// формулу и в других областях.
//
// Полное руководство см. на https://danthompsonsblog.blogspot.com/
//
// Использование:
// 1. Загрузите скетч в Arduino.
// 2. Откройте Serial monitor, чтобы увидеть визуальную обратную связь функции SMOOTHSTEP.
// 3. Прокрутите вывод, чтобы увидеть кривую SMOOTHSTEP.
// 4. Поэкспериментируйте с кодом и адаптируйте его под свои нужды! ;)
#define SMOOTHSTEP(x) ((x) * (x) * (3 - 2 * (x))) //Выражение SMOOTHSTEP.
int j = 0; //Просто итератор.
int i = 0; //Ещё один итератор.
float A = 0.0; //Минимальное входное значение
float B = 100.0; //Максимальное входное значение
float N = 100.0; //Количество шагов перехода
float X; //итоговое сглаженное значение
float v; //переменная выражения smoothstep
void setup() {
Serial.begin(9600); //устанавливаем последовательное соединение для отладки
}
void loop()
{
if (j < N) // Продолжаем цикл, пока не достигнем заранее определённого
// максимального количества шагов
{
v = j / N; // Итерация, делённая на количество шагов.
v = SMOOTHSTEP(v); // Применяем выражение smoothstep к v.
X = (B * v) + (A * (1 - v)); // Применяем выражение линейной интерполяции,
//используя текущий результат smoothstep.
for ( i=0; i < X ; i++) // Этот цикл может быть релевантным кодом для каждого
//шага вашего мотора.
{
Serial.print("1"); //Выводит число "1" для каждого шага.
}
Serial.print(" "); //Ставит пробел между каждой строкой шагов и
//соответствующим значением float
Serial.println(X); // выводит сглаженное значение
Serial.println("CLICK!!!"); // здесь вы могли бы запускать спуск затвора timelapse
j++; // Увеличивает j на 1.
}
}
PCMAudio
Следующий пример был создан Michael Smith и является предшественником библиотеки PCM, созданной David Mellis. Он воспроизводит 8-битное PCM-аудио на пине 11, используя широтно-импульсную модуляцию (PWM). Используются два таймера. Первый изменяет значение сэмпла 8000 раз в секунду. Второй удерживает пин 11 в высоком состоянии в течение 0–255 тиков из 256-тикового цикла, в зависимости от значения сэмпла. Второй таймер повторяется 62500 раз в секунду (16000000 / 256), что намного быстрее, чем частота воспроизведения (8000 Гц), поэтому это звучит почти приемлемо, просто очень тихо на динамике ПК.
Захватывает Timer 1 (16-битный) для таймера 8000 Гц. Это ломает PWM (analogWrite()) для пинов Arduino 9 и 10. Затем захватывает Timer 2 (8-битный) для широтно-импульсной модуляции, ломая PWM для пинов 11 и 13.
Ссылки
Код
#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#define SAMPLE_RATE 8000
/*
* Аудиоданные должны быть беззнаковыми, 8-битными, 8000 Гц и достаточно
* маленькими, чтобы поместиться во flash. Около 10000–13000 сэмплов — это предел.
*
* sounddata.h должен выглядеть так:
* const int sounddata_length=10000;
* const unsigned char sounddata_data[] PROGMEM = { ..... };
*
* Можно использовать wav2c из GBA CSS:
* https://thieumsweb.free.fr/english/gbacss.html
* Затем добавьте "PROGMEM" в нужное место. Я доработал утилиту, чтобы выводить
* сэмплы как беззнаковые, а не знаковые, но это не должно иметь значения.
*
* https://musicthing.blogspot.com/2005/05/tiny-music-makers-pt-4-mac-startup.html
* mplayer -ao pcm macstartup.mp3
* sox audiodump.wav -v 1.32 -c 1 -r 8000 -u -1 macstartup-8000.wav
* sox macstartup-8000.wav macstartup-cut.wav trim 0 10000s
* wav2c macstartup-cut.wav sounddata.h sounddata
*
* (starfox) пр. в sox 12.18 (входит в CentOS 5) мне пришлось запустить
* следующую команду для конвертации wav-файла в нужный формат:
* sox audiodump.wav -c 1 -r 8000 -u -b macstartup-8000.wav
*/
#include "sounddata.h"
int ledPin = 13;
int speakerPin = 11; // Может быть 3 или 11 — два PWM-выхода, подключённых к Timer 2
volatile uint16_t sample;
byte lastSample;
void stopPlayback()
{
// Отключаем прерывание выборки.
TIMSK1 &= ~_BV(OCIE1A);
// Полностью отключаем таймер выборки.
TCCR1B &= ~_BV(CS10);
// Отключаем PWM-таймер.
TCCR2B &= ~_BV(CS10);
digitalWrite(speakerPin, LOW);
}
// Это вызывается с частотой 8000 Гц для загрузки следующего сэмпла.
ISR(TIMER1_COMPA_vect) {
if (sample >= sounddata_length) {
if (sample == sounddata_length + lastSample) {
stopPlayback();
}
else {
if(speakerPin==11){
// Плавное затухание до нуля для уменьшения щелчка в конце воспроизведения.
OCR2A = sounddata_length + lastSample - sample;
} else {
OCR2B = sounddata_length + lastSample - sample;
}
}
}
else {
if(speakerPin==11){
OCR2A = pgm_read_byte(&sounddata_data[sample]);
} else {
OCR2B = pgm_read_byte(&sounddata_data[sample]);
}
}
++sample;
}
void startPlayback()
{
pinMode(speakerPin, OUTPUT);
// Настраиваем Timer 2 для выполнения широтно-импульсной модуляции
// на пине динамика.
// Используем внутренний тактовый генератор (datasheet p.160)
ASSR &= ~(_BV(EXCLK) | _BV(AS2));
// Устанавливаем режим fast PWM (p.157)
TCCR2A |= _BV(WGM21) | _BV(WGM20);
TCCR2B &= ~_BV(WGM22);
if(speakerPin==11){
// Неинвертирующий PWM на пине OC2A (p.155)
// На Arduino это пин 11.
TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
// Без предделителя (p.158)
TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Устанавливаем начальную ширину импульса по первому сэмплу.
OCR2A = pgm_read_byte(&sounddata_data[0]);
} else {
// Неинвертирующий PWM на пине OC2B (p.155)
// На Arduino это пин 3.
TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0);
TCCR2A &= ~(_BV(COM2A1) | _BV(COM2A0));
// Без предделителя (p.158)
TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Устанавливаем начальную ширину импульса по первому сэмплу.
OCR2B = pgm_read_byte(&sounddata_data[0]);
}
// Настраиваем Timer 1 на отправку сэмпла каждое прерывание.
cli();
// Устанавливаем режим CTC (Clear Timer on Compare Match) (p.133)
// OCR1A нужно устанавливать ПОСЛЕ, иначе он сбрасывается в 0!
TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
// Без предделителя (p.134)
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Устанавливаем регистр сравнения (OCR1A).
// OCR1A — 16-битный регистр, поэтому это нужно делать с
// отключёнными прерываниями для безопасности.
OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 8000 = 2000
// Включаем прерывание, когда TCNT1 == OCR1A (p.136)
TIMSK1 |= _BV(OCIE1A);
lastSample = pgm_read_byte(&sounddata_data[sounddata_length-1]);
sample = 0;
sei();
}
void setup()
{
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH);
startPlayback();
}
void loop()
{
while (true);
}
Приведённый выше скетч также требует файла sounddata.h, который вы можете найти ниже:
// sounddata — звук, сделанный wav2c
// (wav2c доработан для использования беззнаковых сэмплов)
/* const int sounddata_sampleRate=8000; */
const int sounddata_length=10000;
const unsigned char sounddata_data[] PROGMEM = {
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
// ... здесь следует массив из 10000 байт сэмплов 8-битного PCM-аудио ...
// Полный массив см. в оригинальной статье: https://docs.arduino.cc/learn/programming/audio/
142, 136, 124, 111, 96, 80, 69, 62, 59, 57, 52, 50, 56, 65, 74, 86, 96, 109, 116
};
Примечание
Полный массив sounddata_data[] содержит 10000 байт сэмплов 8-битного PCM-аудио и здесь обрезан для краткости. Полные данные доступны в оригинальной статье и в репозитории Arduino docs-content.
Rick Roll
Следующий пример был создан Samantha Lagestee в 2017 году. Вдохновлённый популярным мемом, этот код «рикроллит» людей, воспроизводя песню «Never Gonna Give You Up» Rick Astley на пьезоизлучателе. Откройте Serial-порт, чтобы увидеть текст и подпевать.
Примечание
Скетч защищён лицензией GNU GPL v3 (Copyright 2017 Samantha Lagestee). Песня «Never Gonna Give You Up» Rick Astley не является творческой собственностью автора скетча — код просто воспроизводит на пьезоизлучателе версию песни.
Код
Примечание
Ниже приведён технический каркас скетча. Полные массивы нот, ритмов и текста песни (которые защищены авторским правом издателя) опущены — посмотрите оригинальный скетч в Arduino docs-content на GitHub.
/* Rick Roll Code
AUTHOR: Samantha Lagestee
Copyright 2017 samilagestee at gmail dot com
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
*/
// Определения частот нот (Гц)
#define a3f 208
#define b3f 233
#define b3 247
#define c4 261 // средняя До (MIDDLE C)
#define c4s 277
#define e4f 311
#define f4 349
#define a4f 415
#define b4f 466
#define b4 493
#define c5 523
#define c5s 554
#define e5f 622
#define f5 698
#define f5s 740
#define a5f 831
#define rest -1
// меняйте эти пины под свою схему
int piezo = 8;
int led = 9;
volatile int beatlength = 100; // задаёт темп
float beatseparationconstant = 0.3;
int a; // индекс части
int b; // индекс в массиве песни
int c; // индекс в массиве текста
boolean flag; // воспроизведение/пауза
// ВНИМАНИЕ: массивы song1_intro_melody[], song1_intro_rhythmn[],
// song1_verse1_melody[], song1_verse1_rhythmn[], lyrics_verse1[],
// song1_chorus_melody[], song1_chorus_rhythmn[], lyrics_chorus[]
// содержат ноты, ритмы и слова песни «Never Gonna Give You Up».
// Возьмите их из оригинального скетча по ссылке выше.
void setup()
{
pinMode(piezo, OUTPUT);
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
Serial.begin(9600);
flag = true;
a = 4;
b = 0;
c = 0;
}
void loop()
{
// здесь можно дописать условия запуска/паузы
// play next step in song
if (flag == true) {
play();
}
}
void play() {
int notelength;
if (a == 1 || a == 2) { // Intro
notelength = beatlength * song1_intro_rhythmn[b];
if (song1_intro_melody[b] > 0) { // если не пауза, играем ноту
digitalWrite(led, HIGH);
tone(piezo, song1_intro_melody[b], notelength);
}
b++;
if (b >= sizeof(song1_intro_melody) / sizeof(int)) {
a++;
b = 0;
c = 0;
}
} else if (a == 3 || a == 5) { // Verse 1
notelength = beatlength * 2 * song1_verse1_rhythmn[b];
if (song1_verse1_melody[b] > 0) {
digitalWrite(led, HIGH);
Serial.print(lyrics_verse1[c]);
tone(piezo, song1_verse1_melody[b], notelength);
c++;
}
b++;
if (b >= sizeof(song1_verse1_melody) / sizeof(int)) {
a++;
b = 0;
c = 0;
}
} else if (a == 4 || a == 6) { // Chorus
notelength = beatlength * song1_chorus_rhythmn[b];
if (song1_chorus_melody[b] > 0) {
digitalWrite(led, HIGH);
Serial.print(lyrics_chorus[c]);
tone(piezo, song1_chorus_melody[b], notelength);
c++;
}
b++;
if (b >= sizeof(song1_chorus_melody) / sizeof(int)) {
Serial.println("");
a++;
b = 0;
c = 0;
}
}
delay(notelength); // нужно, т.к. piezo на независимом таймере
noTone(piezo);
digitalWrite(led, LOW);
delay(notelength * beatseparationconstant); // разделение между нотами
if (a == 7) { // зацикливаем песню
a = 1;
}
}
MusicalAlgoFun
Это простая песня для Arduino, созданная Alexandre Quessy в 2006 году.
Код
/*
- Au Clair de la Lune на Arduino и динамике ПК.
- Расчёт тонов производится по математической формуле:
-
- timeUpDown = 1/(2 * toneFrequency) = period / 2
- )c( Copyleft AlexandreQuessy 2006 http://alexandre.quessy.net
- Вдохновлено http://www.arduino.cc/en/Tutorial/PlayMelody от D. Cuartielles
*/
int ledPin = 9;
int speakerOut = 8;
/* 2 октавы :: полутона. 0 = до, 2 = ре, и т.д. */
/* MIDI-ноты от 48 до 71. Индексы здесь от 0 до 23. */
int timeUpDown[] = {3822, 3606, 3404, 3214, 3032, 2862,
2702, 2550, 2406, 2272, 2144, 2024,
1911, 1803, 1702, 1607, 1516, 1431,
1351, 1275, 1203, 1136, 1072, 1012};
/* наша песня. Каждое число — это (MIDI-нота - 48) на доле такта. */
byte song[] = {12,12,12,14, 16,16,14,14, 12,16,14,14, 12,12,12,12,
14,14,14,14, 9,9,9,9, 14,12,11,9, 7,7,7,7};
// до до до ре ми ре до ми ре ре до...
byte beat = 0;
int MAXCOUNT = 32;
float TEMPO_SECONDS = 0.2;
byte statePin = LOW;
byte period = 0;
int i, timeUp;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(speakerOut, OUTPUT);
}
void loop() {
digitalWrite(speakerOut, LOW);
for (beat = 0; beat < MAXCOUNT; beat++) {
statePin = !statePin;
digitalWrite(ledPin, statePin);
timeUp = timeUpDown[song[beat]];
period = ((1000000 / timeUp) / 2) * TEMPO_SECONDS;
for (i = 0; i < period; i++) {
digitalWrite(speakerOut, HIGH);
delayMicroseconds(timeUp);
digitalWrite(speakerOut, LOW);
delayMicroseconds(timeUp);
}
/* Раскомментируйте, если хотите, чтобы ноты были раздельными */
/* delay(50); */
}
digitalWrite(speakerOut, LOW);
delay(1000);
}
Улучшенная версия
/*
- Прямоугольная мелодия на Arduino и динамике ПК.
- Расчёт тонов производится по математической формуле:
-
- timeUpDown = 1/(2 * toneFrequency) = period / 2
- )c( Copyleft 2009 Daniel Gimpelevich
- Вдохновлено https://playground.arduino.cc/Code/MusicalAlgoFun от AlexandreQuessy
*/
const byte ledPin = 13;
const byte speakerOut = 11; /* Так стандартный старый PC speaker connector хорошо садится на пины. */
/* 10.5 октав :: полутона. 60 = до, 62 = ре и т.д. */
/* MIDI-ноты от 0, или C(-1), до 127, или G9. */
/* Паузы — это нота с номером -1. */
unsigned int timeUpDown[128];
/* наша песня. Каждая пара чисел — MIDI-нота и символ длительности. */
/* Символы: 1 — целая, -1 — целая с точкой, 2 — половинная, */
/* -2 — половинная с точкой, 4 — четверть, -4 — четверть с точкой, и т.д. */
const byte BPM = 120;
const char song[] = {
64,4,64,4,65,4,67,4, 67,4,65,4,64,4,62,4,
60,4,60,4,62,4,64,4, 64,-4,62,8,62,2,
64,4,64,4,65,4,67,4, 67,4,65,4,64,4,62,4,
60,4,60,4,62,4,64,4, 62,-4,60,8,60,2,
62,4,62,4,64,4,60,4, 62,4,64,8,65,8,64,4,60,4,
62,4,64,8,65,8,64,4,62,4, 60,4,62,4,55,2,
64,4,64,4,65,4,67,4, 67,4,65,4,64,4,62,4,
60,4,60,4,62,4,64,4, 62,-4,60,8,60,2};
int period, i;
unsigned int timeUp, beat;
byte statePin = LOW;
const float TEMPO_SECONDS = 60.0 / BPM;
const unsigned int MAXCOUNT = sizeof(song) / 2;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(speakerOut, OUTPUT);
for (i = 128; i--;)
timeUpDown[i] = 1000000 / (pow(2, (i - 69) / 12.0) * 880);
}
void loop() {
digitalWrite(speakerOut, LOW);
for (beat = 0; beat < MAXCOUNT; beat++) {
statePin = !statePin;
digitalWrite(ledPin, statePin);
i = song[beat * 2];
timeUp = (i < 0) ? 0 : timeUpDown[i];
period = (timeUp ? (1000000 / timeUp) / 2 : 250) * TEMPO_SECONDS
* 4 / song[beat * 2 + 1];
if (period < 0)
period = period * -3 / 2;
for (i = 0; i < period; i++) {
digitalWrite(speakerOut, timeUp ? HIGH : LOW);
delayMicroseconds(timeUp ? timeUp : 2000);
digitalWrite(speakerOut, LOW);
delayMicroseconds(timeUp ? timeUp : 2000);
}
delay(50);
}
digitalWrite(speakerOut, LOW);
delay(1000);
}
Примечание
Лицензия: документация Arduino распространяется под лицензией Creative Commons Attribution-Share Alike 4.0.