• 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);
}

Copyright © 2015 by Bill L. Behrendt
1 Comment
phd writers link
7/9/2019 08:07:53 pm

I owned a really colorful music box in the past. To be honest, I was a huge nerd back in my days. I always loved hearing music, especially those that just came out. In the past, we did not have anything that resembles a phone, the closest thing we have to a mobile phone was an MP3 player. I used to have my MP3 player at me wherever I go, it stored all the music that I loved listening to.

Reply



Leave a Reply.

    Picture
    The Dusty And Rusty Show!
    ISLA Instruments

    Author

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

    Copyright © 2015 by Bill L. Behrendt

    Artificial Music
    Artificial Music
    SOUNDCLOUD

    Archives

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

    Categories

    All
    Aleatory
    Arduino
    Artificialmusic
    Music
    Random Music

    RSS Feed

Powered by Create your own unique website with customizable templates.