|
Музыка на Arduino
|
|
Программа, исполняющая на Arduino музыку в формате PLAY Qbasic Когда-то давным-давно, в 80х годах XX века, первые 8битные компьютеры получили возможность играть музыку. Программировалась музыка на интерпретаторе Бэйсик. Тогда она казалась очень оригинальной и необычной. Сейчас, когда компьютеры, телефоны, часы, игрушки и даже поздравительные открытки забиты MP3 музыкой и полифоническими MIDI, есть ли интерес покупать микроконтроллер, который значительно дороже, чем готовая китайская игрушка, и самому пытаться запрограммировать мелодию ? Тем не менее, программирование музыки при изучении работы микроконтроллера это такое же must be forever, как и незабвенная мигалка светодиодом. Примеры, которые я нашел в Сети либо слишком примитивные, либо слишком сложные и нерабочие. В этой статье описывается программа, которая использует тот же формат нот, что и оператор PLAY QBasic. Таким образом, можно использовать старинные архивы BAS файлов или набирать и отлаживать музыку в Qbasic, а затем переносить готовые строки в программу (скетч) Arduino Есть одна небольшая проблема. QBasic нормально работает под Windows-98 и Windows-XP, но для более поздних версий и для XP лучше запускать его с помощью эмулятора DOSBox
Оператор QBasic PLAY имеет такой вид: PLAY "T120o3L8CDEFGGL16GFL8EFFL16FEDL8CDG4"
что означает
PLAY строка_команд$
строка_команд$ Выражение, состоящее из одной или нескольких
следующих команд PLAY:
(ПРЕФИКСЫ)
Команды октавы и тона:
Oоктава Задает текущую октаву (0 - 6).
< Переход на октаву выше.
> Переход на октаву ниже.
Команды длительности и темпа:
Lразмер Задает длительность каждой ноты по умолчанию (1 - 64).
L1 - целая нота,L2 - 1/2 ноты и т.д.
ML Вид исполнения legato.
(Ноты звучат в полную длительность)
MN Вид исполнения normal.
(Нота звучит 7/8 длительности, затем 1/8 - пауза)
MS Вид исполнения staccato.
(Нота звучит 3/4 длительности, затем 1/4 - пауза)
Tтемп Задает темп исполнения в четвертях в минуту (32-255).
По умолчанию T120.
(НОТА)
A - G Играет определенную ноту текущей октавы.
"CDEFGAB" - до,ре,ми,фа,соль,ля,си
Pпауза Задает паузу (1 - 64).
P1 - пауза в целую ноту,P2 - пауза в 1/2 ноты и т.д.
Nнота НЕ ПОДДЕРЖИВАЕТСЯ моей программой.
(СУФФИКСЫ)
Команды изменения ноты:
# или + Диез. (Нота на полтона выше).
- Бемоль. (Нота на полтона ниже).
. Длительность 3/2 от размера ноты.
(то же самое, что нота с точкой)
Также смотрите PLAY Statement Может показаться, что для выполнения такого множества команд требуется очень сложная программа. На самом деле программа (исходник .ino) получилась лишь чуть длиннее 3 килобайт. Определения и глобальные переменные в начале программы: //PIN к которому подключена пищалка #define SPEAKER 8 //PIN сведодиода на плате #define LED 13 // То же, что и ==, чтобы не мучаться из-за опечаток #define eq == //символ a - цифра ? #define isdigit(a) (((a)>='0') && ((a)<='9')) //Символ a - строчная латинская буква ? #define islower(a) (((a)>='a') && ((a)<='z')) char octave=0; char lendef=1; char flagdiez=0; int tempo=120; char playmode='N'; Так как микроконтроллер у нас 8-битный, используются там, где это возможно, переменные типа char и unsigned char (для уменьшения бинарного кода программы), но за этим однобайтовым типом надо внимательно следить, чтобы избежать переполнений и неверных преобразований. Мы подходим к музыке. Музыка хранится в массиве notes[]. Для ясности программы музыку лучше хранить в .h файлах. Вы просто берёте строки PLAY из программы .BAS Qbasic и копируете их в .h файл. Не забудьте, что музыка в этом примере должна быть только одна, все остальные #include нужно закомментировать. //Выберите массив музыки notes[]. //Закомментируйте все прочие строки include. //char notes[]="T120MSo3L8CDEFGGL16GFL8EFFL16FEDL8CDG4"; //#include "music.h" //#include "jingle.h" //#include "merry.h" #include "silent.h" //#include "bach1.h" //#include "inventio.h" Для вывода звука используются две небольшие функции playTone и playNote:
// собственно аппаратный вывод звука
// tone - полупериод импульса (микросекунды), а не частота звука
// duration - длительность звука (миллисекунды)
void playTone(int tone,int duration)
{digitalWrite(LED,HIGH)
for (unsigned long i=0;i < duration*1000L;i+=(tone*2))
{digitalWrite(SPEAKER,HIGH);
delayMicroseconds(tone);
digitalWrite(SPEAKER,LOW);
delayMicroseconds(tone);
}
digitalWrite(LED,LOW)
}
//Преобразование кода ноты в тон
void playNote(char note,int duration)
{char names[]="CDEFGAB";
int tones[]={
//note tone
1915 , 1700 , 1519 , 1432 , 1275 , 1136 , 1014 ,
//n+
1806 , 1603 , 1433 , 1350 , 1202 , 1071 , 956 ,
//n-
2029 , 1801 , 1610 , 1517 , 1351 , 1204 , 1074};
for (char i=0;i < 7;i++)
{if (names[i]==note)
playTone(tones[i+flagdiez*7] >> octave,duration);}
}
Массив tones[] содержит длительности полупериодов импульсов в микросекундах (не частоты) нот, Первая строка массива соответствует обычным 7 нотам (до,ре,ми,фа,соль,ля,си), вторая строка - это те же тона, что и в первой, но деленные на 1.06 (корень 12й степени из 2), третья строка - те же величины, что и в первой, но умноженные на 1.06. Таким образом, вторая строка соответствует диезам, а третья - бемолям. Наконец, мы подходим к интерпретатору музыкальных команд PLAY. Переменная i продвигает указатель на символы массива notes[i], при этом декодируются команды и ноты. Обратите внимание на подсвеченную строку char notecode=notes[i];i++; Она выполняется в цикле безусловно. Поэтому, если даже в логику обработки префиксов-суффиксов вкрадётся ошибка или что-то напутано в буквах notes[], программа не зависнет, а худшем случае просто не проиграет какие-то ноты, но цикл for безусловно завершится.
void setup()
{//декларация переменных
...
pinMode(LED,OUTPUT);
pinMode(SPEAKER,OUTPUT);
tempo=120;int note1=(int)(240000L/tempo);
//цикл чтения строки PLAY
for (int i=0;notes[i] != 0;)
{
retry:
//обработать команды-префиксы
...
//получить код ноты
char notecode=notes[i];i++;
//обработать команды-суффиксы
...
//играть ноту
if (islower(notecode)) notecode-=32;
if (notecode=='P') delay(notelen); else
{if (playmode eq 'L') {playNote(notecode,notelen);}
else if (playmode eq 'S') {playNote(notecode,notelen*3/4);
delay(notelen/4);}
else {playNote(notecode,notelen*7/8);
delay(notelen/8);}
}
} // конец цикла for
} //конец функции setup
2 апреля 2020 Мелкие ошибки исправлены Загрузить QBasic и коллекцию музыки (422 KB) 29 июня 2020 Добавлена музыка ZX Spectrum, Apple II, IBM PC XT Продолжение. Более простая 1 байтная музыка Arduino |