📜 ⬆️ ⬇️

On Lee effect waves: Pitonizing DAF generation

image According to statistics, 1-4% of the world's population is subject to a speech defect characterized by frequent prolongation of sounds (syllables, words) and / or frequent stops in speech that violate its rhythmic flow. In the common people, this phenomenon is known as stuttering.

At the moment, the world does not know the panacea, 100% relieving from stuttering, however, there is a most interesting method that allows with some success or another to stop this speech disorder in most stutterers. The method is based on the Lie effect, which is the effect of the delay in acoustic acoustic afferentation on the smoothness of speech, and is called DAF (Delayed Auditory Feedback).

Below we consider an example of building a simple voice feedback generator on the knee of Python and PyQt. Oooh, it's gonna be fun!

What's what and why


The effect of Lee, named after the submariner Bernard Lee ( Lee , 1951), is manifested in the fact that an ordinary person listening through headphones own speech, delayed with the help of special equipment for 80-200 ms, (directly at the time of conversation) causes hesitancy, very reminiscent of stuttering. At the same time, a person who is prone to stuttering, the effect of Lee has the opposite effect. This is the basis of the DAF method. The reason for the delay in speech reproduction in headphones is to synchronize the operation of speech centers - the Wernicke hearing center and Broca's speech-motor center ( cryptometaphorus: delay = gamma generation function for the self-synchronizing stream cipher ). Various studies have revealed that the delay in the range of 50-75 ms can reduce stuttering by 60-80% with normal and accelerated speech. A delay of 190 ms turned out to be slightly more efficient than 75 ms, however, the optimal amount of delay is chosen individually based on the sensations of the subject.
')
The idea of ​​a hardware approach to the normalization of speech is as old as the world — the first device operating on the principle of “regulation of feedback” was designed in 1959. Being large, wired and clumsy, it seemed to be ineffective for use in everyday life, but technology does not stand still , and now there are a number of ways to conveniently generate DAF: using separate mini-devices, as well as softin for Vedroid, Apple (searching by the keyword “DAF” in your application store will show the entire selection of such solutions) and PC (here with false, look at the next paragraph).

Why this post


The cost of applications for mobile platforms varies within a few dollars. Permissible. However, for Windows, there is only one similar program costing $ 30 for the basic version for “personal needs only” (I will not give the name - it is based on the same keyword on the first link of the search engine). Here I wondered how many lines of code the self-made implementation of such a trivial functionality would arise. The result of this interest was a lonely GUI-interface window that hides a simple DAF generator under the hood, which I want to share with others - maybe someone will come in handy.

Trial. CLI interface


To begin with we will sketch a concept in the form of a trial script. We will use a bunch of " Python3 + PyAudio ", where PyAudio is a module for working with sound. The kernel will look like this:

CHANNELS = 2 RATE = 44100 def genDAF(delay): bufferSize = floor(delay / 1000 * RATE) device = PyAudio() try: streamIn = device.open( format=paFloat32, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=bufferSize ) streamOut = device.open( format=paFloat32, channels=CHANNELS, rate=RATE, output=True, frames_per_buffer=bufferSize ) except OSError: print('genDAF: error: No input/output device found! Connect and rerun') return print('CTRL-C to stop capture') while streamIn.is_active(): start = clock() audioData = streamIn.read(bufferSize) streamOut.write(audioData) actualDelay = floor((clock() - start) * 1000) print('Actual Delay: {} ms'.format(actualDelay)) 

The genDAF procedure accepts a delay in milliseconds, calculates the required buffer size (based on the optimal bit rate of 44.1 kHz) for voice recording, and then, if there are connections to input and output audio (aka microphone and speakers), it creates two streams, input and output, respectively. Then, in the main loop, reading and instant playback of the recorded piece of audio data begins, while the actual delay is taken against the background, which is required to perform a couple of read / write operations. It took about 20 lines of code.

Full source CLI-implementation under the spoiler:

dafgen_cli.py
 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # Usage: python3 dafgen_cli.py <delay_in_ms> from pyaudio import PyAudio, paFloat32 from math import floor from time import clock import sys CHANNELS = 2 RATE = 44100 def genDAF(delay): bufferSize = floor(delay / 1000 * RATE) device = PyAudio() try: streamIn = device.open( format=paFloat32, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=bufferSize ) streamOut = device.open( format=paFloat32, channels=CHANNELS, rate=RATE, output=True, frames_per_buffer=bufferSize ) except OSError: print('genDAF: error: No input/output device found! Connect and rerun') return print('CTRL-C to stop capture') while streamIn.is_active(): start = clock() audioData = streamIn.read(bufferSize) streamOut.write(audioData) actualDelay = floor((clock() - start) * 1000) print('Actual Delay: {} ms'.format(actualDelay)) def main(): if len(sys.argv) != 2: print('Usage: python3 {} <delay_in_ms>'.format(sys.argv[0])) sys.exit(1) try: delay = int(sys.argv[1]) except ValueError: print('main: error: Invalid input type') sys.exit(1) if not 50 <= delay <= 200: print('main: error: Delay must be in [50; 200] ms') sys.exit(1) print('Delay: {} ms\n'.format(delay)) try: genDAF(delay) except KeyboardInterrupt: print('Stopped') if __name__ == '__main__': main() 


Final. GUI interface


Green letters on the black background of the terminal - romantic, but not always convenient, we can do better. Let's add a framework for graphics to our bundle of tools, it will turn out " Python3 + PyAudio + PyQt5 ".

Sketch in the designer a couple of buttons, a slider and 2 text fields:

image

Let's add logic, distributing the main DAF generation code into two classes: the control application (MainApp) and the class for a separate thread (Worker), responsible for executing the _genDAF while loop , so that the main window does not hang. The full code is given at the end of the paragraph, and now only the main part.

Control Application:
 class MainApp(QMainWindow, Ui_DAFGen): _CHANNELS = 2 _RATE = 44100 def __init__(self): super().__init__() self.setupUi(self) # ... # ... def _startCapture(self): bufferSize = floor(self.delaySlider.value() / 1000 * self._RATE) device = PyAudio() try: streamIn = device.open( format=paFloat32, channels=self._CHANNELS, rate=self._RATE, input=True, frames_per_buffer=bufferSize ) streamOut = device.open( format=paFloat32, channels=self._CHANNELS, rate=self._RATE, output=True, frames_per_buffer=bufferSize ) except OSError: QMessageBox.critical(self, 'Error', 'No input/output device found! Connect and rerun.') return self._workerThread = Worker(bufferSize, streamIn, streamOut) self._workerThread._trigger.connect(self._updateActualDelay) # ... self._workerThread.start() 

Second stream:
 class Worker(QThread): _trigger = pyqtSignal(float) def __init__(self, bufferSize, streamIn, streamOut): QThread.__init__(self) self._bufferSize = bufferSize self._streamIn = streamIn self._streamOut = streamOut def __del__(self): self.wait() def _genDAF(self): while self._streamIn.is_active(): start = clock() audioData = self._streamIn.read(self._bufferSize) self._streamOut.write(audioData) actualDelay = clock() - start self._trigger.emit(actualDelay) def run(self): self._genDAF() 

The source for the logic of the GUI-implementation under the spoiler:

dafgen.py
 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # Usage: python3 dafgen.py from PyQt5.QtWidgets import * from PyQt5.QtCore import * from ui_dafgen import Ui_DAFGen from pyaudio import PyAudio, paFloat32 from math import floor from time import clock import sys class Worker(QThread): _trigger = pyqtSignal(float) def __init__(self, bufferSize, streamIn, streamOut): QThread.__init__(self) self._bufferSize = bufferSize self._streamIn = streamIn self._streamOut = streamOut def __del__(self): self.wait() def _genDAF(self): while self._streamIn.is_active(): start = clock() audioData = self._streamIn.read(self._bufferSize) self._streamOut.write(audioData) actualDelay = clock() - start self._trigger.emit(actualDelay) def run(self): self._genDAF() class MainApp(QMainWindow, Ui_DAFGen): _CHANNELS = 2 _RATE = 44100 def __init__(self): super().__init__() self.setupUi(self) self.stopButton.setEnabled(False) self._updateDelay() self.delaySlider.valueChanged.connect(self._updateDelay) self.startButton.clicked.connect(self._startCapture) self.stopButton.clicked.connect(self._stopCapture) self.quitButton.clicked.connect(QApplication.quit) def _updateDelay(self): self.delayEdit.setPlainText(str(self.delaySlider.value()) + ' ms') def _startCapture(self): bufferSize = floor(self.delaySlider.value() / 1000 * self._RATE) device = PyAudio() try: streamIn = device.open( format=paFloat32, channels=self._CHANNELS, rate=self._RATE, input=True, frames_per_buffer=bufferSize ) streamOut = device.open( format=paFloat32, channels=self._CHANNELS, rate=self._RATE, output=True, frames_per_buffer=bufferSize ) except OSError: QMessageBox.critical(self, 'Error', 'No input/output device found! Connect and rerun.') return self._workerThread = Worker(bufferSize, streamIn, streamOut) self._workerThread._trigger.connect(self._updateActualDelay) self.startButton.setEnabled(False) self.delaySlider.setEnabled(False) self.stopButton.setEnabled(True) self._workerThread.start() def _stopCapture(self): self._workerThread.terminate() self.actualDelayEdit.clear() self.startButton.setEnabled(True) self.delaySlider.setEnabled(True) self.stopButton.setEnabled(False) def _updateActualDelay(self, t): newValue = floor(t * 1000) self.actualDelayEdit.setPlainText(str(newValue) + ' ms') def main(): app = QApplication(sys.argv) win = MainApp() win.show() sys.exit(app.exec_()) if __name__ == '__main__': main() 


Conclusion and code


Actually everything I wanted to tell. Feel free to use.

I will also leave the link to the project entirely: in addition, there are the GUI code and the template for the PyQt Designer.

Thanks for attention!

Literature


Missulovin L. Ya., Yurova M. S. Overcoming stuttering in adolescents and adults with the use of devices such as "AIR" // Scientific-methodical electronic journal "Concept". - 2015. - № S23. - pp. 46–50. - URL: e-koncept.ru/2015/75287.htm .

Source: https://habr.com/ru/post/347580/


All Articles