diff options
| author | schneefux <schneefux+commit@schneefux.xyz> | 2016-02-24 10:26:18 +0100 |
|---|---|---|
| committer | schneefux <schneefux+commit@schneefux.xyz> | 2016-02-24 10:26:18 +0100 |
| commit | 6b77d313c8fef618fedc008f3e1e7ad00f45f8ec (patch) | |
| tree | 500ff46ad6adc13c341e9a7d3a0d16c9b8adb029 | |
| download | mpd-rgb-vis-6b77d313c8fef618fedc008f3e1e7ad00f45f8ec.tar.gz mpd-rgb-vis-6b77d313c8fef618fedc008f3e1e7ad00f45f8ec.zip | |
initial commit
| -rw-r--r-- | README.md | 16 | ||||
| -rw-r--r-- | mpdvis.json | 6 | ||||
| -rw-r--r-- | mpdvis.py | 156 |
3 files changed, 178 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..d0ff765 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +Hyperion MPD music visualisation plugin +======================================= + +Adapted from [this script by `tuckerbuchy`](https://github.com/tuckerbuchy/ledvis/blob/master/audioprocessing.py). + +Add this to your `mpd.conf`: +``` +audio_output { + type "fifo" + name "MPD FIFO" + path "/tmp/mpd.fifo" + format "48000:16:1" +} +``` + +Then, install `numpy` and place `mpdvis.json`, `mpdvis.py` into hyperion's effects folder (`/opt/hyperion/effects` or similiar). diff --git a/mpdvis.json b/mpdvis.json new file mode 100644 index 0000000..5f5384b --- /dev/null +++ b/mpdvis.json @@ -0,0 +1,6 @@ +{ + "name" : "MPD sound visualisation", + "script" : "mpdvis.py", + "args" : + {} +} diff --git a/mpdvis.py b/mpdvis.py new file mode 100644 index 0000000..df1f525 --- /dev/null +++ b/mpdvis.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python2 +import hyperion +import array +import colorsys +import numpy + +DEBUG_MODE = False +FIFO = '/tmp/mpd.fifo' + + +class IIR: + def __init__(self, alpha): + self.alpha = alpha + self.prev = 0 + + def update(self, value): + self.prev = (1-self.alpha)*value + self.alpha*self.prev + return self.prev + + +class SoundToColorProcessor: + def __init__(self): + self.HSV_VALUE = 1 + + self.PEAK_THRESHOLD = 3e6 + self.BAND_LOWER = 40 + self.BAND_UPPER = 500 + + self.MAX_LOUDNESS = 10240 + + self.SATURATION_FIR_DEQUE_SIZE = 20 + self.HUE_IIR_ALPHA = 0.9 + self.VAL_IIR_ALPHA = 0.5 + + self.CHUNK = 2048 + self.CHANNELS = 1 + self.RATE = 48000 + + self.OCTAVES = 1 + + def calculateMagnitude(self, real, imaginary): + ''' + calculate the magnitude of the fourier tranform parts + ''' + magnitudes = [] + x = 0 + while x < len(real) and x < len(imaginary): + magnitudes.append( + numpy.sqrt(real[x]*real[x] + imaginary[x]*imaginary[x])) + x += 1 + return magnitudes + + def getFrequencyIndex(self, freq): + return freq/(self.RATE/self.CHUNK) + + def convertPercentToColorValue(self, percent): + return int(round(percent*255)) + + def mapFrequencyToHue(self, freq): + if freq < self.BAND_LOWER: + freq = self.BAND_LOWER + elif freq > self.BAND_UPPER: + freq = self.BAND_UPPER + + freq_log = numpy.log(freq)/numpy.log(2**self.OCTAVES) + lower_log = numpy.log(self.BAND_LOWER)/numpy.log(2**self.OCTAVES) + upper_log = numpy.log(self.BAND_UPPER)/numpy.log(2**self.OCTAVES) + hue = (freq_log - lower_log) / (1.0*upper_log - 1.0*lower_log) + return hue + + def startProcessing(self): + ''' + Performs the processing of the audio data into color values. + ''' + + if DEBUG_MODE: + import matplotlib.pyplot as plt + + # open the audio stream on the sound card + stream = open(FIFO) + + if DEBUG_MODE: + # Plots and canvas initialization + plt.ion() + fig, ax = plt.subplots() + line, = ax.plot(numpy.random.randn(100)) + plt.axis([20, 5000, 0, 1.1e7]) + plt.show(block=False) + + # initialize the queues for the filtering techniques. + hue_iir = IIR(self.HUE_IIR_ALPHA) + val_iir = IIR(self.VAL_IIR_ALPHA) + saturation_fir_deque = [] + + while not hyperion.abort(): + data = stream.read(self.CHUNK) + nums = array.array('h', data) + results = numpy.fft.fft(nums) + freq_bins = numpy.fft.fftfreq(len(nums), 1.0/self.RATE) + + results = results[0:(len(results)/2 - 1)] + freq_bins = 2 * freq_bins[0:(len(freq_bins)/2 - 1)] + + mags = self.calculateMagnitude(results.real, results.imag) + + lower_band_index = self.getFrequencyIndex(self.BAND_LOWER) + upper_band_index = self.getFrequencyIndex(self.BAND_UPPER) + + max_mag = max(mags[lower_band_index:upper_band_index]) + max_freq_index = mags.index(max_mag) + max_freq = freq_bins[max_freq_index] + + new_frequency = self.BAND_LOWER + if max_mag > self.PEAK_THRESHOLD: + new_frequency = max_freq + + rms = numpy.sqrt(numpy.mean(numpy.square(nums))) + value = val_iir.update(rms) / self.MAX_LOUDNESS + if value > 1.0: + value = 1.0 + averaged_frequency = hue_iir.update(new_frequency) + hue = self.mapFrequencyToHue(averaged_frequency) + + mag_sum = numpy.sum(mags) + saturation_fir_deque.append(mag_sum) + if len(saturation_fir_deque) > self.SATURATION_FIR_DEQUE_SIZE: + saturation_fir_deque.pop(0) + + average_sum = numpy.mean(saturation_fir_deque) + max_sum = max(saturation_fir_deque) + if max_sum == 0: + max_sum = 1 + saturation = 0.85 + (0.15)*average_sum/max_sum + + rgb = colorsys.hsv_to_rgb(hue, saturation, value) + rgb = map(self.convertPercentToColorValue, rgb) + + # send to hyperion + hyperion.setColor(bytearray(rgb * hyperion.ledCount)) + + if DEBUG_MODE: + # graph the fourier stuff + line.set_data(freq_bins, mags) + fig.canvas.draw() + fig.canvas.flush_events() + + if DEBUG_MODE: + plt.close() + stream.stop_stream() + stream.close() + + +def run(): + SoundToColorProcessor().startProcessing() + +run() |
