Jump to content

Python/Musical intervals (numpy matplotlib)/playsound

From Wikiversity

This code uses the module playsound to create the WAV sound files located at:

A musical fifth detuned from just intonation by 2.16 cents

Example: The OGG file shown to the left plays the (perfect) fifth, detuned from 2.16 cents in order to maintain a beat frequency of 1.5 Hz. The WAV file was converted to an OGG file using Audacity.

Installing the playsound module

[edit | edit source]

I wanted to hear to interval each time I ran to code, so I installed the playsound using:

I found it necessary to install the previous version, as per this discussion on GitHub. The following code fails on the current version of playsound, but works if I rename the file to test.wav. My guess is that the dashes confound the current version of playsound (but I'm not sure.)

importplaysound#---------Used old version: pip install playsound==1.2.2playsound.playsound('fifth-300.0-200.25.wav')

defifun(interval):importnumpyasnp#------------------ numerical operations on large arraysimportmatplotlib.pyplotasplt#----- creates and displays plotsimportscipy,sys,os,time,math#---- scipy not used herefromscipyimportsignal#------------ signal not used hereimportsoundfileassf#-------------- soundfile not used yetimportplaysound#--------------------- old version: pip install playsound==1.2.2## global variables #########################################################globalPI;PI=np.piglobalsamplerate;samplerate=44100#-- standard CD sample rateglobaldt;dt=1/samplerate#----------- dt definedglobaldocmatedocmate=r'''My journal for this code is at: docs.google.com/document/d/1hb2NzXxMTkkjhEmckZftRuceN4FsaifUvaEoOxFa7uw G:\My Drive\python\Intervals'''+"\n"defdrint(string):#----------------- maintains docmate journalglobaldocmate;docmate+=string+"\n";returndeftime2int(t):#------------------- converts time to an integerglobalsamplerate;return(round(t*samplerate))defint2time(i):#------------------- converts an integer to timeglobaldt;return(i*dt)start_time=time.time()#----------- code runtime measurement## INPUT PARAMETERS ########################################################makeplot,makesound=True,True#--- realtime, show/hear plot/soundclearOut,shortTest=False,False#--- clears output, performs short segementstandAlone=False#------------------ standAlone runs from hereifstandAlone:interval="fifth"#---- ... else run from pickInterval.pyAmp,clickT,clickA=.25,.005,.25#- Amplitude, click duration, click amplitude## clickmap=[2,3,3,1,1]-> sscccssscs where s/c=silence/click if bar=1clickmap,bar=[4,4,4,1,0],3#----- bar = beats/musical bar (measure)ifshortTest:#----- clickmap, bar =[4,4,4,1,0], 3 (standard)clickmap,bar=[0,1,1,1,0],1#--for testing purposes onlydfstr="q"# -----------------------selects tone (p/q) to be shiftedf_b,f_q=1.5,200#-------------- f_beat, f_q = low frequency toneclickOctave=2#2#------------------- click is clickOctave(s) above p*q*f_0highclick=1.5#--------------------- high-freq click us up a perfect fifth## Modify clickmap if bar>1: ################################################foriinrange(len(clickmap)):clickmap[i]=clickmap[i]*bar## Define Nbeats and ifclick, eg: clickmap=(2,3,3,1,1) => ifclick=(0,1,0,1,0)Nbeats=0;do_click=False;ifclick=[]#---- do_click = "do click" (True/False)foriinrange(len(clickmap)):Nbeats+=clickmap[i]ifclick.append(do_click)#------------ uses fact that clickmap alternatesdo_click=not(do_click)#--------------- between click and no click## CODE SELECTS p,q, f_0=1/T_0, f_p, Df, cents, tmax, datapoints #############ifinterval=="fifth":#--------------i=0p,q,beatFactor=3,2,2ifinterval=="Maj 6th":#------------i=1p,q,beatFactor=5,3,1ifinterval=="fourth":#-------------i=2p,q,beatFactor=4,3,2ifinterval=="Maj 3rd":#------------i=3p,q,beatFactor=5,4,2ifinterval=="min 6th":#------------i=4p,q,beatFactor=8,5,2ifinterval=="min 3rd":#------------i=5p,q,beatFactor=6,5,2ifinterval=="tritone":#------------i=6p,q,beatFactor=7,5,1## Create plotlabel ##########################################################plotlabel=interval+": "+str(bar)+f" beats per bar - "plotlabel+=f"{60/f_b:.2f} beats per second - "plotlabel+=f"{clickmap[2]/bar:.1f} bar rest"## CALCULATE PERIODS AND FREQUENCIES ########################################f_0=f_q/q;T_0=1/f_0#-------------- quasiperiod T_0 = 1/f_0f_p=p*f_0#---------------------------- Defines p-wave pitch=f_pifdfstr=="q":Df_p=0;Df_q=f_b/p/beatFactorelse:Df_q=0;Df_p=f_b/q/beatFactor;cents=abs(1200*(np.log2(1+Df_p/f_p)-np.log2(1+Df_q/f_q)))#- error in centsf_b=abs(p*Df_q-q*Df_p)*beatFactor#----------------------- f_b calculatedT_b=1/f_b;tmax=Nbeats*T_b#-------------------------------- also T_b, tmaxdatapoints=int(round(tmax/dt))#--------- datapoints in passage is integerom_p,om_q=(f_p+Df_p)*2*PI,(f_q+Df_q)*2*PI#-- omega_p, omega_q defined## CREATE ONE CLICK ######################################################om_c=10000#--pitch of clickclickcycles=om_c*clickT/2/PI#---guess number click cycles per clickdrint("\nclickT,clickcycles= %.5e , %.1f (initial)"%(clickT,clickcycles))clickcycles=round(clickcycles)#-actual number click cyclesclickT=2*PI*clickcycles/om_c#---actual (adjusted) click timedrint("clickT,clickcycles= %.5e , %.1f (adjusted)"%(clickT,clickcycles))clickpoints=round(clickT/dt)#---actual (adjusted) integer click lengthdrint("number datapoints per click %i (adjusted)"%clickpoints)## Single click, Duration = (modified) clickT: ###########################clickt=np.linspace(0,clickT,num=clickpoints)#-len(clickt)<<len(t)cy1=clickA*Amp*np.sin(om_c*clickt)#-----------much smaller array w/ clicktcy2=clickA*Amp*np.sin(1.5*om_c*clickt)#-------cy2 is the high frequency click#plt.scatter(clickt,cy2,s=2); plt.show()## Declare 4 numpy arrays: t, yp, yq, yc #################################t=np.linspace(0,tmax,num=datapoints)#-yp=Amp*np.cos(om_p*t)#------------------ yp (numpy array for high pitch)yq=Amp*np.cos(om_q*t)#------------------- yq (numpy array for low pitch)yc=np.zeros(datapoints)#-------------------yc (numpy array for clicks)## Three units of time: seconds, beats, samples.## t is measured in seconds## B is measured in beats: B=t/T_b## X is measured in samples: X=t*samplerateB=0;Blist=[];tlist=[];Xlist=[]j=0#------------------------ Create 3 lists of clicktimes (1 for each time unit)foriinrange(len(clickmap)):ifnotifclick[i]:#- IF this section has no clicks:B+=clickmap[i]# ...advance B but do nothingelse:#-------------- ELSE enter B values into Blistforjinrange(clickmap[i]):Blist.append(B)#------counting beatstlist.append(B*T_b)#--measuring t=time in secondsXlist.append(int(round(B*T_b*samplerate)))#Xlist measures time in "datapoints" of numpy arraysB+=1#-------Hops ahead in time as per clickmap "instructions"# print(i,j)#temp drint('Blist=%s, tlist=%s, Xlist=%s'%(Blist,tlist,Xlist))##clickmap=[2, 3, 3, 1, 1] -> Blist=[2, 3, 4, 8]. Recall that for Blist[i],## ... no clicks appear at the silences at i=0 and i=2. Hence we have:## ... three clicks (at 2,3,4) and 1 click at 8 with silence at 5,6,7## yc currently contain all zeros.j=0#------------------------ Iterates the first count in a barforiinrange(len(Xlist)):#-Insert clicks into yc=[0,0,0...]first=Xlist[i]last=first+clickpoints#-clickpoints is lengh in X variablesifj%bar==0:#--------- bar = number of beats per baryc[first:last]=cy1#print("j==0, yc[first+3] ",j, yc[first+3]) else:#print("j",j)yc[first:last]=.5*cy2j+=1drint("lengths of %i%i%i"%(len(yp),len(yq),len(yc)))y=yp+yq+yc#--------------------- adding numpy arraysNtrim=round(samplerate/(4*f_0))# trim beginning and end of passageforiinrange(Ntrim):y[i]=(i/Ntrim)*y[i]#-------- i/Ntrim acts as time-dependent amplitudej=-Ntrim+i#----------------- at i=0, j is Ntrim points away from end## OUTPUT (all goes into a cleared directory called "interval_output" ########ifnotos.path.exists("interval_output"):os.mkdir("interval_output")forfinos.listdir("interval_output"):ifnotf=="readme.txt":ifclearOut:os.remove(os.path.join("interval_output",f))#------------------------------- output in /image directorynamestring=interval+"-"+str(round(f_p+Df_p,3))+"-"+str(round(f_q+Df_q,3))drint("*"+namestring)path2sound=os.path.join("interval_output",namestring+".wav")sf.write(path2sound,y,samplerate)# sf = soundfilepath2image=os.path.join("interval_output",namestring+".png")#plt.figure(figsize=(1,10))#-------- plt=variable name of plot#----------------------------------- Output, image, documentationdrint("%s seconds"%(time.time()-start_time))drint(f"len(y)={len(y)}")ifmakeplot:plt.figure(figsize=(10,1))#---------------------------Plot figureplt.scatter(t,y,s=2)#plt.plot(t,y);plt.axhline(0,lw=1)plt.xlabel(plotlabel)plt.savefig(path2image,pad_inches=.1,bbox_inches="tight",dpi=300)plt.show()#---plot (and show) sound file y=y(t)drint("\nCHECK Default=Actual?")drint("*beat frequency f_b=1.50=%s"%f_b)drint("*beats present Nbeats=10=%s"%Nbeats)drint("*cents=2.1626911608=%s"%cents)drint("*datapoints=294000=%s"%datapoints)drint("*path2image=interval_output\\fifth-300.0-200.25=%s"%path2image)drint("bar*clipmap=[2, 3, 3, 1, 1]=%s"%clickmap)drint("0.07697105407714844 seconds=>%s"%(time.time()-start_time))drint("END")print(docmate)#--docmate=documentation (string)withopen("interval_output/docmate.txt","w")astext_file:text_file.write(docmate)ifmakesound:playsound.playsound(path2sound)return################## CODE ###########intervalList=["fifth","Maj 6th","fourth","Maj 3rd","min 6th","min 3rd","tritone"]foriinrange(7):print(intervalList[i])ifun(intervalList[i])
close