{"id":18639270,"url":"https://github.com/skyguy126/ledbacklight","last_synced_at":"2026-05-02T03:02:36.949Z","repository":{"id":72156190,"uuid":"85034272","full_name":"skyguy126/LedBacklight","owner":"skyguy126","description":"Multicolor LED audio visualizer/backlight.","archived":false,"fork":false,"pushed_at":"2020-11-11T19:43:00.000Z","size":28,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-27T09:41:49.618Z","etag":null,"topics":["arduino","fft","led","led-strips","signal-processing","visualizer"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skyguy126.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-15T05:51:04.000Z","updated_at":"2020-11-11T19:43:02.000Z","dependencies_parsed_at":"2023-02-25T09:30:31.511Z","dependency_job_id":null,"html_url":"https://github.com/skyguy126/LedBacklight","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyguy126%2FLedBacklight","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyguy126%2FLedBacklight/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyguy126%2FLedBacklight/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyguy126%2FLedBacklight/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skyguy126","download_url":"https://codeload.github.com/skyguy126/LedBacklight/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239440947,"owners_count":19639118,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","fft","led","led-strips","signal-processing","visualizer"],"created_at":"2024-11-07T05:47:56.710Z","updated_at":"2025-11-04T18:30:41.337Z","avatar_url":"https://github.com/skyguy126.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LedBacklight\nMulticolor LED audio visualizer/backlight.\n\n## Concept\n\n*This will focus mostly on the software aspect of the project.*\n\nBefore I begin, here is [the excellent blog post](http://www.swharden.com/wp/2013-05-09-realtime-fft-audio-visualization-with-python/) which helped me start\nthis project.\n\n##### 1. Identify the sound card\n\nImplementing this was pretty straightforward, just needed to know some how Windows identifies the device.\nThe OS names the sound card \"Stereo Mix\" (this varies across different computers) and lists it as a microphone.\nSimple, right? All I needed to do was to do was pull a list of all attached audio devices and see which one\nmatches the name.\n\nUsing [pyaudio](https://people.csail.mit.edu/hubert/pyaudio/) I was able to implement this in very few lines.\n\n```python\ndef find_sound_card():\n    p = pyaudio.PyAudio()\n    device_number = -1\n\n    for i in xrange(0, p.get_host_api_info_by_index(0).get('deviceCount')): # loop through all the attached devices\n        device = p.get_device_info_by_host_api_device_index(0, i)           # get the current device object\n        device_input_channel_count = device.get('maxInputChannels')         # check to see if this device is indeed \"real\"\n        device_name = device.get('name')                                    # get the device name\n\n        if device_input_channel_count \u003e 0 and \"Stereo Mix\" in device_name:  \n            device_number = i\n\n    return device_number                                                    # return the value of Stereo Mix\n```\n\n##### 2. Read PCM data from the sound card\n[Pulse code modulation](https://en.wikipedia.org/wiki/Pulse-code_modulation) abbreviated PCM, is essentially the audio\nwaveform in a digital format (probably cutting a lot of corners here). Again using [pyaudio](https://people.csail.mit.edu/hubert/pyaudio/) the process was very simple. Set up a few buffers (arrays) to\nstore the PCM data and pull data from the sound card every 0.01 seconds.\n\n```python\ndef __init__(self, device_number):\n    self.device_number = device_number  # sound card id from step 1\n    self.rate = 48100                   # standard sampling rate\n    self.buffer_size = 1024             # buffer size (1024 worked best)\n    self.record_time = 0.01\n    self.run = True\n    self.lock = threading.Lock()\n\ndef setup(self):\n    self.num_buffers = int(self.rate * self.record_time / self.buffer_size)\n    if self.num_buffers == 0: self.num_buffers = 1\n    self.audio = numpy.empty((self.num_buffers * self.buffer_size), dtype=numpy.int16)\n\n    # setup the pyaudio input stream for reading\n\n    self.p = pyaudio.PyAudio()\n    self.input_stream = self.p.open(format=pyaudio.paInt16, channels=1, rate=self.rate, input=True,\n        frames_per_buffer=self.buffer_size, input_device_index=self.device_number)\n```\n\nThis process was done on a separate thread to avoid timing issues later (this way there is always fresh data in the buffer\nno matter if a function slowed the rest of the program down, which is very important for real-time processing).\n\n```python\ndef get_audio(self):\n    audio_string = self.input_stream.read(self.buffer_size)\n    return numpy.fromstring(audio_string, dtype=numpy.int16)\n\ndef record(self):\n    while self.run:\n        self.lock.acquire() # to ensure thread safety (reading the data while this thread is writing would cause a crash)\n        for i in xrange(self.num_buffers):\n            # read the PCM data and store in the buffer\n            self.audio[i * self.buffer_size : (i+1) * self.buffer_size] = self.get_audio()\n        self.lock.release()\n```\n\nAdditionally, a few basic start and stop controls were added to the recording class.\n\n##### 3. Process the PCM data\n\nThe key component here was the fast-fourier-transform (abbreviated fft), or better known as the\ndiscrete-fourier-transform. The concept essentially states that any audio (signal) waveform (a complex trigonometric wave)\ncan be broken down into elementary sine and cosine functions of varying frequencies. We know that low frequency waves\nassociate to the low frequencies in the audio playing (same concept for mid and high frequencies).\n\nRead [this](http://practicalcryptography.com/miscellaneous/machine-learning/intuitive-guide-discrete-fourier-transform/)\nfor an excellent introduction to the math behind the algorithm.\n\n```python\ndef get_pcm(self):\n    self.lock.acquire()\n    data = self.audio.flatten() # prevent thread conflicts by acquiring the lock\n    self.lock.release()\n    return data\n\ndef get_fft(self):\n    data = self.get_pcm()\n\n    fft = numpy.abs(numpy.fft.fft(data * numpy.blackman(self.buffer_size)))\n    fft = numpy.divide(fft, 1000)\n    freq = numpy.fft.fftfreq(self.buffer_size, 1.0 / self.rate)\n\n    return freq[:int(len(freq)/2)], fft[:int(len(fft)/2)]\n```\n\nHere we apply the fft to the raw PCM data which is first multiplied by a\n[window function](http://dsp.stackexchange.com/questions/37925/signal-processing-fft-gives-very-high-magnitudes-for-low-frequencies),\nto eliminate signal noise. The data is then scaled down by a factor of 1000 and the associated frequencies are calculated\nusing another handy numpy function. Note: only half of the buffer is returned due to the fact that the other half mirrors\nthe values in the first half _(loosely speaking, given a real input into the Fourier transform, we have conjugate symmetry about x=0)_.\n\n##### 4. Afterword\n\nThe final step was simply to find and connect to the Arduino board over serial. The fft data was post-processed a little more:\nlinearly interpolated to eliminate choppiness, downscaled, and finally used to create threshold percentages. This data was sent over serial to the Arduino, which modified the PWM of the red, green, and blue LEDs based on the values.\n\n###### TLDR\n\n1. A separate thread reads data from the sound card every 0.01 seconds and stores it in a thread-safe buffer.\n2. When the main loop requests frequency data from the other thread, the program performs a fast-fourier-transform\non the raw data and returns the array to the main thread.\n3. The main thread dampens, scales, and interpolates this array based on some pre-determined factors.\n4. Using the modified data, the program calculates the percentage of the magnitude of a set of frequencies in a\npre-determined threshold.\n5. Frequency data is packed into a JSON buffer and is sent over serial to the Arduino board.\n6. The Arduino reads these values and uses them as PWM values to control the brightness and RGB loop speed of the\nLEDS.\n\n## Resources\n- [Google Material Icons ](https://material.io/icons/)\n- A HSV to RGB algorithm which I forgot where I got from, but will update as soon as I find the source.\n- [Excellent intro to Fourier transforms](https://www.ritchievink.com/blog/2017/04/23/understanding-the-fourier-transform-by-example/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyguy126%2Fledbacklight","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskyguy126%2Fledbacklight","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyguy126%2Fledbacklight/lists"}