Разработка интерактивных систем на OpenFrameworks: Интерактивный звук

в 13:42, , рубрики: Блог компании MakeItLab, Программирование, Работа со звуком

Про настройку и визуализацию музыки с помощью openFrameworks мы недавно рассказывали. К сожалению русскоязычной информации по фреймворку OpenFrameworks достаточно мало. Для заполнения этого вакуума — начинаем серию публикаций лекций, что были прочитаны в Екатеринбурге в Институте математики и механики им. Н.Н.Красовского (УрО РАН) Денисом Переваловым.

В этой лекции будут рассказаны теоретические основы цифрового звука, и показан пример создания интерактивного приложения по генерации звука на базе захвата изображения с камеры.

Что такое цифровой звук, и звук вообще?

Разработка интерактивных систем на OpenFrameworks: Интерактивный звук - 1

Звук, в широком смысле — упругие волны, продольно распространяющиеся в среде и создающие в ней механические колебания;
в узком смысле — субъективное восприятие этих колебаний специальными органами чувств животных или человека. Как и любая волна, звук характеризуется амплитудой и частотой.

Представление звука в цифровом виде

Реальный звук захватывается микрофоном, затем подвергается аналого-цифровому преобразованию.

Оно характеризуется
разрешением по времени — частота дискретизации, [процедура — дискретизация]
разрешением по амплитуде — разрядность. [процедура — квантование]

Разработка интерактивных систем на OpenFrameworks: Интерактивный звук - 2

Частота дискретизации
8 000 Гц — телефон, достаточно для речи.
11 025 Гц — игры, сэмплы для электронной музыки.
22 050 Гц — то же, что и 11 025 Гц.
44 100 Гц — многие синтезаторы и библиотеки сэмплов. Audio CD.
48 000 Гц — студии звукозаписи, живые инструменты, вокал. DVD.
96 000 Гц — DVD-Audio (MLP 5.1).
192 000 Гц — DVD-Audio (MLP 2.0).

Разрядность
Разрядность — число бит, используемых для представления отсчетов сигнала при квантовании (в нашем случае — при квантовании амплитуды).

8 бит сэмплы электронной музыки.
12 бит студийные звуковые эффекты.
16 бит компьютерные игры, плееры, сэмплы, Audio CD.18 бит студийные звуковые эффекты
24 бит живые звуки, вокал, DVD-Audio
32 бит представление с плавающей точкой, поэтому точность не теряется для тихих звуков, поэтому используется для внутренней обработки звука.
64 бит также с плавающей точкой, обработка звука.

Представление звука в памяти
Пример
1 секунду 16-битного звука с частотой дискретизации 44100 Гц можно представить в виде вектора
X = ( x _1, x _2, ..., ..., x _44100),
где 0 <= x _i <= 2^16-1 = 65535.
Представление звуков таких способом — с помощью вектора — называется PCM (Pulse Code Modulation).
Оно является наиболее распространенным и аналогично пиксельному представлению изображений.

Фундаментальное различие звука и изображений
С изображениями очень удобно работать на уровне пикселов. В частности:
1. два изображения мы считаем одинаковыми, если значения их пикселов близки.
2. можно изменять изображения, основываясь назначениях соседних пикселов (например, операция сглаживания).

Для звука в PCM формате обе возможности неприменимы, покажем это на примере:

Разработка интерактивных систем на OpenFrameworks: Интерактивный звук - 3

Последние два звука звучат одинаково. А их функции амплитуды — существенно различные. Таким образом, человеческое ухо воспринимает спектр звука, то есть состав его частот, а не амплитудное представление звука.

Что легко/трудно делать «прямо» со звуком в формате PCM

Легко:
Изменение и перестановка отдельных отсчетов, без учета соседей
— переставлять кусочки,
— менять громкость кусочков,
— делать реверс — переворот звука от конца к началу,
— смешивать несколько звуков,
— смешивать и менять стерео-каналы,
— делать простейшую компрессию,
— добавлять простейшее эхо.

Сэмплеры, портастудии и студийные программы делают это виртуозно.

Трудно:
Учет соседних отсчетов
— сравнивать два звука на похожесть,
— подавлять низкие или высокие частоты,
— добавлять реверберацию.

Это обычно делается не прямо в PCM, а через спектральное представление звука (оконное преобразование Фурье).

Форматы хранения звука
WAV
wav = заголовок + байты PCMХранит звук без потери качества
(аналог в изображениях — bmp)

MP3
Данные с потерями, хорошо подходит для хранения музыки.
(аналог в изображениях — jpg)

AMR
Данные с потерями, предназначен для хранения речи.Используется в мобильной телефонии (2011).
(аналог в изображениях — png)

Способы генерации цифрового звука

Есть следующие способы построения PCM-представления некоторого звука или музыки:

1. Сэмплирование
Используется для производства всей музыки. Устройства — сэмплеры

2. (Субтрактивный) Синтез
Используется преимущественно для современной электронной музыки. Устройства — синтезаторы.

3. FM-синтез
4. Аддитивный синтез
5. Гранулярный синтез
6. S&S — Sample & Synthesis — сэмплирование, анализ, последующий синтез — сегодня одна из наиболее лучших технологий воспроизведения«живых» инструментов.

Рассмотри три способа генерации звука: сэмплирование, субтрактивный и аддитивный синтез.

Сэмплирование
Запись: «Живой звук» — микрофон — АЦП — PCM-формат.

Воспроизведение: PCM-формат — ЦАП — динамик.

Дополнительные возможности: можно менять скорость воспроизведения, тогда повысится тон и скорость сэмпла.
Современные алгоритмы также позволяют менять тон сэмпла без изменения его скорости, и наоборот.

Сэмплер Akai MPC1000:
Разработка интерактивных систем на OpenFrameworks: Интерактивный звук - 4

Субтрактивный Синтез
В докомпьютерное время:
несколько простых волн (прямоугольная, синусоидальная, треугольная) обрабатывались набором фильтров (НЧ, ВЧ, вырезание нужной частоты). Полученный звук шел на динамики.

Сейчас:
делается в цифровом виде.
Есть трудности — нужно аккуратно учитывать известные проблемы, связанные с цифровым представлением звука («алиасинг»).

Синтезатор Minimoog:
Разработка интерактивных систем на OpenFrameworks: Интерактивный звук - 5

Аддитивный синтез
Аддитивный синтез основан на построении звука с помощью суммирования множества гармоник (т.е. синусоид разной частоты) с изменяющейся громкостью.

Любой звук можно представить с произвольной точностью как сумму большого числа гармоник с меняющейся громкостью. Но на практике работа с большим числом гармоник требует больших вычислительных ресурсов. Хотя, в настоящее время существует несколько аппаратных и программных аддитивных синтезаторов.

Примеры проектов на базе openFrameworks

Про установку, настройку фреймворка, и IDE для сборки проектов, можно прочитать здесь.

Воспроизведение сэмплов в openFrameworks — проект «звуковой ландшафт»

Суть проекта: пользователь тыкает мышью в разные части экрана и начинает доноситься некоторый звук

Разработка интерактивных систем на OpenFrameworks: Интерактивный звук - 6

 //Объявление переменных
ofSoundPlayer sample;  //проигрыватель сэмпла
ofPoint p; //точка и радиус - для рисования кружка
float rad;

void testApp::setup(){
  sample.loadSound("sound.wav");  //Загрузка сэмпла из папки bin/data
  sample.setVolume(0.5f);  //громкость, [0, 1]
  sample.setMultiPlay(true);  //разрешаем запускать несколько сэмплов 
  ofSetFrameRate( 60 ); //скорость рисования кадров
  ofSetBackgroundAuto( false ); //выключаем стирание фона 
  ofBackground(255,255,255);
}

void testApp::update(){
  ofSoundUpdate();  //обновляем состояние звуковой системы
}

void testApp::draw(){
  //если звук играет, рисовать прозрачный кружок
  ofEnableAlphaBlending();
  if (sample.getIsPlaying()) {
    //случайный цвет
    ofSetColor(ofRandom(0, 255), ofRandom(0, 255), ofRandom(0, 255), 20);
    ofCircle( p.x, p.y, rad );
  }
  ofDisableAlphaBlending();
}

//нажата мышь
void testApp::mousePressed(int x, int y, int button){
  float h = ofGetHeight(); //высота экрана
  //вычисляем желаемую скорость воспроизведения сэмпла,
  //при этом 1.0 - это оригинальная скорость сэмпла
  float speed = (h - y) / h * 3.0;
  if ( speed > 0 ) {
    sample.play();  //запуск нового сэмпла 
    sample.setSpeed( speed );  //установка скорости воспроизведения 

    //запоминаем точку и радиус кружка для рисования
    p = ofPoint( x, y );
    rad = (3 - speed);
    rad = 20 * rad * rad;
  }
}
Сценарий проекта«Аддитивный синтезатор»

Суть проекта: пользователь на белом фоне машет руками перед камерой. Имеется n гармоник. Экран разделен на n
вертикальных полосок, в каждой считается число пикселов, яркость которых меньше некоторого порога. Это число определяет громкость соответствующих гармоник.

Используем n = 20 синусоидальных гармоник, с частотами
100 Гц,
200 Гц,

2000 Гц

Гармоники играются с помощью зацикленных сэмплов, у которых просто меняется громкость.

Исходный код проекта:

//Объявляем переменные

//видео-граббер для "захвата" видеокадров
ofVideoGrabber grabber;
int w;  //ширина кадра
int h;  //высота кадра 

const int n = 20;  //число гармоник 
ofSoundPlayer sample[ n ];  //сэмплы гармоник 
float volume[ n ];  //громкость гармоник 
int N[ n ];  //число пикселов, играющих в этой гармонике

ofSoundPlayer sampleLoop;  //сэмпл барабанной петли

//Инициализация
void testApp::setup(){

  w = 320;
  h = 240;
  grabber.initGrabber(w, h); //подключение камеры
  
  //загрузка сэмплов гармоник
  for (int i=0; i<n; i++) {
    int freq = (i+1) * 100;
    sample[ i ].loadSound( ofToString(freq) + ".wav");  //файлы называются 100.wav,...
    sample[ i ].setVolume(0.0);  //громкость 
    sample[ i ].setLoop(true);  //зацикливаем звук 
    sample[ i ].play();  //запуск звука
  }
}

//Обновление состояния
void testApp::update(){
  grabber.grabFrame(); //захват кадра
  if (grabber.isFrameNew()){ //если пришел новый кадр
    for (int i=0; i<n; i++) { volume[i] = 0; N[i] = 0; }  //сбрасываем гармоникиun
    signed char * input = grabber.getPixels(); //пикселы входного изображения
    for (int y=0; y<h; y++) {for (int x=0; x<w; x++) {
      //входной пиксел (x, y):
      int r = input[ 3 * (x + w * y) + 0 ];
      int g = input[ 3 * (x + w * y) + 1 ];
      int b = input[ 3 * (x + w * y) + 2 ];
      int result = (r + g + b > 400 ) ? 0 : 1;  //пороговая обработка
      int i = (x * n / w);//в какую гармонику писать результат
      volume[ i ] += result;
      N[ i ]++;
    }
  }  //устанавливаем новые громкости гармоник
  for (int i=0; i<n; i++) {
    if ( N[ i ] > 0 ) { volume[ i ] /= N[ i ]; }  //нормируем громкости в [0, 1]
      sample[ i ].setVolume( volume[ i ] / n );  //громкость.
      //Делим на n, иначе будет искажение выходного звука
    }
  }
  ofSoundUpdate();//обновляем состояние звуковой системы}

//Рисование
void testApp::draw() {
  ofBackground(255,255,255);  //задаем цвет фона
  float w = ofGetWidth();  //высота и ширина экрана
  float h = ofGetHeight();
  ofSetColor( 255, 255, 255 );  //иначе картинка кадра нарисуется неверно
  grabber.draw(0, 0, w, h);  //вывод кадра 

  //рисование гармоник 
  ofEnableAlphaBlending(); //включение прозрачности
  ofSetColor( 0, 0, 255, 80 );  //синий цвет с непрозрачностью 80
  for (int i=0; i<n; i++) {
    float harmH = volume[i] * h;//высота столбика гармоники i 
    ofRect( i * w / n, h - harmH, w / n, harmH );
  }
  ofDisableAlphaBlending();  //выключение прозрачности
}

Вот что получилось:

Если вам интересна тематика интерактивных систем, и openFrameworks в частности — то приглашаем в русскоязычную группу по openFrameworks.

Автор: nemilya

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js