Last active
June 7, 2021 01:22
-
-
Save jsbain/d81f771068229f2e0f9e to your computer and use it in GitHub Desktop.
wavpiano
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
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.
Thank you for the quick reply. As stated, I’m a noob. I didn’t realize that was a double underscore. I’ll keep working at it.
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
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
I am a noob. I am trying to get my iPad to play a sound. In particular, I'd like to create a pure sine wave sound at 5 evenly "spaced" frequencies (i.e 25hz, 50hz, 75hz, 100hz, and 125hz.) As a first step, i am trying to get the iPad to play a sound, any sound.
I am trying to use this code on my iPad Pro (10.5-inch) running iOS 14.4.2 and Pythonista 3.3. I have copied the code verbatim, with some small changes in commented areas for my own notes.
I get "NameError: name 'name' is not defined" when I try to run it. Any suggestions for when and were I should define name ?