• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Building a MIDI keytar

granddad

Active Member
I wonder will design by committee ever catch on :) So the pic outputs to U1 and is scanning the U2 /U3 ...when it gets returned a low , or even 10 lows . each key is assigned a 127 count, T1 continually interrupts at specific uSecs, decrements any counter that is not (0) ? not sure now.... waiting for second contact , second contact , stops that key count , moves value to velocity for that channel , now this key is still low and needs to be ignored ? until it goes high ? how i doing ? ( will check on k for PIC internal pull ups ) [ will supply between 50 and 250uAps . bout 50k ]
 
Last edited:

wkrug

Active Member
With enough memory, you could have a variable for each key and store the value from one of the timers as a key press is detected?
When the second contact is detected, read the timer again and calculate the time, plus setting a bit to show the key has been read.
That avoids having to count separately for every key.
Not practical on a small device but with a large RAM one, a full size integer array only takes a fraction of the RAM.
I've implemented Your idea in my source code an that will work much better than mine.
The longest measured time in simulator is 500µs when all the keys ar pressed at the same time ( normally not possible ).
A normal 8 key pressing takes 200µs til 240µs.
A idle one takes about 160µs.
All on the ATMEGA 16 @ 20MHz.

Here the scanning routine, and the MiDi frame generation.
C:
//MiDi variables
#define noteon 0x90                    //Note On Byte
#define noteoff 0x80                //Note Off Byte
#define bufferlengh 168                //Bufferlengh for MiDi messages
#define transpose 20                //Transpose of MiDi notes
uint8_t midibuffer [bufferlengh];    //Buffer for MiDi messages
uint8_t midiwrite = 0;                //MiDi buffer writepointer
uint8_t    midiread = 0;                //MiDi buffer readpointer

//scan process variables
#define idle 0
#define notestart 1
#define midisend 2
#define midicomplete 3
#define midioff 4

volatile uint16_t velocitycount = 0; //Counter to built velocity

//Velocity counter for all keys
uint16_t note_velocity [56] =
{
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0
};
//State memory for all keys
uint8_t note_state [56] =
{
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0
};


//Velocity couter increments every 250µs
ISR (TIMER0_OVF_vect)
{
velocitycount++;
}


void scan_keyboard (void)
{
    uint8_t buffer1 = 0, buffer2 = 0, scanloop = 0, zeiger = 0, i = 0;
    uint16_t buffer3 = 0;
    /* Outer loop - Pulls down the lines after the diodes */
    for(scanloop=0;scanloop<8;scanloop++)
    {
  
        buffer1 = PINA;
        buffer2 = PINC;
        PORTB = ~(1<<(scanloop+1));
        //Inner loop readout start velocity and end velocity lines and generate MiDi messages
        for(i=0;i<7;i++)
        {
            //Pointer to actual note
            zeiger = i*8 + scanloop;
      
            //Scan velocity start first sample
            if((buffer1 & (1<<i)) == 0)
            {
                if(note_state[zeiger] == idle)        //Velocity counting begins
                {
                    note_state[zeiger] = notestart;
                    note_velocity[zeiger] = velocitycount;
                }
            }
            else
            {
                /*MiDi sent, back to idle or send note off
                when key pressed a little and go back to off state
                the mode go back to idle.
                When a complete scan was processed a Note On velocity 0 was sent */
                if(note_state[zeiger] == notestart)
                {
                    note_state[zeiger] = idle;
                    note_velocity[zeiger] = 0;
                }
                if(note_state[zeiger] == midicomplete)
                {
                    note_state[zeiger] = midioff;
                    note_velocity[zeiger] = 0;
                }
            }
      
            //Velocity Stop second sample generate MiDi
            if((buffer2 & (1<<i)) == 0)
            {
                if(note_state[zeiger] == notestart)
                {
                    buffer3 = velocitycount - note_velocity[zeiger];
                    if(buffer3 > 127)
                    {
                        note_velocity[zeiger] = 1;            //Timed out, velocity = 1
                    }
                    else
                    {
                        note_velocity[zeiger] = 128 - buffer3;    //Otherwise calc velocity
                    }
                note_state[zeiger] = midisend;                //Send Note On frame
                }
          
            }
        }
    }
}

void make_midi (void)
{
uint8_t i = 0;
for(i=0;i<56;i++)
{
    switch (note_state[i])
    {
        //Note on with velocity xyz is to send
        case midisend:
        midibuffer[midiwrite++] = noteon;
        if(midiwrite>bufferlengh){ midiwrite=0; }
        midibuffer[midiwrite++]= i+transpose;
        if(midiwrite>bufferlengh){ midiwrite=0; }
        midibuffer[midiwrite++]= note_velocity[i];
        if(midiwrite>bufferlengh){ midiwrite=0; }
        note_state[i]=midicomplete;
        break;
  
        //Note on with velocity 0 is to send = Note off
        case midioff:
        midibuffer[midiwrite++] = noteon;
        if(midiwrite>bufferlengh){ midiwrite=0; }
        midibuffer[midiwrite++]= i+transpose;
        if(midiwrite>bufferlengh){ midiwrite=0; }
        midibuffer[midiwrite++]= 0;
        if(midiwrite>bufferlengh){ midiwrite=0; }
        note_state[i]=idle;
        break;
    }
}
}
New results:
At a 20MIPS Controller C coding is fast enough for keybed scanning, when not many other task's would be done.
My solution would be then, to use a own main controller that get only the processed keybed data via a serial connection.
It can run much slower because the velocity scanning is done, so only the incomming MiDi data must be processed.
The serial interface can be speeded up to lower the delay time.

When the controller make much more than 20MIPS it could do more task's and no additional controller is necessary.
 
Last edited:
Like that solution, but the calculation , ? 1st down read say [004] 2nd read[003] ... has key been fast 001 or slow 128 ?
That's why I suggested a 16 bit timer and 16 bit array integer for the time captures to be stored when the first key contact is detected.

The normal working range is up to 127; anything above that should not trigger a key down event.

It also means the time "overflow" would take around sixteen seconds @ 4KHz, so the main (non interrupt) routine can loop through the array occasionally and cancel any part-press keys that will never be sent before the count gets to the point there could be any confusion.
 

granddad

Active Member
Me thinks it not as easy as first imagined :arghh:
( A) you need 3 8x8 arrays , (B) an 8(7) bit count for each key (C) an interrupt timer to start the scan regardless of how long the scan overhead took ...
 

wkrug

Active Member
has key been fast 001 or slow 128 ?
128 is not allowed, because the MSB bit is set in this case.
The MSB Bit make the difference between a comand ( then it is 1 ) and a parameter ( then it's 0 ) in MiDi Standard.
This allows the so called "Midi Running Mode".
In this case the comand was send once and then only would send the according parameters until the command changes.
At 10 note on commands, at the same channel, You can save 9 Bytes to transmit, but not all MiDi equipment does support these.

The allowed range of velocity is 127 = fast close
and 1 = slow close.
A note on with velocity value of 0 makes nearly the same then a note off signal.
The difference is, the note of can transmit a velocity too and so the sound can be different depending how fast the key was released.
I think most sample players do not use this feature and make no difference between note on velocity 0 and note of with any velocity.
 
Last edited:

wkrug

Active Member
( A) you need 3 8x8 arrays , (B) an 8(7) bit count for each key (C) an interrupt timer to start the scan regardless of how long the scan overhead took
One 16 Bit Array + an 8 Bit Array for state with 56 elements is enough.
When first contact closed store the value of a free running timer ( or counter variable ) in the according integer.
The state array will be set from idle = 0 to 1 = First contact closed.
When the second contact closes the stored value of the key would be subtracted from the actual counter value and stored back into the 16 Bit Array. Then search for not allowed values and make them proper.
The state array will be set to 2 = Ready for Generation of Midi Frame.
In next step the MiDi commands will be built and the state array change to 3 = Note On generated.
When then the first contact opens again the state array goes to 4 = Generate Note Of ( Note On velocity 0 ).
When the MiDi Note Off was generated the state array would be set to 0 = Idle again and the 16 Bit Array to 0 ( But that is not a must ).

So You have to have only 168 Bytes in RAM ( 16Bit x 56 + 8Bit x 56 ).

The whole scanning process is running than as a state machine.

In my example the MiDi generator works into a ring buffer.
Every time when the send buffer of the USART is emty the next MiDi Byte of the ring buffer would be sent.

Some errors have to be intercept:
Contact 1 closed and opened without closing contact 2.
Contact 2 closing opens and closes again without opening of contact 1.
Closing contact 2 without closing contact 1 would show an hardware error of keybed.
And not allowed values for the velocity parameter, of course.
 
Last edited:
Thread starter #110
Some errors have to be intercept:
Contact 1 closed and opened without closing contact 2.
Contact 2 closing opens and closes again without opening of contact 1.
Closing contact 2 without closing contact 1 would show an hardware error of keybed.
And not allowed values for the velocity parameter, of course.
With a commercial keybed, the first three conditions should not happen. With a home made switch matrix, they might happen.

'Anding' the velocity byte with 7fh should take care of overflow. In the case of the key strike being so soft that the velocity would be 00h,
then a value of 01h should be applied. And that's if the key on line was active.

Also, you brought up an important point: Running Status. It is described in the MIDI spec, so it needs to be implemented in software as well.

An aside note: I finished the power supply a few minutes ago. Now, I'm building the MIDI I/O. :)
 
Last edited:

granddad

Active Member
128 is not allowed, because the MSB bit is set in this case.
my point was the timer has counted 128 steps between start and stop contacts .it started at 004 and finished at 003 . what is the velocity value ? don't think giving a explanation of Midi velocity bytes is under debate yet ..
 
Thread starter #112
granddad: On a system reset, all of the keybed velocity counters are initialized to 00h.

When the first switch closes, the counting takes place for every tick that the second switch is open.

When the second switch closes, the MIDI note on message is sent and the corresponding velocity counter is initialized to 00h.

When that same switch turns off, a note off event is sent.
 

granddad

Active Member
When the second contact closes the stored value of the key would be subtracted from the actual counter value and stored back into the 16 Bit Array.
MJ Got all that ... i questioned wkrug post re a free running counter, so a negative number is possible , unless you always subtract the lowest value from the highest counter value
 
Last edited:
MJ - Don't forget to allow for a fairly large circular buffer for the MIDI transmit side, as there could be many simultaneous key down events, each generating data.

Granddad - you cannot get a negative result, even if the timer overflows between the first and second captures.

eg. if it were at 0xfff0 for the first and had wrapped around to 0x0020 for the second, the result of (0x0020 - 0xfff0) = 0x0030, still positive.
The only needed precaution is a slow background check for half-pressed keys, with difference values in the thousands.
They would need to be cancelled; the state set to 3 & velocity 0 or just state 4, using wkrug's state values.
 

granddad

Active Member
you cannot get a negative result, even if the timer overflows between the first and second captures.
Yes but any value over 7F will be 'wrong' if consider the 8 bit byte 0- 127 a positive velocity value .. so what if the result of the subtraction ( finish-start ) is say h' 8B
 

wkrug

Active Member
so what if the result of the subtraction ( finish-start ) is say h' 8B
So the pressing was very soft and a velocity of 1 will be generated.
The velocity counting scale must be fit to the used keybed.

Started 004 and finished at 003
So a overrun appears an a velocity of 1 was generated.

When You mean start was 003 and stop was 004 result = 1 ; 128 - 1 = 127 velocity.

Contact 1 closed and opened without closing contact 2.
Can be appear when the player touch a key, but don't pressing down complete.
So no note on should be applied and the scanning for this key has go to idle.
 
Thread starter #117
Can be appear when the player touch a key, but don't pressing down complete.
So no note on should be applied and the scanning for this key has go to idle.
I think (or should I say I hope) that the geometry of the rubber cups and the striker on the underside of the key
would prevent that from happening. Otherwise, you'd have to wait the max time for the second key switch to close.
 

wkrug

Active Member
Contact 1 closed and opened without closing contact 2
I took this idea from the old E510.
It counts from the "last" opening from the NC rail until the "first" closing of the NO rail to get velocity.

But You can test if this really appears.
 
Thread starter #119
Beginnings...

Here's the current prototype. There are two power supplies; a +5V and a +3.3V supply.

The MIDI I/O is completed and awaiting testing. I'll test the input with a MIDI keyboard and send some not on /off commands.

Next is the CPU. I'm still deciding what pins are needed.

Schematics to follow...

20190111_183139.jpg

20190111_183151.jpg
 

Latest threads

EE World Online Articles

Loading

 
Top