A lovely application of the OneShotTimer involves taking lists of tones and times to play melodies.
In this sampler, a collection of melodies are included at the end of the script, and then the oneShotTimer is used to play these, when they are selected from the menu.
We add some new variables - tempo, key and lists.
Our menu is now growing substantially with each new set of examples.
No changes, other than adding values to the new variables.
After the previous lesson, this application should be relatively easy to follow, as the time counts down the number of terms in the list.
No changes from that previously defined.
These functions are as previously defined, with just the lists of tones and times added at the end.
-- TI Innovator Init Values
platform.apilevel = '2.7'
local screen = platform.window
local date = "021517"
local isASIavailable = false
local hubStrOn, hubStrOff, msgStr, rxValue, timeStr, delay, repeats
local tempo, key, I
local list1, list2, index, dataList = {}, {}, {}, {}
var.store("index", index)
var.store("dataList", dataList)
function addMsg(input)
msgStr = input
print(msgStr)
screen:invalidate()
end
function on.construction()
--the next three lines are required for Hub connection
TI_Innovator.init(TI_InnovatorStateCallback)
TI_Innovator.setReadListener(TI_InnovatorReadCallback)
TI_Innovator.connect()
w = screen:width()
h = screen:height()
hubStrOn, hubStrOff, msgStr, rxValue = "SET LIGHT ON", "SET LIGHT OFF", "SET LIGHT OFF", ""
timeStr, delay, repeats = "", 0, 1 tempo = 1000 -- the times are relative values of note duration. This constant changes those into mSec. Change this value to alter the tempo of the song. key = 0 -- Since the musical scale is a base 2 logrithmic function. The key of the song can be tansposed by raising each note to a power of two. There are 12 piano keys in an octave. An octave is a frequency factor of two. I=1 -- this is the array index that is changed in the wait callback function list1, list2 = {}, {}
index, dataList = {}, {}
var.store("index", index)
var.store("dataList", dataList)
if TI_Innovator.isConnected() then TI_Innovator.Send('BEGIN') addMsg("TI Innovator Hub ready") end
screen:invalidate()
TI_Innovator.Send(hubStrOn) -- This is an example of sending a SET command to the Hub
TI_Innovator.Read() -- This is an example of sending a READ command to the Hub
screen:invalidate()
function str2list(input, def)
local list = {}
if input:find(def) then
list = input:split(def)
table.foreachi(list,print)
else
list = {input}
end
return list
end
function TI_InnovatorReadCallback(port, error_msg) -- this is the callback function that is defined above and catches the result of a READ command send to the Hub.
rxValue = port:getValue() or '' -- this gets the actual return value from the hub and puts it in the variable rxValue. The user may choose any variable name and also may do any calibration or ranging of the value here. Note that rxValue may be numeric, list or string (hence the utility function list2str)
if tonumber(rxValue) then rxValue = math.floor(100*rxValue+0.5)/100
dataList[#dataList+1] = tonumber(rxValue)
else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end
addMsg(hubStrOn)
if tonumber(rxValue) then voltage = (rxValue/2^14)*3.3 end -- an example of converting a raw 14 bit ADC value into voltage.
screen:invalidate()
end
function TI_InnovatorStateCallback(event)
addMsg("TI_InnovatorStateCallback")
if 'ready' == event then
TI_InnovatorConfig()
elseif "disconnected" == event then
-- user may choose to do some clean up or display a msg when the Hub is disconnected.
addMsg("TI_Innovator Hub disconnected")
end
screen:invalidate()
end
function TI_InnovatorConfig() -- this function is called from TI_InnovatorStateCallback() when a ready connection is succesful.
-- place CONNECT and other Hub startup commands here
addMsg("TI_Innovator Hub connected")
end
function wait(input)
if input then delay = input end
time_step = 0.1
if delay >= 0 then
timeStr = "Wait Time: "..delay.." seconds"
delay = math.floor((1/time_step)*(delay - time_step)+0.5)*time_step
TI_Innovator.Send(hubStrOn)
if hubStrOn:find("READ") then TI_Innovator.Read() end
if tonumber(rxValue) then -- This allows our wait function to serve as a timed data collection tool
else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end
oneShotTimer(1000*time_step,wait)
else
TI_Innovator.Send(hubStrOff)
delay = 0
end
screen:invalidate()
end
function blink(rep,waitValue)
local lightState = "OFF"
if waitValue then delay = waitValue end
if rep then repeats = rep end
if repeats > 0 then
timeStr = "blink("..(math.floor(repeats+0.5))..", "..delay..")"
if math.floor(repeats) == repeats then lightState = "ON" else lightState = "OFF" end
TI_Innovator.Send("SET LIGHT "..lightState)
oneShotTimer(1000*delay,blink)
repeats = repeats - 0.5
end
screen:invalidate()
end
function alarm(rep,waitValue)
local tone = 220
if waitValue then delay = waitValue end
if rep then repeats = rep end
if repeats > 0 then
timeStr = "alarm("..(math.floor(repeats+0.5))..", "..delay..")"
if math.floor(repeats) == repeats then tone = 440 else tone = 220 end
TI_Innovator.Send("SET SOUND "..tone)
oneShotTimer(1000*delay,alarm)
repeats = repeats - 0.5
end
screen:invalidate()
end
function waitTone()
if not list1 then list1 = var.recall("list1") end
if not list2 then list2 = var.recall("list2") end
if list1 and #list1 > 0 and list2 and #list2 > 0 then
if I <= #list1 and list1[I] and list2[I] then -- don't go past the lenght of the array
if list1[I] and list2[I] then
local str = "SET SOUND "..list1[I]*2^(key/12).." TIME "..math.floor(100/(list2[I])+0.5)/100
hubStrOn = str
TI_Innovator.Send(str)
oneShotTimer((tempo/list2[I])+20, waitTone)
I = I + 1
local ct = currentTimer
if currentTimer == nil then
timerStop()
end
if currentTimer.oneShot then
currentTimer = nil
timerStop()
end
ct.listenerHandler(unpack(ct.params))
screen:invalidate()
end
-- oneShotTimer ENDS
-- TI Innovator BEGINS
TI_Innovator = { }
function TI_Innovator.init(theStateCallback)
if not pcall(function() require 'asi' end) then
addMsg('Hub NOT available')
return
end
addMsg('Hub available')
isASIavailable = true
local HANDSHAKE_GREETING = 'ISTI\n'
local HANDSHAKE_ANSWER = 'TISTEM'
local portFoundList = { }
local state
local handshakeState
local notifyEvent
local asiStateListener
local startScanning
local stopScanning
local portFoundListener
local portStateListener
local handshake_SendListener
local handshake_readListener
local handshake_port
local TI_Innovator_port
local baudRate = asi.BAUD_RATE_DEFAULT
local readTimeout = asi.READ_TIMEOUT_DEFAULT
-- User callbacks
local stateCallback = theStateCallback
local SendCallback
local readCallback
function notifyEvent(event)
addMsg('notifying '.. event)
if stateCallback then
-- Configure port for normal use
TI_Innovator_port = reportedPort
TI_Innovator_port:setReadTimeout(readTimeout)
TI_Innovator.setWriteListener(SendCallback)
TI_Innovator.setReadListener(readCallback)
-- Notify launchpad is ready
notifyEvent('ready')
end
local result = TI_Innovator_port:write(data .. '\n')
if result then addMsg(tostring(data)..': '.. tostring(result))
else addMsg(tostring(data)) end
return result
end
function TI_Innovator.Read(bytes_to_read)
if not TI_Innovator_port then
return "No TI Hub attached"
end
return TI_Innovator_port:read(bytes_to_read)
end
function TI_Innovator.request(request)
if not TI_Innovator_port then
return "No TI Hub attached"
end
local result = TI_Innovator_port:write(request .. '\n')
if result then addMsg(tostring(request) .. ': '.. tostring(result))
else addMsg(tostring(request)) end
result = TI_Innovator_port:read()
addMsg('read() '.. tostring(result))
if result then
local isASIavailable = false
local hubStrOn, hubStrOff, msgStr, rxValue, timeStr, delay, repeats
local tempo, key, I
local list1, list2, index, dataList = {}, {}, {}, {}
var.store("index", index)
var.store("dataList", dataList)
function addMsg(input)
msgStr = input
print(msgStr)
screen:invalidate()
end
function on.construction()
--the next three lines are required for Hub connection
TI_Innovator.init(TI_InnovatorStateCallback)
TI_Innovator.setReadListener(TI_InnovatorReadCallback)
TI_Innovator.connect()
w = screen:width()
h = screen:height()
hubStrOn, hubStrOff, msgStr, rxValue = "SET LIGHT ON", "SET LIGHT OFF", "SET LIGHT OFF", ""
timeStr, delay, repeats = "", 0, 1 tempo = 1000 -- the times are relative values of note duration. This constant changes those into mSec. Change this value to alter the tempo of the song. key = 0 -- Since the musical scale is a base 2 logrithmic function. The key of the song can be tansposed by raising each note to a power of two. There are 12 piano keys in an octave. An octave is a frequency factor of two. I=1 -- this is the array index that is changed in the wait callback function list1, list2 = {}, {}
index, dataList = {}, {}
var.store("index", index)
var.store("dataList", dataList)
if TI_Innovator.isConnected() then TI_Innovator.Send('BEGIN') addMsg("TI Innovator Hub ready") end
screen:invalidate()
TI_Innovator.Send(hubStrOn) -- This is an example of sending a SET command to the Hub
TI_Innovator.Read() -- This is an example of sending a READ command to the Hub
screen:invalidate()
function str2list(input, def)
local list = {}
if input:find(def) then
list = input:split(def)
table.foreachi(list,print)
else
list = {input}
end
return list
end
function TI_InnovatorReadCallback(port, error_msg) -- this is the callback function that is defined above and catches the result of a READ command send to the Hub.
rxValue = port:getValue() or '' -- this gets the actual return value from the hub and puts it in the variable rxValue. The user may choose any variable name and also may do any calibration or ranging of the value here. Note that rxValue may be numeric, list or string (hence the utility function list2str)
if tonumber(rxValue) then rxValue = math.floor(100*rxValue+0.5)/100
else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end
addMsg(hubStrOn)
if tonumber(rxValue) then voltage = (rxValue/2^14)*3.3 end -- an example of converting a raw 14 bit ADC value into voltage.
screen:invalidate()
end
function TI_InnovatorStateCallback(event)
addMsg("TI_InnovatorStateCallback")
if 'ready' == event then
TI_InnovatorConfig()
elseif "disconnected" == event then
-- user may choose to do some clean up or display a msg when the Hub is disconnected.
addMsg("TI_Innovator Hub disconnected")
end
screen:invalidate()
end
function TI_InnovatorConfig() -- this function is called from TI_InnovatorStateCallback() when a ready connection is succesful.
-- place CONNECT and other Hub startup commands here
addMsg("TI_Innovator Hub connected")
end
function wait(input)
if input then delay = input end
time_step = 0.1
if delay >= 0 then
timeStr = "Wait Time: "..delay.." seconds"
delay = math.floor((1/time_step)*(delay - time_step)+0.5)*time_step
TI_Innovator.Send(hubStrOn)
if hubStrOn:find("READ") then TI_Innovator.Read() end
if tonumber(rxValue) then -- This allows our wait function to serve as a timed data collection tool
else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end
oneShotTimer(1000*time_step,wait)
else
TI_Innovator.Send(hubStrOff)
delay = 0
end
screen:invalidate()
end
function blink(rep,waitValue)
local lightState = "OFF"
if waitValue then delay = waitValue end
if rep then repeats = rep end
if repeats > 0 then
timeStr = "blink("..(math.floor(repeats+0.5))..", "..delay..")"
if math.floor(repeats) == repeats then lightState = "ON" else lightState = "OFF" end
TI_Innovator.Send("SET LIGHT "..lightState)
oneShotTimer(1000*delay,blink)
repeats = repeats - 0.5
end
screen:invalidate()
end
function alarm(rep,waitValue)
local tone = 220
if waitValue then delay = waitValue end
if rep then repeats = rep end
if repeats > 0 then
timeStr = "alarm("..(math.floor(repeats+0.5))..", "..delay..")"
if math.floor(repeats) == repeats then tone = 440 else tone = 220 end
TI_Innovator.Send("SET SOUND "..tone)
oneShotTimer(1000*delay,alarm)
repeats = repeats - 0.5
end
screen:invalidate()
end
function waitTone()
if not list1 then list1 = var.recall("list1") end
if not list2 then list2 = var.recall("list2") end
if list1 and #list1 > 0 and list2 and #list2 > 0 then
if I <= #list1 and list1[I] and list2[I] then -- don't go past the lenght of the array
if list1[I] and list2[I] then
local str = "SET SOUND "..list1[I]*2^(key/12).." TIME "..math.floor(100/(list2[I])+0.5)/100
hubStrOn = str
TI_Innovator.Send(str)
oneShotTimer((tempo/list2[I])+20, waitTone)
I = I + 1
local ct = currentTimer
if currentTimer == nil then
timerStop()
end
if currentTimer.oneShot then
currentTimer = nil
timerStop()
end
ct.listenerHandler(unpack(ct.params))
screen:invalidate()
end
-- oneShotTimer ENDS
-- TI Innovator BEGINS
TI_Innovator = { }
function TI_Innovator.init(theStateCallback)
if not pcall(function() require 'asi' end) then
addMsg('Hub NOT available')
return
end
addMsg('Hub available')
isASIavailable = true
local HANDSHAKE_GREETING = 'ISTI\n'
local HANDSHAKE_ANSWER = 'TISTEM'
local portFoundList = { }
local state
local handshakeState
local notifyEvent
local asiStateListener
local startScanning
local stopScanning
local portFoundListener
local portStateListener
local handshake_SendListener
local handshake_readListener
local handshake_port
local TI_Innovator_port
local baudRate = asi.BAUD_RATE_DEFAULT
local readTimeout = asi.READ_TIMEOUT_DEFAULT
-- User callbacks
local stateCallback = theStateCallback
local SendCallback
local readCallback
function notifyEvent(event)
addMsg('notifying '.. event)
if stateCallback then
-- Configure port for normal use
TI_Innovator_port = reportedPort
TI_Innovator_port:setReadTimeout(readTimeout)
TI_Innovator.setWriteListener(SendCallback)
TI_Innovator.setReadListener(readCallback)
-- Notify launchpad is ready
notifyEvent('ready')
end
local result = TI_Innovator_port:write(data .. '\n')
if result then addMsg(tostring(data)..': '.. tostring(result))
else addMsg(tostring(data)) end
return result
end
function TI_Innovator.Read(bytes_to_read)
if not TI_Innovator_port then
return "No TI Hub attached"
end
return TI_Innovator_port:read(bytes_to_read)
end
function TI_Innovator.request(request)
if not TI_Innovator_port then
return "No TI Hub attached"
end
local result = TI_Innovator_port:write(request .. '\n')
if result then addMsg(tostring(request) .. ': '.. tostring(result))
else addMsg(tostring(request)) end
result = TI_Innovator_port:read()
addMsg('read() '.. tostring(result))
if result then
We can add a whole extra dimension to our sound exploration by including a keyboard. Check out problem 2 of the supporting document for this lesson and you will find a Lua keyboard that lets you create your own Innovator music!
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.
It may be of interest to note that 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 LaunchPad lessons LP5 and LP6.
Next we have a bit of fun and build our very own Innovator Robot!