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

9): Music Box Alpha

8/31/2015

1 Comment

 
Picture
We are moving forward, but in order to do that, we need to go back further in time from the player piano of the 1890's. Almost 100 years earlier, as a matter of fact, to 1796. Purportedly, that is when a gentleman Swiss watch-maker by the name of Antoine Favre-Salomon invented the first “comb” style music box. Long before the transistor radio, the Sony Walkman or any MP3 players, there was the portable music-playing watch.

The comb style of music box is a good choice to model in software. There are three major components to this type of music box: a “comb”, a cylinder with metal pins and an 'engine' to turn the cylinder.

The comb is made from a piece of steel into which cuts are made to create a series of 'comb teeth'. Each tooth is a different length, causing it to have a specific musical pitch.

To activate the notes of this comb 'note generator', a rotating cylinder containing metal pins was positioned so that each pin, as it rotated around, would pluck one or more of the comb's teeth.

The engine driving the cylinder was a spring, originally. Modern music boxes sometimes have tiny electric motors instead.

Each comb was created for a specific tune to be played. To that end, only notes appearing in the song were available. Unlike a piano, with 88 notes available, the music box could have as few as 8 to 12 notes available, on up. Larger models could play a small selection of tunes and the combs were created to cover all necessary notes.

 In order to model this in software, we need to implement the equivalent of a variable speed, rotating cylinder with pins that can pluck the notes of our comb's list of available notes. And, since we are using this as an aleatoric music compositional medium, we will need to be able to vary the rotation speed, pick pins at random and have a way of setting the comb note values which we desire.

It's all very doable.

After thinking a bit, I came up with a sort of wish list.

I wanted to package the “music box” in a way that the user could spawn more than one instance at a time – like having 5 music boxes playing different parts of a song, yet being connected and in sync. Or out of sync, if we like. One music box could handle the bass notes. Another could concentrate on the melody. Others could then provide some sort of harmonic relationship between the bass and melody.

I also wanted it to be usable as a single-cylinder music box, where, like the KeyRoller, all chosen notes could be played simultaneously. This more closely mimics an actual music box and can also be used as a sort of wind chime simulator.

I wanted it to be flexible enough to be able to execute unrestricted and semi-restricted compositional techniques. I also wanted it to set the stage for restricted, or constrained, composition software in the future.

I wanted it to be 'scalable'. Normally, we don't think of cutting back, but in our case, the possibilities of some smaller uses came to mind. I wanted to be able to pare down the object – scale it, in a way – for use on smaller softprint Arduinos. Think wearable music box. I also wanted to be able to expand it into something bigger, as in the Arduino Mega.

When I think of modeling something in software, I think of object-oriented programming. While we could do this in strict C++ format, I decided to water it down a bit. This code is only intended to prove out some techniques. We can always refine it, later. Although making a library out of the code is probably not in the works, if I were to go there, I would 'translate' it into a class at that point. The scope of my investigation, at least at this point, is simple research and not so much 'production' development and my code style reflects that.

A simple struct will do nicely. It can contain member variables and member functions. They are exposed to the outside world (I.e 'public members'), but as I said, this is just proof code. I promise to be careful. The advantage is that each instance of a music box will track its own notes and settings and know how to play its own sounds. The alternative would be a messy multidimensional array of all the values we want to keep track of.

We start off with a rootNote variable that will allow each mBox to have a 'home key and octave'. For instance setting rootNote to a value of 60 will allow any instantiated mBox struct to emit notes based on middle C. This variable, along with listLen and combNote[16] will define our model of a metallic comb for our mBox. rootNote is 'home base'; listLen defines the number of teeth in our comb and combNote[16] will hold the relative values for each tooth in the comb, with a maximum of 16 notes being available. To activate these notes, we have a corresponding pin[] array which will contain any notes from combNote[] where a virtual pin is found (more about pin bit-picking with our byteNote variable, in a moment). The array pinPrev[] will allow us to hold the 'last pin' values so we can send a MIDI note off event.

As an example, let's look at a listLen of 3. We will set combNote[0]=0, combNote[1]=4 and combNote[2]=7. These values will be added to the rootNote value before being sent out on the MIDI channel. So, if rootNote = 60, then we will have the ability to send out three notes: 60, 64 and 67. This corresponds to middle C, the E above middle C and the G above that. So our 'comb' has three 'teeth', representing the notes it can play: C, E and G in the middle octave. If we find a pin at position 0 and 3, we will store the notes 60 and 67 in the pin array, and send them via MIDI a little later.


I've limited the number of “comb teeth” to 16 in this code because we are using a 16 bit (unsigned int) value to store the “cylinder pins” that would be in a real music box. The byteNote value could be changed to a long type for 32 bits and the array size changed to 32, on the pin[] and pinPrev[] arrays. That would cover two and a half octaves, if we entered just scale notes, but would also be too much like the old piano roll method. Since many small music boxes have 12-18 notes, I figured 16 is a nice balance. You can, of course, experiment and change it for your own purposes.

The byteNote has a bit position for every combNote[] array value. We isolate the nth bit in byteNote and, if it is a 1, we play the note corresponding to the nth combNote[] value, added to the rootNote value. So the 'pins' on our 'cylinder' are represented by the bit pattern in byteNote. The musical notes of our metal 'comb' are the values in the combNote[] array. The notes the virtual pin would hit are stored in the pin[] array.

There are two techniques for pulling out the notes. The first way is a closer model to an actual music box. It scans all the virtual pins, stores the note values, then plays them all. This is somewhat like the KeyRoller method, in that it is possible to have all available notes fire off at the same time. Obviously, that could sound really bad, depending on the notes stored in the comb. One way to get around this is to make a combNote list of notes that always sound good together – say a pentatonic scale. A list something like {1, 3, 6, 8, 10, 13, 15, 18, 20, 22} would do nicely. This single-cylinder model is supported by the mBox function playPins().

The other technique pulls each pinned note out, one at a time, so that the cylinder only plays melodies or bass lines. We can access these pinned notes in three different ways. There are two sequential modes and a random mode supported by the mBox function playNextNote().

In the default mode, sequential descending, we start at the Most Significant Bit of byteNote and descend through the array values: starting at 15 and decrementing until we reach bit 0 / array element 0. We are using another variable, bitCount to track where we are. This mode is activated by setting the startLeft variable to true.

Setting startLeft to false enables sequential ascending mode, which allows us to ascend up the array indices, starting at element 0 and ending at element 15. In both cases, if the bit in the current position is a 1, we take the rootNote value and add it to the value held in the combNote[] array at that index position. For example (going either direction), if we find bit 5 is a 1, then we look at the value in combNote[5], add the rootNote value and send it out to the MIDI device.

Picture
The third mode is the random mode. A random value between 0 and 15 is chosen and that bit in byteNote is examined. If it is a 1, we select the array index of that bit position to generate the MIDI note output. For example, if we get a random value of 12, we would look at the 12th bit position of byteNote. If it is a 1, we choose the value held in combNote[12], add the rootNote value and send it out to the MIDI device.

We can use both techniques simultaneously: but with the catch that we need two separate instances of the mBox. One would use the playPins() method to generate a harmonic sequence, while the other would create a melodic sequence with playNextNote().

Since we can theoretically spawn a gang of music box instances (with limits imposed by available memory and processor speed), we can have a bass line coming from one instance, melody from another and a chordal harmonic structure coming from a third. Or, we could run two instances using playNextNote() for a counterpoint 'music box duo'. It's all open for wherever you wish to take it. This method actually lends itself to more organized compositions, albeit falling under the category of 'highly restricted' composition. In short, we can arrange notes for each cylinder array such that it will always be in tune, at the expense of some of the aleatoric flavor.

One of the reasons to use a structure here, is that a change to one piece of code allows every created instance to have the new functionality. For instance, you can add another variable to the structure (say byte channel; ) and create a new function (void setChannel(byte chanNo) { this->channel = chanNo; }) and then change the sendMIDI calls in playNextNote() and playPins() to add the channel number. This way, you can have multi-instrument patches on your sound source, and each mBox instance can play on its own channel. Note that the outside world MIDI channel numbers are 1 through 16. However, the MIDI command protocol numbers them 0 through 15. If you want to reference the outside world designation in code (i.e. setChannel using numbers 1 – 16), you would need to use the code { this->channel = chanNo-1; } to make sure you send on the correct MIDI channel.

Have fun !


/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: music box alpha     |
| 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

//forward declaration so structure mBox can use it
byte getARand(byte rType, byte loVal, byte hiVal);

// structure definition
struct mBox
{
  byte rootNote;
  byte listLen;
  byte combNote[16];
  byte pin[16];
  byte pinPrev[16];
  byte lastBitNote;
  unsigned int byteNote;
  signed int bitCount;
  boolean isRandomPick;
  byte rnd_type;
  byte rnd_low;
  byte rnd_high;
  boolean startLeft;
  boolean isSeqDone;
  byte rotateSpeed;
  byte rotateCount;
  boolean recycleNote;

  mBox(byte mRoot = 60, byte lSize = 8, boolean msbFirst = true) {
    this->rootNote = mRoot;
    this->listLen = lSize > 16 ? 16 : lSize; // safety net: can't be more than 16 values
    for (byte i = 0; i < this->listLen; i++) {
      this->combNote[i] = 0;
      this->pin[i] = 255;
      this->pinPrev[i] = 255;
    }
    this->byteNote = 8191;
    this->bitCount = msbFirst ? this->listLen + 1 : 0;
    this->startLeft = msbFirst;
    this->lastBitNote = 255;
    this->isSeqDone = false;
    this-> rnd_type = RND_GAUSS;
    this-> rnd_low = 0;
    this-> rnd_high = 127;
    this ->rotateSpeed = 1;
    this->rotateCount = 17;
    this->recycleNote = false;
  }

  mBox(byte mRoot, byte lSize, byte aryList[], boolean msbFirst = true) {
    this->rootNote = mRoot;
    this->listLen = lSize > 16 ? 16 : lSize; // safety net: can't be more than 16 values
    for (byte i = 0; i < this->listLen; i++) {
      this->combNote[i] = aryList[i];
      this->pin[i] = 255;
      this->pinPrev[i] = 255;
    }
    this->byteNote = 8191;
    this->bitCount = msbFirst ? this->listLen + 1 : 0;
    this->startLeft = msbFirst;
    this->lastBitNote = 255;
    this->isSeqDone = false;
    this-> rnd_type = RND_GAUSS;
    this-> rnd_low = 0;
    this-> rnd_high = 12;
    this->rotateSpeed = 1;
    this->rotateCount = 17;
    this->recycleNote = false;
  }

  void setRandom(byte rType, byte rLow = 0, byte rHigh = 12) {
    this-> rnd_type = rType;
    this-> rnd_low = rLow;
    this-> rnd_high = rHigh;
    this->isRandomPick = true;
  }

  void setList(byte arraySize, byte aryList[], byte keyRoot = 60) {
    this->rootNote = keyRoot;
    this->listLen = arraySize > 16 ? 16 : arraySize; // safety net: can't be more than 16 values
    this->bitCount = this->startLeft ? this->listLen + 1 : 0;
    for (byte i = 0; i < this->listLen ; i++) {
      this->combNote[i] = aryList[i];
    }
  }

  void putByteNote(unsigned int noteVal) {
    this->byteNote = noteVal;
    this->isSeqDone = false;
  }

  unsigned int getByteNote(void) {
    return this->byteNote;
  }

  byte getNextNote(void) {
    byte tmpBit;
    byte emit;

    if (!this->isRandomPick) {
      tmpBit = bitRead(this->byteNote, this->bitCount);
      emit = tmpBit == 1 ? this->combNote[bitCount] + this->rootNote : 255; //should flag a note off
    } else {
      tmpBit = bitRead(this->byteNote, map(getARand(this->rnd_type, this->rnd_low, this->rnd_high), 0, 127, 0, 12));
      emit = tmpBit == 1 ? this->combNote[random(0, this->listLen)] + this->rootNote : 255; //255 flags a note off
    }
    if (!this->startLeft && this->bitCount == this->listLen - 1) {
      this->isSeqDone = true;
    }
    if (this->startLeft && this->bitCount == 0) {
      this->isSeqDone = true;
    }
    this->bitCount = this->startLeft ? this->bitCount - 1 : this->bitCount + 1;
    if (!this->startLeft && this->bitCount == this->listLen) {
      this->bitCount = 0;
    }
    if (this->startLeft && this->bitCount < 0) {
      this->bitCount = this->listLen ;
    }
    return emit;
  }

  void setCylinderSpeed(byte rotate, boolean repeat = false) {
    if (rotate > 16) rotate = 16;
    this->rotateSpeed = 18 - rotate;
    this->recycleNote = repeat;
  }

  void playNextNote(void) {
    byte tmpEmit;
    boolean holdNote = false;

    this->rotateCount -= 1;
    if (this->rotateCount == 0) this->rotateCount = this->rotateSpeed;
    if (this->rotateCount % this->rotateSpeed == 1) {
      tmpEmit = getNextNote();
    } else {
      if (this->recycleNote) {
        tmpEmit = this->lastBitNote;
        holdNote = true;
      } else {
        holdNote = false;
        tmpEmit = 255;
      }
    }
    if (this->lastBitNote != 255) {
      if (!holdNote) sendMIDI(0x80, this->lastBitNote, 64);
    }
    if (!holdNote) {
      sendMIDI(tmpEmit < 255 ? 0x90 : 0x80, tmpEmit < 255 ? tmpEmit : 0, tmpEmit < 255 ? 64 : 0);
    }
    this->lastBitNote = tmpEmit;
  }

  void getPins(void) {
    byte tmpBit;
    byte rndCount = 0;

    do {
      if (!this->isRandomPick) {
        tmpBit = bitRead(this->byteNote, this->bitCount);
        this->pin[this->bitCount] = tmpBit == 1 ? this->combNote[bitCount] + this->rootNote : 255; //should flag a note off
      } else {
        tmpBit = bitRead(this->byteNote, map(getARand(this->rnd_type, this->rnd_low, this->rnd_high), 0, 127, 0, 12));
        this->pin[this->bitCount] = tmpBit == 1 ? this->combNote[random(0, this->listLen)] + this->rootNote : 255; //255 flags a note off
        if (++rndCount == this->listLen - 1) {
          this->isSeqDone = true;
        }
      }
      if (!this->startLeft && this->bitCount == this->listLen - 1) {
        this->isSeqDone = true;
      }
      if (this->startLeft && this->bitCount == 0) {
        this->isSeqDone = true;
      }
      this->bitCount = this->startLeft ? this->bitCount - 1 : this->bitCount + 1;
      if (!this->startLeft && this->bitCount == this->listLen) {
        this->bitCount = 0;
      }
      if (this->startLeft && this->bitCount < 0) {
        this->bitCount = this->listLen - 1;
      }
    } while (!this->isSeqDone);
    for (byte i = 0; i < 17; i++) {//move pin array up and load previous pin array
      this->pinPrev[i] = this->pin[i];
    }
  }

  void playPins(void) {
    byte tmpEmit;
    byte tmpHold;

    this->rotateCount -= 1;
    if (this->rotateCount == 0) this->rotateCount = this->rotateSpeed;
    if (this->rotateCount % this->rotateSpeed == 1) {
      getPins();
      for (byte i = 0; i < this->listLen; i++) {
        tmpEmit = pin[i];
        tmpHold = pinPrev[i];
        sendMIDI(tmpEmit < 255 ? 0x90 : 0x80, tmpEmit < 255 ? tmpEmit : 0, tmpEmit < 255 ? 64 : 0);
        if (tmpEmit != tmpHold) sendMIDI(0x80, tmpHold, 0);
      }
    }
  }

  void setDescend(boolean isLeft) {
    this->startLeft = isLeft;
    this->bitCount = isLeft ? this->listLen - 1 : 0;
    this->isSeqDone = false;
    this->isRandomPick = false;
  }
};

//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, 2}; // for use in getARand() function
byte noteVal;
byte lCount;
byte notePins1[] = {0, 2, 4, 5, 7, 9, 11, 12, 14, 16};
byte notePins2[] = {0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24};
byte notePins3[] = {0, 2, 4, 5, 7, 9, 11};
byte notePins4[] = { -7, -5, 0, 2, 4, 5, 7, 9, 11};
byte notePins5[] = { -7, -5, 0, 5, 7, 11};
byte notePins6[] = {-9, -6, -4, -2, 1, 3, 6, 8, 10};
byte notePins7[] = {1, 3, 6, 8, 10, 13, 15, 18, 20, 22};
mBox chordBox;
mBox melodyBox;
mBox bassBox;

//-----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(int 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 (byte((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);
  }
}

unsigned int getBigRand(byte rType1, byte rType2) {
  unsigned int b1, b2;
  b1 = getARand(rType1);
  b2 = getARand(rType2);
  return ((unsigned int)((b1 << 8) + b2));
}

void setup() {
  Serial.begin(115200); // for looking at debug things
  MIDI.begin(31250); // our software serial MIDI connection
  //turn all notes off, all channels
  allNotesOff();
  audioBootTest();
  //get a random seed from an unattached analog pin
  randomSeed(analogRead(0));
  //init the mBox sequencer
  chordBox.setList(sizeof(notePins6), notePins6, 72);
  chordBox.setRandom(RND_GAUSS2);
  chordBox.setCylinderSpeed(8, true);
  chordBox.isSeqDone = true;
  
  melodyBox.setList(sizeof(notePins7), notePins7, 72);
  melodyBox.setRandom(RND_SIN);
  melodyBox.setCylinderSpeed(16, true);
  melodyBox.isSeqDone = true;
  
  bassBox.setList(sizeof(notePins6), notePins6, 60);
  bassBox.setRandom(RND_VOSSF);
  bassBox.setCylinderSpeed(8, true);
  bassBox.isSeqDone = true;
  chordBox.putByteNote(getBigRand(RND_VOSSPREV, RND_GAUSS));
  melodyBox.putByteNote(getBigRand(RND_SIN, RND_GAUSS));
  melodyBox.putByteNote(getBigRand(RND_VOSSF, RND_GAUSS));
}

void loop() {
  //random types are:
  // RND_GAUSS, RND_GAUSS2, RND_LIN, RND_SIN, RND_TAN, RND_VOSSF, RND_VOSSPREV and RND_VOSSGAUSS
  if (chordBox.isSeqDone) chordBox.putByteNote(getBigRand(RND_VOSSPREV, RND_GAUSS));
  if (melodyBox.isSeqDone) melodyBox.putByteNote(getBigRand(RND_SIN, RND_GAUSS));
  if (bassBox.isSeqDone) melodyBox.putByteNote(getBigRand(RND_VOSSF, RND_GAUSS));
  chordBox.playPins();
  melodyBox.playNextNote();
  bassBox.playNextNote();
  delay(100);
}

1 Comment

8).  ARTY GOES INTERACTIVE

8/24/2015

1 Comment

 
Picture
One of the nice things about using an Arduino (or clone) is that, not only can you reprogram at will, you can add circuitry, as well. In this episode, I'd like to discuss making the “random gaze final” code into an interactive aleatory generator.

But first, the hardware. This can be bread-boarded or put into a fine walnut and bronze case – your choice is unlimited. I, being the Junkbox Junkie, decided to put my interactive control panel into what I call “The Seedy CD Case”. I know, I know. Sounds like a detective novel...

At any rate, I used an old cylindrical (empty) CD case and added:
  • 6 potentiometers (variable resistors)
  • 1 SPDT (run / stop) slide switch
  • 1 bi-color LED run / stop indicator (one could use two LEDs, if need be)
  • a handful of wire, solder, etc.

It uses the Arduino's analog inputs A0 – A5 and three digital GPIO lines; one input for the run/stop switch and 2 outputs for the LED indicator.

~~~Safety!~~~
As I've said before, if you know your way around a soldering iron and electronics, great. If not, please consult with someone who does. This isn't fun if you get damaged in the process!
~~~Safety!~~~

The schematic is on the Arty Schematics page. It is pretty straightforward. I managed to wire the potentiometers 'backwards' so that increasing values are obtained by moving the knob counter clockwise. I caught the goof, but then decided to keep it that way just to be different. You may do whatever you choose.

Some changes to the code need to happen to accommodate the use of six knobs and a switch. The interactive code is facilitated by using constants instead of #defines. I don't want to spawn any age-old C programming disagreements. You can find arguments on either side. Either way gets the job done, especially in the Arduino. These define our GPIO and analog port numbers for the 6 knobs and the red and green sides of the LED. It also provides constants for the three ways we can coerce the 0 to 1023 value range of the analog inputs down to our usable range of 0 to 127. More on that in just a bit.

The 'mapping' of the physical knobs can be a sticky point. In my case, I organized the six knobs in clock-wise order around the Seedy Case. Keeping with the analog port numbers, the lower left knob is knob0 and is connected to A0. Next, going clock-wise, knob1 is attached to A1. Finally, the sixth knob is knob5, attached to A5. This helps keep it all straight in my configuration. This is also flexible, because you can re-map which analog port goes to which knob, if you need to. As it stands, now, iKnobs[X] is filled by reading the analog port using the knobX constant, which in my system is connected to physical analog port AX (where X is 0 through 5).  


Picture
Whatever the values of the knobs are, when we capture them, we will store them in the iKnobs[] array. So, my lower left knob is iKnobs[0] and the lower right is iKnobs[5] and I can now pick and choose which knob I want to use for whatever.

One of the possibilities is using the knobs to set the range of our random functions. I've modified the getARand function by adding an optional 'switch' to the arguments list. interAct will be false by default, so using x=getARand(rType); will operate as it has in the past. A slight code change will be needed for cases where we used something like x=getARand(rType, value) or x=getARand(rType,loValue, hiValue); in that the interAct parameter must now be supplied as 'false'. So, they will have to be changed to x=getARand(rType, false, value) or x=getARand(rType, false, loValue, hiValue); respectively.

Interaction can be 'hard-coded', as I show in the RND_GAUSS, RND_GAUSS2, RND_SIN and RND_VOSSPREV cases in getARand(). Use this method if you are locked in to always using that knob for that function when interAct is true.

Another way to do is, with interAct being false, is to pass the knob values into the getARand() function as the loVal and hiVal arguments: x=getARand(RND_VOSSF,false,iKnobs[2],iKnobs[3]); for instance. I was shooting for maximum flexibility here, since there are so many ways to approach this whole aleatoric business.

In support of the run/stop switch and the LEDs used as running status indicators, a new function called setILED() is available. It has a default argument which is overridden at startup ('reboot') of the Arduino. Since I most deviously selected two PWM lines (digital GPIO 5 & 6) for the LED signals, this startup code will turn my bi-color LED orange if the run/stop switch is in run mode at startup. It does this using the analogWrite() function built-in to the Arduino language. Otherwise, if the Seedy Case is in run mode, the LED is green (and can be made to flicker, if you'd like). When I put it in stop mode, the LED is red.

The updateKnobs() function is what we use to grab the analog values of the knobs, and scale them to a value we can use. The first step, of course, is to read the knob values and store them in the iKnobs[] array. Then we can do one of three scaling operations.

updateKnobs() takes an argument which defaults to the upSHIFT mode of scaling. upSHIFT moves the bits of the binary number representing the knob value to the right three places, dropping off the three least significant bits of the number. A right shift operation is essentially like doing an integer divide by 2. We do it three times so it is like integer dividing 1023 by 8. It has an advantage that it cuts down on the 'jitter' that can happen as the analog signal is converted into a digital number. For example, the decimal number 955 is binary 1110111011. Pushing the three rightmost bits off into the right bit bucket makes the binary number = 1110111, or 119 decimal. 955 / 8 = 119.375, but as I said, this is an integer division, so the fractional value is simply chopped off, giving 119.

upTRUNC simply chops off the top, or three most significant bits of the number. The odd effect of this is that the number will go from 0 to 127 eight times during the turning of the knob from minimum to maximum, since we will only keep the lower 7 bits of the 10 bit number.

upSQUISH uses another built-in function called map(). This is almost, but not quite exactly like the upSHIFT function. Whereas the upSHIFT method simply drops bits, the map function does some math you can find at the reference page. The map() function, on the plus side, can let you spread the values however you want, as opposed to the upSHIFT method being pretty fixed. The downside is this post on the Arduino forum, the gist being it doesn't work 100% correctly, hence I have wrapped it with yet another built-in function, constrain().

Another new function is the checkRunStop() function, which reads the SPDT switch to determine if we are running or stopping. Actually, it just checks if we asked it to stop, but you could add an else if you have some other code you might want to run each time the switch is found to be in run mode. To stop, we call setILED() to change the color of the status LED and then turn off any notes that may be lingering. I also run the audioBootTest() function for audio confirmation of the stop. This was useful to me while recording samples, because I could hear that I had hit stop, rather than having just recorded a really long pause. It's not a necessary call in most cases.

setup() gets a few changes, too, as we define the pinMode for the two LED lines and the run/stop switch sense GPIOs. We call setILED(), and since this is at the start of everything, we set the argument of startup to true. This will make sure the LED is orange if we left the run/stop switch it in run mode before booting. Again, this was just a feature I added because I was recording samples and didn't want it to fire off in run mode before I was ready for it.

Last, but not least, we get to the main loop() function. For the basic example we run the three new functions at the head of the loop, for housekeeping and pulling in the knob values. Next, we pick a random note value, using the secondary method of passing the knob values into the getARand() function. When we send the MIDI information, the velocity value is determined from the iKnobs[2] value, making that knob function as a volume knob. Next, we manually turn off the green run LED to make it flicker as sort of a visual tempo indicator. Lastly, we use iKnobs[3] as a “tempo” control to change the speed of the delay. After turning off the MIDI note just sent, the loop repeats.
/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: random interactive  |
|                     based on random gaze final  |
| 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

//interactive constants and variables
const byte runStop = 4; //1=run; 0=stop
const byte gLED = 5;
const byte rLED = 6;
const byte knob0 = 0;
const byte knob1 = 1;
const byte knob2 = 2;
const byte knob3 = 3;
const byte knob4 = 4;
const byte knob5 = 5;
const byte upSHIFT = 0;
const byte upTRUNC = 1;
const byte upSQUISH = 2;
//array for knob values
unsigned int iKnobs[6];
//interactive

//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, 2}; // for use in getARand() function
byte noteVal;

//-----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(int 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 (byte((std * 0.707106781 * (summ - 12) + mean) * range + 1));
}

byte getARand(byte rType, boolean interAct = false, byte loVal = 0, byte hiVal = 127) {
  //get random stuffs
  // added interactive 08/08/2015
  byte retVal = 0;
  byte tmpVal;

  pi_eye += 0.05;
  switch (rType) {
    case RND_GAUSS:
      if (interAct) {
        retVal = Gauss_R(iKnobs[0]);
      } else {
        retVal = Gauss_R(random(loVal, hiVal + 1)); //must be hiVal+1 to include 127 (random is loVal to hiVal-1)
      }
      break;
    case RND_GAUSS2:
      if (interAct) {
        retVal = Gauss_R(iKnobs[1]) + Gauss_R(iKnobs[2]);
      } else {
        retVal = Gauss_R(loVal) + Gauss_R(hiVal);
      }
      break;
    case RND_LIN:
      retVal = random(loVal, hiVal + 1) - prevVal[rType];
      break;
    case RND_SIN:
      if (interAct) {
        retVal = sin(pi_eye / (iKnobs[5] + 1)) * iKnobs[4];
      } else {
        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:
      if (interAct) {
        retVal = Voss_f(prevVal[rType]) * iKnobs[4];
      } else {
        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);
  }
}

void setILED(boolean startup = false) {
  digitalWrite(rLED, LOW);
  digitalWrite(gLED, LOW);
  if (startup) {
    if (digitalRead(runStop) == 1) {
      analogWrite(rLED, 127);
      analogWrite(gLED, 200);
    } else {
      analogWrite(rLED, 0);
      analogWrite(gLED, 0);
    }
    while (digitalRead(runStop) == 1);
  } else {
    if (digitalRead(runStop) == 1) {
      digitalWrite(rLED, LOW);
      digitalWrite(gLED, HIGH);
    } else {
      digitalWrite(rLED, HIGH);
      digitalWrite(gLED, LOW);
    }
  }
}

void updateKnobs(byte style = upSHIFT) {
  iKnobs[0] = analogRead(knob0);
  iKnobs[1] = analogRead(knob1);
  iKnobs[2] = analogRead(knob2);
  iKnobs[3] = analogRead(knob3);
  iKnobs[4] = analogRead(knob4);
  iKnobs[5] = analogRead(knob5);
  switch (style) {
    case upSHIFT:
      //drop off the 3 LSB's by shifting right - cuts down on 'jitter' and keeps the value from 0-127
      iKnobs[0] = iKnobs[0] >> 3;
      iKnobs[1] = iKnobs[1] >> 3;
      iKnobs[2] = iKnobs[2] >> 3;
      iKnobs[3] = iKnobs[3] >> 3;
      iKnobs[4] = iKnobs[4] >> 3;
      iKnobs[5] = iKnobs[5] >> 3;
      break;
    case upTRUNC:
      // mask the value - will cause 'roll over' to start at 0 
      //as the value goes over 128, 256 and 512
      iKnobs[0] &= 127;
      iKnobs[1] &= 127;
      iKnobs[2] &= 127;
      iKnobs[3] &= 127;
      iKnobs[4] &= 127;
      iKnobs[5] &= 127;
      break;
    case upSQUISH:
      //use the map function (may be slow) to condense 0-1023 to 0-127
      iKnobs[0] = constrain(map(iKnobs[0], 0, 1024, 0, 128),0,127);
      iKnobs[1] = constrain(map(iKnobs[1], 0, 1024, 0, 128),0,127);
      iKnobs[2] = constrain(map(iKnobs[2], 0, 1024, 0, 128),0,127);
      iKnobs[3] = constrain(map(iKnobs[3], 0, 1024, 0, 128),0,127);
      iKnobs[4] = constrain(map(iKnobs[4], 0, 1024, 0, 128),0,127);
      iKnobs[5] = constrain(map(iKnobs[5], 0, 1024, 0, 128),0,127);
      break;
  }
}

void checkRunStop(void) {
  if (digitalRead(runStop) == LOW)
  {
    setILED();
    chanNotesOff();
    audioBootTest();
    while (digitalRead(runStop) == LOW);
    setILED();
  }
}

void setup() {
  Serial.begin(115200); // for looking at debug things
  MIDI.begin(31250); // our software serial MIDI connection
  //interactive setup
  pinMode(runStop, INPUT);
  pinMode(rLED, OUTPUT);
  pinMode(gLED, OUTPUT);
  setILED(true);
  //end interactive setup
  //turn all notes off, all channels
  allNotesOff();
  //get a random seed from an unattached analog pin
  randomSeed(analogRead(0));
}

void loop() {
  //random types are:
  // RND_GAUSS, RND_GAUSS2, RND_LIN, RND_SIN, RND_TAN, RND_VOSSF, RND_VOSSPREV and RND_VOSSGAUSS
setILED();
  updateKnobs(upSQUISH);
  checkRunStop();
  noteVal = getARand(RND_GAUSS,false,iKnobs[0],iKnobs[1]);
  sendMIDI(CMD_NOTE_ON, noteVal, iKnobs[2]);
  digitalWrite(gLED, LOW);
  delay(100+(2*iKnobs[3]));  //a tempo of sorts
  sendMIDI(CMD_NOTE_OFF, noteVal, 64);
}
Arty is now interactive!

Some things to explore are using upTRUNC and upSHIFT as the parameter to the updateKnobs() function; changing the random function selecting noteVal, including using the interAct = true feature. To randomize the loudness in the notes, try something like:

sendMIDI(CMD_NOTE_ON, noteVal, constrain((getARand(RND_SIN)/4)+(iKnobs[2]),0,127)) 

which will allow random variation in the notes hit, and still allow the knob to be used as a 'volume' control.

Have fun with it!
Copyright © 2015 by Bill L. Behrendt
1 Comment

7):  ROLLING UP THE CARPET

8/18/2015

0 Comments

 
The KeyRoller I program presented last time is a good foundation for generating music. One of the endlessly intriguing aspects of computer generated music is the endless possibilities for tweaking here and there and everywhere.

If you've been following along, you probably realize that we have been bordering on semi-restricted composition. That is, initially, we just let the random numbers out of the computer to go make whatever music came up, also known as unrestricted composition. It may be hard to think of it as music, exactly, but it is definitely aleatoric.

Now we are going to venture further into the realm of semi-restricted composition. One aspect of that is the code, of course, and whatever rules we decide to apply. This is the technical part. For the artistic part of the puzzle, we want to seek out those things that make interesting music: still random; still not classically composed western music, but a more or less new(-ish) style of creating music that blends technical acumen with artistic license.

To that end, I have made additions in KeyRoller II. I have added two global variables. All the other changes are at the bottom of the code, in the fillRoll() and the loop() functions. The two globals are byte oldBit=0; and byte devCnt=0;

The change to the fillRoll() function allows the Arduino a little more variation by adding a choice between picking a note or picking silence. In the definition of music, a rest, or not playing a note, is just as important as actually playing a note. Rests are what begin to form a deeper concept of rhythm in music. Note lengths and rests, used judiciously together, help to create motion and emotion in a piece. Whether it's fast and bouncy like Rossini's 'William Tell Overture', or slow and dreamy like Beethoven's 'Moonlight Sonata', the rhythm is the framework on which the pitches hang.

In the loop() function, we add a little variation to note lengths. This requires the use of the first global variable I added. The oldBit variable will indicate the previous bit's value as we loop through the bitPick variable, or 0 at the start of the bitPick for loop. We then compare that to the current bit picked out. If they are both a '1', we skip sending a MIDI note on event. This has the effect of letting the note ring, instead of triggering a second hit on the note in question. Ultimately, it allows notes to be stretched out over longer periods and prevents a 'choppy' sound. So a note being given a value of 15 (00001111) would last four times longer than the same note given a value of 1 (00000001);

The other addition to the loop is one we played with earlier: variations in the loudness of a note by randomizing the velocity parameter in the sendMIDI() function call. This has two effects on the music output. For sounds that are 'simple', like a piano or guitar patch, it simulates a soft or loud expression as a piano or guitar would if the strings are struck harder. However, there are patches on the sound source that may have alternative uses for the velocity value. On my equipment, the “PianoMorph” patch not only uses velocity for the loudness of the piano tone, but it modifies the background string effect's attack and sustain levels. More about that type of thing, later.

The other global value was added to stop the music output. One of the drawbacks to this system is that it is hard to guess what values to use to create any given desired effect. It's more of a 'poke and hope' situation, where we poke in some RND_fx value and then hope that it sounds good. I personally found it annoying to let the thing go on and on as I was changing values, so I added code to let the loop run a few times and then stop. Using the reset button, or uploading new code (or opening the serial monitor window) restarts the code.

So, at this point in the project, we can start adding a lot of variation by changing patches on our sound source, as well as changing code elements. We are fully in aleatoric semi-restricted composition mode! You will probably notice that the music sounds a lot better in the 'arpeggio mode'. It's also slower. When I comment out the arpeggio, I have to bump up the 'tempo' delay() to over 250 mS. With the arpeggio code active, a value of 50 to 70 mS seems about right. We will fix that in the not too distant future.

If you're just joining us, or otherwise curious about how this sounds, I've added a link to a few audio examples on my SoundCloud account. You can go there any time, but please note that there will be samples there of things yet to come.

/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: KeyRoller II        |
| 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;
byte oldBit = 0;
byte devCnt=0;

//-----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, 70, 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, 70, 64);
}

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

void allNotesOff(void) {
  for (int anoff = 0; anoff < 16; anoff++) {
    chanNotesOff(anoff);
  }
}

void setup() {
  Serial.begin(115200); // for looking at debug things
  MIDI.begin(31250); // our software serial MIDI connection
  delay(50);
  //turn all notes off, all channels
  allNotesOff();
  audioBootTest();//swap with delay to clear voices with sustain
  delay(1000);
  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 = 96;
}

void fillRoll(byte rndType) {
  chanNotesOff();
  for (int filler = rollLow; filler < rollHi + 1; filler++) {
    if (getARand(RND_SIN, 14, 148) > 85) {
      pianoRoll[filler] = getARand(rndType);
    } else {
      pianoRoll[filler] = 0;
    }
  }
}

void loop() {
  //random types are:
  // RND_GAUSS, RND_GAUSS2, RND_LIN, RND_SIN, RND_TAN, RND_VOSSF, RND_VOSSPREV and RND_VOSSGAUSS
  fillRoll(RND_VOSSPREV);
  for (bitPick = 0; bitPick < 7; 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
      oldBit = bitPick > 0 ? bitRead(pianoRoll[noteSlot], bitPick - 1) : 0; // give us the previous bit or 0 if at start
      if ((oldBit & noteVal) != 1) {
        sendMIDI(noteVal == 1 ? CMD_NOTE_ON : CMD_NOTE_OFF, noteSlot, constrain(getARand(RND_LIN), 22, 70));
      }
      if (noteSlot > 0) delay(20 + getARand(RND_GAUSS)); // create an arpeggio
    }
    delay(50);  //a tempo of sorts
  }
  if (devCnt++ == 2)
  {
    chanNotesOff();
    audioBootTest();
    while (1==1);
  }
}
KeyRoller IIA is a variation of KeyRoller, with changes only to loop(). This change randomizes the bits we pick out of the pianoRoll[] array. Where, before, we pulled out the bits in order, from least significant to most (i.e. right-to-left), we now pick one of the eight bits at random. This nullifies the concept of the oldBit variable used for note stretching, so that code is commented out. An unfortunate side effect of that is, we will sometimes trigger a note which never gets turned back off. To fix this, I've included several chanNotesOff() calls in the code that stops the endless play. I'm not sure you will need all three, but my equipment seems to need it to silence all the hanging notes, when they do appear (which is actually less often than I expected).
/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: KeyRoller IIA       |
| 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/>.
*/

//
// No changes here... removed to save space
//


void loop() {
  //random types are:
  // RND_GAUSS, RND_GAUSS2, RND_LIN, RND_SIN, RND_TAN, RND_VOSSF, RND_VOSSPREV and RND_VOSSGAUSS
  fillRoll(RND_GAUSS2);
  for (int iCnt = 0; iCnt < 8; iCnt++) { // loop 8 times
    bitPick = getARand(RND_VOSSPREV) & 0x07; //each loop, pull out a random bit slot to play
    for (noteSlot = rollLow; noteSlot < rollHi + 1; noteSlot++) { // in each note slot
      noteVal = bitRead(pianoRoll[noteSlot], bitPick); // give us a 1 or a 0
      //    oldBit = bitPick > 0 ? bitRead(pianoRoll[noteSlot], bitPick - 1) : 0; // give us the previous bit or 0 if at start
      //    if ((oldBit & noteVal) != 1) {
      sendMIDI(noteVal == 1 ? CMD_NOTE_ON : CMD_NOTE_OFF, noteSlot, constrain(getARand(RND_LIN), 22, 70));
      //    }
      if (noteSlot > 0) delay(60 + getARand(RND_GAUSS)); // create an arpeggio
    }
    delay(130);  //a tempo of sorts
  }
  if (devCnt++ == 2)
  {
    chanNotesOff();
    chanNotesOff();
    chanNotesOff();
    audioBootTest();
    while (1 == 1);
  }
}
KeyRoller IIB changes the fillRoll() function by adding two optional parameters to pass a low and high note range. In order not to break code, we make them default to the entire range when not provided. There is a method to my madness, as this step adds flexibility, allowing us to pass a range to the random function used to pick values for each note slot. Previously, the line pianoRoll[filler] = getARand(rndType); forced it to the default range of 0..127. Now we can specify a limit to the range, as loVal and hiVal are passed on to the getARand() function.

If you haven't independently discovered it, there is also a way to fill 'sections' of the pianoRoll[] array, while ignoring others. This is demonstrated here, as well. The first batch of statements in the loop() function set different rollLow and rollHi values, and then calls the fillRoll() function for those ranges. Lastly, we reset the rollLow and rollHi range values to cover all the slots – so that the noteSlot loop can pick them all up. We can do things like generate an octave of bass, an octave at mid-range and an octave at the higher end: or we could skip the mid range, or the bass, or however we want to do it. It all adds flexibility to our choices.
/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: KeyRoller IIB       |
| 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/>.
*/

//
// No changes here... removed to save space
//


void fillRoll(byte rndType, byte loVal = 0, byte hiVal = 127) {
  chanNotesOff();
  for (int filler = rollLow; filler < rollHi + 1; filler++) {
    //if (getARand(RND_SIN, 14, 148) > 85) {
    pianoRoll[filler] = getARand(rndType, loVal, hiVal);
    //} else {
    //  pianoRoll[filler] = 0;
    //}
  }
}

void loop() {
  //random types are:
  // RND_GAUSS, RND_GAUSS2, RND_LIN, RND_SIN, RND_TAN, RND_VOSSF, RND_VOSSPREV and RND_VOSSGAUSS
  rollLow = 24;
  rollHi = 36;
  fillRoll(RND_VOSSPREV, 32, 37);
  rollLow = 48;
  rollHi = 60;
  fillRoll(RND_GAUSS2, 49, 55);
  rollLow = 72;
  rollHi = 80;
  fillRoll(RND_SIN, 70, 71);
  rollLow = 24;
  rollHi = 80;
  for (int iCnt = 0; iCnt < 8; iCnt++) { // loop 8 times
    for (noteSlot = rollLow; noteSlot < rollHi + 1; noteSlot++) { // in each note slot
      bitPick = getARand(RND_SIN) & 0x07; //each note, pull out a random bit to play
      noteVal = bitRead(pianoRoll[noteSlot], bitPick); // give us a 1 or a 0
      oldBit = bitPick > 0 ? bitRead(pianoRoll[noteSlot], bitPick - 1) : 0; // give us the previous bit or 0 if at start
      if ((oldBit & noteVal) != 1) {
        sendMIDI(noteVal == 1 ? CMD_NOTE_ON : CMD_NOTE_OFF, noteSlot, constrain(getARand(RND_LIN), 22, 70));
      }
      if (noteSlot > 0) delay(20 + getARand(RND_SIN, 8, 10)); // create an arpeggio
    }
    delay(40);  //a tempo of sorts
  }
  if (devCnt++ == 2)
  {
    chanNotesOff();
    chanNotesOff();
    chanNotesOff();
    audioBootTest();
    while (1 == 1);
  }
}
KeyRoller IIC is actually a separate fork from KeyRoller IIA. It shows a completely different tactic. I include it just because it is another interesting way to go.

KeyRoller IIC sort of combines the KeyRoller method with the previous method. The pianoRoll[] array here is used only as a trigger for a note to be played. The actual note played is determined by the line:

noteVal=map(getARand(RND_VOSSF),0,127,36,42); // give a note val to play if bitPick == 1

This can be interesting for drum patches. As a matter of fact, the audio sample link has some examples of the Arduino playing a drum solo. Have fun with that until next time!
/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: KeyRoller IIC       |
| 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/>.
*/

//
// No changes here... removed to save space
//


void loop() {
  //random types are:
  // RND_GAUSS, RND_GAUSS2, RND_LIN, RND_SIN, RND_TAN, RND_VOSSF, RND_VOSSPREV and RND_VOSSGAUSS
  fillRoll(RND_VOSSF);
  //this is best with a drum patch that has individual drum assignments for each note.
    for (noteSlot = rollLow; noteSlot < rollHi + 1; noteSlot++) { // in each note slot
    for (int iCnt = 0; iCnt < 8; iCnt++) { // loop 8 times
      bitPick = iCnt; //each note, pull the next bit to play
      //bitPick = getARand(RND_SIN,8) & 0x07; //each note, pull out a random bit to play
      noteVal=map(getARand(RND_VOSSGAUSS),0,127,31,55); // give a note val to play if bitPick == 1
      oldBit = bitPick > 0 ? bitRead(pianoRoll[noteSlot], bitPick - 1) : 0; // give us the previous bit or 0 if at start
      if ((oldBit & noteVal) != 1) {
        sendMIDI(bitPick == 1 ? CMD_NOTE_ON : CMD_NOTE_OFF, noteVal, constrain(getARand(RND_LIN), 22, 70));
      }
      //if (noteSlot > 0) delay(getARand(RND_LIN,8,10)%15); // another way to create an arpeggio
    }
    delay(100);  //a tempo of sorts
  }
  if (devCnt++ == 8)
  {
    chanNotesOff();
    chanNotesOff();
    chanNotesOff();
    audioBootTest();
    while (1 == 1);
  }
}
Copyright © 2015 by Bill L. Behrendt
0 Comments

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
0 Comments

5): RANDOM THOUGHTS II

8/7/2015

0 Comments

 
Now that you have been introduced to the basis of Arty's aleatoric nature, it's time to get seriously random. While the Arduino's random() function works well, it essentially provides a statistically similar pseudo-pattern without much change. Listen to it for a while and your ear will start picking up a bland sameness as time goes on. In order to get a bigger, spicier palette for generating music by way of non-restricted, semi-restricted and restricted methods, I decided on a handful of different 'tasting' random functions to start with. The getARand() code is open ended enough to have up to 255 types, but we will start off with four basic types, with variations, for eight total functions.

These functions come to us from a PC program I wrote many years ago. I do not remember exactly how they came about, but I had done some heavy research, and they are part of the result. We will be working with a watered-down, mutilated Gaussian-like function and a simplistic approximation of something similar to an almost recognizable Voss Fractal function. In addition, we'll be using two non-random functions, just to mix things up. And for good measure, we will throw in one which relies on the random() function.

To support these new random number types, we now have a Voss_f() and a Gauss_R() function, new #defines for the random types and another global variable. The float pi_eye is for the tan() and sin() functions. Also, the array holding the previous random value, prevVal[], now has 8 initial values.

I'm not a math wizard. I do remember, vaguely, in 1991, getting some books from the local college library and coming up with the Gauss_R() and Voss_f() functions for a program called “Jazzy”. Jazzy was an MS-DOS program, written in Turbo Pascal, and was a menu driven aleatoric MIDI output program. So, this code is known to work in this context, but frankly, I would have to research it all again to explain how it works. If any math types reading this would care to comment on the inner workings of these functions, please feel free.

In this version of the code, we simply pick out an endless melody by continually throwing random MIDI note numbers out, at a constant loudness and tempo. I've included charts of the first 100 numbers which came out of my Arduino for each RND_* function using the default loVal=0 and hiVal=127. This code, random_gazeFinal.ino, allows you to get an 'audio' feel for the functions. Change the line noteVal=getARand(RND_GAUSS); to experiment with different parameters, as we did last time. Although it is easier to get acquainted with the randomness by listening to only the changing MIDI note values, you can also throw in a randomness to the velocity parameter of the sendMIDI() function and vary the tempo by adding a random value to the delay() parameter, as we did last time.

You can, at your discretion, add functions to getARand(). Just remember to add a #define and another element to the 'previous value' array. Also keep in mind the memory limitations of the Arduino. Finally, as silly as it sounds, even though 'random music' seems to rely heavily on good, solid randomness: well... it doesn't. It's all about how the random numbers are used.

There are several techniques for non-restricted composition, one of which we are using here: just throwing out a random sequence of notes. This generates a melody, of course, which, aside from being random, has no other sort of 'key feeling' attributes necessary for well-formed Western style music. We could add a second sendMIDI() and use a noteVal2 to generate a random counterpoint line. You could keep stacking more notes for a triad chord (three notes sounding at once) and so on. That method quickly blows up the number of lines of code and variable space, though, and becomes a monster to control when you want to start semi- or totally restricting the music output by adding musical composition rules.

So, now that we have a good, basic method for getting the aleatory out of our Arduino, we can begin exploring the methods of converting that into musical output in an easier way. Next time, we'll start heading to the future of our music generation by first going back in time to the late 1890's, when I will present the piano roll method of Arty's Aleatoric Abilities.

/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: random gaze final   |
| 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, 2}; // for use in getARand() function
byte noteVal;

//-----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(int 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 (byte((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 allNotesOff(void) {
   for (int anoff = 0; anoff < 16; anoff++) {
    sendMIDI(0xB0 + anoff, 0x7B, 0x00);
  } 
}

void setup() {
  Serial.begin(9600); // for looking at debug things
  MIDI.begin(31250); // our software serial MIDI connection
  //turn all notes off, all channels
  allNotesOff();
  audioBootTest();
  //get a random seed from an unattached analog pin
  randomSeed(analogRead(0));
}

void loop() {
  //random types are:
  // RND_GAUSS, RND_GAUSS2, RND_LIN, RND_SIN, RND_TAN, RND_VOSSF, RND_VOSSPREV and RND_VOSSGAUSS
  noteVal=getARand(RND_GAUSS);
  sendMIDI(CMD_NOTE_ON,noteVal,64);
  delay(200);  //a tempo of sorts
  sendMIDI(CMD_NOTE_OFF,noteVal,64);
}
Copyright © 2015 by Bill L. Behrendt
0 Comments

4): Random thoughts I

8/2/2015

0 Comments

 
Now that we have a test unit – an Arduino Uno Rev 3 clone with the Arty Bridge MIDI out – we can start looking at what makes aleatory tick. As we discussed, the word aleatory comes from the Latin for 'dice player'. So, we need to teach the Arduino (or any computer that will generate music) how to throw the dice.

Unfortunately, that's a bit more difficult than one might imagine.

Simply put, a random number sequence is supposed to be a group of numbers in which the order is not predictable. So a sequence of, say {2,4,6,8,10} would not be considered as being random since there is a common difference of 2 between each number. The set {1,8,13,14,34,99} is more than likely a random set of numbers. However, it could be that there is a relationship: it just may not be readily apparent. That is, by and large, one of the problems with randomness. When you don't want it, it rears its ugly head. When you want it, it makes itself scarce. I'm not exactly a math whiz, but I'm pretty sure there's a point at which a sequence of numbers can be considered as 'random enough'.

In our case, we have to settle with what the Arduino has, which is actually a pseudo-random number generator. For our purposes, it is 'random enough'. And if it isn't, we will pretend it is.

One aspect of the pseudo-random number is called the random seed number. We're going to follow the simple suggestion found on the Arduino reference pages and use an initializer based on looking at the value of an unconnected analog port.

I've added some things to the previous test_MIDI code. First, a global variable called noteVal, to hold our randomly chosen MIDI note. Secondly, in setup(), a call to seed the random number generator. Finally, the loop() code now sends out random notes with a random loudness at a random interval over the MIDI cable, rather than the linear 0 to 127 notes at a constant velocity (loudness) and tempo. You can spend half a day playing with all these random settings and get some output that almost entirely but not quite exactly sound like something that may approach being a distant cousin to something akin to a musical melody.

Picture
/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: random gaze 0       |
| 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

//global variables
SoftwareSerial MIDI(MIDI_IN, MIDI_OUT); // RX, TX
byte noteVal;

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 allNotesOff(void) {
   for (int anoff = 0; anoff < 16; anoff++) {
    sendMIDI(0xB0 + anoff, 0x7B, 0x00);
  } 
}

void setup() {
  Serial.begin(115200); // for looking at debug things
  MIDI.begin(31250); // our software serial MIDI connection
  //turn all notes off, all channels
  allNotesOff();
  audioBootTest();
  //get a random seed from an unattached analog pin
  randomSeed(analogRead(0));
}

void loop() {
  //trying out the Arduino pseudo-random nature
  noteVal=random(12,96); //change range to limit spread of notes do things like random(30,72)%random(60,96)
  Serial.println(noteVal);
  sendMIDI(CMD_NOTE_ON,noteVal,32+random(64)); // or use random(32,96)
  //delay(130+random(130));  //a tempo of sorts or use random(130,260)
  delay(random(130,260)); //another way to limit the tempo variation
  sendMIDI(CMD_NOTE_OFF,noteVal,64);
}
It's worth mentioning that the sound source of your destination MIDI device may not handle all notes from 0 to 127. So, in some cases, you may experience 'drop out' which means that you sent a command to turn on a note that your sound source doesn't support. Just write it off as a musical rest. Generally, you can use random(36,96) which I would think is a range supported by most MIDI devices of the last 100 years or so.

I am a proponent of modularizing code whenever possible, so I am going to present a second code listing. This will also be a framework for the future as we discover more and more ways to make Arty sing. The main change is adding a function called getARand(). This function will replace having to type in random(0,127) and allow you to experiment more freely with randomness of your own choosing. I've added three random functions and used #defines to name them RND_0 through RND_2. I've added a global variable array to hold a previous value for each random function. So, if you add your own RND_X functions, you'll also need to add a #define entry and a value in the initialized array for each addition.

The parameters passed to getARand() are a required 'rType' – which is where the #defined names come in handy. There are also two optional parameters to allow any of the functions added to be more flexible. So, getARand(RND_0), getARand(RND_0,36) and getARand(RND_0,36,96) all work because the RND_0 function is created with 'random(loVal, hiVal)'. So, in the first case, the end result of getARand(RND_0) is the same as random(0,127), because the default values are used when no values are supplied. getARand(RND_0,36,96), however, is the same as random(36,96) because both of the optional values were supplied. Finally, getARand(RND_0,36) is the same as random(36,127), with the supplied 36 overriding the default 0. Sadly, getARand(RND_0,,55) will not work and will generate an 'expected primary-expression' compiler error. The rule being, you must supply optional values from the first to the last (from left to right) without skipping over any of them.
/*
+-------------------------------------------------+
| Arty The Aleatoric Arduino: random gaze 1       |
| 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_0 0
#define RND_1 1
#define RND_2 2

//global variables
SoftwareSerial MIDI(MIDI_IN, MIDI_OUT); // RX, TX
byte prevVal[] = {72, 60, 96}; // for use in getARand() function
byte noteVal;

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

  switch (rType) {
    case RND_0:
      retVal = random(loVal,hiVal);
      break;
    case RND_1:
      retVal = random(loVal,hiVal)+prevVal[rType];
      break;
    case RND_2:
      retVal = random(loVal,hiVal)%(prevVal[rType]*3);
      break;
    default:
      retVal=random(0,127);
  }
  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 allNotesOff(void) {
   for (int anoff = 0; anoff < 16; anoff++) {
    sendMIDI(0xB0 + anoff, 0x7B, 0x00);
  } 
}

void setup() {
  Serial.begin(115200); // for looking at debug things
  MIDI.begin(31250); // our software serial MIDI connection
  //turn all notes off, all channels
  allNotesOff();
  audioBootTest();
  //get a random seed from an unattached analog pin
  randomSeed(analogRead(0));
}

void loop() {
  //rather than changing the random functions here, use getARand()
  // RND_0, RND_1 and RND_2
  noteVal=getARand(RND_2);
  sendMIDI(CMD_NOTE_ON,noteVal,64);
  delay(200);  //a tempo of sorts
  sendMIDI(CMD_NOTE_OFF,noteVal,64);
}
There are a wealth of things you can do, aside from adding your own functions. Initially, it might help to get a feel for the random numbers by listening to the differences in the default sounds. Do this by simply changing the parameter to getARand(). First, noteVal=getARand(RND_0); then noteVal=getARand(RND_1); and finally noteVal=getARand(RND_2);.

We will be adding other functions soon. In the meantime, here are a few ideas to play with.
noteVal = getARand(RND_0,12,36)+getARand(RND_1,36,60);

Recursive calls:
noteVal=getARand(RND_0, getARand(RND_1), getARand(RND_2));

Change the velocity (loudness):
sendMIDI(CMD_NOTE_ON,noteVal,64+getARand(RND_2, 0, 32));
sendMIDI(CMD_NOTE_ON,noteVal,getARand(RND_0, 64, 96));


Change the tempo / interval between notes:
delay(getARand(RND_0) + 150);

Have fun until next time! :)
Picture
Picture
Picture
Copyright © 2015 by Bill L. Behrendt
0 Comments
    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.