Repository: psychopy/psychopy

Issue:
Microphone: Cannot function in loop because thisTrialN used for saving
Data from the microphone cannot be stored if the routine is not enclosed in a loop. Produces error:

"C:\Users\44797\Documents\PsychoPy_Job\development\new_release_tests\microphone\basic_no_loop_lastrun.py", line 204, in <module>
    thisExp.addData('mic.clip', os.path.join(micRecFolder, 'recording_mic_thisExp_%s.wav' % thisExp.thisTrialN))
AttributeError: 'ExperimentHandler' object has no attribute 'thisTrialN'

This is because an attribute of the trial handler is used to create the variable name 
https://github.com/psychopy/psychopy/blob/dev/psychopy/experiment/components/microphone/__init__.py#L360



Discussion:
It's worse than that, in JS I actually hard coded it to be `trials` regardless of the loop/experiment handler's actual name... Whoops! Will change the boilerplate now!

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
@@ -324,10 +324,11 @@ def writeRoutineEndCode(self, buff):
         inits = getInitVals(self.params)
         # Alter inits
         if len(self.exp.flow._loopList):
-            currLoop = self.exp.flow._loopList[-1]  # last (outer-most) loop
+            inits['loop'] = self.exp.flow._loopList[-1].params['name']
+            inits['filename'] = f"'recording_{inits['name']}_{inits['loop']}_%s.{inits['outputType']}' % {inits['loop']}.thisTrialN)"
         else:
-            currLoop = self.exp._expHandler
-        inits['loop'] = currLoop.params['name']
+            inits['loop'] = ""
+            inits['filename'] = f"'recording_{inits['name']}'"
         transcribe = inits['transcribe'].val
         if inits['transcribe'].val == False:
             inits['transcribeBackend'].val = None
@@ -357,7 +358,7 @@ def writeRoutineEndCode(self, buff):
         buff.setIndentLevel(-1, relative=True)
         code = (
             ")\n"
-            "%(loop)s.addData('%(name)s.clip', os.path.join(%(name)sRecFolder, 'recording_%(name)s_%(loop)s_%%s.%(outputType)s' %% %(loop)s.thisTrialN))\n"
+            "%(loop)s.addData('%(name)s.clip', os.path.join(%(name)sRecFolder, %(filename)s)\n"
         )
         buff.writeIndentedLines(code % inits)
         if transcribe:
@@ -371,6 +372,13 @@ def writeRoutineEndCode(self, buff):
     def writeRoutineEndCodeJS(self, buff):
         inits = getInitVals(self.params)
         inits['routine'] = self.parentName
+        if len(self.exp.flow._loopList):
+            inits['loop'] = self.exp.flow._loopList[-1].params['name']
+            inits['filename'] = f"'recording_{inits['name']}_{inits['loop']}_' + {inits['loop']}.thisN"
+        else:
+            inits['loop'] = ""
+            inits['filename'] = f"'recording_{inits['name']}'"
+
         # Write base end routine code
         BaseComponent.writeRoutineEndCodeJS(self, buff)
         # Store recordings from this routine
@@ -390,7 +398,7 @@ def writeRoutineEndCodeJS(self, buff):
         buff.setIndentLevel(-1, relative=True)
         code = (
             "});\n"
-            "psychoJS.experiment.addData('%(name)s.clip', 'recording_%(name)s' + trials.thisN);\n"
+            "psychoJS.experiment.addData('%(name)s.clip', %(filename)s);\n"
             "// start the asynchronous upload to the server\n"
             "%(name)s.lastClip.upload();\n"
         )


Test patch: