Skip to content

Commit

Permalink
refactor oscilloscope; infrastructure for vectorscope
Browse files Browse the repository at this point in the history
  • Loading branch information
huang0h committed Jun 10, 2022
1 parent 92be7c3 commit 296f2d9
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 13 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
todo.txt
__pycache__
__pycache__
imgs
*.mp4
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ Simply run ``` python main.py ``` in the constellatia directory to start the vis
You can determine what audio is played/visualized by setting ```FILENAME``` to the path to an audio file.
Currently, it runs immediately after processing, and will automatically stop after playing through the song.

This repo comes with three songs in the ```audio``` folder:
This repo comes with some songs in the ```audio``` folder:
- <a href="https://www.youtube.com/watch?v=b6D6iGeEl1o">Deep Blue by The Midnight</a>
- <a href="https://www.youtube.com/watch?v=cnpqLWBrNw0">Missing by Orax</a>
- <a href="https://www.youtube.com/watch?v=9wCJPm19XYQ">Reckoner by Radiohead</a>
- <a href="https://www.youtube.com/watch?v=lpbJJmOJLz8">Something Memorable by Kn1ght</a>
- <a href="https://www.youtube.com/watch?v=VTJcLE_VVX8">They Might As Well Be Dead by Chris Christodoulou</a>

as well as two test files, one of a sine wave and one of a square wave.

Expand Down
Binary file added audio/memorable.mp3
Binary file not shown.
Binary file added audio/mightaswellbedead.wav
Binary file not shown.
26 changes: 15 additions & 11 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# pip install pygame
from pydub import AudioSegment
import pygame
from scipy import signal

import math
import sys
Expand All @@ -11,9 +12,10 @@

from star import Star
from nebula import Nebula
from oscilloscope import Oscilloscope

# config vars - edit these to alter the visualizer
FILENAME = "audio/missing.mp3"
FILENAME = "audio/mightaswellbedead.wav"

WINDOW_SIZE = 400
HOP_SIZE = 100
Expand Down Expand Up @@ -55,12 +57,17 @@ def rand_color(base: int) -> pygame.Color:
monos: list = audio.split_to_mono()
channel_samples: list = [mono.get_array_of_samples() for mono in monos]

left_samples: list = []
right_samples: list = []
avg_sample: list = []
if len(channel_samples) == 1:
avg_sample = channel_samples[0]
left_samples = channel_samples[0]
right_samples = channel_samples[0]
else:
avg_sample = list(map(lambda x, y: (x + y) / 2,
channel_samples[0], channel_samples[1]))
left_samples, right_samples = channel_samples

frame = 0
MAX_AMP = 2 ** (audio.sample_width * 8 - 1)
Expand Down Expand Up @@ -91,6 +98,8 @@ def rand_color(base: int) -> pygame.Color:
RUNNING = True
RECORDING = False

oscope = Oscilloscope(screen, SCOPE_WIDTH, SCOPE_HEIGHT, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, MAX_AMP)

while total_elapsed * smp_per_second + WINDOW_SIZE < NUM_SAMPLES and RUNNING:
for event in pygame.event.get():
if event.type == pygame.QUIT:
Expand Down Expand Up @@ -136,6 +145,9 @@ def rand_color(base: int) -> pygame.Color:

# calculate the frame data
window = avg_sample[total_elapsed * smp_per_second : total_elapsed * smp_per_second + WINDOW_SIZE]
left_win = left_samples[total_elapsed * smp_per_second : total_elapsed * smp_per_second + WINDOW_SIZE]
right_win = right_samples[total_elapsed * smp_per_second : total_elapsed * smp_per_second + WINDOW_SIZE]

window_rms = rms(window)
rms_prop = window_rms / MAX_AMP

Expand All @@ -160,15 +172,7 @@ def rand_color(base: int) -> pygame.Color:
for neb in nebulae:
neb.draw()

# draw the oscilloscope
for i, amp in enumerate(window):
if abs(amp / MAX_AMP) < GATE:
continue
half_win = len(window) / 2
x = ((i - half_win) / half_win) * SCOPE_WIDTH + (SCREEN_WIDTH / 2)
y = (amp / MAX_AMP * SCOPE_HEIGHT) + (SCREEN_HEIGHT / 2)
# (x, y) = gravitate( (x, y), mouse, gravitation)
pygame.draw.circle(screen, pygame.Color(255, 255, 255), (x, y), 1)
oscope.draw(window)

pygame.display.flip()

Expand All @@ -181,4 +185,4 @@ def rand_color(base: int) -> pygame.Color:
print(len(screens))
for i, screen in enumerate(screens):
print(f"Processing frame {i} of {len(screens)}")
pygame.image.save(screen, "")
pygame.image.save(screen, f"imgs/frame{i}.png")
18 changes: 18 additions & 0 deletions oscilloscope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pygame

class Oscilloscope:

def __init__(self, screen, width, height, x, y, max_amp):
self.screen = screen
self.width = width
self.height = height
self.x = x
self.y = y
self.max_amp = max_amp

def draw(self, window: list):
for i, amp in enumerate(window):
half_win = len(window) / 2
x = ((i - half_win) / half_win) * self.width + self.x
y = (amp / self.max_amp * self.height) + self.y
pygame.draw.circle(self.screen, pygame.Color(255, 255, 255), (x, y), 1)
31 changes: 31 additions & 0 deletions vector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pygame

class Vectorscope:
def __init__(self, screen, width, height, x, y, max_amp):
self.screen = screen
self.width = width
self.height = height
self.x = x
self.y = y
self.max_amp = max_amp

def draw(self, left_samples, right_samples, resolution):
# x is determined by proportion of magnitude on left/right
# i.e. 100% of signal left = max on left axis
# y is determined by ??? - needs more experimentation
pass

def samples_to_hsla(samples: list) -> pygame.Color:
color: pygame.Color = pygame.Color(0, 0, 0)
window = signal.windows.blackmanharris(10000)

resample = signal.resample(samples, 10000)
chunk_fft = np.abs(scipy.fft.fft(np.multiply(resample, window)))
freq = np.argmax(chunk_fft[1:]) + 1

if freq > 10000 or freq < 24:
return color

# formula adapted from https://en.wikipedia.org/wiki/Piano_key_frequencies
hue: int = math.ceil(41.9 * math.log(freq / 440, 2) + 171.11)
# saturation = rms(samples)

0 comments on commit 296f2d9

Please sign in to comment.