Home ← TI-Nspire Scripting HQ ← STEM HQ ← Getting Started with TI LaunchPads ← TI LaunchPad Lesson 6
Lesson 6 - Making Music via BLE
Use your Chrome browser on Mac, Android or ChromeBook or the LightBlue App for iOS or Android to test this sketch.
Texas Instruments TI-Nspire Scripting Support Page
Download the TI-Nspire LaunchPad document for this lesson
Lesson 6 - Making Music via BLE
Lesson 7 - Real world data at your fingertips: Light, Ultrasonic Motion and more...
Lesson 8 - Build your own BLE ultrasonic motion detector for under $USD30
Sending Multiple Characters to the Board - and Making Music!
The nature of serial communication is essentially to send one character at a time. This works well for very simple situations, as we have just seen, but very soon we will need a better system. For example, suppose we wanted to send numbers like 220, and 440 (the frequencies for A3 and A4 on the musical scale)?
What we need to do is to catch each character as it is read, convert to a string, concatenate (glue) these together, until an end of string signal (like, say, a carriage return or enter key) is received. This way, instead of reading "2", then "2" then "0", we read "220".
Study the LaunchPad sketch below and see how this is accomplished.
Note some useful commands - like isDigit, string.toInt() and useful terms like \n (carriage return/new line). Note, too, the syntax for concatenating: inString += (char)recvChar;.
The Lua script is essentially the same script as that used for the last lesson - it searches for the enterKey() equivalent (string.char(10)) and uses this to recognise the BLE input.
For free options to test and explore our BLE code, we can use the LightBlue App for iOS or Android, or your Chrome browser on Mac, Android or ChromeBooks.
Hacking the Hub™
Note, in the sketch below the sound pin used is pin 34 - which is the pin allocated to the Innovator Hub speaker!
Remember: You can always reflash your Innovator Hub to return it to its original state, using the tools that TI has made available (just follow the Resources tab, and then "Keep your Innovator up to date"). So it is possible to explore coding the Hub in new ways!
TI Innovator™ Hub (with MSP432 LaunchPad)
LaunchPad Sketch
(Copy and paste into Energia)String inString = "";
int pin = 34;
int duration = 2000;void setup()
{Serial1.begin(9600);
}void loop()
{
while(Serial1.available()){
char recvChar = Serial1.read();}
if (isDigit(recvChar)) { // check if the input is a digit
inString += (char)recvChar; // add that character to a string}
if (recvChar == '\n') { // if you get a newline, play the string
int input = inString.toInt(); // convert the string to an integerinString = ""; // clear the string for new input:
if (input == 0 ) {
noTone(pin); // stop playing the tone:} else {
tone(pin, input, duration); // play that frequency eg 440}
}
}Lua Script (copy and paste full script into TI-Nspire Script Editor)
platform.apilevel = '2.5'
screen = platform.window
w = screen:width()
h = screen:height()
local date = "110216"pcall(function () require 'bleCentral' end)
require "color"
local nameList = {'HMSoft'}
local bleState = ''
local bleStatus = 'Stand by'
local peripheralName = ''
local myPeripheral = nillocal groveBLE = 'FFE1'
local keyPress = 0
local alert = nil
local myChar = nillocal textBox = D2Editor.newRichText()
local boxX, boxY, boxWidth, boxHeight
local fontSize = 12
local button1 = "Scan for BLE"
local button2 = "Reset"-- Layout Functions (resize and paint)
endfunction on.resize()
w = screen:width() or 841screen:invalidate()
h = screen:height() or 567pcall(function() ble.addStateListener(listenerCallback) end)
refreshMenu()alert = nil
keyPress = 0fontSize = math.floor(h/24 + 0.5)
fontSize = fontSize < 25 and fontSize or 24
fontSize = fontSize > 6 and fontSize or 7
local width, height = 0.4*w, 0.075*h
local spacer = 0.025*w
boxX, boxY, boxWidth, boxHeight = 0.35*w, 0.3*h, 0.3*w, 0.3*htextBox:move(boxX + spacer, boxY + spacer)
textBox:resize(boxWidth - 2*spacer, boxHeight - 2*spacer)
textBox:setFontSize(fontSize)
textBox:setText("")
textBox:setFocus(true)textBox:setTextChangeListener(
function()end)
if textBox:getText() thenend
local tmp = textBox:getText()else
if tmp and tmp:find(string.char(10)) then
alert = tmp
local alert1 = tmp:gsub(string.char(10), '') or ''
textBox:setText(alert1)
alert = nilend
if myChar and alert then myChar:write(alert) end
function on.paint(gc)
w = screen:width() or 841end
h = screen:height() or 567gc:setFont("sansserif", "b", fontSize)
local sw = gc:getStringWidth("send(")
gc:drawString("send (", boxX - 0.01*w - sw, boxY + boxHeight*0.35, "middle")
gc:drawString(")", boxX + boxWidth + 0.025*w, boxY + boxHeight*0.35, "middle")if bleState:find("ON") then gc:setColorRGB(color.blue) else gc:setColorRGB(color.red) end
gc:setFont("sansserif", "b", fontSize-5)
local sw = gc:getStringWidth(bleState)
gc:drawString(bleState, 0.05*w, 0.925*h)if bleStatus == 'Connected' then
gc:setColorRGB(color.blue)else
gc:setColorRGB(color.gray)end
for n = 1, #nameList do
local sw = gc:getStringWidth(nameList[n])end
if peripheralName:find(nameList[n]) then gc:setColorRGB(color.green) end
gc:drawString(nameList[n], 0.95*w - sw, 0.95*h - (n-1)*0.1*h, 'middle')
if keyPress ~= 0 and tonumber(alert) and tonumber(alert) ~= 0 then
local str = "PUSH Button: "..keyPressend
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.25*h, 'middle')
gc:setColorRGB(color.green)
gc:fillRect(0, 0, 0.5*w, 0.1*h)
gc:setColorRGB(color.black)
gc:drawRect(0, 0, 0.5*w, 0.1*h)
local sw = gc:getStringWidth(button1)
gc:drawString(button1, 0.25*w - sw/2, 0.05*h, "middle")gc:setColorRGB(color.red)
gc:fillRect(0.5*w, 0, 0.5*w, 0.1*h)
gc:setColorRGB(color.black)
gc:drawRect(0.5*w, 0, 0.5*w, 0.1*h)
local sw = gc:getStringWidth(button2)
gc:drawString(button2, 0.75*w - sw/2, 0.05*h, "middle")
--Menu, Keyboard and Mouse Functions--------------
function refreshMenu()
Menu={
end{"About",
{" ©2016 Texas Instruments", function() end},},
{" Version "..date, function() end},
{" Contact: steve@compasstech.com.au", function() end},
{"Controls",
{"Scan and Connect", function() peripheralOn() end},},
{"Disconnect", function() peripheralOff() end},
{"Reset", function() reset() end},
{"Select action",
{"C4", function() alert = 262 if myChar then myChar:write(alert) end end},}, } toolpalette.register(Menu)
{"A4", function() alert = 440 if myChar then myChar:write(alert) end end},
{"C5", function() alert = 523 if myChar then myChar:write(alert) end end},
{"No Tone", function() alert = 0 if myChar then myChar:write(alert) end end},
function on.enterKey()
if not bleStatus:find("Connect") thenend
peripheralOn()else
peripheralOff()end
function on.escapeKey()
reset()end
function reset()
on.resize()end
if myChar then myChar:write(0) end
screen:invalidate()
function on.mouseUp(x, y)
if x < 0.5*w and y < 0.1*h thenend
on.enterKey()end
if x > 0.5*w and y < 0.1*h then
reset()end
screen:invalidate()
-- BLE General Functions -----------
function listenerCallback(state, scriptError)
if state == ble.ON thenend
bleState = 'BLE ON'elseif state == ble.OFF then
bleState = 'BLE OFF'elseif state == ble.RESETTING then
bleState = 'BLE RESET'elseif state == ble.UNSUPPORTED then
bleState = 'UNSUPPORTED'end
if scriptError then
print('Error message: BLE not supported')end
screen:invalidate()
function peripheralOn()
bleCentral.startScanning(callbackScan)
bleStatus = 'Scanning'
screen:invalidate()end
function peripheralOff()
bleCentral.stopScanning() if myPeripheral then
myPeripheral:disconnect()
endend
bleStatus = 'Stand by'
peripheralName = ''
reset()
screen:invalidate()
function callbackScan(peripheral)
if peripheral ~= nil thenend
peripheralName = peripheral:getName()end
for n =1, #nameList do
if peripheralName and peripheralName:find(nameList[n]) thenend
peripheral:connect(callbackConnect)end
screen:invalidate()
function callbackConnect(peripheral, event)
endif event == bleCentral.CONNECTED then
bleCentral.stopScanning()
bleStatus = 'Connected'
myPeripheral = peripheral
button1 = bleStatus
peripheral:discoverServices(callbackServices)elseif event == bleCentral.DISCONNECTED then
bleStatus = 'Disconnected'end
peripheralName = ''
button1 = "Scan for BLE"
screen:invalidate()
function callbackServices(peripheral)
endif peripheral ~= nil and peripheral:getState() and peripheral:getState() == bleCentral.CONNECTED then
endlocal services = peripheral:getServices()
for _,service in ipairs(services) do
service:discoverCharacteristics(callbackCharacteristics)
end
screen:invalidate()
-- BLE Specific Functions ---------
function callbackCharacteristics(service)
endlocal characteristicsList = service:getCharacteristics()
for _,characteristic in ipairs(characteristicsList) doif characteristic:getUUID() == groveBLE thenend
myChar = characteristicend
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)
function callbackCharacteristic(characteristic)
endif characteristic:getUUID() == groveBLE then
endlocal value = characteristic:getValue()
if value then
local groveList = value:split(string.char(10)) or {}end
if groveList and #groveList > 0 then
for g = 1, #groveList doend
if groveList[g]:find("P") thenend
alert = groveList[g]:gsub("P", "")elseif groveList[g] ~= '' then
keyPress = alert
break
alert = groveList[g]else
break
alert = 0end
screen:invalidate()
platform.apilevel = '2.5'
screen = platform.window
w = screen:width()
h = screen:height()
local date = "110216"pcall(function () require 'bleCentral' end)
require "color"
local nameList = {'HMSoft'}
local bleState = ''
local bleStatus = 'Stand by'
local peripheralName = ''
local myPeripheral = nillocal groveBLE = 'FFE1'
local keyPress = 0
local alert = nil
local myChar = nillocal textBox = D2Editor.newRichText()
local boxX, boxY, boxWidth, boxHeight
local fontSize = 12
local button1 = "Scan for BLE"
local button2 = "Reset"-- Layout Functions (resize and paint)
endfunction on.resize()
screen:invalidate()w = screen:width() or 841
h = screen:height() or 567pcall(function() ble.addStateListener(listenerCallback) end)
refreshMenu()alert = nil
keyPress = 0fontSize = math.floor(h/24 + 0.5)
fontSize = fontSize < 25 and fontSize or 24
fontSize = fontSize > 6 and fontSize or 7
local width, height = 0.4*w, 0.075*h
local spacer = 0.025*w
boxX, boxY, boxWidth, boxHeight = 0.35*w, 0.3*h, 0.3*w, 0.3*htextBox:move(boxX + spacer, boxY + spacer)
textBox:resize(boxWidth - 2*spacer, boxHeight - 2*spacer)
textBox:setFontSize(fontSize)
textBox:setText("")
textBox:setFocus(true)textBox:setTextChangeListener(
function()end)
if textBox:getText() thenend
local tmp = textBox:getText()else
if tmp and tmp:find(string.char(10)) then
alert = tmp
local alert1 = tmp:gsub(string.char(10), '') or ''
textBox:setText(alert1)
alert = nilend
if myChar and alert then myChar:write(alert) end
function on.paint(gc)
w = screen:width() or 841
h = screen:height() or 567gc:setFont("sansserif", "b", fontSize)
local sw = gc:getStringWidth("send(")
gc:drawString("send (", boxX - 0.01*w - sw, boxY + boxHeight*0.35, "middle")
gc:drawString(")", boxX + boxWidth + 0.025*w, boxY + boxHeight*0.35, "middle")if bleState:find("ON") then gc:setColorRGB(color.blue) else gc:setColorRGB(color.red) end
gc:setFont("sansserif", "b", fontSize-5)
local sw = gc:getStringWidth(bleState)
gc:drawString(bleState, 0.05*w, 0.925*h)if bleStatus == 'Connected' then
gc:setColorRGB(color.blue)else
gc:setColorRGB(color.gray)end
for n = 1, #nameList do
local sw = gc:getStringWidth(nameList[n])
if peripheralName:find(nameList[n]) then gc:setColorRGB(color.green) end
gc:drawString(nameList[n], 0.95*w - sw, 0.95*h - (n-1)*0.1*h, 'middle')end
if keyPress ~= 0 and tonumber(alert) and tonumber(alert) ~= 0 then
local str = "PUSH Button: "..keyPressend
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.25*h, 'middle')
gc:setColorRGB(color.green)
gc:fillRect(0, 0, 0.5*w, 0.1*h)
gc:setColorRGB(color.black)
gc:drawRect(0, 0, 0.5*w, 0.1*h)
local sw = gc:getStringWidth(button1)
gc:drawString(button1, 0.25*w - sw/2, 0.05*h, "middle")gc:setColorRGB(color.red)
gc:fillRect(0.5*w, 0, 0.5*w, 0.1*h)
gc:setColorRGB(color.black)
gc:drawRect(0.5*w, 0, 0.5*w, 0.1*h)
local sw = gc:getStringWidth(button2)
gc:drawString(button2, 0.75*w - sw/2, 0.05*h, "middle")end
--Menu, Keyboard and Mouse Functions--------------
function refreshMenu()
Menu={
end{"About",
{" ©2016 Texas Instruments", function() end},},
{" Version "..date, function() end},
{" Contact: steve@compasstech.com.au", function() end},
{"Controls",
{"Scan and Connect", function() peripheralOn() end},},
{"Disconnect", function() peripheralOff() end},
{"Reset", function() reset() end},
{"Select action",
{"C4", function() alert = 262 if myChar then myChar:write(alert) end end},}, } toolpalette.register(Menu)
{"A4", function() alert = 440 if myChar then myChar:write(alert) end end},
{"C5", function() alert = 523 if myChar then myChar:write(alert) end end},
{"No Tone", function() alert = 0 if myChar then myChar:write(alert) end end},
function on.enterKey()
if not bleStatus:find("Connect") then
peripheralOn()else
peripheralOff()end
end
function on.escapeKey()
reset()
end
function reset()
on.resize()
if myChar then myChar:write(0) end
screen:invalidate()end
function on.mouseUp(x, y)
if x < 0.5*w and y < 0.1*h then
on.enterKey()end
if x > 0.5*w and y < 0.1*h then
reset()end
screen:invalidate()
end
-- BLE General Functions -----------
function listenerCallback(state, scriptError)
if state == ble.ON then
bleState = 'BLE ON'elseif state == ble.OFF then
bleState = 'BLE OFF'elseif state == ble.RESETTING then
bleState = 'BLE RESET'elseif state == ble.UNSUPPORTED then
bleState = 'UNSUPPORTED'end
if scriptError then
print('Error message: BLE not supported')end
screen:invalidate()
end
function peripheralOn()
bleCentral.startScanning(callbackScan)
bleStatus = 'Scanning'
screen:invalidate()end
function peripheralOff()
bleCentral.stopScanning()
if myPeripheral then
myPeripheral:disconnect()
endbleStatus = 'Stand by'
peripheralName = ''
reset()
screen:invalidate()end
function callbackScan(peripheral)
if peripheral ~= nil thenend
peripheralName = peripheral:getName()end
for n =1, #nameList do
if peripheralName and peripheralName:find(nameList[n]) thenend
peripheral:connect(callbackConnect)end
screen:invalidate()
function callbackConnect(peripheral, event)
if event == bleCentral.CONNECTED then
bleCentral.stopScanning()
bleStatus = 'Connected'
myPeripheral = peripheral
button1 = bleStatus
peripheral:discoverServices(callbackServices)elseif event == bleCentral.DISCONNECTED then
bleStatus = 'Disconnected'end
peripheralName = ''
button1 = "Scan for BLE"
screen:invalidate()
end
function callbackServices(peripheral)
if peripheral ~= nil and peripheral:getState() and peripheral:getState() == bleCentral.CONNECTED then
endlocal services = peripheral:getServices()
for _,service in ipairs(services) do
service:discoverCharacteristics(callbackCharacteristics)
end
screen:invalidate()
end
-- BLE Specific Functions ---------
function callbackCharacteristics(service)
endlocal characteristicsList = service:getCharacteristics()
for _,characteristic in ipairs(characteristicsList) doif characteristic:getUUID() == groveBLE thenend
myChar = characteristicend
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)
function callbackCharacteristic(characteristic)
endif characteristic:getUUID() == groveBLE then
endlocal value = characteristic:getValue()
if value then
local groveList = value:split(string.char(10)) or {}end
if groveList and #groveList > 0 then
for g = 1, #groveList doend
if groveList[g]:find("P") thenend
alert = groveList[g]:gsub("P", "")elseif groveList[g] ~= '' then
keyPress = alert
break
alert = groveList[g]else
break
alert = 0end
screen:invalidate()
A Practical Application
By adding a Lua-based keyboard, these scripts become the basis for an interesting and extendable document. Using variables to transfer information across the windows means that the keyboard can send tones to the speaker, and can even be used to drive a graphical interpretation of the different notes.
All based upon a small and inexpensive LaunchPad board and BLE module!
We can add a whole extra dimension to our sound exploration by including a keyboard. Check out problem 2 of the attached document and you will find a Lua keyboard that lets you create your own Innovator OR BLE music!
This same document will work with both the TI Innovator Hub and with the TI-Nspire iPad Apps using BLE (BlueTooth Low Energy) and the TI MSP432 LaunchPad. Use the free Energia software to flash the attached sketch to the LaunchPad via USB, then follow the guides outlined in this and the previous LaunchPad lesson.
There are also options to explore some of the interesting applications of the harmonic mean to creating alternative tunings, as developed in Mathematics: A Search for Harmony.
Or (if you are using Chrome browser on Mac, Android or Chromebook, or the WebBLE app on iPad) you might just try this for yourself!
BLE LaunchPad Controls
BLE LaunchPad Arc Piano
Home ← TI-Nspire Scripting HQ ← STEM HQ ← Getting Started with TI LaunchPads ← TI LaunchPad Lesson 6