Web MIDI API

SOUND SYNTHESIS 101

@xpktro - LimaJS

Puede verse esta presentación online visitando:

El código fuente mostrado aquí puede encontrarse en la siguiente dirección:

Previously on LimaJS...

  var   context = getAudioContext()
  , frequencies = [220, 440, 880]
  , currentTime = audioContext.currentTime
  ,    duration = 1;

  frequencies.forEach(function(frequency, index) {
    var sineosc = context.createOscillator()
    , startTime = currentTime + (index * duration);
    sineosc.type = 'sine';
    sineosc.frequency.value = frequency;
    sineosc.connect(context.destination);
    sineosc.start(startTime);
    sineosc.stop(startTime + duration);
    sineosc.onended = function(){ log('Finished ' + frequency + 'Hz tone.'); };
  });

  log('Playing octaves: ' + frequencies);

Qué veremos hoy:

  • Fundamentos de síntesis de audio: Sintetizador rudimentario
  • Web MIDI API
  • Livecoding con Gibberwocky

Micro-taller: Síntesis 101

Oscilador (CCO)

Envolvente (AR CCA)

Filtro de paso

de banda (CCF)

Delay

Oscilador

(Code Controlled Oscillator - CCO)

  • El CCO (En el mundo real llamado VCO) genera las ondas que serán luego usadas por otros módulos/nodos.
  • Generalmente producen más de un tipo de onda, en distintas frecuencias (lo que produce distintas notas).
class Synth {
  start() {
    this.context = getAudioContext();
    this.createOscillator();
    this.connectNodes();
  }

  createOscillator() {
    this.oscillator                 = this.context.createOscillator();
    this.oscillator.frequency.value = 440;

    let oscillatorElement      = document.getElementById('oscillator');
    this.oscillator.type       = oscillatorElement.value;
    oscillatorElement.onchange = () => {
      this.oscillator.type = oscillatorElement.value;
    }

    this.oscillator.start();
  }

  connectNodes() {
    this.oscillator.connect(this.context.destination);
  }

  stop() {
    this.context.close();
  }
}

Envolvente

(Code Controlled Amplitude - CCA)

  • Llamado normalmente VCA se encarga de controlar la amplitud de la onda de entrada a lo largo del tiempo.
  • En este taller, sólo controlaremos el ataque y la liberación (Attack/Release)
class Synth {
  start() {
    // ...
    this.createAR();
    this.connectNodes();
  }

  createAR() {
    this.cca            = this.context.createGain();
    this.cca.gain.value = 0;
    this.attack         = 0.001;
    this.release        = 0.001;
  }

  connectNodes() {
    this.oscillator.connect(this.cca);
    this.cca.connect(this.context.destination);
  }

  setAttack(value) {
    this.attack = (value / 127) * 2;
  }

  setRelease(value) {
    this.release = (value / 127) * 2;
  }

  // ...
}
class Synth {
  // ...

  noteOn(midiNote) {
    let now = this.context.currentTime;

    this.cca.gain.cancelScheduledValues(0);
    this.cca.gain.linearRampToValueAtTime(1, now + this.attack);

    // https://en.wikipedia.org/wiki/MIDI_tuning_standard#Frequency_values
    let frequency = Math.pow(2, (midiNote - 69) / 12) * 440;
    this.oscillator.frequency.setValueAtTime(frequency, now);
  }

  noteOff() {
    let now = this.context.currentTime;

    this.cca.gain.cancelScheduledValues(0);
    this.cca.gain.setValueAtTime(this.cca.gain.value, now);
    this.cca.gain.linearRampToValueAtTime(0, now + this.release);
  }

  // ...
}

MIDI: Musical Instrument Digital Interface

Es un estándar que define un protocolo de comunicaciones, interfaz digital y conectores para la comunicación entre dispositivos musicales.

En este taller, recibiremos mensajes MIDI emitidos por un dispositivo externo (controlador)

class MIDIHandler {
  constructor() {
    navigator
      .requestMIDIAccess()
      .then((access) => this.accessGranted(access))
      .catch(log);
  }

  accessGranted(midiAccess) {
    this.access = midiAccess;

    let deviceSelector = document.getElementById('devices');
    this.access.inputs.forEach((entry) => {
      let option       = document.createElement('option');
      option.value     = entry.id;
      option.innerHTML = entry.name;
      deviceSelector.appendChild(option);
    });
  }
}
class MIDIHandler {
  // ...
  useSynth(synth) {
    this.currentNote = 0;

    let selectedDevice = document.getElementById('devices').value;
    let midiDevice     = this.access.inputs.get(selectedDevice);
    midiDevice.onmidimessage = (midiEvent) => {
      let message = midiEvent.data[0]
      ,     data1 = midiEvent.data[1]
      ,     data2 = midiEvent.data[2];

      switch (message) {
        case 144:
          // data1 = MIDI note | data2 = velocity
          synth.noteOn(data1);
          this.currentNote = data1;
          log('received note ' + data1);
          break;
        case 128:
          // data1 = MIDI note | data2 = aftertouch
          if(this.currentNote === data1) {
            synth.noteOff();
            log('note off');
          }
          break;
        case 176:
          // Next slide...
      }
    }
  }
}
case 176:
  // data1 = CC number | data2 = value
  switch (data1) {
    case 74:
      synth.setAttack(data2);
      break;
    case 71:
      synth.setRelease(data2);
      break;
  }
  log('control change ' + data1);
  break;

synth = new Synth();
midi  = new MIDIHandler();

// Para iniciar el sintetizador:
synth.start();
midi.useSynth(synth);

// Para detener el sintetizador:
synth.stop();

Filtro de paso de banda

(Code Controlled Filter - CCF)

  • También llamado VCF altera la señal de entrada permitiendo el paso de determinadas frecuencias únicamente.
class Synth {
  start() {
    // ...
    this.createFilter();
    this.connectNodes();
  }

  createFilter() {
    this.filter = this.context.createBiquadFilter();
    this.filter.type = 'bandpass';
    this.filter.frequency.value = 20000;
    this.filter.Q.value = 0;
  }

  connectNodes() {
    this.oscillator.connect(this.cca);
    this.cca.connect(this.filter);
    this.filter.connect(this.context.destination);
  }

  setFilterFrequency(value) {
    this.filter.frequency.value = (value / 127) * 20000;
  }

  setFilterQ(value) {
    this.filter.Q.value = (value / 127) * 10;
  }
}
class MIDIHandler {
  useSynth(synth) {
    // ...
        case 73:
          synth.setFilterFrequency(data2);
          break;
        case 75:
          synth.setFilterQ(data2);
          break;
    // ...
  }
}

Delay

Un módulo de delay recibe una señal y retrasa su salida luego de un tiempo

Para obtener el efecto de eco, conectaremos la salida del delay a su propia entrada

DelayNode
Gain
Gain
In
Out
class Synth {
  start() {
    // ...
    this.createFilter();
    this.createDelay();
    this.connectNodes();
  }

  createDelay() {
    this.delay = this.context.createDelay(5);
    this.delay.delayTime.value = 0;

    this.feedbackGain = this.context.createGain();
    this.feedbackGain.gain.value = 0;

    this.delayGain = this.context.createGain();
    this.delayGain.gain.value = 0;
  }

  connectNodes() {
    // ...
    this.filter.connect(this.delay);
    this.delay.connect(this.feedbackGain);
    this.feedbackGain.connect(this.delay);
    this.delay.connect(this.delayGain);
    this.delayGain.connect(this.context.destination);
  }
}
class Synth {
  // ...

  setDelayAmount(value) {
    this.delayGain.gain.value = value / 127;
  }

  setDelayRate(value) {
    this.delay.delayTime.value = (value / 127) * 1.5;
  }

  setDelayFeedback(value) {
    this.feedbackGain.gain.value = (value / 127) * 0.8;
  }

  // ...
}
class MIDIHandler {
  useSynth(synth) {
    // ...
        case 72:
          synth.setDelayAmount(data2);
          break;
        case 91:
          synth.setDelayRate(data2);
          break;
        case 93:
          synth.setDelayFeedback(data2);
          break;
    // ...
  }
}

Pendientes / A dónde ir

  • Polifonía
  • Envolvente para el filtro
  • ADSR
  • Múltiples osciladores
  • LFO

Web MIDI API

By Moisés Gabriel Cachay Tello

Web MIDI API

  • 996