141 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| # The idea is to have only one thread accessing the audio input source instead
 | |
| # of every music enabled thread itself. also, different fuctions calculating
 | |
| # frequencys and so on shoud run in own threads to the leds get more updates
 | |
| #
 | |
| # in history one thread was calculating bpm, freqence average and max values in one thread
 | |
| # before updating the leds, what the pi was a bit slow for and you cloud count every update 
 | |
| # of the leds
 | |
| 
 | |
| # i think most of it is from https://github.com/shunfu/python-beat-detector/
 | |
| 
 | |
| 
 | |
| import numpy
 | |
| import scipy
 | |
| import pyaudio
 | |
| import threading
 | |
| 
 | |
| import time
 | |
| 
 | |
| 
 | |
| class pyAudioRecorder:
 | |
|     """Simple, cross-platform class to record from the default input device."""
 | |
| 
 | |
|     def __init__(self):
 | |
|         self.RATE = 44100
 | |
|         self.BUFFERSIZE = 2**12
 | |
|         self.secToRecord = .1
 | |
|         self.kill_threads = False
 | |
|         self.has_new_audio = False
 | |
|         self.setup()
 | |
| 
 | |
|         # since the server only can handly one input (for now) this thread will calculate
 | |
|         # some basic things for the musicEffectsThreads, so they can update the leds more often.
 | |
|         # it is always posible to catch the fft() function and do your own thing in your musikEffect.
 | |
| 
 | |
|         # calculate bpm, this is the same for all clientThreads
 | |
|         self.beats_idx = 0
 | |
| 
 | |
|         self.bpm_list = []
 | |
|         self.prev_beat = time.perf_counter()
 | |
|         self.low_freq_avg_list = []
 | |
| 
 | |
|         self.lastTime = time.time()
 | |
|         
 | |
| 
 | |
|         self.recorderClients = []
 | |
| 
 | |
|     def setup(self):
 | |
|         self.buffers_to_record = int(
 | |
|             self.RATE * self.secToRecord / self.BUFFERSIZE)
 | |
|         if self.buffers_to_record == 0:
 | |
|             self.buffers_to_record = 1
 | |
|         self.samples_to_record = int(self.BUFFERSIZE * self.buffers_to_record)
 | |
|         self.chunks_to_record = int(self.samples_to_record / self.BUFFERSIZE)
 | |
|         self.sec_per_point = 1. / self.RATE
 | |
| 
 | |
|         self.p = pyaudio.PyAudio()
 | |
|         # start the PyAudio class
 | |
|         for i in range(self.p.get_device_count()):
 | |
|             devinfo = self.p.get_device_info_by_index(i)
 | |
|             print(i, devinfo["name"])
 | |
|         # make sure the default input device is broadcasting the speaker output
 | |
|         # there are a few ways to do this
 | |
|         # e.g., stereo mix, VB audio cable for windows, soundflower for mac
 | |
|         self.in_stream = self.p.open(format=pyaudio.paInt16,
 | |
|                                      channels=1,
 | |
|                                      rate=self.RATE,
 | |
|                                      input=True,
 | |
|                                      frames_per_buffer=self.BUFFERSIZE)
 | |
|         print("Using default input device: {:s}".format(
 | |
|             self.p.get_default_input_device_info()['name']))
 | |
| 
 | |
|         self.audio = numpy.empty(
 | |
|             (self.chunks_to_record * self.BUFFERSIZE), dtype=numpy.int16)
 | |
| 
 | |
|     def close(self):
 | |
|         print("pyAudioRecorder closed")
 | |
|         self.kill_threads = True
 | |
|         self.p.close(self.in_stream)
 | |
| 
 | |
|     ### RECORDING AUDIO ###
 | |
| 
 | |
|     def get_audio(self):
 | |
|         """get a single buffer size worth of audio."""
 | |
|         audio_string = self.in_stream.read(self.BUFFERSIZE)
 | |
|         return numpy.fromstring(audio_string, dtype=numpy.int16)
 | |
| 
 | |
|     def record(self):
 | |
|         while not self.kill_threads:
 | |
|             for i in range(self.chunks_to_record):
 | |
|                 self.audio[i*self.BUFFERSIZE:(i+1)
 | |
|                            * self.BUFFERSIZE] = self.get_audio()
 | |
|             self.has_new_audio = True
 | |
| 
 | |
|     def start(self):
 | |
|         print("pyAudioRecorder started")
 | |
|         self.t = threading.Thread(target=self.record)
 | |
|         self.t.start()
 | |
| 
 | |
|     ### MATH ###
 | |
| 
 | |
|     def downsample(self, data, mult):
 | |
|         """Given 1D data, return the binned average."""
 | |
|         overhang = len(data) % mult
 | |
|         if overhang:
 | |
|             data = data[:-overhang]
 | |
|         data = numpy.reshape(data, (len(data) / mult, mult))
 | |
|         data = numpy.average(data, 1)
 | |
|         return data
 | |
| 
 | |
|     def fft(self, data=None, trim_by=10, log_scale=False, div_by=100):
 | |
|         if not data:
 | |
|             data = self.audio.flatten()
 | |
|         left, right = numpy.split(numpy.abs(numpy.fft.fft(data)), 2)
 | |
|         ys = numpy.add(left, right[::-1])
 | |
|         if log_scale:
 | |
|             ys = numpy.multiply(20, numpy.log10(ys))
 | |
|         xs = numpy.arange(self.BUFFERSIZE/2, dtype=float)
 | |
|         if trim_by:
 | |
|             i = int((self.BUFFERSIZE/2) / trim_by)
 | |
|             ys = ys[:i]
 | |
|             xs = xs[:i] * self.RATE / self.BUFFERSIZE
 | |
|         if div_by:
 | |
|             ys = ys / float(div_by)
 | |
|         return xs, ys
 | |
| 
 | |
|     ### multithreading things ###
 | |
| 
 | |
|     def registerRecorderClient(self,recorderClient):
 | |
|         self.recorderClients.append(recorderClient)
 | |
| 
 | |
|     def unregisterRecorderClient(self,recorderClient):
 | |
|         self.recorderClients.remove(recorderClient)
 | |
| 
 | |
|     class recorderClient(threading.Thread):
 | |
|         def __init__():
 | |
|             # toggle to true on beat, false when client got the value
 | |
|             self.onBeat = False
 | |
|             # when registering the client i want to be able to define how long the avg list should be
 | |
|             self.y_max_freq_avg_list = []
 |