Wiki - README‎ > ‎Programming‎ > ‎Python‎ > ‎

PyChrono

a script to chrono a paintball marker.  there are a lot of (unwritten) TODOs and cleaning up that will be done *some day*... also, i haven't compared the results with a true chronograph, but it's close enough to get everyone shooting the same.

#!/usr/bin/env python

#http://www.python-forum.org/pythonforum/viewtopic.php?f=3&t=7431#p35020
import pyaudio
import wave
import threading
import struct
import sys
import os
import subprocess

# constants
FEET_PER_METER = 3.2808399
SPEED_SOUND_MPS = 340.29
SPEED_SOUND_FPS = SPEED_SOUND_MPS * FEET_PER_METER
# TODO: compensate for speed of sound

# detection range
MIN_FPS = 150.0
MAX_FPS = 350.0
AMPLITUDE_THRESHOLD = 1.5

# recording rates
BUFFER_SIZE = 1024
RATE = 44100
SECONDS_PER_SAMPLE = 1.0 / float(RATE)


# other recording settings
RECORD_SECONDS = 20
CHANNELS = 1
FORMAT = pyaudio.paFloat32
recording_done = False

# get distances in feet
DISTANCE_ST = raw_input( 'shooter-target distance in feet (10): ' )
DISTANCE_ST = float(DISTANCE_ST) if DISTANCE_ST else 10.0
#DISTANCE_MS = raw_input( 'mic-shooter distance in feet (5): ' )
#DISTANCE_MS = float(DISTANCE_MS) if DISTANCE_MS else 5.0
#DISTANCE_MT = raw_input( 'mic-target distance in feet (5): ' )
#DISTANCE_MT = float(DISTANCE_MT) if DISTANCE_MT else 5.0


MIN_SAMPLES_THRESHOLD = int( DISTANCE_ST / MAX_FPS * float(RATE) )
MAX_SAMPLES_THRESHOLD = int( DISTANCE_ST / MIN_FPS * float(RATE) )
print( 'MIN: %i (%ifps)' % (MIN_SAMPLES_THRESHOLD,MAX_FPS) )
print( 'MAX: %i (%ifps)' % (MAX_SAMPLES_THRESHOLD,MIN_FPS) )


pya = pyaudio.PyAudio()
stream = pya.open(
    format = FORMAT,
    channels = CHANNELS,
    rate = RATE,
    input = True,
    frames_per_buffer = BUFFER_SIZE,
    )

def stop_recording():
    global recording_done
    recording_done = True
#t = threading.Timer( RECORD_SECONDS, stop_recording )
#t.start()


# for displaying
l_loud = ' .*@#'
c_loud = len(l_loud)

time = long(0)
last_peak = []
iters = 0
max_amp = 0.0
sum_amp,total = 0.0,0.0

while not recording_done:
    iters += 1
    data = stream.read( BUFFER_SIZE )
    data = struct.unpack( '<{0}f'.format( BUFFER_SIZE ), data )
    
    data = [ abs(x) for x in data ]
    
    #loud = [ l_loud[ min(int(abs(x)),c_loud-1) ] for x in data ]
    #sys.stdout.write( ''.join( loud ) )
    #continue
    
    max_amp = max( max_amp, max( x for x in data ) )
    sum_amp += sum( data )
    total += float(BUFFER_SIZE)
    if int(iters % (RATE / BUFFER_SIZE / 2)) == 0:
        avg_amp = sum_amp / total
        print( 'amplitude: max = %0.3f, avg = %0.3f' % (max_amp,avg_amp) )
        max_amp = 0.0
        sum_amp = 0.0
        total = 0.0
    
    # find peaks
    peaks = [ long(t)+time for t,x in enumerate(data) if x > AMPLITUDE_THRESHOLD ]
    if len(peaks): print( 'peaks: %i' % (len(peaks),) )
    peaks = last_peak + peaks
    if peaks: last_peak = [ peaks[-1] - BUFFER_SIZE ]
    else: last_peak = []
    
    #if len(peaks): print( 'peaks: %s' % ' '.join([ str(peak) for peak in peaks ]) )
    if len(peaks) <= 1: continue
    
    # find peak-distances that are within the range we're interested
    nsamps = []
    lt = peaks[0]
    for t in peaks[1:]:
        dt = t - lt
        if dt > MIN_SAMPLES_THRESHOLD and dt < MAX_SAMPLES_THRESHOLD:
            nsamps += [ dt ]
        lt = t
    
    if not nsamps: continue
    
    for s in nsamps:
        t = s * SECONDS_PER_SAMPLE
        print( '%0.5fsecs (%isamps) = %0.2ffps' % (t, s, DISTANCE_ST / t) )
        subprocess.Popen( [ 'espeak', '"%i"' % int( DISTANCE_ST / t ) ], stdout=subprocess.PIPE, stderr=subprocess.PIPE )
    

stream.close()
pya.terminate()



Comments