GospelToolbox/Labs

View on GitHub
engines/set_live/app/javascript/packs/sets_show.js

Summary

Maintainability
B
6 hrs
Test Coverage
import "core-js/stable";
import "regenerator-runtime/runtime";

import Metronome from "metronome";
import Launchpad from "launchpad";

// Entrypoint
window.addEventListener('load', init, false);
function init() {
  loadAudioContext();

  setupBpmAdjustments();
  loadClickBuffer();
  loadPadBuffers();

  connectLaunchpad();

  $('#btn_stop_all').click(function() {
    stopAllPads();
    metronome.stop();
  });

  $('#btn_stop_click').click(function() {
    metronome.stop();
  });

  $('#btn_prev').click(function() {
    prevSong();
  })

  $('#btn_next').click(function() {
    nextSong();
  });
}

var audioContext;
var metronome;
var launchpad;
var clickBuffer;
var padBuffers = {};
var padsToLoad = 0;
var padSources = {};
var padGains = {};
var currentSongNdx = 0;

function loadAudioContext() {
  // Fix up for prefixing
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  audioContext = new AudioContext();
}

function setupBpmAdjustments() {
  data.songs.forEach(function(song) {
    let num_bpm = $('#num_bpm_' + song.id)
    num_bpm.change(function() {
      song.bpm = num_bpm.val();
      metronome.tempo = song.bpm;
    });
  });
}

function loadClickBuffer() {  
  loadBuffer('https://gtbox-setlive.s3.amazonaws.com/Metronome.wav', function (buffer) {
    metronome = new Metronome(84, buffer, audioContext);
  });
}

function loadPadBuffers() {
  function onlyUnique(value, index, self) { 
    return self.indexOf(value) === index;
  }   

  var keys = data.songs.map(function(s) { return s.key; })
                   .filter(onlyUnique);
  padsToLoad = keys.length;

  keys.forEach(function(key) {
      let ids = data.songs.filter(function(song) { return song.key == key; })
                      .map(function(song) { return song.id; });

      ids.forEach(function(id) {
        $('#' + id + '_pad_loading_status').text('Loading...');
      });

      loadBuffer('https://gtbox-setlive.s3.amazonaws.com/Key_of_' + encodeURIComponent(key) + '_Pads_5.mp3', function (buffer) {
        padBuffers[key] = buffer;
        padsToLoad--;

        ids.forEach(function(id) {
          $('#' + id + '_pad_loading_status').text('');
          onPadLoaded(id, key);
        });
      });
    });
  }

  function onPadLoaded(id, key) {

    $('#play_btn_'+id).click(function() {
      var idx = data.songs.findIndex(function(song) {
        return song.id == id;
      });
      startMaster(idx);
      currentSongNdx = idx;
    });

    $('#mute_pad_'+id).click(function() {
        padGains[id].gain.setValueAtTime(padGains[id].gain.value, audioContext.currentTime);
        padGains[id].gain.exponentialRampToValueAtTime(1 - padGains[id].gain.value, audioContext.currentTime + 1  );
    });
  }

  function stopAllPads() {
    for(var prop in padSources) {
      if(padSources[prop]) {
        padGains[prop].gain.setValueAtTime(padGains[prop].gain.value, audioContext.currentTime);
        padGains[prop].gain.linearRampToValueAtTime(0.001, audioContext.currentTime + 2  );
        padSources[prop].stop(audioContext.currentTime + 2);
      }
    }
  }
  

  function loadBuffer(url, setter) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    // Decode asynchronously
    request.onload = function () {
      audioContext.decodeAudioData(request.response, setter, function (err) {
        console.error('Failed to load audio file', err);
      });
    }
    request.send();
  }

  function playBuffer(buffer, time, pan) {
    var source = audioContext.createBufferSource(); // creates a sound source
    source.buffer = buffer; // tell the source which sound to play

    var panNode = audioContext.createStereoPanner();
    panNode.pan.setValueAtTime(pan, time);

    var gainNode = audioContext.createGain();
    gainNode.gain.setValueAtTime(0.99999, time);

    source.connect(gainNode); // connect the source to the context's destination (the speakers)
    gainNode.connect(panNode);
    panNode.connect(audioContext.destination);

    source.start(time);

    return [source, gainNode];
  }

  function toggleMetronome() {
    if(metronome.playing) {
      metronome.stop();
    } else {
      metronome.start();
    }
  }

  function prevSong() {
    // Make sure we're not at the first song
    if(currentSongNdx == 0) {
      return;
    }

    currentSongNdx--;
    startMaster(currentSongNdx);
  }

  function nextSong() {
    if(currentSongNdx == data.songs.length - 1) {
      return;
    }

    currentSongNdx++;
    startMaster(currentSongNdx);
  }

  function startMaster(ndx) {

    resetLaunchpadColors();
    if(ndx < 8) {
      launchpad.setColor(6, 7-ndx, 122);
      launchpad.setColor(7, 7-ndx, 122);
      launchpad.setColor(8, 7-ndx, 122);
    }

    var song = data.songs[ndx];

    stopAllPads();
    var response = playBuffer(padBuffers[song.key], audioContext.currentTime, 1);
    padSources[song.id] = response[0];
    padGains[song.id] = response[1];

    metronome.tempo = song.bpm;
    metronome.start();
  }

  function connectLaunchpad() {
    launchpad = new Launchpad(navigator);

    launchpad.onConnect(function() {
      resetLaunchpadColors();
    });
    launchpad.connect();

    launchpad.onKeyDown(function(x,y) {
      var idx = 7-y;

      if (x == 0 && y == 0) {
        prevSong();
        return;
      } else if (x == 2 && y == 0) {
        nextSong();
        return;
      }

      var song = data.songs[idx];
      if (song) {
        var id = song.id;

        switch (x) {
          case 6: // Toggle pad
            if(padGains[id]) {
              padGains[id].gain.setValueAtTime(padGains[id].gain.value, audioContext.currentTime);
              padGains[id].gain.exponentialRampToValueAtTime(1 - padGains[id].gain.value, audioContext.currentTime + 1  );
            }
          break;

          case 7: // Toggle click
            toggleMetronome();
          break;

          case 8: // Toggle master
            currentSongNdx = idx;
            startMaster(idx);
          break;
        
          default:
            break;
        }
      }
    });
    
  }

  function resetLaunchpadColors() {
    clearLaunchpadColors();

    // Prev and Next buttons
    launchpad.setColor(0,0, 67);
    launchpad.setColor(2,0, 67);

    for(var i = 0; i < data.songs.length; i++) {
      var y = 7-i;

      // Color midi toggle
      launchpad.setColor(6, y, 67);

      // Color click toggle
      launchpad.setColor(7, y, 107);

      // Color master toggle
      launchpad.setColor(8, y, 40);
    }
  }

  function clearLaunchpadColors() {
    for(var i = 0; i < 9; i++) {
      for(var j = 0; i < 8; i++) {

      // Color midi toggle
      launchpad.setColor(i, j, 0);
    }
  }
}