Музыка в Arduino может быть фоновой. У меня появилась идея написать новую музыкальную программу или библиотеку для музыкальной программы.
Идеи появляются не на пустом месте. Первый музыкальный автомат я собрал 30 лет назад, в нём был генератор, подстроечные резисторы и дешифратор. Недостатки этой схемы побудили меня разработать свою схему и тогда я разработал цифровой таймер-счётчик на D-триггерах, управляемый по программе из прожигаемого ПЗУ. Последние 8 лет я программирую микроконтроллеры. Первый мой музыкальный автомат был разработан на Ассемблере для м-к ATtiny13. Тогда, почему-то, захотелось написать генератор нот без использования таймеров-счётчиков м-к. И это получилось! Писал для Arduino с использованием таймеров-счётчиков м-к. Это просто. Недавно я закончил серьёзный проект на ATmega328. В этом проекте задачи добавлялись несколько раз так, что в конце, чтобы всё заработало, пришлось оптимизировать программу по объёму, и главное по использованию памяти, переписать насколько библиотек и разработать 2 свои. Теперь я более оптимистично смотрю на ресурсы Ардуино.
Сегодня появилась идея к которой не приступишь в лоб. Нужна концепция. Вот об этом и буду писать.
- Музыка в Arduino может быть фоновой, она может звучать в то время как выполняется основная программа. Должна быть многозадачность. Значит надо будет использовать прерывания.
- Одного байта достаточно чтобы хранить код ноты и её длительность. Отведём 3 бита под длительности 1, 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, "Конец". 5 бит под коды нот (31 нота + пауза). Номер паузы 0.
- Так как массив нот будет храниться в шифрованном виде, готовить этот массив надо будет во вспомогательной программе, например в Open Office.
- Так как оперативной памяти в м-к мало, коды нот будем хранить в Flash памяти программ м-к.
- Так как музыкальное произведение чаще всего состоит из повторяющихся фрагментов, в программе должны быть предусмотрены вызовы этих фрагментов.
- В программе должны быть предусмотрены команды управления воспроизведением (вперёд, назад, стоп, по номеру, повтор, повтор всего).
Кажется, с концепцией определились.
Возможная техническая реализация.
Длительность ноты.
Пусть WDT (Watchdog) отвечает за длительность ноты. Распишем коды длительностей:
Длительность | Сек | Команда | Код команды | Код 3 бита | Инверсный код |
---|---|---|---|---|---|
1/64 | 0.015625 | WDTCSR = (1 << WDIE ); | 0b01000000 | 000 | 7 |
1/32 | 0 .03125 | WDTCSR = (1 << WDP0) | (1 << WDIE ); | 0b01000001 | 001 | 6 |
1/16 | 0.0625 | WDTCSR = (1 << WDP1) | (1 << WDIE ); | 0b01000010 | 010 | 5 |
1/8 | 0.125 | WDTCSR = (1 << WDP0) | (1 << WDP1) | (1 << WDIE ); | 0b01000011 | 011 | 4 |
1/4 | 0.25 | WDTCSR = (1 << WDP2) | (1 << WDIE ); | 0b01000100 | 100 | 3 |
1/2 | 0.5 | WDTCSR = (1 << WDP0) | (1 << WDP2) | (1 << WDIE ); | 0b01000101 | 101 | 2 |
1 | 1 | WDTCSR = (1 << WDP1) | (1 << WDP2) | (1 << WDIE ); | 0b01000110 | 110 | 1 |
Конец | 111 | 0 |
Табл. 1. Коды длительностей нот.
Лист. 1. Биты регистра WDTCSR микроконтроллера ATmega328
Частота ноты.
Пусть 16-ти битный таймер отвечает за частоту ноты (высоту тона).
В м-к ATmega328 один 16-ти битный таймер-счётчик - это "Timer/Counter1". Timer/Counter1 (ТС1) может работать в режиме генератора частоты.
Таймер-счётчик ТС1 микроконтроллера, работая в режиме СТС (сравнение и сброс таймера при совпадении, один из четырёх режимов), ведёт подсчёт тактовых импульсов до совпадения с числом, заданным программистом в любом из регистров совпадения(OCR1A или OCR1B). В случае совпадения, сигнал, на выходе счётчика меняется на противоположный и счёт начинается сначала. Таймер/счётчик TC1 имеет выходы на порты микроконтроллера PB1 и PB2 (9 и 10 pin Arduino или 15 и 16 pin м-к ATmega328-PU). Таймер-счётчик приводятся в действие от системного тактового генератора, работающего на частоте 16 МГц. Если записать в регистр совпадения 0, Таймер-счётчик делит частоту на входе на 2. Если мы хотим получить с помощью таймера-счётчика, заданную частоту, мы должны записать в 16-ти битный регистр совпадения (OCR1A или OCR1B) коэффициент деления, который можно вычислить по формуле, Формуле. 1.
Формула. 1. Коэффициент деления ТС1,
В datashit на м-к ATmega328 в формуле 1 записано (1+k), но это в нашей задаче не принципиально.
Частота любой музыкальной ноты F может быть вычислена по формуле Ф.2, где N порядковый номер ноты.
Формула. 2. Частота любой музыкальной ноты.
Нота ЛЯ первой октавы имеет порядковый номер 48 и её частота, вычисленная по этой формуле равна 440 Гц. В октаве 7 нот (ДО, РЕ, МИ, ФА, СОЛЬ, ЛЯ, СИ), однако, они распределены по частоте неравномерно. На фортепианной клавиатуре вы видели что есть белые и чёрные клавиши. Белые клавиши мы уже перечислили, это основные ноты. Чёрные клавиши дополняют белые так, что все ноты в октаве, а их теперь 12, распределяются по частоте равномерно, следуя математическому закону по Формуле.2.
Перечислим все 12 нот одной октавы: ДО ДО# РЕ РЕ# МИ ФА ФА# СОЛЬ СОЛЬ# ЛЯ ЛЯ# СИ Знак # читается как диез и ставится на нотной линейке перед нотой или в начале строки. Отменяется знаком бекар ????. Теперь должно быть понятно, что нота ЛЯ второй октавы имеет номер 60 и её частота 880 Гц. Очевидно, что частота двух одноименных нот из соседних октав отличается в ровно 2 раза. Эти ноты воспринимаются на слух, как очень похожие, только одна выше другой. В программе на языке Си для Ардуино, чтобы извлечь желаемую ноту, мы должны будем записать (присвоить) в 16-ти битный регистр совпадения (OCR1A или OCR1B) коэффициент деления вычисленный для этой ноты.
Для автоматизации расчёта коэффициентов деления для всех нот мы воспользовались электронной таблицей Google документы (Можно использовать LibreOffice Calc). В результате получилась таблица табл. 2.
A | B | C | D | E | F | |
---|---|---|---|---|---|---|
1 | № | N | Октава | Нота | F Gz | К дел. |
2 | 1 | 27 | 0 | До | 131 | 61156 |
3 | 2 | 28 | 0 | До# | 139 | 57724 |
4 | 3 | 29 | 0 | Ре | 147 | 54484 |
5 | 4 | 30 | 0 | Ре# | 156 | 51426 |
6 | 5 | 31 | 0 | Ми | 165 | 48540 |
7 | 6 | 32 | 0 | Фа | 175 | 45815 |
8 | 7 | 33 | 0 | Фа# | 185 | 43244 |
9 | 8 | 34 | 0 | Соль | 196 | 40817 |
10 | 9 | 35 | 0 | Соль# | 208 | 38526 |
11 | 10 | 36 | 0 | Ля | 220 | 36364 |
12 | 11 | 37 | 0 | Ля# | 233 | 34323 |
13 | 12 | 38 | 0 | Си | 247 | 32396 |
14 | 13 | 39 | 1 | До | 262 | 30578 |
15 | 14 | 40 | 1 | До# | 277 | 28862 |
16 | 15 | 41 | 1 | Ре | 294 | 27242 |
17 | 16 | 42 | 1 | Ре# | 311 | 25713 |
18 | 17 | 43 | 1 | Ми | 330 | 24270 |
19 | 18 | 44 | 1 | Фа | 349 | 22908 |
20 | 19 | 45 | 1 | Фа# | 370 | 21622 |
21 | 20 | 46 | 1 | Соль | 392 | 20408 |
22 | 21 | 47 | 1 | Соль# | 415 | 19263 |
23 | 22 | 48 | 1 | Ля | 440 | 18182 |
24 | 23 | 49 | 1 | Ля# | 466 | 17161 |
25 | 24 | 50 | 1 | Си | 494 | 16198 |
26 | 25 | 51 | 2 | До | 523 | 15289 |
27 | 26 | 52 | 2 | До# | 554 | 14431 |
28 | 27 | 53 | 2 | Ре | 587 | 13621 |
29 | 28 | 54 | 2 | Ре# | 622 | 12856 |
30 | 29 | 55 | 2 | Ми | 659 | 12135 |
31 | 30 | 56 | 2 | Фа | 698 | 11454 |
32 | 31 | 57 | 2 | Фа# | 740 | 10811 |
33 | 32 | 58 | 2 | Соль | 784 | 10204 |
34 | 33 | 59 | 2 | Соль# | 831 | 9631 |
35 | 34 | 60 | 2 | Ля | 880 | 9091 |
36 | 35 | 61 | 2 | Ля# | 932 | 8581 |
37 | 36 | 62 | 2 | Си | 988 | 8099 |
38 | 37 | 63 | 3 | До | 1047 | 7645 |
39 | 38 | 64 | 3 | До# | 1109 | 7215 |
40 | 39 | 65 | 3 | Ре | 1175 | 6810 |
41 | 40 | 66 | 3 | Ре# | 1245 | 6428 |
42 | 41 | 67 | 3 | Ми | 1319 | 6067 |
43 | 42 | 68 | 3 | Фа | 1397 | 5727 |
44 | 43 | 69 | 3 | Фа# | 1480 | 5405 |
45 | 44 | 70 | 3 | Соль | 1568 | 5102 |
46 | 45 | 71 | 3 | Соль# | 1661 | 4816 |
47 | 46 | 72 | 3 | Ля | 1760 | 4545 |
48 | 47 | 73 | 3 | Ля# | 1865 | 4290 |
49 | 48 | 74 | 3 | Си | 1976 | 4050 |
Табл. 2. Коэффициенты деления для всех нот.
В табл. 2 в ячейку E2 мы вставили формулу =ОКРУГЛТ(27,5*2^(B2/12); 1) и растянули её на всю колонку E. В ячейку F2 мы вставили формулу =ОКРУГЛТ(8000000/(27,5*2^(B2/12)); 1) и растянули её на всю колонку F.
На основании табл. 2 мы можем составить массив коэффициентов деления {61156, 57724, 54484, 51426, 48540, 45815, 43244, 40817, 38526, 36364, 34323, 32396, 30578, 28862, 27242, 25713, 24270, 22908, 21622, 20408, 19263, 18182, 17161, 16198, 15289, 14431, 13621, 12856, 12135, 11454, 10811, 10204, 9631, 9091, 8581, 8099, 7645, 7215, 6810, 6428, 6067, 5727, 5405, 5102, 4816, 4545, 4290, 4050}.
С таким же успехом, можно использовать 12 коэффициентов деления, но тогда прерывания будут выполнятся дольше, за счёт лишних операций по вычислению коэффициентов деления.
Замечание, так как мы планируем использовать в любом музыкальном произведении только 31 ноту (2,5 октавы), то у нас будет необходимость транспонировать, повышать или понижать тональность произведения, что мы легко сможем сделать сдвигая цифры в колонке A вверх или вниз.
Управление таймером-счётчиком TC1.
Лист. 2. Программное управление таймером счётчиком TC1.
Рис. 1. Осциллограмма снятая на 9 ножке платы Arduino Nano.
Принципиальная электрическая схема.
В программе лист. 2 мы организовали подключение выхода таймера-счётчика TC1 на порт микроконтроллера PB1, который, в свою очередь, подключён к pin 9 платы Arduino NANO или UNO.
Рис. 2. Принципиальная электрическая схема музыкального автомата.
На принципиальной электрической схеме рис. 2 однокаскадный импульсный усилитель на транзисторе 2N2222 своим входом подключён к pin 9 платы Arduino, а нагрузкой этого усилителя является высокоомный динамик. У транзистора 2N2222 типичный коэффициент усиления по току в схеме с общим эмиттером равен 300. В ключевом режиме работы ток коллектора транзистора примерно равен 5/30=0,16 Ампер. Где 5 Вольт напряжение питания схемы, а 30 Ом сопротивление динамика. Сопротивлением открытого транзистора можно пренебречь. Для полного открывания транзистора ток базы этого транзистора должен быть не менее 0,16/300=0,00055 Ампер. Где 0,16 Ампер ток коллектора транзистора, а 300 коэффициент усиления по току. Ограничивающий ток базы резистор должен быть не более 4,5/0,00055=8100 Ом. Где 4,5 Вольт напряжение единичного импульса на выходе микроконтроллера.
Например, Вы не нашли высокоомный динамик, типичное сопротивление маломощных динамиков 8 Ом, и такой у Вас есть. Задача: рассчитать сопротивление резистора в цепи базы транзистора. 5/8=0,625А. 0,625/300=0,002А. 4,5/0,02=2160 Ом.
Нотная грамотность.
Хороший программист, создавая свой программный продукт для целей автоматизации какого либо рода деятельности человека в производстве, науке или искусстве должен разбираться в этой области на уровне специалиста. Создавая программу музыкального автомата, программист должен, как минимум, изучить музыкальную нотацию. Не плохо было бы подтянуть себя по сольфеджио.
Рис. . Основы музыкальной нотации.
Лист. 3. Ноты Рождественской песенки Jingle Bells.
Закодируем эти ноты в соответствии с таблицами 1 и 2, для начала, каждой ноте будет соответствовать 2 цифры № ноты и её длительность. Коды сразу пишем в таблицу Google документы или LibreOffice Calc.
A | B | C | |
---|---|---|---|
1 | № ноты | Длительность | Код |
3 | 17 | 3 | 139 |
5 | 17 | 3 | 139 |
7 | 17 | 2 | 138 |
9 | 20 | 3 | 163 |
11 | 13 | 4 | 108 |
13 | 17 | 1 | 137 |
14 | 0 | 0 | 0 |
Табл. 3. Коды нот первых четырёх тактов песенки Jingle Bells.
В ячейку C3 табл. 3 мы поместили формулу =A2*8+B2 и растянули её на все следующие ячейки в колонке C.
В результате, у нас есть массив из кодов нот первых четырёх тактов песенки Jingle Bells и их длительностей:
{139, 139, 138, 139, 139, 138, 139, 163, 107, 108, 124, 137, 0}
Управление прерываниями Watchdog таймера (WDT).
Напишем программу инициализации прерываний WDT с примером использования этих прерываний.
Лист. 4. Программа исполняющая первые четыре такта песенки Jingle Bells.
В результате разработки программы лист. 4, был выработан формат массива кодов нот музыкального фрагмента и массива коэффициентов деления для таймера-счётчика TC1. Массив кодов нот музыкального фрагмента должен заканчиваться "0". Массив коэффициентов деления для таймера-счётчика TC1 должен начинаться с "0".
Размещение массива данных в Flash-памяти м-к.
Программа лист. 4 использует память м-к следующим образом:
Если хранить коды нот в динамической памяти, то мы сможем записать в м-к всего 1-2 мелодии. Flash память программ в м-к может хранить в десятки раз больше мелодий, но доступ к данным в Flash памяти специфичен. Перепишем программу лист. 4 так, что-бы массивы данных размещались в Flash памяти.
Лист. 5. Программа исполняющая песенку Jingle Bells с использованием Flash памяти для хранения констант.
Программа лист. 5 использует меньше динамической памяти чем программа лист. 4.
Управление воспроизведением музыкальных фрагментов.
Массив кодов нот песенки Jingle Bells, не содержит повторяющихся частей. Но, практически, любая песня содержит куплет и припев и, как правило, ноты всех куплетов не отличаются друг от друга, а у припева, и подавно.
Но, для дальнейшего программирования, мы возьмём ещё боле сложное и интересное музыкальное произведение чем песенка.
Лист. 6. Ноты фортепианной пьесы Людвига ван Бетховена «К Элизе» (нем. Für Elise).
Обратите внимание на лист. 6, мы нашли в произведении Людвига ван Бетховена «К Элизе» один фрагмент мелодии повторяющийся 3 раза (окрашено в розовый цвет) и один такт повторяющийся 2 раза. Но это ещё не все повторения в этом произведении. Первая половина произведения «К Элизе» повторяется 2 раза. Причём, сначала звучит 9 тактов (в девятом такте нота ля длительностью 1/4). Обратите внимание, девятый и первый такт не полные. После звучания ноты ля длительностью 1/4 из девятого такта, начинается воспроизведение с первого такта в котором 2 ноты длительностью 1/16 каждая. В результате девятый такт вместе с первым имеют длительность 3/8.
Потом звучит 2, 3, 4, 5, 6, 7, 8 такты, а далее 10, 11 .... и до конца произведения.
Программист все повторения может просто запрограммировать. Но, для начала нам надо закодировать все музыкальные фрагменты произведения Людвига ван Бетховена «К Элизе» без повторений.
A | B | C | D | |
---|---|---|---|---|
1 | № ноты | Длительность | Код | Адрес фрагмента |
2 | 0 | 2 | 2 | 0 |
3 | 29 | 4 | 236 | 1 |
4 | 28 | 4 | 228 | 2 |
5 | 0 | 0 | 0 | 3 |
6 | 29 | 4 | 236 | 4 |
7 | 28 | 4 | 228 | 5 |
8 | 29 | 4 | 236 | 6 |
9 | 24 | 4 | 196 | 7 |
10 | 27 | 4 | 220 | 8 |
11 | 25 | 4 | 204 | 9 |
12 | 22 | 3 | 179 | 10 |
13 | 0 | 4 | 4 | 11 |
14 | 13 | 4 | 108 | 12 |
15 | 17 | 4 | 140 | 13 |
16 | 22 | 4 | 180 | 14 |
17 | 0 | 0 | 0 | 15 |
18 | 24 | 3 | 195 | 16 |
19 | 0 | 4 | 4 | 17 |
20 | 17 | 4 | 140 | 18 |
21 | 21 | 4 | 172 | 19 |
22 | 24 | 4 | 196 | 20 |
23 | 25 | 3 | 203 | 21 |
24 | 17 | 4 | 140 | 22 |
25 | 29 | 4 | 236 | 23 |
26 | 28 | 4 | 228 | 24 |
27 | 0 | 0 | 0 | 25 |
28 | 24 | 3 | 195 | 26 |
29 | 0 | 4 | 4 | 27 |
30 | 17 | 4 | 140 | 28 |
31 | 25 | 4 | 204 | 29 |
32 | 24 | 4 | 196 | 30 |
33 | 0 | 0 | 0 | 31 |
34 | 22 | 2 | 178 | 32 |
35 | 0 | 0 | 0 | 33 |
36 | 22 | 3 | 179 | 34 |
37 | 0 | 4 | 4 | 35 |
38 | 24 | 4 | 196 | 36 |
39 | 25 | 4 | 204 | 37 |
40 | 27 | 4 | 220 | 38 |
41 | 29 | 3 | 235 | 39 |
42 | 0 | 4 | 4 | 40 |
43 | 20 | 4 | 164 | 41 |
44 | 30 | 4 | 244 | 42 |
45 | 29 | 4 | 236 | 43 |
46 | 27 | 3 | 219 | 44 |
47 | 0 | 4 | 4 | 45 |
48 | 18 | 4 | 148 | 46 |
49 | 29 | 4 | 236 | 47 |
50 | 27 | 4 | 220 | 48 |
51 | 25 | 3 | 203 | 49 |
52 | 0 | 4 | 4 | 50 |
53 | 17 | 4 | 140 | 51 |
54 | 27 | 4 | 220 | 52 |
55 | 25 | 4 | 204 | 53 |
56 | 24 | 3 | 195 | 54 |
57 | 0 | 4 | 4 | 55 |
58 | 17 | 4 | 140 | 56 |
59 | 29 | 4 | 236 | 57 |
60 | 28 | 4 | 228 | 58 |
61 | 0 | 0 | 0 | 59 |
62 | 22 | 2 | 178 | 60 |
63 | 0 | 0 | 0 | 61 |
Табл. 4. Коды нот произведения Людвига ван Бетховена «К Элизе» без повторений.
В программе на языке C++ мы должны предусмотреть возможность воспроизводить некоторые музыкальные фрагменты по несколько раз.
Лист. 7. Программа исполняющая фортепианную пьесу Людвига ван Бетховена «К Элизе».
Массивы в музыкальной программе.
Массив order в программе лист. 7 содержит адреса первых нот музыкальных фрагментов в массиве notaCode. Фактически, в массиве order записаны индексы массива notaCode. Каждый элемент массива order - это инструкция, в каком порядке следует исполнять музыкальные фрагменты. Но, в нашей музыкальной программе так же нужны инструкции, позволяющие использовать в музыкальном произведении более 31 ноты. То есть, нужна инструкция, позволяющая тридцать одному номеру ноты поставить в соответствие различные наборы нот.
В программе лист. 7 в строке lengthNote = lengthNote / 8 мы из онобайтного кода ноты и её длительности вычисляем код ноты. Если к полученному коду ноты прибавить число (смещение), то мы получим новый набор соответствий кодов нот (номеров нот) и их частоте (высоте ноты). Чтобы можно было гибко выбирать музыкальный диапазон из 31 ноты, нам нужны смещения от 1 до 49 - 31 = 18 и мы охватим 3 октавы (можно и больше). Для 18 смещений создавать новый массив команд, или изменять структуру массива order, считаю, не рационально. Можно в массиве order считать число от 1 до 18 командой изменить высоту нот в музыкальном фрагменте, а числа от 20 до 32767 считать адресом музыкального фрагмента. Такой подход наложит не существенные ограничения на использование младших 20 адресов в массиве notaCode.
А что если использовать в массиве order значения меньше 32000 как адреса музыкального фрагмента, а числа от 32001 до 32018 как команду изменить высоту нот. Это рационально так как старшие адреса Flash памяти микроконтроллера не будут использоваться для хранения массива notaCode. Значит, так и поступим.
Лист. 8. Ноты Полонеза Огинского.
Лист. 9. Программа исполняющая первую часть Полонеза Огинского.