Synthesizing Chords with /dev/audio on Linux

  • Thread starter Strilanc
  • Start date
In summary, the player is trying to play chords, but they sound wrong. They believe the issue is how they are combining the waves made by the notes, but they don't know how to do that correctly. Increasing the sample rate did not improve the situation. They are working on a project to generate music based on note-following probabilities, and as long as the amplitude is between -1 and +1, the playback does not need to be great.
  • #1
Strilanc
Science Advisor
612
229
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:
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:
Technology news on Phys.org
  • #2
Do things improve if you increase sampleRate?
 
  • #3
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:
  • #4
Could there be some clipping going on?
Does the synthesized waveform look correct?
Can you do a spectral analysis on the waveform?
 
  • #5
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
Here are shots of the analog and digitized wave for C-E-G.

Am I handling negative numbers correctly?
 

Attachments

  • analog.png
    analog.png
    11.6 KB · Views: 392
  • digital.png
    digital.png
    12 KB · Views: 368
  • #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.
 
  • #9
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
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
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
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
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
 
Last edited by a moderator:
  • #14
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:
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
 
  • #15
I found a silly bug that fixed a lot of the issues.

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

Related to Synthesizing Chords with /dev/audio on Linux

What is /dev/audio on Linux?

/dev/audio is a special device file in the Linux operating system that allows for low-level access to the audio hardware. It is used for recording and playing audio data.

How can I use /dev/audio to synthesize chords?

/dev/audio can be used in conjunction with other audio programming tools, such as the ALSA library, to generate sound waves of different frequencies and combine them to create chords.

Can I synthesize chords with /dev/audio on any Linux distribution?

Yes, /dev/audio is a standard device file that is present on most Linux distributions. However, the specific commands and methods for using it may vary slightly depending on the distribution.

Do I need any special hardware to use /dev/audio for synthesizing chords?

No, /dev/audio can be used with any standard audio hardware. However, for best results, it is recommended to have a high-quality sound card or audio interface.

Are there any alternative methods for synthesizing chords on Linux besides /dev/audio?

Yes, there are many other audio programming tools and libraries available on Linux that can be used for synthesizing chords, such as JACK, PulseAudio, and PortAudio. Each has its own unique features and functions, so it's worth exploring different options to find the best fit for your needs.

Similar threads

  • Advanced Physics Homework Help
Replies
6
Views
1K
  • Special and General Relativity
8
Replies
264
Views
28K
Back
Top