Aegidius
 Plüss   Aplulogo
 
in examples
Use the asterisk (*) character for wildcard searches
Searches on more than one word will be treated "as a phrase"
 
serverstatus
 www.aplu.ch
 www.java-online.eu
     Print Text 
© 2018, V10.0
 
  Jython
 
 

 

Digitized sound is omnipresent in modern life. An good understanding how sound is coded into numbers that can be transmitted and stored like computer data can help in many situations. Because computers easily work with numbers, programming with sound is simple and motivating. We do not give an introduction here how sound is digitized in various formats, nor on such an interesting subject as sound compression, but just present some examples how to work with sound. TigerJython includes a global wrapper for the sound related classes that are part of the ch.aplu.util package to simplify the code.

You should know that digitized sound consists of "samples" represented by natural numbers. Sound samples are taken at equal time intervals. The number of samples per second (in Hertz) is called the sample rate or sampling frequency and is standardized for WAV sounds to 8000, 11025, 16000, 22050 and 44100 Hertz (with possible extensions). For a stereo sound the values in a WAV file alternate between the left und the right channel.

In the first example we load a song clip in WAV format from the disk and play it. It only takes 3 lines of code. First all global functions of the sound library are imported. Then a SoundPlayer using the given sound file is prepared to be played. (The path may be absolute or relative to the current folder where TigerJython.jar resides.) Finally the sound starts to play. You may show the sound format of the sound file by calling getAudioFormat().

# ClipPlayer.py

from soundsystem import *
from ch.aplu.util import MessageDialog

openSoundPlayer("wav/bird.wav")
play()
MessageDialog("Audio Format: " + str(getAudioFormat())).show()
Select ClipPlayer.py (Ctrl+C to copy, Ctrl+V to paste)
Execute the program locally using WebStart.
Download
bird.wav

We may be interested to trace the sound wave function. Using GPanel's user definable double coordinate system, this is a matter of just a few lines of code.

# ClipTracer.py

from soundsystem import *
from gpanel import *

samples = getWavMono("wav/bird.wav")

makeGPanel(0, len(samples), -33000, 33000)
for i in range(len(samples)):
   draw(i, samples[i])

openMonoPlayer(samples, 22050)
play()
Select ClipTracer.py (Ctrl+C to copy, Ctrl+V to paste)
Execute
the program locally using WebStart.

Discussion: Independent of the WAV audio format of the given file, the sound samples are returned by getWavMono() as list with integer values in the range -32767..32768 (16 bit range), so we set GPanel's y-axis span to -33000..33000.

Because we want to trace the full time domain, the x-axis span is set to 0 and the samples size. These coordinate ranges are selected by the makeGPanel() parameters.

At the end we play the sound by passing the samples list to a different version of openMonoPlayer() where we also pass the sample rate. Keep in mind that the sound clip cannot exceed a certain length, because all of its data is contained in a Python list.

 
PythonFun
 
Sound trace of bird.wav

If you use getWavStereo(), the sound samples are returned as a list of integers with values alternating from the left to the right channel (again independent of the WAV audio format of the given file). To play the sound, either extract the left or right channel data from the list or use openStereoPlayer() that takes the list with stereo data.

The TigerJython JAR distribution contains some few sound clips in the _wav subfolder. To play them, use the URLfromJAR() function. Keep in mind that play() returns immediately. To block the program until the clip is completely played, use blockingPlay().

# NotifyPlayer.py

from soundsystem import *

openSoundPlayer(URLfromJAR("_wav/bird.wav"))
blockingPlay()
openSoundPlayer(URLfromJAR("_wav/notify.wav"))
play()
Select NotifyPlayer.py (Ctrl+C to copy, Ctrl+V to paste)
Execute
the program locally using WebStart.

Currently the following files are distributed: bird.wav, boing.wav, click.wav, dummy.wav, explode.wav, fade.wav, frog.wav, mmm.wav, notify.wav, ping.wav.

If you want to be completely informed about the player state, you may register a sound player state notifier callback function notify() by passing the function name as second parameter to openSoundPlayer().

# PlayerState.py

from soundsystem import *

def notify(reason, mixerIndex):
   print "Notification from sound device %d" %(mixerIndex)
   if reason == 0:
      print "Start playing at " + str(getCurrentPos())
   elif reason == 1:
      print "Resume playing at " + str(getCurrentPos())
   elif reason == 2:
      print "Pause playing at " + str(getCurrentPos())
   elif reason == 3:
      print "Stop playing at " + str(getCurrentPos())
   elif reason == 4:
      print "End of resource at " + str(getCurrentPos())
   else:
     pass

audioFile = "c:/scratch/mysong.wav"

openSoundPlayer(audioFile, notify)
play()
delay(5000)
pause()
delay(3000)
play()
delay(5000)
stop()
Select PlayerState.py (Ctrl+C to copy, Ctrl+V to paste)
Execute
the program locally using WebStart.

The sound library has all features that are needed to construct a full-fledge audio player. You can play, pause, resume, rewind, advance, stop the sound and increase/decrease the volume. In the following example keyboard keys are used to control the player.

# AudioPlayer.py

from gpanel import *
from soundsystem import *

def getPlayTime():
  return "{:4.1f}".format(getCurrentTime() /  1000) + " s"

makeGPanel()
addStatusBar(30)
setStatusText("Status: Use keyboard for commands")

openSoundPlayer("c:/scratch/mysong.wav")
volume = getVolume()
text(0.4, 0.9, "Command List:")
text(0.1, 0.8, "Cursor up: play")
text(0.1, 0.7, "Cursor down: pause")
text(0.1, 0.6, "Cursor left: rewind")
text(0.1, 0.5, "Cursor right: advance")
text(0.1, 0.4, "Key v: Volume increase")
text(0.1, 0.3, "Key c: Volume decrease")
text(0.1, 0.2, "Key s: Stop")

while True:
   code  = getKeyCode()
   if code == 65535: # no key
      delay(100)
      setStatusText("Status: Current play time " + getPlayTime())
      continue
   if code == 38:  # cursor up
      play()
      setStatusText("Status: Play")
   if code == 40:  # cursor down
      pause()
      setStatusText("Status: Pause")
   if code == 37:  # cursor left
      rewindTime(10000)
      setStatusText("Status: Rewind")
   if code == 39:  # cursor right
      advanceTime(10000)
      setStatusText("Status: Advance")
   if code == 83:  # s key
      stop()
      setStatusText("Status: Stop")
   if code == 86:  # v key
      volume += 50;
      if volume > 1000:
         volume = 1000
      setVolume(volume)
      setStatusText("Status: Volume increase to " + str(volume))
   if code == 67:  # c key
      volume -= 50;
      if volume < 0:
         volume = 0
      setVolume(volume)
      setStatusText("Status: Volume decrease to " + str(volume))
   delay(1000)
Select AudioPlayer.py (Ctrl+C to copy, Ctrl+V to paste)
Execute the program locally using WebStart.  

The GPanel provides only a rudimentary GUI for our music player. A more sophisticated player with button control requires some knowledge of the Java Swing API. (In Jython the Tkinter GUI library is not supported.) You may download the code from here and copy/paste it in the TigerJython editor window. As you see here, it looks quite professional.

jythoneex10

Execute the program locally using WebStart.  

In the following example you build an application to record sound from a microphone or a audio source plugged into the line-in port.

# SoundRecorder.py

from soundsystem import *
from ch.aplu.util import MessagePane

sampleRate = 44100  # 8000,11025,16000,22050,44100

# ---------- Capture sound ---------------
openStereoRecorder(sampleRate)
mp = MessagePane(50)
mp.setText("Recording...")
capture()
delay(5000)
stopCapture()
mp.setText("Stopped")

# ---------- Play captured sound ---------
sound = getCapturedSound()
openStereoPlayer(sound, sampleRate)
play()

# ---------- Save captured sound ---------
filename = "d:/scratch/mysound.wav"
if writeWavFile(sound, filename):
   mp.setText(filename + " successfully saved")
else:
   mp.setText("Can't save " + filename)

Select SourndRecorder.py (Ctrl+C to copy, Ctrl+V to paste)
Execute the program locally using WebStart.

Discussion: The program is simple: openStereoRecorder() prepares the sound recording system using the given sample rate for recording stereo (two channels) data. capture() starts the recording and returns immediately. To stop the recording after a fixed recording time stopCapture() is called.

We retrieve the recorded sound samples in 16bit-stereo format in a Python integer list paired for the left and right channel by calling getCapturedSound(). This list is passed to a version of openStereoPlayer() that takes the list instead of a file name. We also save the sound in a WAV file by calling writeWavFile(), so it can be played with any WAV player. The audio format is fixed to 16bit stereo (signed, little endian).

You may also want to show the recorded sound wave. Just add the following lines to your code:

# ---------- Show captured sound ---------
from gpanel import *
makeGPanel(0, len(sound) / 2, -33000, 33000)
for t in range(len(sound) / 2):
   draw(t, sound[2 * t])  # only left channel
 

It is very instructive to listen to a sound generated by some periodical time function you define yourself. Here we start with the classical sine wave sound to demonstrate how it works. After that you may easily generate some other interesting sounds, like square , triangle, sawtooth waves, beat sounds, etc.

# SineGenerator.py

from soundsystem import *
import math

sampleRate = 22050 # 8000,11025,16000,22050,44100
duration = 3  # in s
amplitude = 30000
frequency = 1000 # in Hz
nbFrames = int(duration * sampleRate)

t = 0
dt = 1.0 / sampleRate
data = []
for i in range(nbFrames):
   soundData = int(amplitude * math.sin(2 * math.pi * frequency * t))
   data.append(soundData)
   t += dt;

openMonoPlayer(data, sampleRate)
play()

openMonoRecorder(sampleRate)
rc = writeWavFile(data, "d:/scratch/sine.wav")
if rc:
   print "Sound file d:/scratch/sine.wav successfully created"
Select SineGenerator.py (Ctrl+C to copy, Ctrl+V to paste)
Execute
the program locally using WebStart.
 

Discussion: The sine wave function is defined as

  sine

where A is the amplitude and f the sampling frequency. We compute the samples at equals time intervals dt = 1 / f and fill the values into a Python list. Because we must provide data with sample values in the range -32768..32767, we use an amplitude of 30000. Like in the example above, openMonoPlayer() takes this data, so it can play it with its play() method. It may be interesting to store the generated sound as WAV file on the disk, so we can play it with any standard music player.