Theremin

Hey all!

I’m currently working on a theremin in Codea. This is not a serious app, but it did teach me a lot about codea… Especially sound. Anyway, here is my code for main:

--Theremin
--by Brooks MacBeth
--started on March 23, 2012
--current form finished March 28, 2012

supportedOrientations(LANDSCAPE_ANY)
displayMode(FULLSCREEN)

-- Use this function to perform your initial setup
function setup()
    print("Hello World!")
    print("This is my Theremin... enjoy!!!")
--  This is where the multi-touches get stored...    
    touches = {}
--  Just setting up the text positions...    
    textSize(50)
    textAlign(CENTER)
end
-- Copied this from the multi-touch template given to us...
function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
    else
        touches[touch.id] = touch
    end
end

function drawBackground()
    stroke(255, 255, 255, 255)
    fill(255, 255, 255, 255)
    line(WIDTH/2, 0, WIDTH/2, HEIGHT)
    text("Volume", WIDTH/4, HEIGHT - 55)
    text("Pitch" , WIDTH/4 * 3, HEIGHT - 55)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    drawBackground()
    -- Detect the touches and produce the sounds...
    volume = nil
    pitch = nil
    for k,touch in pairs(touches) do
        -- Use the touch id as the random seed
        math.randomseed(touch.id)
        -- This ensures the same fill color is used for the same id
        fill(math.random(255),math.random(255),math.random(255))
        -- Draw ellipse at touch position
        ellipse(touch.x, touch.y, 100, 100)
        
        if touch.x < WIDTH/2 then
            volume = touch.y/HEIGHT
        else
            pitch = touch.y/HEIGHT
        end
        Sound:make(pitch, volume)
    end
end

And for the sound class…


Sound = class()

function Sound:init(x)
    -- you can accept and set parameters here
    self.x = x
end

function Sound:make(freq, vol)
    if freq == nil then
        freq = nil
    end
    if vol == nil then
        vol = 0.5
    end
    sound({StartFrequency = freq, 
           AttackTime = 0, 
           SustainTime = 0.14, 
           SustainPunch = 0, 
           DecayTime = 0, 
           MinimumFrequency = 0, 
           Slide = 0.5, 
           DeltaSlide = 0, 
           VibratoDepth = 0.5, 
           VibratoSpeed = 0.5, 
           ChangeAmount = 0, 
           ChangeSpeed = 0, 
           SquareDuty = 0, 
           DutySweep = 0, 
           RepeatSpeed = 0, 
           PhaserSweep = 0, 
           LowPassFilterCutoff = 1, 
           LowPassFilterCutoffSweep = 0.5, 
           LowPassFilterResonance = 0.5, 
           HighPassFilterCutoff = 0.5, 
           HighPassFilterCutoffSweep = 0.5, 
           Waveform = 2, 
           Volume = vol})
end

function Sound:draw()
    -- Codea does not automatically call this method
end

function Sound:touched(touch)
    -- Codea does not automatically call this method
end

Please let me know what you think. I used the Newton Cradle project posted on here not too long ago to get the first sounds produced, and then after playing with the sound slider project I got to the sound it produces now. I still wand a better, smoother sound throughout, but i think it sounds pretty good right now.

Brooks

Cool!

Fantastic little sound-generator, @blmacbeth! Played with it this morning.

Any way you can think to get rid of the popping noise in the background?

Well done!

@blmacbeth you can actually generate your own PCM sound data using a soundbuffer object (see the docs for more info). This lets you write the audio bytes directly, and play them, that may be able to solve the popping issue to some extent, but it’s a lot more manual work.

I look forward to trying your Theremin when I have a chance.
With the soundBuffer approach, the trick is to match the level of the sound wave you will create at its beginning and end so it becomes an uninterrupted wave when you loop it.
But you will also need to consider the change in frequency by small amounts as befits a Theremin. Either you might generate soundBuffers on the fly and risk lag, or pregenerate mountains of them with a loop and call them when needed.

This is real cool. Multitouch too! Let me see if I can add on to it.

Hi @blmacbeth, I played around with your Theremin code, and converted it to use soundbuffer, but still grappling with clicks and pops… :slight_smile:



--# Main
--Theremin
--by Brooks MacBeth
--started on March 23, 2012
--current form finished March 28, 2012
-- modified to use soundbuffer by Fred

supportedOrientations(LANDSCAPE_ANY)
--displayMode(FULLSCREEN)
watch("DeltaTime")

-- Use this function to perform your initial setup
function setup()
    print("This is not quite a Theremin... still got clicks.")
--  This is where the multi-touches get stored...    
    touches = {}
--  Just setting up the text positions...    
    textSize(50)
    textAlign(CENTER)
    Sound:init()
    time = 0
    gnLength = 0.05
end
-- Copied this from the multi-touch template given to us...
function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
    else
        touches[touch.id] = touch
    end
end

function drawBackground()
    stroke(255, 255, 255, 255)
    fill(255, 255, 255, 255)
    line(WIDTH/2, 0, WIDTH/2, HEIGHT)
    text("Volume", WIDTH/4, HEIGHT - 55)
    text("Pitch" , WIDTH/4 * 3, HEIGHT - 55)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    drawBackground()
    -- Detect the touches and produce the sounds...
    volume = nil
    pitch = nil
    for k,touch in pairs(touches) do
        -- Use the touch id as the random seed
        math.randomseed(touch.id)
        -- This ensures the same fill color is used for the same id
        fill(math.random(255),math.random(255),math.random(255))
        -- Draw ellipse at touch position
        ellipse(touch.x, touch.y, 100, 100)
        
        if touch.x < WIDTH/2 then
            volume = touch.y/HEIGHT
        else
            pitch = touch.y  * 10
       
            time = time + DeltaTime
            if time > gnLength then
                Sound:make(pitch, volume)
                time = 0
            end
        end
        
    end
end

--# Sound
Sound = class()

function Sound:init(x)
    
    soundBufferSize(30)
end

function Sound:make(freq, vol)
    
    if vol == nil then
        vol = 0.5
    end
    if pitch == nil then
        pitch = 0
    end
    
        b = Sound:makeBuffer(pitch,gnLength)
        sound(b,vol)
   
end

function Sound:makeBuffer(f,l)
    local freq = f
    local length = l
    local data = ""
    datum = "\\127\\255\\127\\0" 
    numSamples = freq*length
    --print(math.ceil(numSamples/#datum))
    for i = 1,math.ceil(numSamples/#datum) do
        data = data .. datum
    end
    
   --print(#datum)

    return soundbuffer(data,FORMAT_MONO8,freq)
end

I’ll have to try this. I was playing with altering this too, but I thought the amplitude of the ‘datum’ would determine the volume, and ‘sound’ doesn’t take a 2nd arg if the first is a buffer?

I think we’ll have to adjust samples’ make-up, lengths/period, frequencies or all of them on the fly to make it seamless. I’m a beginner in Codea, and obviously know little about sound processing yet from my first post elsewhere–but I love the possibilities so far. Spending more time with it than anything else on the iPad while I learn more.

I’ll stick to MONO8 for now, but anyone know which byte (low or high) needs to come first if 16-bit formats are chosen? I see some utilities that will output raw PCM (Audacity for one) but will then have to convert to get into text I can cut and paste.

Another thing I’d like to try eventually, draw and store the basic wave envelope as drawn into the sample (‘datum’) But first I must learn more of the basics. Programming is fun again! Yay Codea!

@kfin63 volume can be controlled with that extra argument to sound() or by reducing the amplitude of the wave. Last night I tried making the datum /120/134 which gave me a quiet tone. Still experimenting!

I like the sound buffer change. No volume control will hopefully be fixed as soon as I can wrap my head around “datum” and “data.” Those don’t make the most sense to me, but I’m figuring it out.

I’m not sure all the clicks and pops are within our control. I was playing with the Spritely included project, and I get crackling thru the headphones just picking a color. No sound functions involved in that code at all that I could see.