'''
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()