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 numpy as np
import scipy.io.wavfile 
import os
import sys

import tempfile
import subprocess
import ui   # from thLib

# "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] subprocess.call(cmd) print('Infile converted from ' + ext + ' to ".wav"') inFile = outFile self.source = outFile try: self.rate, self.data = scipy.io.wavfile.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] subprocess.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() scipy.io.wavfile.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()