Submit Blog  RSS Feeds

Thursday, June 28, 2012

Generating sounds using the Open Sound System audio interface

Recently I started implementing a tool for learning music scales. I've done some research and found a way of utilising /dev/dsp in python. If you don't know what is that device file responsible I'll give you a hint. Make sure you have your speakers on, and type in:

~ cat /dev/urandom > /dev/dsp

If the device ain't busy or otherwise locked you should hear a hum... it ain't bad for a random input stream. But with a little help from python and some basic knowledge about digital sound processing we can get much more than that.

Let's start with some basics. To generate a random hum (just like in the example above) using python ossaudiodev module you could try the following:

  1 import ossaudiodev
  2 import os
  3
  4 dsp = ossaudiodev.open('/dev/dsp', 'w')
  5 dsp.write(os.urandom(5000))
  6 dsp.close()


Let's move on to some signal processing theory. In order to generate a specific tone we should pass a discrete approximation of a sine function for the analysed time ranged instead of a random array. In presented source I will assume 44.1k samples per second (why is that? check the Shanonn's Law), a frequency of 440Hz (also known as A440, it serves as a general instrument tuning standard), and tone duration of 5 seconds.

  1 import ossaudiodev
  2 import math
  3 import wave
  4
  5 freq, sr, t = 440.0, 44100.0, 5.0
  6 total_samples = sr*t
  7 period = sr / freq
  8 natural_freq = 2.0*math.pi/period
  9 #evaluate x-axis / time-axis positions
 10 time_axis = map(lambda x: float(x)*natural_freq, range(int(period)))
 11 #evaluate singal amplitudes for the period
 12 period_amp_data = map(lambda x: 16*math.sin(x), time_axis)
 13 #repeat the singal, and pack as short, 16 bit values
 14 output_signal = ''
 15 for i in range(int(total_samples/period)):
 16     for j in range(len(period_amp_data)):
 17         output_signal += wave.struct.pack('h', period_amp_data[j])
 18 dsp = ossaudiodev.open("/dev/dsp", "w")
 19 #16 bit big endian coding, 1 channel, 44.1kHz
 20 dsp.setparameters(ossaudiodev.AFMT_U16_BE, 1, sr)
 21 dsp.write(output_signal)
 22 dsp.close()


You can easily refactor this code making it possible to produce any tone you want (generating multi-tone sounds and effects is a bit more tricky). It may be a bit hard to get through without some background in digital signal processing, but having a working example programmers can do magic.

A good way of optimising this code is using numpy arrays instead of python lists, because they support many matrix like transformations (no mapping function would be needed).

P.S. I'm still using Mint 9 Isadora LTS version and I am happy to have a /dev/dsp device file, however Ubuntu users are not so lucky: /dev/dsp is not present in the kernel since v. 10.10... well... you can always try recompiling it :-)

~KR


3 comments:

  1. Thanks for the article, I'm hacking together an altitude variometer for my drone and this info got me started.

    In never Ubuntu versions you just have to type:
    padsp python program.py
    to get it working :)
    This simulates a /dev/dsp for the time the process runs.

    ReplyDelete
    Replies
    1. You're welcome! :)

      Thanks for bringing up padsp, recently I updated my system and /dev/dsp is not available anymore. the PulseAudio OSS wrapper is just what I need.

      Delete
  2. Hi! I have an issue opening /dev/dsp for reading. Always getting "Inappropriate ioctl for device /dev/dsp". Also I'm getting the same error opening any file in /dev/snd directory. Maybe you had the same error or you can do some suggestion. Thanks!

    ReplyDelete

free counters