Dismiss Notice
Join Physics Forums Today!
The friendliest, high quality science and math community on the planet! Everyone who loves science is here!

Synthesizing Chords

  1. Dec 9, 2007 #1

    Strilanc

    User Avatar
    Science Advisor

    I'm trying to play chords using /dev/audio on my linux box. The idea is, given a set of notes, I generate the audio wave created and pass it to /dev/audio (which plays it through the speakers).

    The problem is it's not working. Single notes sound appropriate, but chords sound wrong. What comes out is some kind of garbled I-don't-know-what.

    I believe the issue is how I'm combining the waves made by the notes. I just add them together. Am I supposed to use some other transformation?

    Here is my source code (python):
    Code (Text):

    import math
    import random

    #constants
    midC = 60
    midA = midC + 9
    sampleRate = 8000

    #returns true with probability p
    def chance(p):
        return random.uniform(0,1) < p

    #returns the frequency of the given note
    def noteFreq(note):
        note -= midA
        return pow(2.0, note/12.) * 440.

    #converts amplitudes to a character sequence for /dev/audio
    def digitize(amplitudes):
        s = ""
        for d in amplitudes:
            d = abs(d)
            if chance(d%1.): d+=1 #anti-alias
            s += chr(int(min(d,255)))
        return s

    #Plays notes through /dev/audio
    class NotePlayer:
        def __init__(self, amplitude=63, dt=0.4):
            self.dt = dt
            self.amplitude = amplitude
            self.audio = file('/dev/audio', 'wb')
            self.t = 0

        def __del__(self):
            self.audio.close()

        #play a set of notes
        def playNotes(self, notes):
            freqs = map(noteFreq, notes)
            data = []
            tf = self.t + int(self.dt*sampleRate)

            #compute the wave over a time dt, from t to t-final
            for i in range(self.t, tf):
                x = (i+self.t)*math.pi/sampleRate #the sample time
                #combine the waves to get a sample
                d = 0
                for f in freqs:
                    d += math.sin(x*f)
                d *= self.amplitude
                data += [d]
           
            #play the sound
            self.audio.write(digitize(data))
            self.t = tf

        #plays a single note
        def playNote(self, note):
            self.playNotes((note,))

    #The actual run-time code
    p = NotePlayer()
    while 1==1:
        p.playNotes((midC,midC+4,midC+7)) #play the major chord C-E-G
            #sounds really bad
        #ctrl+c to kill
     
     
    Last edited: Dec 9, 2007
  2. jcsd
  3. Dec 9, 2007 #2

    robphy

    User Avatar
    Science Advisor
    Homework Helper
    Gold Member

    Do things improve if you increase sampleRate?
     
  4. Dec 9, 2007 #3

    Strilanc

    User Avatar
    Science Advisor

    The sampleRate variable represents how quickly /dev/audio plays the data I give it. Technically I can't modify it because then I give data to /dev/audio sampled at an incorrect rate.

    However, you might be right, so I tried increasing the sample rate by a factor of 4. The pitch lowered (of course), but the garbling noise was still loud and clear. I don't think the sampling rate is the issue.

    A little more information about what is going wrong:
    - 1 note: seems fine
    - 2 notes: plays well, but seems not loud enough
    - 3 notes and beyond: garbled mess
     
    Last edited: Dec 9, 2007
  5. Dec 9, 2007 #4

    robphy

    User Avatar
    Science Advisor
    Homework Helper
    Gold Member

    Could there be some clipping going on?
    Does the synthesized waveform look correct?
    Can you do a spectral analysis on the waveform?
     
  6. Dec 9, 2007 #5

    Strilanc

    User Avatar
    Science Advisor

    How do I do those things? Keep in mind I don't have any background in signal processing or things of that nature.

    I'll try to cobble something together to plot the sine wave.
     
  7. Dec 9, 2007 #6

    robphy

    User Avatar
    Science Advisor
    Homework Helper
    Gold Member

  8. Dec 9, 2007 #7

    Strilanc

    User Avatar
    Science Advisor

    Here are shots of the analog and digitized wave for C-E-G.

    Am I handling negative numbers correctly?
     

    Attached Files:

  9. Dec 9, 2007 #8
    My guess is that the garbling is due to you not scaling the amplitude of the resulting sound wave to a reasonable level. I'm using Max/MSP for a project, and I know I have to keep the amplitude between -1 and +1 or else there'll be distortion. If you just play sin(440x), you have something which oscillates between -1 and +1, and so you're fine, but if you then add sin(880x), you have something which varies between -2 and +2, and you're bound to hear distortion. Try dividing your amplitude by 3 after making the chord.
     
  10. Dec 9, 2007 #9

    robphy

    User Avatar
    Science Advisor
    Homework Helper
    Gold Member

    JoAuSc describes what I called "clipping" above.

    Strilanc, I don't think that you should take the absolute value of the synthesized waveform. Did you do this in the single tone case??

    Did you try to do the synthesis in (say) audacity?
     
  11. Dec 9, 2007 #10

    Strilanc

    User Avatar
    Science Advisor

    Dividing by the number of notes gave a very large improvement. I can recognize tunes now.

    There is still garble. Changing the value to the sum of absolutes instead of the absolute of the sums helped a little more. I'm guessing this is because of me still combining the signals incorrectly.

    I think I need to figure out how to give negative values to /dev/audio so I don't need to use absolute.
     
  12. Dec 9, 2007 #11

    robphy

    User Avatar
    Science Advisor
    Homework Helper
    Gold Member

    1. Find the largest negative number. Add its absolute-value to each waveform point.
    2. If your sum ever exceeds 255, you have clipping.... so you might want to then multiply each by 255./max (where max is the maximum value after step 1).
     
  13. Dec 9, 2007 #12

    Strilanc

    User Avatar
    Science Advisor

    Adding a constant of 128 to the wave causes no audible sound to come from the speakers. I'm not sure how /dev/audio interprets what I write to it.

    In any case, the playback doesn't need to be great. I only needed to be able to hear the notes. I'm writing a Markov Babbler, which generates music based on note-following probabilities from existing music.
     
  14. Dec 10, 2007 #13

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    1) You are not handling negative numbers properly. You need to produce a signal in the range -128 to +127, convert it to an integer, and add 128 to it. That will produce a signal that ranges from 0 to 255. Obviously, your "analog" and "digital" waveforms should look the same, just with different numbers along the y-axis -- but they do not.

    2) What you're currently creating is called PCM (pulse-code modulated) data. Your /dev/audio device by default expects mu-law encoding, not PCM. It won't ever sound right unless you reconfigure it to expect PCM data.

    3) Consider using a better interface to /dev/audio than just throwing bytes at it. Try ossaudiodev, http://docs.python.org/lib/ossaudio-device-objects.html You should be able to set /dev/audio to accept PCM data first, and then it should be able to handle your sound data properly.

    - Warren
     
  15. Dec 10, 2007 #14

    Strilanc

    User Avatar
    Science Advisor

    I changed over to ossaudiodev. I'm now having more issues: even single notes sound wrong. I don't understand what I'm doing incorrectly.

    - I set up /dev/audio to use PCM (also tried other formats to see if that worked)
    - I compute the signal as the sum of the sine waves of the notes, divided by the number of waves
    - I sample the signal, convert it to a series of whole numbers on [0,256) by adding a constant of 128 and rounding
    - I pass these to /dev/audio

    Code (Text):

    import math
    import random
    import ossaudiodev

    #constants
    midC = 60
    midA = midC + 9

    #returns true with probability p
    def chance(p):
        return random.uniform(0,1) < p

    #returns the frequency of the given note
    def noteFreq(note):
        note -= midA
        return pow(2.0, note/12.) * 440.
       
    #Plays notes through /dev/audio
    class NotePlayer:
        def __init__(self, amplitude=50, notePeriod=1.0, sampleRate=8000):
            self.notePeriod = notePeriod
            self.amplitude = amplitude
            self.sampleRate = sampleRate
            self.t = 0

            self.audio = ossaudiodev.open('/dev/audio', 'w')
            self.audio.setfmt(ossaudiodev.AFMT_IMA_ADPCM)
            self.audio.speed(self.sampleRate)

        def __del__(self):
            self.audio.close()

        #digitizes the given analog amplitude
        def digitize(self, d):
            d += 128
            if chance(d%1.): d+=1 #anti-alias
            d = max(d, 0)
            d = min(d, 255)
            return int(d)

        #passes amplitudes to /dev/audio as a character sequence
        def playSignal(self, signal):
            s = ""
            data = map(self.digitize, signal)
            for d in data:
                s += chr(d)
            self.audio.write(s)

        #plays a set of notes
        def playNotes(self, notes):
            dt = int(self.notePeriod*self.sampleRate)
            data = []
            freqs = map(noteFreq, notes)

            #compute the signal
            for i in range(self.t, self.t + dt):
                d = 0
                if len(freqs) > 0:
                    x = (i+self.t)*math.pi/self.sampleRate
                    for f in freqs:
                        d += math.sin(x*f)
                    d *= self.amplitude/len(freqs)
                data += [d]
           
            #output the signal
            self.playSignal(data)
            self.t += dt

    #actual run-time
    p = NotePlayer()
    while 1==1:
        notes = ((midC,))
        p.playNotes(notes)
        notes = ((midC,midC+7))
        p.playNotes(notes)
            #ctrl+c to kill
     
     
  16. Dec 10, 2007 #15

    Strilanc

    User Avatar
    Science Advisor

    I found a silly bug that fixed a lot of the issues.

    Code (Text):

    x = (i+self.t)*math.pi/self.sampleRate //self.t already included in i!!
     
    However, I still hear static in the background. Surprisingly (at least to me), the static gets worse if I increase the sample rate. It is present even for single notes.
     
    Last edited: Dec 10, 2007
Know someone interested in this topic? Share this thread via Reddit, Google+, Twitter, or Facebook

Have something to add?



Similar Discussions: Synthesizing Chords
Loading...