Page 1 of 1

How to Play MIDI over SPI VS1053B

Posted: Thu 2019-01-17 2:24
by jack
Many thanks to all concerned for this forum.

I have built an ESP32/VS1053B based sytem for playing MP3 tracks with all the bells and whistles that I wanted but could not find in an off-the-shelf unit.

See following for the VS1053B module: http://www.geeetech.com/wiki/index.php/ ... th_SD_card. And the library see: https://mpflaga.github.io/Sparkfun-MP3- ... hield.html

The MP3 track navigation and play is all working fine. The system has an RTC and I would like to make use of the VS1053B MIDI instruments as alarm sounds - actually I don't want to waste these inbuilt sounds by not hearing them!

So my question is specifically how can I send MIDI commands to play notes on any of the inbuilt instruments. All transactions with the VS1053B is done via SPI.
The above library has a function:

Code: Select all

void SFEMP3Shield::SendSingleMIDInote(void)
The above function sends the following MIDI data over SPI (see SingleMIDInoteFile[] array):

Code: Select all

/*
 * Format of a MIDI file into a char arrar. Simply one note on and then off.
*/
// MIDI Event Specifics
#define MIDI_NOTE_ON             9
#define MIDI_NOTE_OFF            8

// MIDI File structure
// Header Chunk
#define MIDI_HDR_CHUNK_ID     0x4D, 0x54, 0x68, 0x64  // const for MIDI
#define MIDI_CHUNKSIZE           0,    0,    0,    6
#define MIDI_FORMAT              0,    0              // VSdsp only support Format 0!
#define MIDI_NUMBER_OF_TRACKS    0,    1              // ergo must be 1 track
#define MIDI_TIME_DIVISION       0,   96
// Track Chunk
#define MIDI_TRACK_CHUNK_ID   0x4D, 0x54, 0x72, 0x6B  // const for MIDI
#define MIDI_CHUNK_SIZE          0,    0,    0, sizeof(MIDI_EVENT_NOTE_ON) + sizeof(MIDI_EVENT_NOTE_OFF) + sizeof(MIDI_END_OF_TRACK) // hard coded with zero padded
// Events
#define MIDI_EVENT_NOTE_ON       0, (MIDI_NOTE_ON<<4) + MIDI_CHANNEL, MIDI_NOTE_NUMBER, MIDI_INTENSITY
#define MIDI_EVENT_NOTE_OFF   MIDI_NOTE_DURATION, (MIDI_NOTE_OFF<<4) + MIDI_CHANNEL, MIDI_NOTE_NUMBER, MIDI_INTENSITY
//
#define MIDI_END_OF_TRACK        0, 0xFF, 0x2F,    0

/**
 * brief a MIDI File of one Note
 *
 * This is string containing a complete MIDI format 0 file of one Note ON and then Off.
 *
 */
PROGMEM const uint8_t SingleMIDInoteFile[] = {MIDI_HDR_CHUNK_ID, MIDI_CHUNKSIZE, MIDI_FORMAT, MIDI_NUMBER_OF_TRACKS, MIDI_TIME_DIVISION, MIDI_TRACK_CHUNK_ID, MIDI_CHUNK_SIZE, MIDI_EVENT_NOTE_ON, MIDI_EVENT_NOTE_OFF, MIDI_END_OF_TRACK};
Running this function does produce a single note, it sounds like a sine-wave not an instrument (I thought the default was piano).
I have tried using the 0xC0 (instrument) command before sending the MIDI_EVENT_NOTE_ON but then I don't hear any output.
I really don't know anything much about MIDI but after studying the MIDI for awhile and trying different things still haven't got anywhere with this.
Is it too simplistic to try to modify the above array of data to produce a series of notes by an instrument of my choice?

Any help to show me what the array data needs to be to control the type of instrument + the duration of the note + it's volume. And how to specify the pause between successive notes (without using software delays - as the VS1053B is 'fed' by hardware interrupt) would be VERY much appreciated.

Also for what it's worth I'm willing to share all project code on request: MP3 player + Clock + FM Radio + TFT with touch.

Re: How to Play MIDI over SPI VS1053B

Posted: Thu 2019-01-17 16:19
by pasi
Hi,

It looks like you are trying to generate a MIDI file on the fly. Have you considered using the Real-Time MIDI mode?

The MIDI file uses a variable-length timing code, which might be where you're tripping up. Another potential issue is that the serial data interface (SDI) receives 2 bytes before they are passed to the decoder, thus with an odd alignment, the MIDI command may not be read and executed until the next command is sent.

The real-time MIDI should be much easier to use in real-time apps. See also vs1053b Real-Time MIDI application from http://www.vlsi.fi/en/support/software/ ... tions.html . It fixes one instrument and increases the receive FIFO size.

If you're not using real-time, then are the notes always the same?

Re: How to Play MIDI over SPI VS1053B

Posted: Sat 2019-01-19 0:18
by jack
Many thanks for replying Pasi.

I intend to make full use of the real-time mode in the future. In this case there are no spare pins to serialise data which I think is needed.

There's been a bit more progress in another library file I found some previously missing definitions - see below. It seems channel 9 is being used (percusion bank) and the instrument is selected by the MIDI_NOTE_NUMBER - this does work but honestly I have no idea how. The instrument was set to 'cowbell' which I didn't hear was in fact an instrument.

I would like to create on-the-fly MIDI files composed of a few notes (<10) played on different instruments. To this end it would help my understanding if you could help clarify the following points for me.

Could you spell out what you mean by:
The MIDI file uses a variable-length timing code
Does this mean the MIDI bytes sent must be aligned in some way or they must be sent in a time synched way?

Can I use real-time mode MIDI data examples in this on-the-fly mode. My problem is that I don't have a grasp what MIDI data needs to be sent - a simple example would really help ie to select a none percussion instrument, play a note of a given duration then play another note on a different instrument (without pausing).

Again, thank you for your input.

Previously missing MIDI macro definitions:

Code: Select all

/**
 * \def MIDI_CHANNEL
 * \brief A macro used to specify the MIDI channel
 *
 * Where used in the SingleMIDInoteFile array for sending quick beeps with function SFEMP3Shield::SendSingleMIDInote()
 *
 * \note Where Ch9 is reserved for Percussion Instruments with single note
 */
#define MIDI_CHANNEL             9 

/**
 * \def MIDI_NOTE_NUMBER
 * \brief A macro used to specify the MIDI note
 *
 * Where used in the SingleMIDInoteFile array for sending quick beeps with function SFEMP3Shield::SendSingleMIDInote()
 *
 * \note So for Ch9's the note is GM Bank Percussion Instrument, not actual note. e.g 56 is cowbell. This removes the necassasity to send other commands.
 */
#define MIDI_NOTE_NUMBER        35

/**
 * \def MIDI_NOTE_DURATION
 * \brief A macro used to specify the duration of the MIDI note
 *
 * Where used in the SingleMIDInoteFile array for sending quick beeps with function SFEMP3Shield::SendSingleMIDInote()
 *
 * \warning format is variable length, must keep it small. As not to break hardcoded header format
 */
#define MIDI_NOTE_DURATION     70


/**
 * \def MIDI_INTENSITY
 * \brief A macro used to specify the intensity of the MIDI note
 *
 * Value ranges from 0 to 127(full scale). Where used in the SingleMIDInoteFile array for sending both the ON and off of the quick beep with function SFEMP3Shield::SendSingleMIDInote()
 */
#define MIDI_INTENSITY         127 // Full scale.

Re: How to Play MIDI over SPI VS1053B

Posted: Mon 2019-01-21 11:26
by pasi
MIDI doesn't define note duration, it defines note on and note off, and the duration of the note is determined by the time between note on and note off.

A MIDI file consists of a header chunk and a data chunk. The data chunk consists of <delta-time> and <event> pairs. The delta time tells how much time to wait (taking tempo and time division into account) before processing the event.

See for example https://www.csie.ntu.edu.tw/~r92092/ref/midi/ and http://www.music.mcgill.ca/~ich/classes ... ormat.html .

You will be first interested in NoteOn and NoteOff, the delta time format, and then changing instruments.

MIDI channels that are set for percussions (by default channel 10 if I recall correctly, subtract 1 to get the value to use in the midi commands) do not have pitch like melodic channels. For percussion channels the pitch defines which percussion is played.

Re: How to Play MIDI over SPI VS1053B

Posted: Tue 2019-01-22 1:44
by jack
The fragments of knowledge are falling into place now, thanks for making the overview and the relevant answers clear. Now when I use channel 0 I do get piano with the given pitch. I'm sure that I can take it from here ... much appreciated.