Tone library music mode/key explorer...

Hi! I'm just settling on a simplistic design for a sequencer along the lines of some of the analog stuff I've been working on. It's going to be a 4 voice polyphonic, using the tone library and an arduino mega. I was going to wire it up all nutso and analog-like, but I think instead I'm going for 1 serlcd display, one pot, and one momentary (up/down/middle) toggle to handle selections; per channel (for 4 channels). So from that standpoint it'll be more or less totally open.

I have lots of ways to make an arduino tickle an oscillator (5940 and 16 pwm vactrols?), but I like the idea of

tone1.play(NOTE_A4);

in order to get a nice even-tempered note.

So what I'm aiming for is something that takes an analog input and makes that a timebase multiplier for tempo, a routine that decides to play a tone or not (or continuous), and something that sorts the array of available notes with a bitmask for musical key and musical mode (I'd use the info right on the wiki entries to sort this)...

Each voice will feed to an analog bandpass filter, controlled via an spi digital pot. The output of this filter will also be fed thru a pot, for digitally controlled attenuation. I'd like to set up ADSR for those, using the 256-step pots. Finally, all ouputs would mix to an analog onboard amp and speaker, etc.

Interfacewise, you'd be able to toggle the menu on the LCD for a given voice and adjust:

Tempo
Musical Key
Musical Scale
Pause
Voice Range
Voice Scale
Filter ADSR
Output ADSR

Using the analog pot.

Sound fun? Four polyvoices and filters picking from musical modes and keys to assemble a pseudorandom aleatronic chord/arpeggiation experimentation device. Hybrid digital analog setup. I was into the YMZ chips for a while, and may ease this into the CEM chips I have, but for now, play() is about as easy as it gets. (oh, and would work just fine with the CEM3396 chips, now that I think about it)

I'll be steadily working on hardware until it's done (probably 6 weeks in this iteration), it'll be a shield with a quad opamp for the filtering and 4 dual ISP pots for the controlling... any input is appreciated, especially with where to go for bitmasking/handling the note selection, and most likely with the ADSR. Feel free to drop me a me a line if you're dying to have one & are comfortable coding the thing with me. The hardware stuff is easy, the bitmasking will be fun to learn. I have the thing toodling away here next to me with a garland of piezos around it.

Got the array of notes to work, so the thing is picking notes now.
I think I'll work with 12 bit words for the music stuff,
C maj = 101011010101
C# maj = 110101101010; just move the pattern one bit...
now to apply this to a note picking routine.

hokay, I have built a list of all the musical modes I could get my hands on, in 12-bit strings and decimal equivalents (which I assume I will be able to work with for bitwise operations anyway)

I've entered a list of keys as an array, numbered 1-11 depending on how much to the right they'd shift the musical mode string of bits.

I need a command that rotates and shifts bits in this string, I think this may be it:

if (MusicalScale & KeySig) MusicalScale | = 0x1000;
MusicalScale >> = KeySig;

Shield design is done, I suppose I should go solder together a prototype shield but 4 equivalent bandpass filters, a DIP quad opamp, and a big fat DIP digital pot make for finicky soldering. The production shield is all small surface mount stuff.

...not so much.

I guess I'll keep rummaging around. 12 bit string. Need to rotate it.

one alternative option I think may be something like this:

int OctaveKeys[] = {C, CS, D, DS, E, F, FS, G, GS, A, AS, B };
int Major[] = {1,0,1,0,1,1,0,1,0,1,0,1};

a '1' in the bottom array decides a note is playable from the top array, via a logical NAND operation; add the value KeySig (0-11) to transpose... wraparound values, so if they go past B, they begin at C... no ringbuffer etc required and I think I can figure this out.

I think I'll try something like this when time permits:

int Persian = 3305;
{

while
(tone1.isPlaying() == false)
{
OctaveRange = 24;
OctaveOffset = 0;
KeySig = F;
ModeSig = Persian;
int SelectNote = random (OctaveRange);
if ((bitRead( ModeSig, SelectNote)) == 1)
{
PlayNote = (SelectNote + KeySig + OctaveOffset);
}
if
(PlayNote > OctaveRange)
{
PlayNote = PlayNote - OctaveRange;
}

tone1.stop();
int Voice1Duration = ((random(500)) + 100);
tone1.play(Notes[PlayNote], Voice1Duration);
randomSeed(analogRead(1));
}

works!

Any simple an concise means of going about ADSR or the sequencing?
I'm assuming I'm going to work with millisec() more than delay on this one...

boiled the note picker down further but it down't work. I need to figure out how to work with 'tone' variables in an array or something.

{

for (CurrentVoice = 1; CurrentVoice <= NumVoices; CurrentVoice++)
{
if (tone(CurrentVoice).isplaying() == false)
{
OctaveRange = CurrentOctaveRange[CurrentVoice];
OctaveOffset = CurrentOctaveOffset[CurrentVoice];
KeySig = CurrentKeySig[CurrentVoice];
ModeSig = CurrentModeSig[CurrentVoice];
int SelectNote = random (OctaveRange);
if ((bitRead( ModeSig, SelectNote)) ==1)
{
PlayNote = (SelectNote + KeySig + OctaveOffset);
}
if (PlayNote > OctaveRange)
{
PlayNote = PlayNote - OctaveRange;
}

tone(CurrentVoice).stop();
int VoiceDuration = ((random(500)) + 100);
tone(CurrentVoice).play(Notes[PlayNote], VoiceDuration);
randomSeed(analogRead(CurrentVoice));
}

How come this whole thing works if I break it up and handle all the

tone1.isPlaying()

type commands all separately,

but if I try and do this

tone(variable).isPlaying()

I get a spanking?

Well, that's pretty simple if I declare the Tone objects as an array, like this:

Tone NotePlayer[NumVoices];

Now to figure out how to time the loops. this version is glitchy:

void loop()
{

for (CurrentVoice = 0; CurrentVoice <= NumVoices; CurrentVoice++)
{
time = millis();
//if (NotePlayer[CurrentVoice].isPlaying() == false)
if (time > Lasttime + NoteLength [CurrentVoice])

{

OctaveRange = CurrentOctaveRange[CurrentVoice];
OctaveOffset = CurrentOctaveOffset[CurrentVoice];
KeySig = CurrentKeySig[CurrentVoice];
ModeSig = CurrentModeSig[CurrentVoice];
int SelectNote = random ((OctaveRange12));
if ((bitRead( ModeSig, SelectNote)) ==1)
{
PlayNote = (SelectNote + KeySig + OctaveOffset);
}
if (PlayNote > (OctaveRange
12))
{
PlayNote = PlayNote - (OctaveRange*12);
}

NotePlayer[CurrentVoice].stop();
NotePlayer[CurrentVoice].play(Notes[PlayNote]);
randomSeed(analogRead(CurrentVoice));
Lasttime = millis();

}
}
}

Well, here's the whole mess as it is making sounds now, lots of experimental this and that in there. Probably doesn't work right but spits out notes on the piezos I have hooked up. Will breadboard a digital pot to play with envelope generation for filtering and output. I think I need to get this whole thing working with millis(), I'm looking in bafflement at the TLC fades library for clues on how to handle envelope fading.

#include <Tone.h>

//Declare scale

#define scaleC 0
#define scaleC# 1
#define scaleD 2
#define scaleD# 3
#define scaleE 4
#define scaleF 5
#define scaleF# 6
#define scaleG 7
#define scaleG# 8
#define scaleA 9
#define scaleA# 10
#define scaleB 11

int NumVoices = 4;
int CurrentVoice;
int CurrentOctaveRange [] = {1, 2, 2, 1 };
int CurrentOctaveOffset [] = {4, 4, 4, 6 };
int NoteLength [4] = {1200,800,400,200};

unsigned long time;
unsigned long Lasttime = millis();
//TIME

//declare tone array!
Tone NotePlayer[4];

//SigOffsets
int C = 0;
int CS = 1;
int D = 2;
int DS = 3;
int E = 4;
int F = 5;
int FS = 6;
int G = 7;
int GS = 8;
int A = 9;
int AS = 10;
int B = 11;
int KeySig;
int ModeSig;
int OctaveRange;
int OctaveOffset;
int PlayNote;
int CurrentKeySig [] = {C, G, B, A};
int OctaveKeys[] = {C, CS, D, DS, E, F, FS, G, GS, A, AS, B };
int Major = 2773;
int Minor = 2906;
int Adonai = 3670;
int Algerian = 2937;
int Altered = 3498;
int Augmented = 2457;
int Blues = 2418;
int Chromatic = 4095;
int Arabic = 3289;
int Enigmatic = 3243;
int HalfDim = 2922;
int Bebop = 3449;
int HarmMaj = 2777;
int HarmMin = 2905;
int Hirajoshi = 3170;
int HuGypsy = 2875;
int HuMinor = 2873;
int Insen = 3162;
int Iwato = 2840;
int Locrian = 3434;
int Lydian = 2742;
int Hemitonic = 3352;
int Mixolydian = 2774;
int NeaMajor = 3413;
int NeaMinor = 3417;
int Octatonic = 3510;
int Persian = 3305;
int Phrygian = 3290;
int Prometheus = 2726;
int Slendro = 2642;
int Tritone = 3250;
int UkrDorian = 2869;
int WholeTon = 2730;

int CurrentModeSig [] = {Bebop, Bebop, Bebop, Bebop};

int Notes[] = {
NOTE_B0,
NOTE_C1, 
NOTE_CS1, 
NOTE_D1, 
NOTE_DS1, 
NOTE_E1, 
NOTE_F1,  
NOTE_FS1, 
NOTE_G1,
NOTE_GS1,
NOTE_A1,
NOTE_AS1,
NOTE_B1,
NOTE_C2,
NOTE_CS2,
NOTE_D2,
NOTE_DS2,
NOTE_E2,
NOTE_F2,
NOTE_FS2,
NOTE_G2,
NOTE_GS2,
NOTE_A2,
NOTE_AS2,
NOTE_B2,
NOTE_C3,
NOTE_CS3,
NOTE_D3,
NOTE_DS3,
NOTE_E3,
NOTE_F3,
NOTE_FS3,
NOTE_G3,
NOTE_GS3,
NOTE_A3,
NOTE_AS3,
NOTE_B3,
NOTE_C4,
NOTE_CS4,
NOTE_D4,
NOTE_DS4,
NOTE_E4,
NOTE_F4,
NOTE_FS4,
NOTE_G4,
NOTE_GS4,
NOTE_A4,
NOTE_AS4,
NOTE_B4,
NOTE_C5,
NOTE_CS5,
NOTE_D5,
NOTE_DS5,
NOTE_E5,
NOTE_F5,
NOTE_FS5,
NOTE_G5,
NOTE_GS5,
NOTE_A5,
NOTE_AS5,
NOTE_B5,
NOTE_C6,
NOTE_CS6,
NOTE_D6,
NOTE_DS6,
NOTE_E6,
NOTE_F6,
NOTE_FS6,
NOTE_G6,
NOTE_GS6,
NOTE_A6,
NOTE_AS6,
NOTE_B6,
NOTE_C7,
NOTE_CS7,
NOTE_D7,
NOTE_DS7,
NOTE_E7,
NOTE_F7,
NOTE_FS7,
NOTE_G7,
NOTE_GS7,
NOTE_A7,
NOTE_AS7,
NOTE_B7,
NOTE_C8,
NOTE_CS8,
NOTE_D8,
NOTE_DS8
 };
void setup()
{
      NotePlayer[1].begin(41);
    NotePlayer[2].begin(43);
    NotePlayer[3].begin(45);
    NotePlayer[4].begin(47);
}
void loop()
{
  
for (CurrentVoice = 0; CurrentVoice <= NumVoices-1; CurrentVoice++)
{
  //time = millis();
if (NotePlayer[CurrentVoice].isPlaying() == false)
//if (time > Lasttime + NoteLength [CurrentVoice])

{

  OctaveRange = CurrentOctaveRange[CurrentVoice];
  OctaveOffset = CurrentOctaveOffset[CurrentVoice];
  KeySig = CurrentKeySig[CurrentVoice];
  ModeSig = CurrentModeSig[CurrentVoice];
  int SelectNote = random ((OctaveRange*12));

  if ((bitRead(ModeSig, SelectNote)) ==1)
  {
    PlayNote = (SelectNote + KeySig + OctaveOffset);
  }
 // else{return;}
 
  if (PlayNote > (OctaveRange*12))
  {
    PlayNote = PlayNote - (OctaveRange*12);
  }
 
  
//NotePlayer[CurrentVoice].stop();
NotePlayer[CurrentVoice].play(Notes[PlayNote], (NoteLength [CurrentVoice]));
randomSeed(analogRead(CurrentVoice));
//Lasttime = millis();

}
//else{return;}
}
}

This can't possibly be making sounds because it can't possibly have compiled. The # character is not legal in a C name.

Pete

I'll post a video if you'd like. I can't say that it works correctly though, but it does seem to follow the key signatures and note widths.

I'm trying to work in ADSR figures now,I think this FOR loop (nested within the loop that handles the values for each tone channel) should handle the generation of the ADSR values, now for the hard part...

    for (currentADSRfunction = 0; currentADSRfunction <=1; currentADSRfunction++)
    {
    ADSRtotal[currentADSRfunction] = 
    (
    attack[currentADSRfunction]+
    decay[currentADSRfunction]+
    sustain[currentADSRfunction]+
    release[currentADSRfunction]
    );
    
    attackLength[currentADSRfunction] = 
    ((NoteLength [CurrentVoice] * attack[currentADSRfunction])/ADSRtotal[currentADSRfunction]);
    decayLength[currentADSRfunction] =
     ((NoteLength [CurrentVoice] * decay[currentADSRfunction])/ADSRtotal[currentADSRfunction]);
    sustainLength[currentADSRfunction] =
    ((NoteLength [CurrentVoice] * sustain[currentADSRfunction])/ADSRtotal[currentADSRfunction]);
    releaseLength[currentADSRfunction] =
    ((NoteLength [CurrentVoice] * release[currentADSRfunction])/ADSRtotal[currentADSRfunction]);

Here's a rough sketch for some thinking about envelopes; this would happen in the loop if I get a TRUE return on a given tone isPlaying()

    delta = (255/ attackLength[currentADSRfunction]);
    
    end_time1 = Lasttime[CurrentVoice] + attackLength[currentADSRfunction];
    
    while (millis() < end_time1)
{
    end_time2 = millis() + 1;
    while (millis() < end_time2) 
    ENVlevel[currentADSRfunction]; =  ENVlevel[currentADSRfunction] + delta;
}
    delta = (255 - sustainLevel[CurrentADSRfunction]/decayLength[currentADSRfunction]);
    end_time1 = millisec() + decayLength[currentADSRfunction];
    while (millis() < end_time1)
{
    end_time2 = millis() + 1;
    while (millis() < end_time2) 
    ENVlevel[currentADSRfunction]; =  ENVlevel[currentADSRfunction] - delta;
}
    
    end_time1 = millisec() + sustainLength[currentADSRfunction];
    while (millis() < end_time1)
{
    end_time2 = millis() + 1;
    while (millis() < end_time2) 
    ENVlevel[currentADSRfunction]; = sustainLevel[CurrentADSRfunction];
}

    delta = (sustainLevel[CurrentADSRfunction]/releaseLength[currentADSRfunction]);
    end_time1 = millisec() + releaseLength[currentADSRfunction];
    while (millis() < end_time1)
{
    end_time2 = millis() + 1;
    while (millis() < end_time2) 
    ENVlevel[currentADSRfunction]; =  ENVlevel[currentADSRfunction] - delta;
}

another thought on ADSR, this one doesn't compile yet but I'm kludging slowly away at it. Got the LCDs today so I'm making measurements for an enclosure and working a little further on the circuit. I think I'll go with a small board that is not a shield in order to keep things a little less noisy.

startTime[0] = lastTime[currentVoice];

endTime[0] = startTime[0] + attackLength[currentADSRfunction];
while (millis() <= endTime[0])
{
  ENVlevel[currentADSRfunction] = 255 * (millis() - startTime[0]) / (endTime[0] - startTime[0]);
}
startTime[1] = lastTime[currentVoice] + endTime[0];
endTime[1] = startTime[1] + decayLength[currentADSRfunction];
while (millis() <= endTime[1])
{
 ENVlevel[currentADSRfunction] = (([currentADSRfunction] - (255 + (sustainLevel[currentADSRfunction] - 255)) * ((millis() - startTime[1]) / (endTime[1] - startTime[1])));
 //this is screwed up^^^
}
startTime[2] = lastTime[currentVoice] + endTime[1];
endTime[2] = startTime[2] + decayLength[currentADSRfunction];
while (millis() =< endTime[2])
{
 ENVlevel[currentADSRfunction] = sustainLevel[currentADSRfunction];
}
startTime[3] = lastTime[currentVoice] + endTime[2];
endTime[3] = startTime[3] + decayLength[currentADSRfunction];
while (millis() =< endTime[3])
{
 ENVlevel = sustainLevel[currentADSRfunction] - ((sustainLevel[currentADSRfunction]) * (millis() - startTime[3]) / (endTime[3] - startTime[3]));
}
else if
//if (time > Lasttime + noteLength [currentVoice])
//setup and play a note...