Create reaction.py
This commit is contained in:
parent
be1ef3adbc
commit
31b40af0a6
1 changed files with 169 additions and 0 deletions
169
CPB_Quick_Draw_Duo/reaction.py
Normal file
169
CPB_Quick_Draw_Duo/reaction.py
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
# cpx-reaction-timer v1.0.1
|
||||||
|
# A human reaction timer using light and sound
|
||||||
|
|
||||||
|
# Measures the time it takes for user to press the right button
|
||||||
|
# in response to alternate first NeoPixel and beeps from onboard speaker,
|
||||||
|
# prints times and statistics in Mu friendly format.
|
||||||
|
|
||||||
|
import time
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import array
|
||||||
|
import gc
|
||||||
|
import board
|
||||||
|
import digitalio
|
||||||
|
import analogio
|
||||||
|
import os
|
||||||
|
|
||||||
|
# This code works on both CPB and CPX boards by bringing
|
||||||
|
# in classes with same name
|
||||||
|
try:
|
||||||
|
from audiocore import RawSample
|
||||||
|
except ImportError:
|
||||||
|
from audioio import RawSample
|
||||||
|
try:
|
||||||
|
from audioio import AudioOut
|
||||||
|
except ImportError:
|
||||||
|
from audiopwmio import PWMAudioOut as AudioOut
|
||||||
|
|
||||||
|
import neopixel
|
||||||
|
|
||||||
|
def seed_with_noise():
|
||||||
|
"""Set random seed based on four reads from analogue pads.
|
||||||
|
Disconnected pads on CPX produce slightly noisy 12bit ADC values.
|
||||||
|
Shuffling bits around a little to distribute that noise."""
|
||||||
|
a2 = analogio.AnalogIn(board.A2)
|
||||||
|
a3 = analogio.AnalogIn(board.A3)
|
||||||
|
a4 = analogio.AnalogIn(board.A4)
|
||||||
|
a5 = analogio.AnalogIn(board.A5)
|
||||||
|
random_value = ((a2.value >> 4) + (a3.value << 1) +
|
||||||
|
(a4.value << 6) + (a5.value << 11))
|
||||||
|
for pin in (a2, a3, a4, a5):
|
||||||
|
pin.deinit()
|
||||||
|
random.seed(random_value)
|
||||||
|
|
||||||
|
# Without os.urandom() the random library does not set a useful seed
|
||||||
|
try:
|
||||||
|
os.urandom(4)
|
||||||
|
except NotImplementedError:
|
||||||
|
seed_with_noise()
|
||||||
|
|
||||||
|
# Turn the speaker on
|
||||||
|
speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
|
||||||
|
speaker_enable.direction = digitalio.Direction.OUTPUT
|
||||||
|
speaker_enable.value = True
|
||||||
|
|
||||||
|
audio = AudioOut(board.SPEAKER)
|
||||||
|
|
||||||
|
# Number of seconds
|
||||||
|
SHORTEST_DELAY = 3.0
|
||||||
|
LONGEST_DELAY = 7.0
|
||||||
|
|
||||||
|
red = (40, 0, 0)
|
||||||
|
black = (0, 0, 0)
|
||||||
|
|
||||||
|
A4refhz = 440
|
||||||
|
midpoint = 32768
|
||||||
|
twopi = 2 * math.pi
|
||||||
|
|
||||||
|
def sawtooth(angle):
|
||||||
|
"""A sawtooth function like math.sin(angle).
|
||||||
|
Input of 0 returns 1.0, pi returns 0.0, 2*pi returns -1.0."""
|
||||||
|
|
||||||
|
return 1.0 - angle % twopi / twopi * 2
|
||||||
|
|
||||||
|
# make a sawtooth wave between +/- each value in volumes
|
||||||
|
# phase shifted so it starts and ends near midpoint
|
||||||
|
vol = 32767
|
||||||
|
sample_len = 10
|
||||||
|
waveraw = array.array("H",
|
||||||
|
[midpoint +
|
||||||
|
round(vol * sawtooth((idx + 0.5) / sample_len
|
||||||
|
* twopi
|
||||||
|
+ math.pi))
|
||||||
|
for idx in range(sample_len)])
|
||||||
|
|
||||||
|
beep = RawSample(waveraw, sample_rate=sample_len * A4refhz)
|
||||||
|
|
||||||
|
# play something to get things inside audio libraries initialised
|
||||||
|
audio.play(beep, loop=True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
audio.stop()
|
||||||
|
audio.play(beep)
|
||||||
|
|
||||||
|
# brightness 1.0 saves memory by removing need for a second buffer
|
||||||
|
# 10 is number of NeoPixels on CPX/CPB
|
||||||
|
numpixels = 10
|
||||||
|
pixels = neopixel.NeoPixel(board.NEOPIXEL, numpixels, brightness=1.0)
|
||||||
|
|
||||||
|
# B is right (usb at top)
|
||||||
|
button_right = digitalio.DigitalInOut(board.BUTTON_B)
|
||||||
|
button_right.switch_to_input(pull=digitalio.Pull.DOWN)
|
||||||
|
|
||||||
|
def wait_finger_off_and_random_delay():
|
||||||
|
"""Ensure finger is not touching the button then execute random delay."""
|
||||||
|
while button_right.value:
|
||||||
|
pass
|
||||||
|
duration = (SHORTEST_DELAY +
|
||||||
|
random.random() * (LONGEST_DELAY - SHORTEST_DELAY))
|
||||||
|
time.sleep(duration)
|
||||||
|
|
||||||
|
|
||||||
|
def update_stats(stats, test_type, test_num, duration):
|
||||||
|
"""Update stats dict and return data in tuple for printing."""
|
||||||
|
stats[test_type]["values"].append(duration)
|
||||||
|
stats[test_type]["sum"] += duration
|
||||||
|
stats[test_type]["mean"] = stats[test_type]["sum"] / test_num
|
||||||
|
|
||||||
|
if test_num > 1:
|
||||||
|
# Calculate (sample) variance
|
||||||
|
var_s = (sum([(x - stats[test_type]["mean"])**2
|
||||||
|
for x in stats[test_type]["values"]])
|
||||||
|
/ (test_num - 1))
|
||||||
|
else:
|
||||||
|
var_s = 0.0
|
||||||
|
|
||||||
|
stats[test_type]["sd_sample"] = var_s ** 0.5
|
||||||
|
|
||||||
|
return ("Trial " + str(test_num), test_type, duration,
|
||||||
|
stats[test_type]["mean"], stats[test_type]["sd_sample"])
|
||||||
|
|
||||||
|
run = 1
|
||||||
|
statistics = {"visual": {"values": [], "sum": 0.0, "mean": 0.0,
|
||||||
|
"sd_sample": 0.0},
|
||||||
|
"auditory": {"values": [], "sum": 0.0, "mean": 0.0,
|
||||||
|
"sd_sample": 0.0},
|
||||||
|
"tactile": {"values": [], "sum": 0.0, "mean": 0.0,
|
||||||
|
"sd_sample": 0.0}}
|
||||||
|
|
||||||
|
print("# Trialnumber, time, mean, standarddeviation")
|
||||||
|
# serial console output is printed as tuple to allow Mu to graph it
|
||||||
|
while True:
|
||||||
|
# Visual test using first NeoPixel
|
||||||
|
wait_finger_off_and_random_delay()
|
||||||
|
# do GC now to reduce likelihood of occurrence during reaction timing
|
||||||
|
gc.collect()
|
||||||
|
pixels[0] = red
|
||||||
|
start_t = time.monotonic()
|
||||||
|
while not button_right.value:
|
||||||
|
pass
|
||||||
|
react_t = time.monotonic()
|
||||||
|
reaction_dur = react_t - start_t
|
||||||
|
print(update_stats(statistics, "visual", run, reaction_dur))
|
||||||
|
pixels[0] = black
|
||||||
|
|
||||||
|
# Auditory test using onboard speaker and 444.4Hz beep
|
||||||
|
wait_finger_off_and_random_delay()
|
||||||
|
# do GC now to reduce likelihood of occurrence during reaction timing
|
||||||
|
gc.collect()
|
||||||
|
audio.play(beep, loop=True)
|
||||||
|
start_t = time.monotonic()
|
||||||
|
while not button_right.value:
|
||||||
|
pass
|
||||||
|
react_t = time.monotonic()
|
||||||
|
reaction_dur = react_t - start_t
|
||||||
|
print(update_stats(statistics, "auditory", run, reaction_dur))
|
||||||
|
audio.stop()
|
||||||
|
audio.play(beep) # ensure speaker is left near midpoint
|
||||||
|
|
||||||
|
run += 1
|
||||||
Loading…
Reference in a new issue