victordiaz / PHONK

PHONK is a coding playground for new and old Android devices
https://phonk.app
GNU General Public License v3.0
457 stars 27 forks source link

Midi controller capabilities #111

Closed polhomarkho closed 2 years ago

polhomarkho commented 2 years ago

Hi!

I wanted to create a midi controller to control my software synthesizers on my computer and send chords by pushing a single button as I'm too lazy learning the piano 😅

Here is a new PMidiController that allows you to send midi to a computer if the Android device is able to (midi capabilities + Android 6 minimum). I tried it on an emulator running Android 5.1 and the midi part doesn't work but doesn't break anything either.
The existing PMidi didn't do what I needed as it cannot be used to control a computer as far as I know.

To try it, you need to first plug your Android device with a USB cable and select:
image

Then using the script below and a software synths (for example Vital which is free), you can play for example the chords of the C major scale and the A minor scale by pushing the buttons (1 button = 1 chord of the scale represented by its degree): demo incoming soon

Screenshot with the C major chord played

The script I used (could be a bit optimized but it works):

/*  
 *  Description simple midi controller to control a soft synth on a computer 
 *  by Paul-Emile Sublet <polhomarkho@gmail.com>
 */

const midi = media.startMidiController();
const inputs = midi.findAvailableMidiInputs();

if (!inputs.length) {
  ui.toast('no midi input available :(');
  app.close();
}

// globals
const velocity = 127;  // max volume!
const channel = 0;  // midi channel
const greyColor = '#474747';
const redColor = '#FF0000';
const greenColor = '#00FF00';

var buttonPositionX;
var buttonPositionY;
const buttonSize = 0.24;
const buttonSpacing = 0.01;

// Select a midi input to send data to the computer
ui.popup()
  .title('Choose a midi input:')
  .choice(inputs.map(function (input) {
    return input.name;
  }))
  .onAction(function (selectedElement) {
    const selectedElementIndex = selectedElement.answerId;
    const selectedDeviceInputId = inputs[selectedElementIndex].deviceInputId;
    midi.setupMidi(selectedDeviceInputId);
    displayUi();
  })
  .show();

// Display
function displayUi() {
  // major chords (C major)
  buttonPositionX = 0;
  buttonPositionY = 0;

  const notesInMajorScale = [];
  var note = 60;  // middle C
  const majorIntervals = [2, 2, 1, 2, 2, 2, 1];
  majorIntervals.forEach(function (interval, i) {
    notesInMajorScale.push(note);
    note += interval;
  });

  displayChordButtons(notesInMajorScale, redColor);

  // minor chords (A minor)
  buttonPositionX = 0;
  buttonPositionY = 0.5;

  const notesInMinorScale = [];
  note = 57;  // A just below middle C
  const minorIntervals = [2, 1, 2, 2, 1, 2, 2];
  minorIntervals.forEach(function (interval, i) {
    notesInMinorScale.push(note);
    note += interval;
  });

  displayChordButtons(notesInMinorScale, greenColor);
}

function displayChordButtons(scale, colorWhenPlayed) {
  ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'].forEach(function (chordDegree, i) {
    const button = ui.addButton(chordDegree, buttonPositionX, buttonPositionY, buttonSize, buttonSize);
    button.setProps({
      background: greyColor
    });
    const firstNote = scale[i];
    const secondNote = scale[(i + 2) % 7];  // mod 7 will do inversions
    const thirdNote = scale[(i + 4) % 7];
    button.onPress(function () {
      midi.noteOn(channel, firstNote, velocity);
      midi.noteOn(channel, secondNote, velocity);
      midi.noteOn(channel, thirdNote, velocity);
      button.setProps({
        background: colorWhenPlayed
      });
    });
    button.onRelease(function () {
      midi.noteOff(channel, firstNote, velocity);
      midi.noteOff(channel, secondNote, velocity);
      midi.noteOff(channel, thirdNote, velocity);
      button.setProps({
        background: greyColor
      });
    });

    // Buttons are displayed from left to right and go to newline when overflowing
    if (buttonPositionX + buttonSize + buttonSpacing >= 1) {
      buttonPositionX = 0;
      buttonPositionY += buttonSize + buttonSpacing;
    } else {
      buttonPositionX += buttonSize + buttonSpacing;    
    }
  });
}

Be careful, this PR depends on https://github.com/victordiaz/PHONK/pull/110! As soon as https://github.com/victordiaz/PHONK/pull/110 is merged, I'll remove the commit https://github.com/victordiaz/PHONK/pull/111/commits/1ade5d313860a3210a209e9ae8bcefc6817371db from this PR.
I'll probably expand the PMidiController as I find what I need for my controller but it will be in another PR.

As I'm not sure if the project is still alive, if anyone is interested in this feature I'll publish an apk on my repo as I don't have a Google Play developer license.