def playback(self): """Plays the saved .wav file, as just recorded or resampled """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = '%s: Playback requested but no saved file' % self.loggingId logging.error(msg) raise ValueError(msg) # prepare a player for this file: t0 = core.getTime() self.sfplayer = SfPlayer(self.savedFile, speed=1, loop=False) self.sfplayer2 = self.sfplayer.mix( 2) # mix(2) -> 2 outputs -> 2 speakers self.sfplayer2.out() logging.exp('%s: Playback: prep %.3fs' % (self.loggingId, core.getTime() - t0)) # play the file; sfplayer was created during record: t0 = core.getTime() self.sfplayer.play() core.wait(self.duration) # set during record() t1 = core.getTime() logging.exp('%s: Playback: play %.3fs (est) %s' % (self.loggingId, t1 - t0, self.savedFile))
def playback(self): """Plays the saved .wav file which was just recorded """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = '%s: Playback requested but no saved file' % self.loggingId logging.error(msg) raise ValueError(msg) # prepare a player for this file: t0 = time.time() self.sfplayer = SfPlayer(self.savedFile, speed=1, loop=False) self.sfplayer2 = self.sfplayer.mix(2) # mix(2) -> 2 outputs -> 2 speakers self.sfplayer2.out() logging.exp('%s: Playback: prep %.3fs' % (self.loggingId, time.time()-t0)) # play the file; sfplayer was created during record: t0 = time.time() self.sfplayer.play() time.sleep(self.duration) # set during record() t1 = time.time() logging.exp('%s: Playback: play %.3fs (est) %s' % (self.loggingId, t1-t0, self.savedFile))
class SimpleAudioCapture(): """Capture a sound sample from the default sound input, and save to a file. Execution will block until the recording is finished. **Example**:: from psychopy import microphone microphone.switchOn(sampleRate=16000) # do once when starting, can take 2-3s mic = microphone.SimpleAudioCapture() # prepare to record; only one can be active mic.record(1) # record for 1.000 seconds, save to a file mic.playback() savedFileName = mic.savedFile microphone.switchOff() # do once, at exit Also see Builder Demo "voiceCapture" :Author: Jeremy R. Gray, March 2012 """ def __init__(self, name='mic', file='', saveDir=''): """ :Parameters: name : Stem for the output file, also used in logging. file : optional file name to use; default = 'name-onsetTimeEpoch.wav' saveDir : Directory to use for output .wav files. If a saveDir is given, it will return 'saveDir/file'. If no saveDir, then return abspath(file) """ self.name = name self.saveDir = saveDir if file: self.wavOutFilename = file else: self.wavOutFilename = os.path.join(self.saveDir, name + ONSET_TIME_HERE +'.wav') if not self.saveDir: self.wavOutFilename = os.path.abspath(self.wavOutFilename) self.onset = None # becomes onset time, used in filename self.savedFile = False # becomes saved file name self.status = NOT_STARTED # for Builder component # pyo server good to go? if not serverCreated(): raise AttributeError('pyo server not created') if not serverBooted(): raise AttributeError('pyo server not booted') self.loggingId = self.__class__.__name__ if self.name: self.loggingId += ' ' + self.name def __del__(self): pass def reset(self): """Restores to fresh state, ready to record again""" logging.exp('%s: resetting at %.3f' % (self.loggingId, time.time())) self.__del__() self.__init__(name=self.name, saveDir=self.saveDir) def record(self, sec, block=True): """Capture sound input for duration <sec>, save to a file. Return the path/name to the new file. Uses onset time (epoch) as a meaningful identifier for filename and log. """ RECORD_SECONDS = float(sec) self.onset = time.time() # note: report onset time in log, and use in filename logging.data('%s: Record: onset %.3f, capture %.3fs' % (self.loggingId, self.onset, RECORD_SECONDS) ) self.savedFile = self.wavOutFilename.replace(ONSET_TIME_HERE, '-%.3f' % self.onset) inputter = Input(chnl=0, mul=1) # chnl=[0,1] for stereo input t0 = time.time() # launch the recording, saving to file: recorder = Record(inputter, self.savedFile, chnls=2, fileformat=0, # .wav format sampletype=0, buffering=4) # 4 is default # launch recording, block as needed, and clean up: clean = Clean_objects(RECORD_SECONDS, recorder) # set up to stop recording clean.start() # the timer starts now, ends automatically whether block or not if block: time.sleep(RECORD_SECONDS - 0.0008) # Clean_objects() set-up takes ~0.0008s, for me logging.exp('%s: Record: stop. %.3f, capture %.3fs (est)' % (self.loggingId, time.time(), time.time() - t0) ) else: logging.exp('%s: Record: return immediately, no blocking' % (self.loggingId) ) self.duration = RECORD_SECONDS # used in playback() return self.savedFile # filename, or None def playback(self): """Plays the saved .wav file which was just recorded """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = '%s: Playback requested but no saved file' % self.loggingId logging.error(msg) raise ValueError(msg) # prepare a player for this file: t0 = time.time() self.sfplayer = SfPlayer(self.savedFile, speed=1, loop=False) self.sfplayer2 = self.sfplayer.mix(2) # mix(2) -> 2 outputs -> 2 speakers self.sfplayer2.out() logging.exp('%s: Playback: prep %.3fs' % (self.loggingId, time.time()-t0)) # play the file; sfplayer was created during record: t0 = time.time() self.sfplayer.play() time.sleep(self.duration) # set during record() t1 = time.time() logging.exp('%s: Playback: play %.3fs (est) %s' % (self.loggingId, t1-t0, self.savedFile))
class AudioCapture(object): """Capture a sound sample from the default sound input, and save to a file. Untested whether you can have two recordings going on simultaneously. **Examples**:: from psychopy import microphone from psychopy import event, visual # for key events microphone.switchOn(sampleRate=16000) # do once # Record for 1.000 seconds, save to mic.savedFile mic = microphone.AudioCapture() mic.record(1) mic.playback() # Resample, creates a new file discards orig mic.resample(48000, keep=False) # Record new file for 60 sec or until key 'q' w = visual.Window() # needed for key-events mic.reset() mic.record(60, block=False) while mic.recorder.running: if 'q' in event.getKeys(): mic.stop() microphone.switchOff() # do once Also see Builder Demo "voiceCapture". :Author: Jeremy R. Gray, March 2012 """ class _Recorder(object): """Class for internal object to make an audio recording using pyo. Never needed by end-users; only used internally in __init__: self.recorder = _Recorder(None) # instantiate, global Then in record(), do: self.recorder.run(file, sec) This sets recording parameters, starts recording. To stop a recording that is in progress, do self.stop() This class never handles blocking; AudioCapture has to do that. Motivation: Doing pyo Record from within a function worked most of the time, but failed catastrophically ~1% of time with a bus error. Seemed to be due to a namespace scoping issue, which using globals seemed to fix; see pyo mailing list, 7 April 2012. This draws heavily on Olivier Belanger's solution. """ def __init__(self, file, sec=0, sampletype=0): self.running = False if file: inputter = Input(chnl=0, mul=1) self.recorder = Record(inputter, file, chnls=2, fileformat=0, sampletype=sampletype, buffering=4) self.clean = Clean_objects(sec, self.recorder) def run(self, file, sec, sampletype): self.__init__(file, sec, sampletype) self.running = True self.clean.start( ) # controls recording onset (now) and offset (later) threading.Timer(sec, self._stop).start() # set running flag False def stop(self): self.recorder.stop() self._stop() def _stop(self): self.running = False def __init__(self, name='mic', file='', saveDir='', sampletype=0): """ :Parameters: name : Stem for the output file, also used in logging. file : optional file name to use; default = 'name-onsetTimeEpoch.wav' saveDir : Directory to use for output .wav files. If a saveDir is given, it will return 'saveDir/file'. If no saveDir, then return abspath(file) sampletype : bit depth pyo recording option: 0=16 bits int, 1=24 bits int; 2=32 bits int """ self.name = name self.saveDir = saveDir if file: self.wavOutFilename = file else: self.wavOutFilename = os.path.join(self.saveDir, name + '.wav') if not self.saveDir: self.wavOutFilename = os.path.abspath(self.wavOutFilename) else: if not os.path.isdir(self.saveDir): os.makedirs(self.saveDir, 0770) self.onset = None # becomes onset time, used in filename self.savedFile = False # becomes saved file name self.status = NOT_STARTED # for Builder component # pyo server good to go? if not serverCreated(): raise AttributeError('pyo server not created') if not serverBooted(): raise AttributeError('pyo server not booted') self.loggingId = self.__class__.__name__ if self.name: self.loggingId += ' ' + self.name # the recorder object needs to persist, or else get bus errors: self.recorder = self._Recorder(None) self.sampletype = sampletype # pass through .run() def __del__(self): pass def stop(self): """Interrupt a recording that is in progress; close & keep the file. Ends the recording before the duration that was initially specified. The same file name is retained, with the same onset time but a shorter duration. The same recording cannot be resumed after a stop (it is not a pause), but you can start a new one. """ if not self.recorder.running: logging.exp('%s: Stop requested, but no record() in progress' % self.loggingId) return self.duration = core.getTime() - self.onset # new shorter duration self.recorder.stop() logging.data('%s: Record stopped early, new duration %.3fs' % (self.loggingId, self.duration)) def reset(self): """Restores to fresh state, ready to record again""" logging.exp('%s: resetting at %.3f' % (self.loggingId, core.getTime())) self.__del__() self.__init__(name=self.name, saveDir=self.saveDir) def record(self, sec, file='', block=True): """Capture sound input for duration <sec>, save to a file. Return the path/name to the new file. Uses onset time (epoch) as a meaningful identifier for filename and log. """ while self.recorder.running: pass self.duration = float(sec) self.onset = core.getTime( ) # note: report onset time in log, and use in filename logging.data('%s: Record: onset %.3f, capture %.3fs' % (self.loggingId, self.onset, self.duration)) if not file: onsettime = '-%.3f' % self.onset self.savedFile = onsettime.join( os.path.splitext(self.wavOutFilename)) else: self.savedFile = os.path.abspath(file).strip('.wav') + '.wav' t0 = core.getTime() self.recorder.run(self.savedFile, self.duration, self.sampletype) self.rate = pyoServer.getSamplingRate() if block: core.wait(self.duration - .0008) # .0008 fudge factor for better reporting # actual timing is done by Clean_objects logging.exp('%s: Record: stop. %.3f, capture %.3fs (est)' % (self.loggingId, core.getTime(), core.getTime() - t0)) else: logging.exp('%s: Record: return immediately, no blocking' % (self.loggingId)) return self.savedFile def playback(self): """Plays the saved .wav file, as just recorded or resampled """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = '%s: Playback requested but no saved file' % self.loggingId logging.error(msg) raise ValueError(msg) # prepare a player for this file: t0 = core.getTime() self.sfplayer = SfPlayer(self.savedFile, speed=1, loop=False) self.sfplayer2 = self.sfplayer.mix( 2) # mix(2) -> 2 outputs -> 2 speakers self.sfplayer2.out() logging.exp('%s: Playback: prep %.3fs' % (self.loggingId, core.getTime() - t0)) # play the file; sfplayer was created during record: t0 = core.getTime() self.sfplayer.play() core.wait(self.duration) # set during record() t1 = core.getTime() logging.exp('%s: Playback: play %.3fs (est) %s' % (self.loggingId, t1 - t0, self.savedFile)) def resample(self, newRate=16000, keep=True): """Re-sample the saved file to a new rate, return the full path. Can take several visual frames to resample a 2s recording. The default values for resample() are for google-speech, keeping the original (presumably recorded at 48kHz) to archive. A warning is generated if the new rate not an integer factor / multiple of the old rate. To control anti-aliasing, use pyo.downsamp() or upsamp() directly. """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = '%s: Re-sample requested but no saved file' % self.loggingId logging.error(msg) raise ValueError(msg) if newRate <= 0 or type(newRate) != int: msg = '%s: Re-sample bad new rate = %s' % (self.loggingId, repr(newRate)) logging.error(msg) raise ValueError(msg) # set-up: if self.rate >= newRate: ratio = float(self.rate) / newRate info = '-ds%i' % ratio else: ratio = float(newRate) / self.rate info = '-us%i' % ratio if ratio != int(ratio): logging.warn('%s: old rate is not an integer factor of new rate' % self.loggingId) ratio = int(ratio) newFile = info.join(os.path.splitext(self.savedFile)) # use pyo's downsamp or upsamp based on relative rates: if not ratio: logging.warn('%s: Re-sample by %sx is undefined, skipping' % (self.loggingId, str(ratio))) elif self.rate >= newRate: t0 = time.time() downsamp(self.savedFile, newFile, ratio) # default 128-sample anti-aliasing logging.exp( '%s: Down-sampled %sx in %.3fs to %s' % (self.loggingId, str(ratio), time.time() - t0, newFile)) else: t0 = time.time() upsamp(self.savedFile, newFile, ratio) # default 128-sample anti-aliasing logging.exp( '%s: Up-sampled %sx in %.3fs to %s' % (self.loggingId, str(ratio), time.time() - t0, newFile)) # clean-up: if not keep: os.unlink(self.savedFile) self.savedFile = newFile self.rate = newRate return os.path.abspath(newFile)
class AudioCapture(object): """Capture a sound sample from the default sound input, and save to a file. Untested whether you can have two recordings going on simultaneously. **Example**:: from psychopy import microphone microphone.switchOn(sampleRate=16000) # do once when starting, can take 2-3s mic = microphone.AudioCapture() # prepare to record; only one can be active mic.record(1) # record for 1.000 seconds, save to mic.savedFile mic.playback() mic.resample(48000, keep=False) # creates a new file, discards original mic.resample(24000, keep=False) # only int ratios are supported # mic.savedFile is now the name of the re-re-sampled file microphone.switchOff() # do once, at exit Also see Builder Demo "voiceCapture". :Author: Jeremy R. Gray, March 2012 """ class _Recorder(object): """Class for internal object to make an audio recording using pyo. Never needed by end-users; only used internally in __init__: self.recorder = _Recorder(None) # instantiate, global Then in record(), do: self.recorder.run(file, sec) This sets recording parameters, starts recording. This class never handles blocking; SimpleAudioCapture has to do that. Motivation: Doing pyo Record from within a function worked most of the time, but failed catastrophically ~1% of time with a bus error. Seemed to be due to a namespace scoping issue, which using globals seemed to fix; see pyo mailing list, 7 April 2012. This draws heavily on Olivier Belanger's solution. """ def __init__(self, file, sec=0, sampletype=0): self.running = False if file: inputter = Input(chnl=0, mul=1) recorder = Record(inputter, file, chnls=2, fileformat=0, sampletype=sampletype, buffering=4) self.clean = Clean_objects(sec, recorder) def run(self, file, sec, sampletype): self.__init__(file, sec, sampletype) self.running = True self.clean.start() # controls recording onset (now) and offset (later) threading.Timer(sec, self.stop).start() # set running flag False def stop(self): self.running = False def __init__(self, name='mic', file='', saveDir='', sampletype=0): """ :Parameters: name : Stem for the output file, also used in logging. file : optional file name to use; default = 'name-onsetTimeEpoch.wav' saveDir : Directory to use for output .wav files. If a saveDir is given, it will return 'saveDir/file'. If no saveDir, then return abspath(file) sampletype : bit depth pyo recording option: 0=16 bits int, 1=24 bits int; 2=32 bits int """ self.name = name self.saveDir = saveDir if file: self.wavOutFilename = file else: self.wavOutFilename = os.path.join(self.saveDir, name + '.wav') if not self.saveDir: self.wavOutFilename = os.path.abspath(self.wavOutFilename) else: if not os.path.isdir(self.saveDir): os.makedirs(self.saveDir, 0770) self.onset = None # becomes onset time, used in filename self.savedFile = False # becomes saved file name self.status = NOT_STARTED # for Builder component # pyo server good to go? if not serverCreated(): raise AttributeError('pyo server not created') if not serverBooted(): raise AttributeError('pyo server not booted') self.loggingId = self.__class__.__name__ if self.name: self.loggingId += ' ' + self.name # the recorder object needs to persist, or else get bus errors: self.recorder = self._Recorder(None) self.sampletype = sampletype # pass through .run() def __del__(self): pass def reset(self): """Restores to fresh state, ready to record again""" logging.exp('%s: resetting at %.3f' % (self.loggingId, core.getTime())) self.__del__() self.__init__(name=self.name, saveDir=self.saveDir) def record(self, sec, file='', block=True): """Capture sound input for duration <sec>, save to a file. Return the path/name to the new file. Uses onset time (epoch) as a meaningful identifier for filename and log. """ while self.recorder.running: pass self.duration = float(sec) self.onset = core.getTime() # note: report onset time in log, and use in filename logging.data('%s: Record: onset %.3f, capture %.3fs' % (self.loggingId, self.onset, self.duration) ) if not file: onsettime = '-%.3f' % self.onset self.savedFile = onsettime.join(os.path.splitext(self.wavOutFilename)) else: self.savedFile = os.path.abspath(file).strip('.wav') + '.wav' t0 = core.getTime() self.recorder.run(self.savedFile, self.duration, self.sampletype) self.rate = pyoServer.getSamplingRate() if block: core.wait(self.duration - .0008) # .0008 fudge factor for better reporting # actual timing is done by Clean_object in _theGlobalRecordingThread() logging.exp('%s: Record: stop. %.3f, capture %.3fs (est)' % (self.loggingId, core.getTime(), core.getTime() - t0) ) else: logging.exp('%s: Record: return immediately, no blocking' % (self.loggingId) ) return self.savedFile def playback(self): """Plays the saved .wav file, as just recorded or resampled """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = '%s: Playback requested but no saved file' % self.loggingId logging.error(msg) raise ValueError(msg) # prepare a player for this file: t0 = core.getTime() self.sfplayer = SfPlayer(self.savedFile, speed=1, loop=False) self.sfplayer2 = self.sfplayer.mix(2) # mix(2) -> 2 outputs -> 2 speakers self.sfplayer2.out() logging.exp('%s: Playback: prep %.3fs' % (self.loggingId, core.getTime()-t0)) # play the file; sfplayer was created during record: t0 = core.getTime() self.sfplayer.play() core.wait(self.duration) # set during record() t1 = core.getTime() logging.exp('%s: Playback: play %.3fs (est) %s' % (self.loggingId, t1-t0, self.savedFile)) def resample(self, newRate=16000, keep=True): """Re-sample the saved file to a new rate, return the full path. Can take several visual frames to resample a 2s recording. The default values for resample() are for google-speech, keeping the original (presumably recorded at 48kHz) to archive. A warning is generated if the new rate not an integer factor / multiple of the old rate. To control anti-aliasing, use pyo.downsamp() or upsamp() directly. """ if not self.savedFile or not os.path.isfile(self.savedFile): msg = '%s: Re-sample requested but no saved file' % self.loggingId logging.error(msg) raise ValueError(msg) if newRate <= 0 or type(newRate) != int: msg = '%s: Re-sample bad new rate = %s' % (self.loggingId, repr(newRate)) logging.error(msg) raise ValueError(msg) # set-up: if self.rate >= newRate: ratio = float(self.rate) / newRate info = '-ds%i' % ratio else: ratio = float(newRate) / self.rate info = '-us%i' % ratio if ratio != int(ratio): logging.warn('%s: old rate is not an integer factor of new rate'% self.loggingId) ratio = int(ratio) newFile = info.join(os.path.splitext(self.savedFile)) # use pyo's downsamp or upsamp based on relative rates: if not ratio: logging.warn('%s: Re-sample by %sx is undefined, skipping' % (self.loggingId, str(ratio))) elif self.rate >= newRate: t0 = time.time() downsamp(self.savedFile, newFile, ratio) # default 128-sample anti-aliasing logging.exp('%s: Down-sampled %sx in %.3fs to %s' % (self.loggingId, str(ratio), time.time()-t0, newFile)) else: t0 = time.time() upsamp(self.savedFile, newFile, ratio) # default 128-sample anti-aliasing logging.exp('%s: Up-sampled %sx in %.3fs to %s' % (self.loggingId, str(ratio), time.time()-t0, newFile)) # clean-up: if not keep: os.unlink(self.savedFile) self.savedFile = newFile self.rate = newRate return os.path.abspath(newFile)
def reloadAudioFile(self): self.sfplayer = SfPlayer(self.outputPath, loop=False, mul=1).out()