def __init__(self, engine, controlnum, samprate=44100): Task.__init__(self) self.engine = engine self.controlnum = controlnum devnum = self.engine.input.controls.micDevice[controlnum] if devnum == -1: devnum = None self.devname = pa.get_default_input_device_info()['name'] else: self.devname = pa.get_device_info_by_index(devnum)['name'] self.mic = pa.open(samprate, 1, pyaudio.paFloat32, input=True, input_device_index=devnum, start=False) self.analyzer = pypitch.Analyzer(samprate) self.mic_started = False self.lastPeak = 0 self.detectTaps = True self.tapStatus = False self.tapThreshold = -self.engine.input.controls.micTapSensitivity[controlnum] self.passthroughQueue = [] passthroughVolume = self.engine.input.controls.micPassthroughVolume[controlnum] if passthroughVolume > 0.0: log.debug('Microphone: creating passthrough stream at %d%% volume' % round(passthroughVolume * 100)) self.passthroughStream = MicrophonePassthroughStream(engine, self) self.passthroughStream.setVolume(passthroughVolume) else: log.debug('Microphone: not creating passthrough stream') self.passthroughStream = None
class Microphone(Task): def __init__(self, engine, controlnum, samprate=44100): Task.__init__(self) self.engine = engine self.controlnum = controlnum devnum = self.engine.input.controls.micDevice[controlnum] if devnum == -1: devnum = None self.devname = pa.get_default_input_device_info()['name'] else: self.devname = pa.get_device_info_by_index(devnum)['name'] self.mic = pa.open(samprate, 1, pyaudio.paFloat32, input=True, input_device_index=devnum, start=False) self.analyzer = pypitch.Analyzer(samprate) self.mic_started = False self.lastPeak = 0 self.detectTaps = True self.tapStatus = False self.tapThreshold = -self.engine.input.controls.micTapSensitivity[controlnum] self.passthroughQueue = [] passthroughVolume = self.engine.input.controls.micPassthroughVolume[controlnum] if passthroughVolume > 0.0: log.debug('Microphone: creating passthrough stream at %d%% volume' % round(passthroughVolume * 100)) self.passthroughStream = MicrophonePassthroughStream(engine, self) self.passthroughStream.setVolume(passthroughVolume) else: log.debug('Microphone: not creating passthrough stream') self.passthroughStream = None def __del__(self): self.stop() self.mic.close() def start(self): if not self.mic_started: self.mic_started = True self.mic.start_stream() self.engine.addTask(self) log.debug('Microphone: started %s' % self.devname) if self.passthroughStream is not None: log.debug('Microphone: starting passthrough stream') self.passthroughStream.play() def stop(self): if self.mic_started: if self.passthroughStream is not None: log.debug('Microphone: stopping passthrough stream') self.passthroughStream.stop() self.engine.removeTask(self) self.mic.stop_stream() self.mic_started = False log.debug('Microphone: stopped %s' % self.devname) # Called by the Task machinery: pump the mic and shove the data through the analyzer. def run(self, ticks): while self.mic.get_read_available() > 1024: try: chunk = self.mic.read(1024) except IOError as e: if e.args[1] == pyaudio.paInputOverflowed: log.notice('Microphone: ignoring input buffer overflow') chunk = '\x00' * 4096 else: raise if self.passthroughStream is not None: self.passthroughQueue.append(chunk) self.analyzer.input(np.frombuffer(chunk, dtype=np.float32)) self.analyzer.process() pk = self.analyzer.getPeak() if self.detectTaps: if pk > self.tapThreshold and pk > self.lastPeak + 5.0: self.tapStatus = True self.lastPeak = pk # Get the amplitude (in dB) of the peak of the most recent input window. def getPeak(self): return self.analyzer.getPeak() # Get the microphone tap status. # When a tap occurs, it is remembered until this function is called. def getTap(self): retval = self.tapStatus self.tapStatus = False return retval def getFormants(self): return self.analyzer.getFormants() # Get the note currently being sung. # Returns None if there isn't one or a pypitch.Tone object if there is. def getTone(self): return self.analyzer.findTone() # Get the note currently being sung, as an integer number of semitones above A. # The frequency is rounded to the nearest semitone, then shifted by octaves until # the result is between 0 and 11 (inclusive). Returns None is no note is being sung. def getSemitones(self): tone = self.analyzer.findTone() if tone is None: return tone return int(round((math.log(tone.freq) - LN_440) * 12.0 / LN_2) % 12) # Work out how accurately the note (passed in as a MIDI note number) is being # sung. Return a float in the range [-6.0, 6.0] representing the number of # semitones difference there is from the nearest occurrence of the note. The # octave doesn't matter. Or return None if there's no note being sung. def getDeviation(self, midiNote): tone = self.analyzer.findTone() if tone is None: return tone # Convert to semitones from A-440. semitonesFromA440 = (math.log(tone.freq) - LN_440) * 12.0 / LN_2 # midiNote % 12 = semitones above C, which is 3 semitones above A semitoneDifference = (semitonesFromA440 - 3.0) - float(midiNote % 12) # Adjust to the proper range. acc = math.fmod(semitoneDifference, 12.0) if acc > 6.0: acc -= 12.0 elif acc < -6.0: acc += 12.0 return acc