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

Kudos to the Worldwide Improv Community

6/27/2020

2 Comments

 
Hello. My name is Rusty Bill and I am an Improv addict.

As of late June 2020, I have done fifty-plus online Improv "jams". Some may turn up their nose in distain because it isn't 'live' theatre. In reality, it is, in some ways, better than 'live'.

Sure, there are limitations. (What Improv form doesn't impose limitations?). It is hard to create a large space when you're confined to a small rectangle on-screen. It is difficult sometimes to not 'step' on someone else's line, which is made worse by the audio prioritization of the software.

But the Improv Community, by and large, has not let that stop them. Creative minds have a way of overcoming / working around / dealing with problems. In my experience, it is a success: it has far exceeded 'just doing Improv'.

Firstly, I have been in jams where 'the cast' is composed of people from all across the US: from New York, Pennsylvania, Georgia, Florida, Missouri, Nebraska, Nevada, California and Oregon to name a few. A few jams went international, with participants from the UK, Spain, France and Russia. You know people are into Improv if they are doing it at 3 AM their local time.

Secondly, there is the exposure. Instead of waiting to see one or two previously unknown (to you) troupes at some yearly Festival, you can get a daily dose of worldwide diversity online. 

Thirdly, speaking of diversity, most people I've met online are inclusive of what should, in a sane world, be included. I'd dare say all the people I've met, but you know how statistics are...

Fourthly, seeing any given troupe's local endeavors for rights equality, inclusiveness and other good ideas to make the world a better place can quickly be echoed to other parts of the country or the world. Again, creative minds such as exist in the Improv domain can (and have) come up with some pretty kicking ideas.

Fifthly, and nearly the end of this lengthy missive, is that many jams welcome the novitiate. The jam might be based in Portland, Oregon and the 'newbie' might be in Tallahassee, Florida. While there may be or have been a local 'live' group for the Tallahassee-ite to join, the exposure to all the different styles and forms and people can't hurt...

And maybe that last point is the most important one. 'Improv people', in my experience, are accepting, loving, caring people. If we can change the hearts of mankind, even one at a time, then we would not need barely useful legislation(s) to attempt to force us to change our collective minds about what is right and what isn't.

Thanks for reading...
2 Comments

Happy New Year!

1/1/2018

1 Comment

 
KordBot playlist
1 Comment

Going to KnobCon...

8/10/2017

2 Comments

 
Picture
2 Comments

Musica Turing

1/31/2016

1 Comment

 
Happy Ada Lovelace Day! First programmer and first to document that music could be created by computer :)

https://www.theguardian.com/technology/2016/oct/11/ada-lovelace-day-tech-company-diversity-equality?CMP=share_btn_tw
Picture
09/13/2016 - Update:

Musica Turing was used to create the chordal outline of a song to be used in an upcoming fan-film - Major Who - by Retcon Studios. 

You can watch the video here!

​==================================================================================================
As work slows on the ISLA KordBot, I have been doing a small amount of work building something I call "Musica Turing" which will be a full-featured aleatoric music composing program. Basically, a program that writes its own music... More on that as time allows, but for the time being, you can hear some of the results on my SoundCloud account. I put up four pieces in a playlist called Logical Emotions which you can check out at

https://www.reverbnation.com/billlbehrendt

Enjoy! And keep those cards and letters coming into ISLA! 

 https://www.facebook.com/islainstruments

~ Bill, The Junkbox Junkie
1 Comment

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
<<Previous
    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.