Несколько приёмов для создания произведений процедурального (генеративного) искусства.

image

Настройка объекта для микрофона – вещь достаточно лёгкая, хоть и сначала может показаться сложной. Не надо волноваться. Даже если вы это не очень понимаете, то и не нужно. Потому что когда всё настроено и работает, вам больше никогда не придётся проделывать это всё снова…

Куча туториалов могут объяснить это более подробно, но я что-то не видел советов, как перейти с Hello world на следующий уровень. Об этом я и хочу вам рассказать.
Не откладывая в долгий ящик предупрежу, что это не работает в Safari. И никогда не работало. Есть баги, благодаря которым может сработать… Но давайте реально, меня уже достал поиск обходных путей в этих не ахти каких браузерах. Как и вас, думаю.

И ещё заметьте: с прошлого года нужно https соединение, чтобы использовать микрофон. Если вы используете что-то вроде Live Server от Atom (а его использовать стоит, поверьте мне), микрофон настроится автоматически.

Итак, сначала очень скучное дело, настройка микрофона… Только не пугайтесь…

function Microphone (_fft) {  var FFT_SIZE = _fft || 1024;  this.spectrum = [];
  this.volume = this.vol = 0;
  this.peak_volume = 0;  var self = this;
  var audioContext = new AudioContext();
  var SAMPLE_RATE = audioContext.sampleRate;
  
  // это просто проверка браузера на то, 
  // поддерживает ли он AudioContext и getUserMedia
  window.AudioContext = window.AudioContext ||  window.webkitAudioContext;
  navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;  //теперь просто ждите запуска микрофона
  window.addEventListener('load', init, false);  function init () {
      try {
        startMic(new AudioContext());
      }
      catch (e) {
        console.error(e);
        alert('Web Audio API is not supported in this browser');
      }
  }  function startMic (context) {    navigator.getUserMedia({ audio: true }, processSound, error);    function processSound (stream) {     //анализатор определяет частоту, колебательный сигнал и т. д.
     var analyser = context.createAnalyser();
     analyser.smoothingTimeConstant = 0.2;
     analyser.fftSize = FFT_SIZE;     var node = context.createScriptProcessor(FFT_SIZE*2, 1, 1);     node.onaudioprocess = function () {       // число битов возвращает массив, а это половина FFT_SIZE
       self.spectrum = new Uint8Array(analyser.frequencyBinCount);       // getByteFrequencyData выдаёт амплитуду для каждой ячейки
       analyser.getByteFrequencyData(self.spectrum);
       // getByteTimeDomainData определяет громкость за определённое время
       // analyser.getByteTimeDomainData(self.spectrum);

       self.vol = self.getRMS(self.spectrum);
       // get peak – костыль, если громкость низкая
       if (self.vol > self.peak_volume) self.peak_volume = self.vol;
       self.volume = self.vol;     };     var input = context.createMediaStreamSource(stream);
     input.connect(analyser);
     analyser.connect(node);
     node.connect(context.destination);  }  function error () {
     console.log(arguments);
  }}//////// SOUND UTILITIES ..... потом вставим сюда ещё чего-нибудь....
return this;};
var Mic = new Microphone();

Это даст нам массив амплитуд на наших частотах (использую 512 по умолчанию), представленные как Mic.spectrum. Окей, давайте начнём играть…

Получение полной громкости


image
Пошумим!

Я нечасто это использую, но это удобно и можно использовать в куче других утилит. Чтобы получить полную громкость или уровни, мы используем функцию getRMS(). RMS лучше указывает громкость входящего звука, он суммирует все громкости всех частот спектра. Итак, функция, которую мы добавим в Mic() выглядит так:

// A more accurate way to get overall volume
this.getRMS = function (spectrum) {var rms = 0;
  for (var i = 0; i < vols.length; i++) {
    rms += spectrum[i] * spectrum[i];
  }
  rms /= spectrum.length;
  rms = Math.sqrt(rms);
  return rms;
 }

И мы можем сделать простенькую анимацию, как ту, что выше, так:

var ctx = createCanvas("canvas1");
ctx.background(235);function draw(){var s = Mic.getRMS();
  ctx.fillStyle = rgb(s*2);
  ctx.HfillEllipse(w/2, h/2, s*5, s*5);
}

Получение спектра звука


image

Наш Mic() выдаёт Mic.spectrum, массив амплитуд или громкостей по всему спектру (или FFT_SIZE, у меня 512 по умолчанию). Мы можем использовать их как есть, вот так:

var ctx = createCanvas("canvas1");// сделайте сетку 200 по ширине
// Как создавать сетки я показал тут [https://hackernoon.com/creative-coding-grids-2e6bcaa07596#.u4zyrccxr]
var grid = new Grid(200, 1);function draw(){ctx.background(235);for (var i = 0; i < grid.length; i++) {
    var s = Mic.spectrum[i];
    ctx.fillStyle = hsl(map(i, 0, grid.length, 0, 360), 80, 50);
    ctx.fillRect(grid.x[i], h - s, grid.spacing_x-1, s);
  }}

Однако, я считаю, что было бы удобнее и эффективнее построить функцию отображения звука.

Отображение звука


image

Если бы у нас было, к примеру, только 20 объектов, но наш спектр выдавал 512 частот (что является значением по умолчанию для объекта Mic()), было бы разумно распределить амплитуды, чтобы получить более чёткое отображение того, что происходит с нашим звуком. Поэтому давайте переотобразим наш спектр.

Чтоб начинающим было легче, я буду достаточно часто использовать простую map() функцию, которая хранится в моем главном файле creative_coding.js, и выглядит вот так:

function map(value, min1, max1, min2, max2) {
var returnvalue = ((value-min1) / (max1 - min1) * (max2-min2)) + min2;
return returnvalue;
};

Основываясь на этом, мы можем сделать mapSound() функцию, чтобы равномерно распределять значения. Это, скорее всего, самый ценный инструмент анализа звука. Я использую его почти каждый день. Супер удобно и просто.

this.mapSound = function(_me, _total, _min, _max){if (self.spectrum.length > 0) {
   // обозначьте значения по умолчанию, если другие не даны
   var min = _min || 0;
   var max = _max || 100;
   //actual new freq
   var new_freq = Math.floor(_me * self.spectrum.length /_total);
   // обозначьте громкость до хороших значений
   return map(self.spectrum[new_freq], 0, self.peak_volume, min, max);
  } else {
    return 0;
  }
      
}

Мы затем можем легко создать спектр вот так:

image
Тест, тест, 1, 2, 3.

Визуализация спектра сделана из сетки, звук обозначается по длине сетки. Я также обозначил выданные значения (_min и _max) как 5 и высоту/4, чтобы лучше выглядело:

var ctx = createCanvas("canvas1");// make a grid 200 wide
var grid = new Grid(200, 1);function draw(){ctx.clearRect(0,0,w,h);for (var i = 0; i < grid.length; i++) {
    // Mic.mapSound(_me, _total, _min, _max)
    var s = Mic.mapSound(i, grid.length, 5, h/4);
    ctx.fillStyle = rgb(0);
    ctx.fillRect(grid.x[i], grid.y[i] - s/2, grid.spacing_x-0.5, s);
  }}

На самом деле, mapSound() сможет позаботиться о всех нуждах, связанных с анализом звука. Чаще всего, именно простота лучше всего работает для визуализация звука. Фишка творческого кодинга – просто выстраивать простые биты друг на друге, чтобы получились сложно выглядящие творения. Так, например, мы можем использовать некоторые приёмы Math.cos и Math.sin, чтобы сделать красивый круглый спектр:

image
Мам, смотри, круглый спектр

Вот код-пример: просто создать кучи частиц и распределить длину каждой линии частицы к соответствующей громкости:

var ctx = createCanvas("canvas1");var num_items = 200;
var radius = 100;
ctx.lineWidth = 2;
var particles = [];// работаем над углами 
for (var i = 0; i < num_items; i++) {
  var angle = radians(distributeAngles(i, num_items));
  particles[i] = {
    x: w/2 + Math.cos(angle) * radius,
    y: h/2 + Math.sin(angle) * radius,
    angle: angle
  }
}
function draw(){ ctx.background(0); for (var i = 0; i < num_items; i++) {  var p = particles[i];
  var s = Mic.mapSound(i, num_items, 5, 100);
  // map to greyscale
  //ctx.strokeStyle = rgb(map(i, 0, num_items, 0, 255));
    
  x2 = w/2 + Math.cos(p.angle) * (s + radius);
  y2 = h/2 + Math.sin(p.angle) * (s + radius);
  ctx.line(p.x, p.y, x2, y2); }}

И этот код может быть адаптирован в нечто типа этого — это одно из моих первых визуально-звуковых творений на Javascript.

image

Вот и всё. Конец. Как-нибудь мы можем добавить кучу полезных утилит, чтобы добавить басы, средние и высокие частоты, тон и ещё много чего, сделав визуализацию звука ещё проще…

Как всегда, полный код можно найти у меня на github.

Счастливого кодинга!