summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2016-02-24 10:26:18 +0100
committerschneefux <schneefux+commit@schneefux.xyz>2016-02-24 10:26:18 +0100
commit6b77d313c8fef618fedc008f3e1e7ad00f45f8ec (patch)
tree500ff46ad6adc13c341e9a7d3a0d16c9b8adb029
downloadmpd-rgb-vis-6b77d313c8fef618fedc008f3e1e7ad00f45f8ec.tar.gz
mpd-rgb-vis-6b77d313c8fef618fedc008f3e1e7ad00f45f8ec.zip
initial commit
-rw-r--r--README.md16
-rw-r--r--mpdvis.json6
-rw-r--r--mpdvis.py156
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()