├── README.md ├── images ├── guitar.png └── preview.gif └── video-synthesizer.py /README.md: -------------------------------------------------------------------------------- 1 | # Video Synthesizer in Pygame 2 | 3 | ![Video Synthesizer Example](https://github.com/burningion/pygame-video-synthesizer/blob/master/images/preview.gif?raw=true) 4 | 5 | This project uses Aubio and Onset Detection to create real time visual effects to accompany somebody playing music. 6 | 7 | It accompanies a blog post and video available at [makeartwithpython.com](https://www.makeartwithpython.com/blog/video-synthesizer-in-python/). 8 | 9 | ## Usage 10 | 11 | ```bash 12 | $ python3 video-synthesizer.py 13 | ``` 14 | You'll get back out a list of audio inputs built into your computer. You can then call the program again with the correct number for your chosen input, and play back in real time. 15 | 16 | ```bash 17 | $ python3 video-synthesizer.py -input 3 18 | ``` 19 | 20 | You can then play away! 21 | 22 | ## Architecture 23 | 24 | ![Video Synthesizer Architecture](https://github.com/burningion/pygame-video-synthesizer/blob/master/images/guitar.png?raw=true) 25 | 26 | We take in audio, in this example from a [Rocksmith](http://amzn.to/2A2HCjR) cable. We then pass the raw audio into a thread to handle and detect onsets within our Python program. 27 | 28 | This thread then pushes any detected onsets onto a queue, which is picked up by our main Pygame thread. 29 | 30 | -------------------------------------------------------------------------------- /images/guitar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burningion/pygame-video-synthesizer/b99d7f8e525c39b5a5e80b01f9312faa7f1cc26c/images/guitar.png -------------------------------------------------------------------------------- /images/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burningion/pygame-video-synthesizer/b99d7f8e525c39b5a5e80b01f9312faa7f1cc26c/images/preview.gif -------------------------------------------------------------------------------- /video-synthesizer.py: -------------------------------------------------------------------------------- 1 | import pyaudio 2 | import sys 3 | import numpy as np 4 | import aubio 5 | 6 | import pygame 7 | import random 8 | 9 | from threading import Thread 10 | 11 | import queue 12 | import time 13 | 14 | import argparse 15 | 16 | parser = argparse.ArgumentParser() 17 | parser.add_argument("-input", required=False, type=int, help="Audio Input Device") 18 | parser.add_argument("-f", action="store_true", help="Run in Fullscreen Mode") 19 | args = parser.parse_args() 20 | 21 | if not args.input: 22 | print("No input device specified. Printing list of input devices now: ") 23 | p = pyaudio.PyAudio() 24 | for i in range(p.get_device_count()): 25 | print("Device number (%i): %s" % (i, p.get_device_info_by_index(i).get('name'))) 26 | print("Run this program with -input 1, or the number of the input you'd like to use.") 27 | exit() 28 | 29 | pygame.init() 30 | 31 | if args.f: 32 | screenWidth, screenHeight = 1024, 768 33 | screen = pygame.display.set_mode((screenWidth, screenHeight), pygame.FULLSCREEN | pygame.HWSURFACE | pygame.DOUBLEBUF) 34 | 35 | else: 36 | screenWidth, screenHeight = 800, 800 37 | screen = pygame.display.set_mode((screenWidth, screenHeight)) 38 | 39 | white = (255, 255, 255) 40 | black = (0, 0, 0) 41 | 42 | class Circle(object): 43 | def __init__(self, x, y, color, size): 44 | self.x = x 45 | self.y = y 46 | self.color = color 47 | self.size = size 48 | 49 | def shrink(self): 50 | self.size -= 3 51 | 52 | colors = [(229, 244, 227), (93, 169, 233), (0, 63, 145), (255, 255, 255), (109, 50, 109)] 53 | circleList = [] 54 | 55 | # initialise pyaudio 56 | p = pyaudio.PyAudio() 57 | 58 | clock = pygame.time.Clock() 59 | 60 | # open stream 61 | 62 | buffer_size = 4096 # needed to change this to get undistorted audio 63 | pyaudio_format = pyaudio.paFloat32 64 | n_channels = 1 65 | samplerate = 44100 66 | stream = p.open(format=pyaudio_format, 67 | channels=n_channels, 68 | rate=samplerate, 69 | input=True, 70 | input_device_index=args.input, 71 | frames_per_buffer=buffer_size) 72 | 73 | time.sleep(1) 74 | 75 | # setup onset detector 76 | tolerance = 0.8 77 | win_s = 4096 # fft size 78 | hop_s = buffer_size // 2 # hop size 79 | onset = aubio.onset("default", win_s, hop_s, samplerate) 80 | 81 | q = queue.Queue() 82 | 83 | def draw_pygame(): 84 | running = True 85 | while running: 86 | key = pygame.key.get_pressed() 87 | 88 | if key[pygame.K_q]: 89 | running = False 90 | for event in pygame.event.get(): 91 | if event.type == pygame.QUIT: 92 | running = False 93 | 94 | if not q.empty(): 95 | b = q.get() 96 | newCircle = Circle(random.randint(0, screenWidth), random.randint(0, screenHeight), 97 | random.choice(colors), 700) 98 | circleList.append(newCircle) 99 | 100 | screen.fill(black) 101 | for place, circle in enumerate(circleList): 102 | if circle.size < 1: 103 | circleList.pop(place) 104 | else: 105 | pygame.draw.circle(screen, circle.color, (circle.x, circle.y), circle.size) 106 | circle.shrink() 107 | 108 | pygame.display.flip() 109 | clock.tick(90) 110 | 111 | def get_onsets(): 112 | while True: 113 | try: 114 | buffer_size = 2048 # needed to change this to get undistorted audio 115 | audiobuffer = stream.read(buffer_size, exception_on_overflow=False) 116 | signal = np.fromstring(audiobuffer, dtype=np.float32) 117 | 118 | 119 | if onset(signal): 120 | q.put(True) 121 | 122 | except KeyboardInterrupt: 123 | print("*** Ctrl+C pressed, exiting") 124 | break 125 | 126 | 127 | t = Thread(target=get_onsets, args=()) 128 | t.daemon = True 129 | t.start() 130 | 131 | draw_pygame() 132 | stream.stop_stream() 133 | stream.close() 134 | pygame.display.quit() --------------------------------------------------------------------------------