Go Down

Topic: Arduino + Musical Instrument Shield (Read 9258 times) previous topic - next topic

jbarchuk


If it's so easy could you please just show me!


I will explain in pseudocode. It's 'plain English' that describes program flow, but without being syntax-specific, and can be translated into any programming language.

I will annotate a bit. With your inexperience you don't know how to glean the clues that the Grump wrote.

First thing though, don't use 'millis' directly, not in numbers. You should define variables that give your constants names. You should define a constant only once. If you use a 'number' in 4,000 places to play 4,000 notes, and you need to change it, that's a lotta search/replace. It' also easier to read English than numbers.

For example, you do a few tests, and figure out how many millis a whole note should be for the tempo. Here's a whole note, a half note, quarter, and triplet. Notice how the -number- is used only -once- to define the timing for 4 variables.

wnote = 1000;
hnote = wnote / 2;
qnote = wnote / 4;
tnote = wnote / 3;

'Priceless,' eh?  :D

Next you need to name your tone frequencies. I don't know what that board uses but for pseudocode purposes I'll pick a format such as Na2 For Note-A-2nd octave. Nbfl3 = b-flat-3rd octave.

Next you need to define your instrument names in English. I don't know what the board uses in terms of ports, channels, voices or whatever but the point is to make 'typing  music' as painless as possible. You might set up voice names piano, cello, and frenchhorn. BUT you need to SHORTEN them to avoid typing, such as pno, clo, and fhn.

For each voice you need a timer, that does all the millis work behind the scenes, such as pnomil and clomil. those are the variables that you will set that when you turn a note -on- it will know 'when in the future' to turn it off.

Next you need a two functions. One turns a note on, and another turns it off. You'll need to translate this description ito whatever format the board uses.

To turn on a note you define a function for each voice, that accepts a note, and a time. Example...

pno (afl4, qnt) {} // I just amended what I said, shortened the typing by leaving off the N, and cutting qnote down to qnt.

The function does two things:

1) Puts the frequency into the voice note register (however the board does that).

2) Calculates the -ending- -millis- for the note, and puts that into the variable you created, such as pnomil - millis() + qnt.

Got that part? You're setting up something -now- that says turn -off- that voice at a certain time.

Personally I think I'd write this in caps because it's easier for these tired old eyes to see, but it's easy for me because I can type continuously by leaning the pinkie on the shift key and using the rest of my fingers to type:

PNO (AFL4, QNT) {appropriate code here}; // Whatever you prefer.

The function that turns a note off looks like PNOOFF() {appropriate code here};

In any case after aaaaaallllllll that setup work you now have all the Plain English interface all defined, constants, variables, and functions. 90% of ease of use of software is the UI. 90% of ease of -writing- the software is the prep work.


Your code should be
Start loop
Has the thing that triggers the note happened ... -> send note on message, set up note off pending time


What that MEANS is that for EACH NOTE you're gonna have a few lines of...

CLO (A3, HNT);
PNO (A2, QNT);
PNO (B2, QNT);

That plays three notes. That's it, keep going.

This is a HUGE PAIN IN THE ASS!!!!!!!!!!!!!! to write any significant amount of notes!!! I find it hard to believe that you're even asking about this, that there isn't already something to translates Mixcraft Piano Roll or whatever into a standard format that can be imported into a music shield sketch. This really really sucks. I'd rather spend 40 hours writing a translator than 40 hours to write just -one- song that -could- have been done on 4 hours plus a translator.

Regardless.

Quote
Is a note off pending and is it time to turn off ...... -> send note off and clear pending indicator


For each voice you test to see if the current note is ended.

if (millis() >= pnomil) {
  Call function to turn off the piano.
}

Quote
End loop


Get it? Got it? Good. :)

Grumpy_Mike

The trick with any development is to split it up into small stages. Test and go on the the next bit.
Quote
I've been trying to make sense of your input all day and haven't had any luck.

Then why have you not posted a single line of code from that effort?
This forum is here to help people it is not a code writing sevice, we get kids here all the time trying to get us to do their homework for them.

You already have some code, you posted this at the start. Refactor (rearrange ) this code so it is in the form we are talking about. Forget for the moment the note off bit, just get it so that it works with the note on. Your loop function should have just one call in it. A call to a function that checks your hardware and if it finds a note needs to be sent it sends it, if not it returns. Write that test it, does it do exactly what you want? If not try and fix it or at least think about what it does not do what you want. Either way you then post the code you have and then you are in a state to implement the next step.

You have to take baby steps, not try and rush at it.

janost

#32
Apr 20, 2014, 11:53 am Last Edit: Apr 20, 2014, 12:06 pm by janost Reason: 1
Grumpy is right.
Learn how to program in a non blocking way and the rest will come.

This is how you blink without using delay.

Code: [Select]

if ((millis()-timing)>1000) {
  //Toggle LED
timing=millis();
}


Yes, I know it fails after 50days but it's up to you to find out why :)

Grumpy_Mike

Quote
Yes, I know it fails after 50days

Not quite.
If it is continuous on for 50 days.
AND you turn the LED on within 1 second of a certain time
Then it could be that the LED will stay on.

So there is a lot of IFs going on before it would fail to turn off. In my opinion the odds against this happening are simply vanishingly small.

janost

Let's forget about the drawbacks (or bugs) it has.

It shows how to do nonblocking events.
If exact timing is not required, the millis() function isn't required either.

cooper_hu

Thanks for the help… I will spend the next few days working this out and give you guys an update. I'm frustrated, but I'm sure I'll start making sense with a little more work. I appreciate you guys taking the time to help.

-Coop

Grumpy_Mike

Good, the key is post and ask frequently.

cooper_hu

This is where I'm stuck… I'm trying to get it to work with the example code provided by Sparkfun-- replacing delay() with mills(),  except it doesn't work the same. Where am I going wrong? (I combined the Sparkfun's example code with BlinkWithoutDelay).
Code: [Select]


#include <SoftwareSerial.h>

SoftwareSerial mySerial(2, 3); // RX, TX

int val = 0;



byte note = 60; //The MIDI note value to be played
byte resetMIDI = 4; //Tied to VS1053 Reset line
byte ledPin = 13; //MIDI traffic inidicator
int  instrument = 0;



// Variables will change:
long previousMillis = 0;        // will store last time LED was updated

// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
unsigned long interval = 1000;           // interval at which to blink (milliseconds)

void setup() {
 
 Serial.begin(57600);

 //Setup soft serial for MIDI control
 mySerial.begin(31250);

 //Reset the VS1053
 pinMode(resetMIDI, OUTPUT);
 digitalWrite(resetMIDI, LOW);
 delay(100);
 digitalWrite(resetMIDI, HIGH);
 delay(100);
 talkMIDI(0xB0, 0x07, 120); //0xB0 is channel message, set channel volume to near max (127)

}

void loop()
{
 
 Serial.println("Basic Instruments");
 talkMIDI(0xB0, 0, 0x00); //Default bank GM1

 //Change to different instrument
 for(instrument = 0 ; instrument < 127 ; instrument++) {

   Serial.print(" Instrument: ");
   Serial.println(instrument, DEC);

   talkMIDI(0xC0, instrument, 0); //Set instrument number. 0xC0 is a 1 data byte command

   //Play notes from F#-0 (30) to F#-5 (90):
   for (note = 60 ; note < 70 ; note++) {
     Serial.print("N:");
     Serial.println(note, DEC);
 
// ****************************************************    
// HERE IS WHERE DELAY() IS REPLACED WITH MILLIS():

     //Note on channel 1 (0x90), some note value (note), middle velocity (0x45):
     noteOn(0, note, 60);
      unsigned long currentMillis = millis();
       if(currentMillis - previousMillis > interval) {
           
           previousMillis = currentMillis;  
             noteOff(0, note, 60);
   }
 }
   delay(100); //Delay between instruments
 }
 
 
}
 
 


//Send a MIDI note-on message.  Like pressing a piano key
//channel ranges from 0-15
void noteOn(byte channel, byte note, byte attack_velocity) {
 talkMIDI( (0x90 | channel), note, attack_velocity);
}

//Send a MIDI note-off message.  Like releasing a piano key
void noteOff(byte channel, byte note, byte release_velocity) {
 talkMIDI( (0x80 | channel), note, release_velocity);
}

//Plays a MIDI note. Doesn't check to see that cmd is greater than 127, or that data values are less than 127
void talkMIDI(byte cmd, byte data1, byte data2) {
 digitalWrite(ledPin, HIGH);
 mySerial.write(cmd);
 mySerial.write(data1);

 //Some commands only have one data byte. All cmds less than 0xBn have 2 data bytes
 //(sort of: http://253.ccarh.org/handout/midiprotocol/)
 if( (cmd & 0xF0) <= 0xB0)
   mySerial.write(data2);

}


This is what the section of the noteOn/noteOff section of Sparkfun's example code looks like:
Code: [Select]


   for (note = 60 ; note < 70 ; note++) {
     Serial.print("N:");
     Serial.println(note, DEC);
     
     //Note on channel 1 (0x90), some note value (note), middle velocity (0x45):
     noteOn(0, note, 60);
     delay(50);

     //Turn off the note with a given off/release velocity
     noteOff(0, note, 60);
     delay(50);
   }

   delay(100); //Delay between instruments
 }



Grumpy_Mike

Quote
I'm trying to get it to work with the example code provided by Sparkfun-- replacing delay() with mills(),  except it doesn't work the same. Where am I going wrong?


Here:-
replacing delay() with mills(),

You can not do that. It is not a direct replacement it is a conceptual replacement. All you are doing in your code is making a section of code behave EXACTLY like delay with all the things wrong with it. In fact it is very close to how delay is actually defined.

As mentioned many times you need to structure your code to look and see if it is time to do something, if so do it if not look to see if is the time to do something else.

You have only two things to do, turn on a note or turn off a note so there are only two if statements in your loop.
1) Is it time to produce a note - that is has a button been pressed to start one? - if so send note on message.
2) Is it time to stop your note - that is has enough time elapsed since the note started - if so send the note off message.

I see no evidence of you trying to do that in your code.

Go Up