• Junkbox Junkie Blog
  • About
  • Contact
  • Arty Schematics
  • Audio Samples
  The Junkbox Junkie Official Home

6): rolling along

8/11/2015

0 Comments

 
Now that we know how to generate random (and other) functions, it is time to put them in a more practical framework. As we've seen (and heard), just throwing out random notes may be interesting for a short time, but can get pretty stale in a hurry. So much for 'fully non-restricted' music composition. While one could spend some time – a lot of time – looking for some 'magic functions' that would always generate pleasing western-style music, I'd like to move on with just our tiny handful.

In the end, it's how you use those random numbers, more so than the functions. And there are plenty of ways to organize the output. We're going to start with some 1890's technology: the piano roll.

If you're not familiar with the 'Pianola' or player piano, here's a rendition of Scott Joplin, himself, playing his Maple Leaf Rag, courtesy of the player piano.

In a nutshell, it works as follows: a piece of paper is stretched across a metal bar, called the 'tracking bar', that has openings spaced across its face. Each hole in the bar has a pneumatic suction behind it, so that it gently grabs the paper, as it rolls by, pulling it to the metal bar. Every once in a while, there are one or more holes punched in specific locations on the paper. As the hole moves over a suction hole, the air will flow and trigger a piano key to move, playing a note. Short notes may have one or two holes in a row: longer notes have a line of holes. On the left edge, holes will activate the piano's pedals, usually the sustain pedal. Varying the speed of the paper moving across the metal bar increases or decreases the speed, or tempo, of the performance. Dynamics, how loudly or softly the notes were played, were captured and encoded separately. Sometimes this was a manual process, apparently. But it varied, depending on the roll manufacturer.

We can electronify that system and go one (or two) better, since we can manipulate 127 note pitches and a velocity as well as the tempo. Not only that, but we can play any sound our MIDI equipment generates: not just a piano sound.

The basic concept is the same. Instead of an air-sucking tracker bar, we'll use an array of bytes. Since MIDI supports almost twice the number of notes of the original 65-note player piano, our array will contain 128 note 'slots', representing each MIDI note available.

To simulate the holes punched in the paper, we'll need to use a special feature of the Arduino. Since, in the paper version, there either is a hole, or isn't a hole, we can go binary and say a 1 represents a hole and a 0 represents no hole. The special feature I mentioned is the bitRead() function. It allows us to take a byte value, having 8 bits, and to extract each bit at will. Using these two concepts, here's what we'll do:

1): turn off all notes, just to make sure there are no hangers-on
2): put a random value in each of the 127 'slots' – one for each possible MIDI note
3): loop through each of the eight bits of all 127 notes. If the current note we are looking at has a 1 in the current bit position we are looking at, we will play the note. Otherwise that note will be turned off.
4): wash, rinse, repeat.

As you can see, this allows us to get eight note events from each single byte value.


Piano Roll Method
In this short example of a single note, the system will play MIDI note #60 when the loop pulls bit 2 and then again when it reaches bit 5. Remember, though, this happens for all 127 notes. Since most keyboards only allow a finite number of notes to be played at a single time, you won't necessarily hear all the notes. Which is okay. That would sound like nothing much musical. Which it won't, anyway. But we will fix that, soon enough.

/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: KeyRoller I         |
| Part I of the Arty series                       |
| Perambulations in the Field                     |
|                   of Artifical Music Generation |
| Brought to you by "Junkbox Junkie, the Musical" |
+-------------------------------------------------+
Copyright © 2015 by Bill L. Behrendt

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdarg.h>
#include <stdio.h>
#include <SoftwareSerial.h>

//software serial port pins for MIDI
#define MIDI_OUT 11
#define MIDI_IN 12 //unused

//MIDI cmd
#define CMD_NOTE_ON 0x90 //channel 1
#define CMD_NOTE_OFF 0x80 //channel 1

//random types
#define RND_GAUSS 0
#define RND_GAUSS2 1
#define RND_LIN 2
#define RND_SIN 3
#define RND_TAN 4
#define RND_VOSSF 5
#define RND_VOSSPREV 6
#define RND_VOSSGAUSS 7

//global variables
SoftwareSerial MIDI(MIDI_IN, MIDI_OUT); // RX, TX
float pi_eye = -3.14159;
byte prevVal[] = {2, 3, 4, 3, 1, 1, 2, 7}; // for use in getARand() function
byte pianoRoll[128];
byte noteVal;
byte bitPick;
byte noteSlot;
byte rollLow = 0;
byte rollHi = 128;

//-----random functions
byte Voss_f(int last) {
  float probit, u;
  int Nu, J, K, L;
  Nu = 0;
  K = random(23);
  L = last;
  probit = 0.029384;
  while (K > 0) {
    J = L / K;
    if (J == 1) L -= K;
    u = random(-10, 21);
    if (u < probit) J = 1 - J;
    Nu += (J * K);
    K /= 2;
    probit *= random(1.25, 2.5);
  }
  return Nu;
}

byte Gauss_R(float range) {
  int count;
  float summ, std, mean;
  summ = 0.0;
  std = 0.2;
  mean = 0.5;
  for (count = 1; count < 25; count++) {
    summ += random(2);
  }
  return (int((std * 0.707106781 * (summ - 12) + mean) * range + 1));
}

byte getARand(byte rType, byte loVal = 0, byte hiVal = 127) {
  //get random stuffs
  byte retVal = 0;
  byte tmpVal;

  pi_eye += 0.05;
  switch (rType) {
    case RND_GAUSS:
      retVal = Gauss_R(random(loVal, hiVal + 1)); //must be hiVal+1 to include 127 (random is loVal to hiVal-1)
      break;
    case RND_GAUSS2:
      retVal = Gauss_R(loVal) + Gauss_R(hiVal);
      break;
    case RND_LIN:
      retVal = random(loVal, hiVal + 1) - prevVal[rType];
      break;
    case RND_SIN:
      retVal = sin(pi_eye / (loVal + 1)) * hiVal;
      break;
    case RND_TAN:
      retVal = tan(pi_eye / (loVal + 1)) * hiVal;
      break;
    case RND_VOSSF:
      retVal = Voss_f(hiVal * prevVal[rType]) + loVal;
      break;
    case RND_VOSSPREV:
      retVal = Voss_f(prevVal[rType]) * hiVal;
      break;
    case RND_VOSSGAUSS:
      retVal = Voss_f(int(Gauss_R((hiVal - loVal) / 2)));
      break;
  }
  prevVal[rType] = (retVal & 127);
  return prevVal[rType];
}
//----- end random functions

void flashWorkLED(char start) {
  if (start == 1) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(250);
    digitalWrite(LED_BUILTIN, LOW);
    delay(250);
  }
  for (char tm = 0; tm < 5; tm++) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(125);
    digitalWrite(LED_BUILTIN, LOW);
    delay(75);
  }
}

void sendMIDI(unsigned char cmd, unsigned char data1, unsigned char data2)
{
  MIDI.write(cmd);  //note on or note off
  MIDI.write(data1); //MIDI note 0-127
  MIDI.write(data2); //velocity (loudness) of note
}

void audioBootTest(void) {
  sendMIDI(CMD_NOTE_ON, 36, 64);
  sendMIDI(CMD_NOTE_ON, 60, 64);
  sendMIDI(CMD_NOTE_ON, 64, 64);
  sendMIDI(CMD_NOTE_ON, 67, 64);
  sendMIDI(CMD_NOTE_ON, 72, 64);
  flashWorkLED(1);
  delay(1000);
  flashWorkLED(0);
  sendMIDI(CMD_NOTE_OFF, 36, 64);
  sendMIDI(CMD_NOTE_OFF, 60, 64);
  sendMIDI(CMD_NOTE_OFF, 64, 64);
  sendMIDI(CMD_NOTE_OFF, 67, 64);
  sendMIDI(CMD_NOTE_OFF, 72, 64);
}

void chanNotesOff(int channel = 0) {
  sendMIDI(0xB0 + channel, 0x7B, 0x00);
}

void allNotesOff(void) {
  for (int anoff = 0; anoff < 16; anoff++) {
    chanNotesOff(anoff);
    delay(100); //add this if your notes aren't always turning off
  }
}

void setup() {
  Serial.begin(115200); // for looking at debug things
  MIDI.begin(31250); // our software serial MIDI connection
  delay(30); //give the arduuino time to set up the serial port.
  allNotesOff();
  audioBootTest();//swap with delay to clear voices with sustain
  delay(1000);
  //turn all notes off, all channels
  allNotesOff();
  //get a random seed from an unattached analog pin
  randomSeed(analogRead(0));
  //for testing, you can adjust highest and lowest note of interest here
  //rollLow = 36;
  //rollHi = 72;
}

void fillRoll(byte rndType) {
  chanNotesOff();
  for (int filler = rollLow; filler < rollHi + 1; filler++) {
    pianoRoll[filler] = getARand(rndType);
  }
}

void loop() {
  //random types are:
  // RND_GAUSS, RND_GAUSS2, RND_LIN, RND_SIN, RND_TAN, RND_VOSSF, RND_VOSSPREV and RND_VOSSGAUSS
  fillRoll(RND_VOSSGAUSS);
  for (bitPick = 0; bitPick < 8; bitPick++) {       // for each bit
    for (noteSlot = rollLow; noteSlot < rollHi + 1; noteSlot++) { // in each note slot
      noteVal = bitRead(pianoRoll[noteSlot], bitPick); // give us a 1 or a 0
      sendMIDI(noteVal == 1 ? CMD_NOTE_ON : CMD_NOTE_OFF, noteSlot, 64); //HOLD YOUR EARS!!!
      //if (noteSlot > 0) delay(10+getARand(RND_LIN)); // create an arpeggio
    }
    delay(150);  //a tempo of sorts
  }
}
If you load up the code and let it run, I'm willing to bet that maybe 15 seconds goes by before you pull the plugs. This is not music. This is even worse than me at age 5, banging on as many piano keys as I could. 

On a positive note, we have certainly got ample room for improvement. If you uncomment the last line of the noteSlot loop in the loop() function, there is a somewhat more pleasing effect. We are inserting a delay to play the notes in sequence – arpeggio is the musical term. By adding a random amount of delay, it starts being more interesting. For a while, anyway.

If you play with the fillRoll(RND_TAN); statement by changing the random type, you'll notice it is difficult to hear any huge difference using the random types. In the previous code, we used the random number to select a pitch. Now, the random number is being used to provide a set of switches, if you will, that turns each note either on or off. The randomness is now masked by the element of time. Since the algorithm determines when a note will sound and not which note will be heard, the ability to 'lock on' to the random pattern is harder.

If you uncomment the last two lines of the setup() function, you can more easily hear a difference, as the system will generate only notes between the rollLow and rollHi MIDI note numbers. Have fun with it, until next time, when we will have several more techniques and start beginning to believe this thing can actually make some music...
Copyright © 2015 by Bill L. Behrendt
Copyright © 2015 by Bill L. Behrendt
0 Comments



Leave a Reply.

    Picture
    The Dusty And Rusty Show!
    ISLA Instruments

    Author

    Junkbox Junkie is an electronics and computer hobbyist who delights in reuse of electronics things.
    Currently working on Arty, The Aleatoric Arduino -- a project / quest to create a music composing computer.

    Copyright © 2015 by Bill L. Behrendt

    Artificial Music
    Artificial Music
    SOUNDCLOUD

    Archives

    June 2020
    January 2018
    August 2017
    January 2016
    August 2015
    July 2015

    Categories

    All
    Aleatory
    Arduino
    Artificialmusic
    Music
    Random Music

    RSS Feed

Powered by Create your own unique website with customizable templates.