Repository: psychopy/psychopy

Issue:
Error in default microphone component code (with mic.stop())
When converting a simple experiment from the Builder into the Coder (containing one microphone component) and running it from there, I get this error:

`Traceback (most recent call last):
  File "D:\Dati Utente\ESPVicon\Desktop\Tom\checknewmics.py", line 218, in <module>
    mic.stop()
  File "C:\Program Files\PsychoPy\lib\site-packages\psychopy\sound\microphone.py", line 802, in stop
    raise AudioStreamError(
psychopy.sound.exceptions.AudioStreamError: Cannot stop a stream that has not been started.`

If I try commenting out one of the `mic.stop()`, I get this error:

`Traceback (most recent call last):
  File "D:\Dati Utente\ESPVicon\Desktop\Tom\checknewmics.py", line 220, in <module>
    micClip = mic.bank(
  File "C:\Program Files\PsychoPy\lib\site-packages\psychopy\sound\microphone.py", line 918, in bank
    self.lastClip = self.getRecording()
  File "C:\Program Files\PsychoPy\lib\site-packages\psychopy\sound\microphone.py", line 974, in getRecording
    raise AudioStreamError(
psychopy.sound.exceptions.AudioStreamError: Cannot get audio clip, recording was in progress. Be sure to call `Microphone.stop` first.`

Sometimes I can find workarounds to this issue, but even with those it's impossible to have more than one microphone component in the experiment.

Thanks!


Discussion:
Could you share the experiment file for this experiment? It looks like there's a call to `mic.start()` being missed
I have the same issue! And can provide an experiment file which you can find here:
[badsinging.zip](https://github.com/psychopy/psychopy/files/9079997/badsinging.zip)

I included the psyexp and the python code it generates. I only added a print statement to try to understand what was going on. 

In my set up the microphone appears to never record. The offending code I think is here: 
```
    if mic.status == NOT_STARTED and t >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        mic.frameNStart = frameN  # exact frame index
        mic.tStart = t  # local t and not account for scr refresh
        mic.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(mic, 'tStartRefresh')  # time at next scr refresh
        # start recording with mic
        mic.start()
        mic.status = STARTED
    if mic.status == STARTED:
        # update recorded clip for mic
        mic.poll()
    if mic.status == STARTED:
        # is it time to stop? (based on global clock, using actual start)
        if tThisFlipGlobal > mic.tStartRefresh + -frameTolerance:
            # keep track of stop time/frame for later
            mic.tStop = t  # not accounting for scr refresh
            mic.frameNStop = frameN  # exact frame index
            win.timeOnFlip(mic, 'tStopRefresh')  # time at next scr refresh
            # stop recording with mic
            mic.stop()
            mic.status = FINISHED
``` 

When the mic is turned on `mic.tStartRefresh =  tThisFlipGlobal` but at the end when you check to see if it's time to turn off, the check is for `tThisFlipGlobal > mic.tStartRefresh + -frameTolerance` so the mic is started and finished in the first frame. In addition at the end of the routine (L340 in my .py) there is a second mic.stop() which elicits the error. 
Aha, I know what this is! We had a similar problem with Camera - essentially, the stop test code needs to not be written if there's no value for stop, otherwise it's written but with the duration as just a blank string. Hence `-frameTolerance` rather than `whateverTheComponentDurationIs-frameTolerance`... This is handled by the base class which all components inherit from, but for things like Microphone and Camera we override the each frame code. I need to be more careful to add that check back in in future... 

I'll put in a PR to fix this, should be simple as it's the exact same solution we applied to Camera!

Resolving patch:
diff --git a/psychopy/experiment/components/microphone/__init__.py b/psychopy/experiment/components/microphone/__init__.py
--- a/psychopy/experiment/components/microphone/__init__.py
+++ b/psychopy/experiment/components/microphone/__init__.py
@@ -293,14 +293,15 @@ def writeFrameCode(self, buff):
         buff.writeIndentedLines(code % inits)
         buff.setIndentLevel(-1, relative=True)
         # Stop recording
-        self.writeStopTestCode(buff)
-        code = (
-            "# stop recording with %(name)s\n"
-            "%(name)s.stop()\n"
-            "%(name)s.status = FINISHED\n"
-        )
-        buff.writeIndentedLines(code % inits)
-        buff.setIndentLevel(-2, relative=True)
+        if self.params['stopVal'].val not in ('', None, -1, 'None'):
+            self.writeStopTestCode(buff)
+            code = (
+                "# stop recording with %(name)s\n"
+                "%(name)s.stop()\n"
+                "%(name)s.status = FINISHED\n"
+            )
+            buff.writeIndentedLines(code % inits)
+            buff.setIndentLevel(-2, relative=True)
 
     def writeFrameCodeJS(self, buff):
         inits = getInitVals(self.params)


Test patch: