MIDI support / with-serial @ 31250 baud


#1

MIDI support would be a great addition to uLisp.

a possible quick way to to hack MIDI would be allowing 31250baud in with-serial, how easy would it be to enable this?


#2

There’s an Arduino MIDI library, so it should be pretty easy to write an extension file that adds MIDI commands to uLisp.

a possible quick way to to hack MIDI would be allowing 31250baud in with-serial, how easy would it be to enable this?

The with-serial form in uLisp simply passes the arguments to the Arduino Serial library, so if the Serial library can support 31250 baud, it should be possible. Because the with-serial baud parameter is the baud rate divided by 100 it would have to be specified as “312”. This would probably be close enough, wouldn’t it? Otherwise with-serial could convert “312” to 31250.


#3

i’ll try the 312 setting. 31250 is defined as “standard” but i’ve never tried it either side. theres a good little hookup guide here

i’ll try it on a MEGA first, altho with the REPL on serial i will have to use a different port.

to get a simple test output i’ll just send a note on, wait a small delay and then send a note off.

so for the (with-serial ( stream port [baud] ) form* ) command how would i package up a note on & a note off? can you help me with some example code please?

for midi channel 1: note on is 0x90 followed by a byte each for note num & velocity, which are in the range 0 to 127. note off is 0x80 with note num & velocity in the same ranges.

so i would send something like :
0x90 [note on]
0x40 [middle note = 64]
0x127 [max velocity]

delay

0x80 [note off]
0x40 [middle note = 64]
0x0 [min velocity]

also note that a lot of websites will say that a note off msg is a note on message with zero velocity. this is not really the way to do it, because many synths/instruments can respond to note off velocity :)

there is also an arduino usb midi library that would be amazing if you could build in support for too?

https://reference.arduino.cc/reference/en/libraries/usb-midi/


#4

You need something like:

(defun play (note del)
  (with-serial (str 1 312)
    (write-byte #x90 str)
    (write-byte note str)
    (write-byte 127 str)
    (delay del)
    (write-byte #x80 str)
    (write-byte note str)
    (write-byte 0 str)))

so to play note 64 for 1 second you’d do:

(play 64 1000)

By the way, I assume you mean 127, not 0x127?

Also, I’m not sure that a baud rate of 31200 will work.

David


#5

wow, thanks!!
hooked the Mega up unbuffered via the TXD1 pin output and combined with 2x 220 ohm resistors [one for the output & one for the 5v] & some croc clips… and amazingly i got a correct note on, delay and note off reading on my midi analyser first go!

i would recommend trying to get the ports going to 31250 standard rate, because once thers a large stream of data going it will likely be more picky…

i’ll have to do a few more tests anyway. i should be able to try out most of the basic commands now thanks to the example.

oops yes, the 127 should have been written as decimal :)


#6

50/31200 is 0.16%, which is well within the worst case 2% tolerance calculated by this article:

Determining Clock Accuracy Requirements for UART Communications

So I don’t think it will be a problem.


#7

thanks for the info and article re the accuracy, sounds fine in that case.

i did a quick test with some other messages on channel 1, and these all came up fine on my analyser

; poly aftertouch

(defun polyaft (note val)
  (with-serial (str 1 312)
    (write-byte #xA0 str)
    (write-byte note str)
    (write-byte val str)))
; channel aftertouch

(defun chaft (val)
  (with-serial (str 1 312)
    (write-byte #xD0 str)
    (write-byte val str)))
; control change

(defun controlchange (num val)
  (with-serial (str 1 312)
    (write-byte #xB0 str)
    (write-byte num str)    
    (write-byte val str)))
; program change

(defun prgchange (val)
  (with-serial (str 1 312)
    (write-byte #xC0 str)
    (write-byte val str)))
; midi transport commands:

(defun start ()
  (with-serial (str 1 312)
    (write-byte #xFA str)))

(defun continue ()
  (with-serial (str 1 312)
    (write-byte #xFB str)))        

(defun stop ()
  (with-serial (str 1 312)
    (write-byte #xFC str)))      

theres some useful tables on this page for folks interested where the common midi hex codes are listed
https://learn.sparkfun.com/tutorials/midi-tutorial/all


#8

Great - thanks! (I’ve added some formatting).


#9

Have done a slight modification so port and MIDI channel etc can be passed. Apologies, I am not sure how to format code using the form here ? I paste it in with tabs from the Arduino editor but they seem to get removed in the preview, even though the editing window shows them…

This code works fine on the Mega with some quick basic testing on a few channels, and on all 3 spare ports.

(defun play (port ch note vel offvel del)
  (with-serial (str port 312)
    (write-byte (logior #x90 (1- ch)) str)
    (write-byte note str)
    (write-byte vel str)
    (delay del)
    (write-byte (logior #x80 (1- ch)) str)
    (write-byte note str)
    (write-byte offvel str)))
; poly aftertouch

(defun polyaft (port ch note val)
  (with-serial (str port 312)
    (write-byte (logior #xA0 (1- ch)) str)
    (write-byte note str)
    (write-byte val str)))
; channel aftertouch

(defun chaft (port ch val)
  (with-serial (str port 312)
    (write-byte (logior #xD0 (1- ch)) str)
    (write-byte val str)))    
; control change

(defun controlchange (port ch num val)
  (with-serial (str port 312)
    (write-byte (logior #xB0 (1- ch)) str)
    (write-byte num str)    
    (write-byte val str))) 
; program change

(defun prgchange (port ch val)
  (with-serial (str port 312)
    (write-byte (logior #xC0 (1- ch)) str)
    (write-byte val str)))
; midi transport commands:

(defun start (port)
  (with-serial (str port 312)
    (write-byte #xFA str)))

(defun continue (port)
  (with-serial (str port 312)
    (write-byte #xFB str)))        

(defun stop (port)
  (with-serial (str port 312)
    (write-byte #xFC str)))

#10

Thanks for the MIDI code!

Apologies, I am not sure how to format code using the form here ? I paste it in with tabs from the Arduino editor but they seem to get removed in the preview, even though the editing window shows them

No problem, but click Edit this post (pencil icon) below your post if you want to see what I’ve added.


#11

aah, thanks I will look.

I just switched the Mega to test the Adafruit Grand Central M4, and I can’t get any serial out other than port 1 on pin1. I did a quick web search and saw this

where it claims only Serial port 1 is defined. Have you heard whether people are having problems with other Serial devices on this board?


#12

I think the original version of Adafruit’s Arduino core for the Grand Central M4 only defined Serial1, but there are now four Serial ports available, as follows:

Port Rx pin Tx pin
1 0 1
2 19 18
3 17 16
4 15 14

I’ll need to update uLisp to recognise the other three; I think all you need to change is one line in the uLisp source:

In the section beginning:

// Streams

change:

#if defined(ARDUINO_SAM_DUE) || defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
#define ULISP_SERIAL3

to:

#if defined(ARDUINO_SAM_DUE) || defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) || defined(ARDUINO_GRAND_CENTRAL_M4)
#define ULISP_SERIAL3

Let me know if it works,
David


#13

Thanks, it gave me these errors back =>

/Users/dave/Documents/LISP/uLisp/installs/ARM-4-4c/ulisp-arm-1st-go/ulisp-arm/ulisp-arm.ino: In function ‘int serial3read()’:
ulisp-arm:2093:37: error: ‘Serial3’ was not declared in this scope; did you mean ‘Serial’?
2093 | inline int serial3read () { while (!Serial3.available()) testescape(); return Serial3.read(); }
| ^~~~~~~
| Serial
ulisp-arm:2093:79: error: ‘Serial3’ was not declared in this scope; did you mean ‘Serial’?
2093 | inline int serial3read () { while (!Serial3.available()) testescape(); return Serial3.read(); }
| ^~~~~~~
| Serial
/Users/dave/Documents/LISP/uLisp/installs/ARM-4-4c/ulisp-arm-1st-go/ulisp-arm/ulisp-arm.ino: In function ‘int serial2read()’:
ulisp-arm:2096:37: error: ‘Serial2’ was not declared in this scope; did you mean ‘Serial’?
2096 | inline int serial2read () { while (!Serial2.available()) testescape(); return Serial2.read(); }
| ^~~~~~~
| Serial
ulisp-arm:2096:79: error: ‘Serial2’ was not declared in this scope; did you mean ‘Serial’?
2096 | inline int serial2read () { while (!Serial2.available()) testescape(); return Serial2.read(); }
| ^~~~~~~
| Serial
/Users/dave/Documents/LISP/uLisp/installs/ARM-4-4c/ulisp-arm-1st-go/ulisp-arm/ulisp-arm.ino: In function ‘void serialbegin(int, int)’:
ulisp-arm:2130:26: error: ‘Serial2’ was not declared in this scope; did you mean ‘Serial’?
2130 | else if (address == 2) Serial2.begin((long)baud*100);
| ^~~~~~~
| Serial
ulisp-arm:2131:26: error: ‘Serial3’ was not declared in this scope; did you mean ‘Serial’?
2131 | else if (address == 3) Serial3.begin((long)baud*100);
| ^~~~~~~
| Serial
/Users/dave/Documents/LISP/uLisp/installs/ARM-4-4c/ulisp-arm-1st-go/ulisp-arm/ulisp-arm.ino: In function ‘void serialend(int)’:
ulisp-arm:2147:27: error: ‘Serial2’ was not declared in this scope; did you mean ‘Serial’?
2147 | else if (address == 2) {Serial2.flush(); Serial2.end(); }
| ^~~~~~~
| Serial
ulisp-arm:2148:27: error: ‘Serial3’ was not declared in this scope; did you mean ‘Serial’?
2148 | else if (address == 3) {Serial3.flush(); Serial3.end(); }
| ^~~~~~~
| Serial
/Users/dave/Documents/LISP/uLisp/installs/ARM-4-4c/ulisp-arm-1st-go/ulisp-arm/ulisp-arm.ino: In function ‘void serial2write(char)’:
ulisp-arm:2212:37: error: ‘Serial2’ was not declared in this scope; did you mean ‘Serial’?
2212 | inline void serial2write (char c) { Serial2.write©; }
| ^~~~~~~
| Serial
/Users/dave/Documents/LISP/uLisp/installs/ARM-4-4c/ulisp-arm-1st-go/ulisp-arm/ulisp-arm.ino: In function ‘void serial3write(char)’:
ulisp-arm:2213:37: error: ‘Serial3’ was not declared in this scope; did you mean ‘Serial’?
2213 | inline void serial3write (char c) { Serial3.write©; }
| ^~~~~~~
| Serial
exit status 1
‘Serial3’ was not declared in this scope; did you mean ‘Serial’?


#15

Ah yes, when I try compiling I get the same problem. Strange – Serial2 and Serial3 definitely seem to be defined in the Arduino core.


#16

There’s something about this topic here:

https://forums.adafruit.com/viewtopic.php?p=965028


#17

Thanks!

Those 2 code snippets actually worked when sending this as a test with ports 2 & 3:

(chaft 2 2 3)

(chaft 3 2 3)

I am not sure how Serial4 would map? I dont really understand why Serial2 uses SERCOM4 and Serial 3 uses SERCOM1 names. eg here in variant.cpp, unless they’ve already used SERCOM2 and 3 for something else… in the body the naming is the same eg uses Serial2.IrqHandler() & Serial3.IrqHandler() etc

Uart Serial2( &SERCOM_SERIAL2, PIN_SERIAL2_RX, PIN_SERIAL2_TX, PAD_SERIAL2_RX, PAD_SERIAL2_TX ) ; 

void SERCOM4_0_Handler() {
  Serial2.IrqHandler();
 etc...
 }
}