# Synthesizing Chords

1. Dec 9, 2007

### Strilanc

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
#ctrl+c to kill

Last edited: Dec 9, 2007
2. Dec 9, 2007

### robphy

Do things improve if you increase sampleRate?

3. Dec 9, 2007

### Strilanc

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.

- 1 note: seems fine
- 2 notes: plays well, but seems not loud enough
- 3 notes and beyond: garbled mess

Last edited: Dec 9, 2007
4. Dec 9, 2007

### robphy

Could there be some clipping going on?
Does the synthesized waveform look correct?
Can you do a spectral analysis on the waveform?

5. Dec 9, 2007

### Strilanc

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.

6. Dec 9, 2007

7. Dec 9, 2007

### Strilanc

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

Am I handling negative numbers correctly?

File size:
38.4 KB
Views:
45
File size:
41.1 KB
Views:
42
8. Dec 9, 2007

### JoAuSc

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.

9. Dec 9, 2007

### robphy

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?

10. Dec 9, 2007

### Strilanc

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.

11. Dec 9, 2007

### robphy

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).

12. Dec 9, 2007

### Strilanc

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.

13. Dec 10, 2007

### chroot

Staff Emeritus
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

14. Dec 10, 2007

### Strilanc

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.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

15. Dec 10, 2007

### Strilanc

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