Skip to content

Instantly share code, notes, and snippets.

@jsbain
Last active June 7, 2021 01:22
Show Gist options
  • Save jsbain/d81f771068229f2e0f9e to your computer and use it in GitHub Desktop.
Save jsbain/d81f771068229f2e0f9e to your computer and use it in GitHub Desktop.
wavpiano
import numpy as N
import wave, sound, os, ui
def get_signal_data(frequency=440, duration=1, volume=32767, samplerate=44100):
"""Outputs a numpy array of intensities"""
samples = duration * samplerate
period = samplerate / float(frequency)
omega = N.pi * 2 / period
t = N.arange(samples, dtype=N.float)
y = volume * N.sin(t * omega)
return y
def numpy2string(y):
"""Expects a numpy vector of numbers, outputs a string"""
signal = "".join((wave.struct.pack('h', item) for item in y))
# this formats data for wave library, 'h' means data are formatted
# as short ints
return signal
class SoundFile:
'''
class representing a sound file which will be written'''
def __init__(self, signal, filename, duration=1, samplerate=44100):
self.file = wave.open(filename, 'wb')
self.signal = signal
self.sr = samplerate
self.duration = duration
def write(self):
self.file.setparams((1, 2, self.sr, self.sr*self.duration, 'NONE', 'noncompressed'))
# setparams takes a tuple of:
# nchannels, sampwidth, framerate, nframes, comptype, compname
self.file.writeframes(self.signal)
self.file.close()
def playSound(sender,cached=True):
'''button callback
interpret button title as frequency. create wav, if cached does not exist
then play the wav of this note.
set cached=false to turn off file caching'''
f=float(sender.title)
duration=0.5
samplerate=8000
file=(os.path.join('notes','{}_{}_{}.wav'.format(sender.title,duration,samplerate) )) if cached else 'note.wav'
if not cached or not os.path.isfile(file): # note caching
data = get_signal_data(f, duration, samplerate=samplerate)
signal = numpy2string(data)
sf = SoundFile(signal, file, duration, samplerate=samplerate)
sf.write()
sound.play_effect(file)
class GridView(ui.View):
''' a class that adds a subview to the end of the existing bounding box
dir==0 horizontal
dir==1 verical'''
def __init__(self,padding=2,dir=0):
self.padding=padding
self.dir=dir if dir<2 else 0
def boundingBox(self):
'''return bounding box of subviews'''
try:
maxx=max([sv.x+sv.width for sv in v.subviews])
minx=min([sv.x for sv in v.subviews])
maxy=max([sv.y+sv.height for sv in v.subviews])
miny=min([sv.y for sv in v.subviews])
return (minx, miny, maxx-minx, maxy-miny)
except ValueError:
return (0,0,0,0)
def addToEnd(self, sv):
'''add subview sv to right or below existing boundingbox, depending in self.dir'''
bb=self.boundingBox()
offset=bb[0+self.dir]+bb[2+self.dir]+self.padding
if self.dir==0:
sv.x=offset
else:
sv.y=offset
self.add_subview(sv)
def createButt(f,c=0,cached=True):
'''create a piano button, for frequency f
c is color, w for white, 1 for black
black keys are half height'''
from functools import partial
colors=[(.9 ,.9, .9), (0,0,0)]
b=ui.Button()
b.title='{:3.3f}'.format(f)
b.width=30
b.height=150+150*(1-c)
b.action=partial(playSound,cached=cached)
b.background_color=b.tint_color=colors[c]
b.border_color=(1,0,0)
return b
c=[0,1,0,1,0,0,1,0,1,0,1,0] # keycolor index.. starting with C
if __name__ == '__main__':
import ui,os
cached=True #i think sound is actually caching anyway, setting to false doesnt work
if not os.path.isdir('notes'):
os.mkdir('notes')
v=GridView(dir=0)
a=pow(2.0,1.0/12.0)
f0=440.0
for n in range(-7,24-7,1): #number of half steps from A440. start at -7==C
b=createButt(f0*pow(a,n),c[(7+n)%12],cached)
v.addToEnd(b)
v.size_to_fit()
v.present('panel')
@jsbain
Copy link
Author

jsbain commented Jun 7, 2021

The only place wher there is a name is on line 101, where it must be written with the dunder (double under) -- eg __name__. Sometimes, if you copy things with iOS with smart keyboard, it might try to be smarter than you are.

@frozenoak
Copy link

frozenoak commented Jun 7, 2021 via email

@jsbain
Copy link
Author

jsbain commented Jun 7, 2021

By the way, you might eventually want to look at more real time methods... See the long thread with many examples, here
https://forum.omz-software.com/topic/5155/real-time-audio-buffer-synth-real-time-image-smudge-tool

@frozenoak
Copy link

I'll give it a look see. Thanks again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment