# Example of playing sounds with a specific note (e.g. C5# or F4b)

In the following sample you can select a note, octave and accidental via buttons and play the sound. There is a function inside which calculates the half steps from the note given as string, e.g. “C5#”:

calcHalfStepsFromNote(note)

The result is used in a magic formula to calculate the frequency and then the Codea Frequency used in the sound command. I have used the formula from the ABCplayer, thanks to @Fred for pointing me to it.

Main tab part 1/1:

``````--
-- Sample how to play a note by name
-- =================================
--
-- The central formula can calculate the StartFrequency
-- (between 0.0 and 1.0) for
-- the sound function from a half-tone stepping. Calculation
-- used from ABCplayer. Thanks @Fred and the others who found
-- that formula :-)
--
-- The magic calculation formula:
--        f = 440*2^((halfSteps-69)/12)
--        startFrequency = math.sqrt(f/44100*100/8-0.001)
--
-- halfSteps is the number of half steps which our note has
-- from the base. If it is 0, then it plays C0.
--
-- Now in the calcHalfStepsFromNote you can pass a string that
-- contains a note (CDEFGAH), an octave (0 to 8) and
-- an accidental (# or b) which changes the note by a halfstep
-- up or down. The function then uses a lookup table to
-- find the number of half steps for the note, multiplies the
-- octave by 12 and then sums all up. The result is
-- used as halfSteps input in the above formula.
--
-- Have fun,
-- Kilam Malik.

curNote = "C"
curOctave = "3"
curAccidental = ""

baseNr = -1
octaveNr = -1
accidentalNr = -1
frequency = -1
codeaFrequency = -1

buttonsNotes = {}
buttonsOctaves = {}
buttonsAccidentals = {}
buttonPlay = nil

-- cdefgah but sorted by alphabet for quick calc a-cdefgh:
baseNoteLookup = { 9, -1, 0, 2, 4, 5, 7, 11 }

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function calcHalfStepsFromNote(note)
local base
local octave
local accidental

if string.len(note) == 2 then
base = string.lower(string.sub(note, 1, 1))
octave = string.sub(note, 2, 2)
accidental = ""
elseif string.len(note) == 3 then
base = string.lower(string.sub(note, 1, 1))
octave = string.sub(note, 2, 2)
accidental = string.lower(string.sub(note, 3, 3))
else
return 0
end

charNr = string.byte(base) - string.byte("a")
baseNr = baseNoteLookup[charNr + 1]

octaveNr = tonumber(octave) + 1

if accidental== "#" then
accidentalNr = 1
elseif accidental == "b" then
accidentalNr = -1
else
accidentalNr = 0
end

local halfTones = octaveNr * 12 + baseNr + accidentalNr
return halfTones
end

function buttonNoteClicked(note)
curNote = note

-- Deselect all other buttons in group... have to make a
-- button group class in the future to do this handling:
for i = 0, table.maxn(buttonsNotes) do
if buttonsNotes[i] == nil then
else
buttonsNotes[i].selected = false
end
end
end

function buttonOctaveClicked(octave)
curOctave = octave

-- Deselect all other buttons in group... have to make a
-- button group class in the future to do this handling:
for i = 0, table.maxn(buttonsOctaves) do
if buttonsOctaves[i] == nil then
else
buttonsOctaves[i].selected = false
end
end
end

function buttonAccidentalClicked(accidental)
curAccidental = accidental

-- Deselect all other buttons in group... have to make a
-- button group class in the future to do this handling:
for i = 0, table.maxn(buttonsAccidentals) do
if buttonsAccidentals[i] == nil then
else
buttonsAccidentals[i].selected = false
end
end
end

function buttonPlayClicked(id)
local halfSteps = calcHalfStepsFromNote(curNote .. curOctave .. curAccidental)

frequency = 440*2^((halfSteps-69)/12)
codeaFrequency = math.sqrt(frequency/44100*100/8-0.001)
instrument = {Waveform = Waveform, StartFrequency = codeaFrequency, AttackTime = AttackTime, SustainPunch = SustainPunch, DecayTime = DecayTime}
sound(instrument)

--encStr = sound( ENCODE, instrument )
--print(encStr)
--sound(DATA, encStr)
end

function createButton(name, x, y, w, h, btype, callback)
local btnImgPressed = image(70, 70)
setContext(btnImgPressed)
fill(200, 200, 200, 255)
rect(0,0,w,h)
fill(121, 225, 162, 255)
rect(5,5,w-10,h-10)
fill(0, 0, 0, 255)
textMode(CENTER)
text(name, w/2, h/2)
fill(255, 255, 255, 255)
text(name, w/2-1, h/2+1)
setContext()

local btnImgNormal = image(w, h)
setContext(btnImgNormal)
tint(255, 255, 255, 127)
sprite(btnImgPressed, w/2, h/2, w, h)
noTint()
setContext()

if btype == 0 then
b = Button(name, x, y, w, h, btnImgPressed, btnImgPressed, btype, callback)
else
b = Button(name, x, y, w, h, btnImgNormal, btnImgPressed, btype, callback)
end
return b
end

function setup()
local b
-- Create note buttons:
b = createButton("C", 50, HEIGHT-50, 70, 70, 1, buttonNoteClicked)
b.selected = true
table.insert(buttonsNotes, b)
b = createButton("D", 150, HEIGHT-50, 70, 70, 1, buttonNoteClicked)
table.insert(buttonsNotes, b)
b = createButton("E", 250, HEIGHT-50, 70, 70, 1, buttonNoteClicked)
table.insert(buttonsNotes, b)
b = createButton("F", 350, HEIGHT-50, 70, 70, 1, buttonNoteClicked)
table.insert(buttonsNotes, b)
b = createButton("G", 450, HEIGHT-50, 70, 70, 1, buttonNoteClicked)
table.insert(buttonsNotes, b)
b = createButton("A", 550, HEIGHT-50, 70, 70, 1, buttonNoteClicked)
table.insert(buttonsNotes, b)
b = createButton("H", 650, HEIGHT-50, 70, 70, 1, buttonNoteClicked)
table.insert(buttonsNotes, b)

-- Create octave buttons:
b = createButton("0", 50, HEIGHT-150, 70, 70, 1, buttonOctaveClicked)
table.insert(buttonsOctaves, b)
b = createButton("1", 150, HEIGHT-150, 70, 70, 1, buttonOctaveClicked)
table.insert(buttonsOctaves, b)
b = createButton("2", 250, HEIGHT-150, 70, 70, 1, buttonOctaveClicked)
table.insert(buttonsOctaves, b)
b = createButton("3", 350, HEIGHT-150, 70, 70, 1, buttonOctaveClicked)
b.selected = true
table.insert(buttonsOctaves, b)
b = createButton("4", 450, HEIGHT-150, 70, 70, 1, buttonOctaveClicked)
table.insert(buttonsOctaves, b)
b = createButton("5", 550, HEIGHT-150, 70, 70, 1, buttonOctaveClicked)
table.insert(buttonsOctaves, b)
b = createButton("6", 650, HEIGHT-150, 70, 70, 1, buttonOctaveClicked)
table.insert(buttonsOctaves, b)
b = createButton("7", 750, HEIGHT-150, 70, 70, 1, buttonOctaveClicked)
table.insert(buttonsOctaves, b)

-- Create accitental buttons:
b = createButton("", 50, HEIGHT-250, 70, 70, 1, buttonAccidentalClicked)
b.selected = true
table.insert(buttonsAccidentals, b)
b = createButton("#", 150, HEIGHT-250, 70, 70, 1, buttonAccidentalClicked)
table.insert(buttonsAccidentals, b)
b = createButton("b", 250, HEIGHT-250, 70, 70, 1, buttonAccidentalClicked)
table.insert(buttonsAccidentals, b)

-- Create play button:
buttonPlay = createButton("Play", 50, HEIGHT-350, 70, 70, 0, buttonPlayClicked)
end

function touched(touch)
for i = 0, table.maxn(buttonsNotes) do
if buttonsNotes[i] == nil then
else
buttonsNotes[i]:touched(touch)
end
end

for i = 0, table.maxn(buttonsOctaves) do
if buttonsOctaves[i] == nil then
else
buttonsOctaves[i]:touched(touch)
end
end

for i = 0, table.maxn(buttonsAccidentals) do
if buttonsAccidentals[i] == nil then
else
buttonsAccidentals[i]:touched(touch)
end
end

buttonPlay:touched(touch)
end
``````

Main tab part 2/2:

``````function draw()
background(255, 255, 255)

for i = 0, table.maxn(buttonsNotes) do
if buttonsNotes[i] == nil then
else
buttonsNotes[i]:draw()
end
end

for i = 0, table.maxn(buttonsOctaves) do
if buttonsOctaves[i] == nil then
else
buttonsOctaves[i]:draw()
end
end

for i = 0, table.maxn(buttonsAccidentals) do
if buttonsAccidentals[i] == nil then
else
buttonsAccidentals[i]:draw()
end
end

buttonPlay:draw()

textMode(CORNER)
local s = "Current selection: " .. curNote .. curOctave .. curAccidental
fill(0, 0, 0, 255)
text(s, 10, HEIGHT - 500)

s = string.format("Note: %d, Octave: %d, Accidental: %d", baseNr, octaveNr, accidentalNr)
fill(0, 0, 0, 255)
text(s, 10, HEIGHT - 530)

s = string.format("Frequency: %f", frequency)
fill(0, 0, 0, 255)
text(s, 10, HEIGHT - 560)

s = string.format("Codea Frequency: %f", codeaFrequency)
fill(0, 0, 0, 255)
text(s, 10, HEIGHT - 590)
end
``````

Button tab:

``````Button = class()

function Button:init(id, x, y, w, h, img1, img2, btype, callback)
-- id is sent back to the callback function to use one callback for multiple buttons.
-- x, y, w, h is position and size.
-- img is the picture of the button.
-- btype = 0 is a normal button, btype = 1 is a toggle button.

self.id = id
self.x = x
self.y = y
self.w = w
self.h = h
self.imgNormal = img1
self.imgPressed = img2
self.btype = btype
self.callback = callback

self.inTouch = false
self.selected = false
end

function Button:draw()
local w = self.w
local h = self.h
if self.selected or self.inTouch then
w = w * 1.2
h = h * 1.2
sprite(self.imgPressed, self.x, self.y, w, h)
else

sprite(self.imgNormal, self.x, self.y, w, h)
end
end

function Button:hit(x, y)
if math.abs(self.x - x) <= (self.w / 2)
and math.abs(self.y - y) <= (self.h / 2) then
return true
end

return false
end

function Button:touched(touch)
if touch.state == BEGAN then
if self:hit(touch.x, touch.y) then
self.inTouch = true
end
elseif touch.state == MOVING and self.inTouch then
if self:hit(touch.x, touch.y) then
self.inTouch = false
end
elseif touch.state == ENDED then
if self.inTouch == true then
self.callback(self.id)

if self.btype == 1 then
-- Callback deselects all buttons, so reselect it if toggle:
self.selected = true
end
self.inTouch = false
end
end
end
``````

@KilamMalik

Nice job. When I want to learn about the sound functions, this is probably the program I’ll use as a reference. I haven’t looked thru the code yet, but I’m sure you cover everything I’ll want to know. The only suggestion I have is that you add “supportedOrientations(LANDSCAPE_ANY)” at the beginning of the code. I normally use the iPad in portrait mode, so when I started your program I was still in portrait mode and the rightmost button was halfway off the screen. Adding the above code forced the display in landscape mode and everything fit OK.

Thanks, added it. I always work in landscape and didnt realize it was missing

@KilamMalik, thank you for this example. Music and sound is my current fad and this will most likely be very helpful.

Would it somehow be possible to retrieve a pointer to the real data behind the sound? Would be nice to visualize the wave

@KilamMali can I resize the buttons size, not in the code?