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.

Screenshot: https://dl.dropbox.com/u/80475380/CodeaForumPictures/sound_notes.png

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 :slight_smile:

@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 :slight_smile:

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