#!/usr/bin/python """Come up with interpolable waveforms at random that sound like vowels? Or maybe approximants, nasals, or liquids. At this point it's generating, displaying, and playing a waveform, but not yet displaying its FFT or permitting interaction. The waveform doesn't sound very promising yet. It took me three hours to program this, which is probably more time than I spent writing it up as text. """ import pygame, numpy random = numpy.random.random if __name__ == '__main__': audio = open('/dev/dsp', 'w') pygame.init() screen = pygame.display.set_mode((1024, 600), pygame.FULLSCREEN) x = 0 # state vector (becomes a vector later) dx = random(4) * .02 # derivative vector kx = random(4) * .002 # springiness vector (for simple harmonic osc) while True: event = pygame.event.poll() if event.type in [pygame.QUIT, pygame.MOUSEBUTTONDOWN, pygame.KEYDOWN]: break elif event.type == pygame.NOEVENT: x = numpy.maximum(0, numpy.minimum(1, x + dx)) dx += kx * (.5 - x) w, h = screen.get_width(), screen.get_height() h_g = h - 100 # bottom margin 100 mind = min(w, h_g) # min dimension bx = (w - mind)//2 # base x by = (h_g - mind)//2 # base y ss = mind // 2 # square size bm = 2*ss # bottom margin start (in y) screen.fill((0,0,0)) for spx, spy, hx, vx in [(0, 0, 0, 1), (0, 1, 0, 3), (1, 0, 2, 1), (1, 1, 2, 3)]: pygame.draw.circle(screen, (134,43,120), (bx + ss * spx + int(round(x[hx] * ss)), by + ss * spy + int(round(x[vx] * ss))), 8) # compute the waveform mot = (len(x) + 1) // 2 # max possible on time ot = x[::2].sum() # actual on time (time high) # transition times, with an extra transition to make the # on time be mot, scaled to fit into 2 * mot so the duty # cycle to time 1 is 50% t = numpy.concatenate(([0], x, [mot - ot])).cumsum() / (2 * mot) #t = numpy.concatenate((t, t + .03)) #t.sort() # add a transition at time 1 t = numpy.concatenate((t, [1])) # draw the waveform wp = [(t[i] * w, bm + 55 + 35 * (-1)**(i+j)) for i in range(len(t)) for j in [0, 1]] pygame.draw.lines(screen, (255,255,255), False, wp) # generate the waveform as a string of bytes fs = 8000 # sample rate f = 220 + 10 * x.sum() # fundamental frequency, fudged a bit ns = fs // f # number of samples ts = (t * ns).round().astype(int) # transition samples sd = numpy.zeros(ns) # sample "derivative" for tsi in ts: # at least the last transition is out of range; others # may be too if tsi < len(sd): sd[tsi] = not sd[tsi] s = numpy.logical_xor.accumulate(sd) # samples sb = ''.join(' ~'[si] for si in s) # sample bytes print(repr(sb)) audio.write(sb * max(1, (f//20))) # plot FFT vertically in left margin fft = numpy.fft.fft(numpy.tile(s - s.mean(), 8).astype(float)) fr = abs(fft[:len(fft)//2]) # FFT, real fsy = bm / len(fr) # FFT scale Y fsx = bx / max(abs(fr)) # FFT scale x pygame.draw.lines(screen, (192, 38, 85), False, [(fr[i], i * fsy) for i in range(len(fr))]) pygame.display.flip() pygame.time.delay(33)