Source code for sounds

'''
Python module to read, play, and write sound data.
For flexibility, FFMPEG is used for non-WAV files.

You can obtain it for free from
    http://ffmpeg.org
    
Mac users should best get binaries from
    - http://www.evermeet.cx/ffmpeg/ffmpeg-2.1.4.7z
    - http://www.evermeet.cx/ffplay/ffplay-2.1.4.7z

Note that FFMPEG must be installed externally!
Please install ffmpeg/ffplay in the following directory:

    - Windows:  "C:\\\\Program Files\\\\ffmpeg\\\\bin\\\\"
    - Mac:  	"/usr/local/bin/" (is already included in the default paths of the Mac terminal.)
    - Linux:	"/usr/bin/"

Compatible with Python 2.x and 3.x

'''

'''
Date:   March 2015
Ver:    1.9
Author: thomas haslwanter

Changes: 1.2 replaced Qt with wxpython, because of timer problems
Changes: 1.3 put ui into thLib; allow "cancel" for "writeWav"
Changes: 1.4 Use FFMPEG for conversion from miscellaneous inputs to ".wav", and FFPLAY to play files non-Windows platforms
Changes: 1.5 replace ui with a version that only uses Tkinter, make it compatible with Python 3.x
Changes: 1.6 if FFMPEG is not found in the default location, the user can locate it interactively.
Changes: 1.7 should make it compatible with Linux and Mac.
Changes: 1.8 fix writing/playing for self-generated data
Changes: 1.9 System identifier for linux corrected; for stereo sounds, both channels are now returned
Changes: 1.10 System identifier for linux corrected (hopefully properly this time)
'''

import os
import sys
import numpy as np
from scipy.io.wavfile import read, write
import tempfile
from subprocess import call
from thLib import ui

# "ffmpeg" has to be installed externally, ideally into the location listed below
if sys.platform == 'win32':
    ffmpeg = r'C:\Progra~1\ffmpeg\bin\ffmpeg.exe'
    ffplay = r'C:\Progra~1\ffmpeg\bin\ffplay.exe'
    
elif sys.platform == 'darwin':
    ffmpeg = r'/usr/local/bin/ffmpeg'
    ffplay = r'/usr/local/bin/ffplay'
    
elif sys.platform.startswith('linux'):
    ffmpeg = r'/usr/bin/ffmpeg'
    ffplay = r'/usr/bin/ffplay'
    
else:
    print('Sorry, only Windows, Mac, and Linux are supported. Location for ffmpeg must be selected by hand.')
    
    
[docs]class Sound: ''' Class for working with sound in Python. A Sound object can be initialized - by giving a filename - by providing "int16" data and a rate - without giving any parameter; in that case the user is prompted to select an infile Parameters ---------- inFile : string path- and file-name of infile, if you get the sound from a file. inData: array manually generated sound data; requires "inRate" to be set, too. inRate: integer sample rate; required if "inData" are entered. Returns ------- None : No return value. Initializes the Sound-properties. Notes ----- For non WAV-files, the file is first converted to WAV using FFMPEG, and then read in. A warning is generated, to avoid unintentional deletion of existing WAV-files. SoundProperties: - source - data - rate - numChannels - totalSamples - duration - bitsPerSample Examples -------- >>> from thLib.sounds import Sound >>> mySound1 = Sound() >>> mySound2 = Sound('test.wav') >>> >>> rate = 22050 >>> dt = 1./rate >>> freq = 440 >>> t = np.arange(0,0.5,dt) >>> x = np.sin(2*np.pi*freq * t) >>> amp = 2**13 >>> sounddata = np.int16(x*amp) >>> mySound3 = Sound(inData=sounddata, inRate=rate) ''' def __init__(self, inFile = None, inData = None, inRate = None): '''Initialize a Sound object ''' if inData is not None: if inRate is None: print('Set the "rate" to the default value (8012 Hz).') rate = 8012.0 self.setData(inData, inRate) else: if inFile is None: inFile = self._selectInput() if os.path.exists(inFile): self.source = inFile self.readSound(self.source) else: print(inFile + ' does NOT exist!')
[docs] def readSound(self, inFile): ''' Read data from a sound-file. Parameters ---------- inFile : string path- and file-name of infile Returns ------- None : No return value. Sets the property "data" of the object. Notes ----- * For non WAV-files, the file is first converted to WAV using FFMPEG, and then read in. Examples -------- >>> mySound = Sound() >>> mySound.readSound('test.wav') ''' # Python can natively only read "wav" files. To be flexible, use "ffmpeg" for conversion for other formats global ffmpeg # Since "ffmpeg" is modified, it has to be declared "global" if not os.path.exists(ffmpeg): (ffmpeg_file, ffmpeg_path) = ui.getfile(DialogTitle='Please locate the "ffmpeg" executable: ' ) ffmpeg = os.path.join(ffmpeg_path, ffmpeg_file) (root, ext) = os.path.splitext(inFile) if ext[1:].lower() != 'wav': outFile = root + '.wav' cmd = [ffmpeg, '-i', inFile, outFile] call(cmd) print('Infile converted from ' + ext + ' to ".wav"') inFile = outFile self.source = outFile try: self.rate, self.data = read(inFile) #if (self.data.shape)==2: # for stereo, take the first channel #self.data = self.data[:,0] self._setInfo() print('data read in!') except IOError: print('Could not read ' + inFile)
[docs] def play(self): ''' Play a sound-file. Parameters ---------- None : Returns ------- None : Notes ----- On "Windows" the module "winsound" is used; on other platforms, the sound is played using "ffplay" from FFMPEG. Examples -------- >>> mySound = Sound() >>> mySound.readSound('test.wav') >>> mySound.play() ''' global ffplay if sys.platform=='win32': import winsound try: tmpFile = None if self.source is None: # If no corresponding sound-file exists, create a temporary file that you can play with an external player tmpFile = tempfile.NamedTemporaryFile(suffix='.wav', delete=False) self.writeWav(tmpFile.name) playFile = tmpFile.name elif os.path.exists(self.source): playFile = self.source else: print('{0} does not exist'.format(self.source)) return if sys.platform=='win32': winsound.PlaySound(playFile, winsound.SND_FILENAME) else: if not os.path.exists(ffplay): (ffplay_file, ffplay_path) = ui.getfile(DialogTitle='Please locate "ffplay" executable: ' ) ffplay = os.path.join(ffplay_path, ffplay_file) cmd = [ffplay, '-autoexit', '-nodisp', '-i', playFile] call(cmd) print('Playing ' + playFile) if not tmpFile is None: tmpFile.close() os.remove(tmpFile.name) except SystemError: print('If you don''t have FFMPEG available, you can e.g. use installed audio-files. E.g.:') print('import subprocess') print('subprocess.call([r"C:\Program Files (x86)\VideoLAN\VLC\vlc.exe", r"C:\Music\14_Streets_of_Philadelphia.mp3"])')
[docs] def setData(self, data, rate): ''' Set the properties of a Sound-object. ''' # If the data are in another format, e.g. "float", convert # them to integer and scale them to a reasonable amplitude if not type(data[0]) == np.int16: defaultAmp = 2**13 data *= defaultAmp / np.max(data) data = np.int16(data) self.data = data self.rate = rate self.source = None self._setInfo()
[docs] def writeWav(self, fullOutFile = None): ''' Write sound data to a WAV-file. Parameters ---------- fullOutFile : string Path- and file-name of the outfile. If none is given, the user is asked interactively to choose a folder/name for the outfile. Returns ------- None : Examples -------- >>> mySound = Sound() >>> mySound.readSound('test.wav') >>> mySound.writeWav() ''' if fullOutFile is None: (outFile , outDir) = ui.savefile('Write sound to ...', '*.wav') if outFile == 0: print('Output discarded.') return 0 else: fullOutFile = os.path.join(outDir, outFile) else: outDir = os.path.abspath(os.path.dirname(fullOutFile)) outFile = os.path.basename(fullOutFile) # fullOutFile = tkFileDialog.asksaveasfilename() write(str(fullOutFile), int(self.rate), self.data) print('Sounddata written to ' + outFile + ', with a sample rate of ' + str(self.rate)) print('OutDir: ' + outDir) return fullOutFile
[docs] def info(self): ''' Return information about the sound. Parameters ---------- None : Returns ------- source : name of inFile rate : sampleRate numChannels : number of channels totalSamples : number of total samples duration : duration [sec] bitsPerSample : bits per sample Examples -------- >>> mySound = Sound() >>> mySound.readSound('test.wav') >>> info = mySound.info() >>> (source, rate, numChannels, totalSamples, duration, bitsPerSample) = mySound.info() ''' return (self.source, self.rate, self.numChannels, self.totalSamples, self.duration, self.dataType)
[docs] def summary(self): ''' Display information about the sound. Parameters ---------- None : Returns ------- None : Examples -------- >>> mySound = Sound() >>> mySound.readSound('test.wav') >>> mySound.summary() ''' import yaml (source, rate, numChannels, totalSamples, duration, dataType) = self.info() info = {'Source':source, 'SampleRate':rate, 'NumChannels':numChannels, 'TotalSamples':totalSamples, 'Duration':duration, 'DataType':dataType} print(yaml.dump(info, default_flow_style=False))
def _setInfo(self): '''Set the information properties of that sound''' if len(self.data.shape)==1: self.numChannels = 1 self.totalSamples = len(self.data) else: self.numChannels = self.data.shape[1] self.totalSamples = self.data.shape[0] self.duration = float(self.totalSamples)/self.rate # [sec] self.dataType = str(self.data.dtype) def _selectInput(self): '''GUI for the selection of an in-file. ''' (inFile, inPath) = ui.getfile('*.wav;*.mp3', 'Select sound-input: ') fullInFile = os.path.join(inPath, inFile) print('Selection: ' + fullInFile) return fullInFile
""" def module_exists(module_name): '''Check if a module exists''' if globals().get(module_name, False): return True return False """ def main(): # Main function, to test the module import os dataDir = r'C:\Users\p20529\Documents\Teaching\ETH\CSS\Exercises\Ex_Auditory\sounds\mp3' inFile = 'tiger.mp3' fullFile = os.path.join(dataDir, inFile) mySound = Sound(fullFile) mySound.play() ''' import numpy as np # Test with self-generated data rate = 22050 dt = 1./rate t = np.arange(0,0.5,dt) freq = 880 x = np.sin(2*np.pi*freq*t) sounddata = np.int16(x*2**13) inSound = Sound(inData=sounddata, inRate=rate) inSound.summary() inSound.play() # Test if type conversion works inSound2 = Sound(inData=x, inRate=rate) inSound2.play() # Test with GUI inSound = Sound() inSound.play() inSound.writeWav() ''' if __name__ == '__main__': main()