Change Contents of the Bubble
Welcome to CS1315. Click on the python to add comments.

Looking for the book? They have it at the Engineer's Bookstore at 748 Marietta St NW. Here is there website: http://www.engrbookstore.com/ - Monica

Hotspots: Slides and CodeTA CornerComments?AnnouncementsFAQStatic Webspace
View this PageEdit this Page (locked)Uploads to this PageHistory of this PageHomeRecent ChangesSearchHelp Guide

Midterm Review, Sound Coding Q5

Q5 Write a function called addNotes which takes in 2 sounds, adds them together and reverses the resulting sound. You must adjust for clipping while adding the sounds.

This is MUCH more difficult than any question that will appear on the test. However, all the components in solving this problem are the kinds of things that will appear. I have broken this down into stages to re-create the way I would solve this in class (or anywhere, for that matter!)

Version 1


First thing I do is write the definition. It takes two sounds, so I MUST get that right even if my function initially does nothing

def addNotes(sound1, sound2):
  pass


Version 2


Do I want to normalize the sounds first to prevent clipping, add them together and then normalize, reverse them before or after adding them?? Also, am I going to add one
of the sounds into the other one or add them both into an empty canvas?

Well, reversing them can be done at any time. Since we only need to reverse once if we
do this on the final sound, whereas we have to reverse twice, if we reverse the original
sounds before adding them, let's leave reversing until the end. I'll need a reverse
function to reverse the samples.

I had better do the normalizing before I superimpose them. If I get clipping, it will
be too late to do anything about it. But I don't need to normalize properly. I can
just divide every sample in both sounds by two. That way, no sample can ever exceed
the maximum value. I will need a function div2(sound) that does this reduction in
sample values.

As for how we are going to add these sounds together, I think it is logically cleaner
to make a new empty sound canvas that we copy the two sounds into than copying one
sound on top of the other one, so that is what I'll do. I'll need a superimposeSounds()
function that takes two sounds, a source to copy and a canvas to copy over. Also, I
need to work out how long to make the soundCanvas. It should be at least as long as
the longer of the two sounds. (We could assume that they are the same length, but let's
be perfectionist about this.)

def addNotes(sound1, sound2):
  soundCanvas = makeEmptySound(seconds) # But how many seconds?
  div2(sound1)
  div2(sound2)
  superimposeSound(sound1, soundCanvas)
  superimposeSound(sound2, soundCanvas)
  reverse(soundCanvas)

def div2(sound):
  pass

def superimposeSound(source, canvas):
  pass

def reverse(sound):
  pass


Version 3


OK. Reducing the sample values is easy in principle. A few weeks ago, the code below
would have scared you. But it's all details. Just set the values to be half of what
they currently are...

def div2(sound):
  for snum in range(1, getLength(sound)+1):
    setSampleValueAt(sound, snum, getSampleValueAt(sound, snum) / 2)


(We could use getSamples() as in the book, but I want to make this work with JES 3).

Version 4


How do we superimpose sounds? I can't wrap my head around a one-line loop this time,
so we will use two variables – a current value and a value to be added to it –
making a three-line loop in all.

def superimposeSound(source, canvas):
  for snum in range(1, getLength(canvas)+1): # Guaranteed to be long enough
    currentValue = getSampleValueAt(canvas, snum)
    addedValue = getSampleValueAt(source, snum)
    setSampleValueAt(canvas, snum, currentValue + addedValue)


Version 5


How do we reverse sounds? Previously, we have done this with copying. But it is
simpler to look at the first and last samples and swap them and then to move in toward
the center, swapping sample values as we go.

This is much easier to understand with some helper variables. Think about having two
places that the samples are at: one on the left and one on the right. As we go through
the loop, each moves toward the center. And there are therefore two values that we are
interested in: one on the left and one on the right. These are what we interchange.

def reverse(sound):
  for snum in range(1, getLength(sound)/2+1):
    leftPlace = snum
    rightPlace = getLength(sound)+1-snum
    leftValue = getSampleValueAt(sound, leftPlace)
    rightValue = getSampleValueAt(sound, rightPlace)
    setSampleValueAt(leftPlace, rightValue)
    setSampleValueAt(rightPlace, leftValue)


Finally


Coming back to being perfectionist. How long does the soundCanvas need to be? It's an
exact number of seconds, so it needs to be that number of seconds that is exactly equal
to or one greater than the number of seconds needed to contain the samples it will
contain. And the number of samples is going to be the greater (i.e. max) of the two
sounds' lengths.

There is a four-line block now at the beginning that computes this.

def addNotes(sound1, sound2):

  duration1 = getLength(sound1) / getSamplingRate(sound1)
  duration2 = getLength(sound2) / getSamplingRate(sound2)
  seconds = int(max(duration1, duration2) + 1)
  soundCanvas = makeEmptySound(seconds)

  div2(sound1)
  div2(sound2)
  superimposeSound(sound1, soundCanvas)
  superimposeSound(sound2, soundCanvas)
  reverse(soundCanvas)
  return soundCanvas


Really finally (!)


It didn't really work first time. There are a couple of off-by-one errors in the above and a small change to superimposeSound. (Can you find it and work out why it is needed?) Here is the final version.

def addNotes(sound1, sound2):
  duration1 = getLength(sound1) / getSamplingRate(sound1)
  duration2 = getLength(sound2) / getSamplingRate(sound2)
  seconds = int(max(duration1, duration2) + 1)
  soundCanvas = makeEmptySound(seconds) # But how many seconds?
  div2(sound1)
  div2(sound2)
  superimposeSound(sound1, soundCanvas)
  superimposeSound(sound2, soundCanvas)
  reverse(soundCanvas)
  return soundCanvas

def div2(sound):
  for snum in range(1, getLength(sound)):
    setSampleValueAt(sound, snum, getSampleValueAt(sound, snum) / 2)

def superimposeSound(source, canvas):
  for snum in range(1, getLength(source)): # Guaranteed to be long enough
    currentValue = getSampleValueAt(canvas, snum)
    addedValue = getSampleValueAt(source, snum)
    setSampleValueAt(canvas, snum, currentValue + addedValue)

def reverse(sound):
  for snum in range(1, getLength(sound)/2):
    leftPlace = snum
    rightPlace = getLength(sound)+1-snum
    leftValue = getSampleValueAt(sound, leftPlace)
    rightValue = getSampleValueAt(sound, rightPlace)
    setSampleValueAt(sound, leftPlace, rightValue)
    setSampleValueAt(sound, rightPlace, leftValue)




Link to this Page