Develop a MIDI note music generator with interesting visuals on the Lightpad Block.
Launch the BLOCKS CODE application and open the script called MusicGen.littlefoot
. You can find the script in the littlefoot/scripts/Example Scripts
folder. If you don't have the BLOCKS CODE IDE installed on your system, please refer to the section Getting started with BLOCKS CODE for help.
Let's first start by defining global parameters for our app such as the speed, root note and chord for our music generator like so:
We also need to define global variables such as colours, bullet direction and coordinates, blob direction and coordinates as well as the last note played and the number of note generators on the screen.
In the initialise() function of our script, we first clear the display and send a MIDI CC message to turn all MIDI notes off thus resetting both the visual and audio states of the app. We also initialise all the variables to default values and set the colours we want to use.
In order to reset the state of the music generator at anytime, we implement the handleButtonDown() callback to initialise the state of the app when the side button of the Lightpad is pressed.
In the repaint() function we first clear the display and perform 4 sequential operations every time the screen is refreshed: paint blobs, draw bullets, update bullets and detect bullets. These functions are each defined later on.
Now let's take a look at drawing various elements on the screen. The blobs that start shooting the note bullets are drawn using the following paintBlob()
function:
Depending on the direction of the blob we decide to draw the shapes using different colours and we use the blendRect() function to blend the pixel of the overlapping coordinate. Similarly, we draw the bullets with the drawBullet()
function depending on the direction of the corresponding blob.
Up until now, the script would not draw anything on the screen as touch events are not handled yet and default coordinates are set to be out of bounds with the screen coordinates. Let's implement the touchStart()
callback to process touch events.
In the above function, we first convert the device coordinates into LED grid coordinates by multiplying both x and y variables by 7. Device coordinates are defined using the number of DNA connectors on the side of the device so for example in the case of a Lightpad Block, the device has a size of 2x2 and therefore the device coordinates will range from 0.0 to 2.0 on each x and y dimensions. Multiplying this range by 7 gives us the LED grid coordinates ranging from 0 to 14 inclusive.
Now using these grid coordinates, we use the touchBlob()
helper function defined below to check whether the touch event was performed on a previously drawn blob and return its index. If no previous blobs were touched, we return 0 to indicate the creation of a new one.
The changeBlob()
function is called in the touchStart() callback when an existing blob is touched and updates the index for its direction to update the orientation.
If the creation of a new blob was requested from the touchStart() callback, depending on the pressure of the touch event we spawn a different type of blob at the specified coordinate and call the corresponding function to spawn a bullet.
To spawn a bullet we simply assign the coordinates and direction of the blob to the bullet which overwrites the default values and makes the bullet appear within the screen coordinates.
The position of the bullet is updated from the repaint() function by incrementing or decrementing the corresponding x or y coordinate by the speed variable defined as an IDE parameter which consequently moves the bullet on the screen.
Now that all the visuals are implemented we have to generate some MIDI messages to trigger sounds from the host. The detectBullet()
function is called periodically in the repaint() function and performs some basic collision detection.
Here we check if any of the bullets have crossed the screen boundaries and spawn a new bullet when the old bullet becomes off-screen. Notice here we make sure the default value of -99 defined in the initialise() function does not trigger the spawning of a bullet. We also generate a MIDI note by calling the helper function midiNote()
defined hereafter:
In order to play a harmonious set of MIDI notes, the above function generates specific notes that form the chord with a root note and major/minor quality as defined in the parameters. It follows a simple set of rules as follows:
The selected note is then passed to the following helper function in order to stop the previously ringing note and start a new one using respectively the sendNoteOff() and sendNoteOn() functions with the channel number, the note number and the note velocity as arguments.
In this example, we learnt how to create a music generator app that sends MIDI messages to a host.