PLAY("CDEFGABAGFEDC"), или плеер MML на С

Формат MML (Music Macro Language) впервые появился в интерпретаторах MS Basic (оператор PLAY: Microsoft music command language) и служил для компактной записи музыкальных фрагментов с помощью ASCII символов.


Формат строки — последовательность команд:
  • Tn — темп (n=32..255). n=120 (по умолчанию) соответствует длительности целой ноты 2 сек;
  • Ln — длительность нот по умолчанию. 1 — целая, 2 — 1/2, ..., 64 — 1/64;
  • On — выбор текущей октавы (n=0..8, 0-суб-контр октава, 8 — 5я октава);
  • <,> — сдвиг текущей октавы вниз/вверх;
  • Vn — громкость (диапазон n зависит от реализации, обычно 1..15);
  • C,D,E,F,G,A,B — ноты До… Си, после ноты может указываться ее длительность 1..64 и модификаторы:
    • # или + — повышение на полтона (диез, sharp);
    • - — понижение на полтона (бемоль, flat);
    • . — увеличение длительности на пол-интервала (dot);
    • & — слияние соседних нот (tie);
  • Rn или Pn — пауза;


Частоты нот рассчитываются относительно Ля 2й октавы (440Гц) по формуле F = 440 * 2 ^ ((n — 49)/12), n=1..88 для клавиш пианино;

Генератор частот 9ти октав (от суб-контр до 5й) на Javascript:
var s = [];
for (var i=-48-9; i<=48+2; i++) {
    s.push(Math.floor(4400 * Math.pow(2, i/12) + 0.5) / 10.0);
    s.push(",");
    if (((i-2) % 12) == 0) s.push("\n");
}
alert( s.join("") );

Проверить можно тут: jsfiddle.net/.

Библиотека написана на С, и позволяет в реальном времени парсить/воспроизводить несколько каналов MML.

.h: codetidy.com/4079/
.c: codetidy.com/4080/

Пример использования и генератор Raw (44100/Signed 16-bit) / Ogg: docs.google.com/open?id=0B4EdBOyGBHKnWkF1YnRQS2hNZkE

Настройка и применение:
#define MML_CHANNELS       1 //количество каналов
#define MML_BUF_SIZE       0 //размер приемного буфера каналов (0 если не используется)


Запуск воспроизведения строки:
setup_mml(); //настройка параметров каналов по умолчанию
mml_play_s(
  0, //номер канала
  //строка для воспроизведения
  "d+16 d16 d8 d+16 d16 d8 d+16 d16 d8 b-4b-16 a16 g8 g16 f16 e-8 e-16 d16 c8 c4d16 c16 c8 d16 c16 c8 d16 c16 c8 a4a16 g16 f+8 f+16 e-16 d8 d16 c16 < b-8 b-4 >b-16 a16 a8 > c8 < f+8 a8 g8 d4b-16 a16 a8 > c8 < f+8 a8 g8 b-8 a16 g16 f16 e-16d2 c+2 d4 c+4 d8 r8 r8",
  0, //=0 - не копировать строку во внутренний буфер
  MML_FMT_MODERN //формат - современный
);


Для обработки каналов нужно вызывать функцию mml_update(); с частотой 1кГц.

Для воспроизведения звука нужно определить callback-функции задания частоты и громкости на соответствующем канале (пример для опубликованного недавно DDS-генератора):

//задать частоту <strong>freq</strong> (с точностью 0.1Гц) для канала <strong>с</strong>
void mml_set_freq(U8 c, U16 freq) {
  audio_gen_set_tone(0, c, AUDIO_GEN_SINE, freq, 1);
}

//задать громкость <strong>vol</strong> для канала <strong>с</strong>
void mml_set_vol(U8 c, U8 vol) {
  //смасштабировать в диапазон 0..128
  audio_gen_set_volume(0, c, (vol - MML_VOL_MIN)*(AUDIO_GEN_VOLUME_MAX / (MML_VOL_MAX - MML_VOL_MIN)));
}


Пример настройки таймера 3 и вывода PB0 STM32 на выдачу меандра заданной частоты:
TIM_TimeBaseInitTypeDef  snd_TIM_TimeBaseStructure;
TIM_OCInitTypeDef        snd_TIM_OCInitStructure;
GPIO_InitTypeDef         snd_GPIO_InitStructure;

void setup_sound(void) {
        // initialize GPIO
        GPIO_StructInit(&snd_GPIO_InitStructure);
        snd_GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0;
        snd_GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_OD;
        snd_GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
        GPIO_Init(GPIOB, &snd_GPIO_InitStructure);

        //enable TIM3 clock
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
}

void mml_set_freq(U8 c, U16 freq) {
        if (freq == 0) {
                TIM_Cmd(TIM3, DISABLE);
                snd_GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_OD;
                GPIO_Init(GPIOB, &snd_GPIO_InitStructure);
        } else {
                snd_GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
                GPIO_Init(GPIOB, &snd_GPIO_InitStructure);
                /* Time Base configuration */
                snd_TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
                snd_TIM_TimeBaseStructure.TIM_Prescaler = 0;
                snd_TIM_TimeBaseStructure.TIM_ClockDivision = 0;
                snd_TIM_TimeBaseStructure.TIM_Period = ((F_CPU * 10) / (freq << 1)) - 1;// TimerPeriod;
                snd_TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
                TIM_TimeBaseInit(TIM3, &snd_TIM_TimeBaseStructure);

                snd_TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
                snd_TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
                snd_TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
                TIM_OC3Init(TIM3, &snd_TIM_OCInitStructure);

                TIM_Cmd(TIM3, ENABLE);
        }
}
  • +7
  • 29 ноября 2012, 02:34
  • reptile
  • 1
Файлы в топике: ul_audio_mml.zip

Комментарии (4)

RSS свернуть / развернуть
Прикольно) Плохо только что строки не во флеше.
0
А, понял. В этих ваших СТМ будут во флеше.
0
во флеше. С avr-gcc можно выкрутиться — обьявить как PSTR(), в функции чтения/декодирования символов использовать pgm_read_byte()
0
Я уже и забыл про play в basic'е.
Спасибо за библиотечку.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.