Музыка на 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 |