"""Exploratory mass-string/body/contact model. No recorded audio. SI units; uncalibrated dimensions/masses. Velocity Verlet at 88.2 kHz. String and body exchange equal/opposite bridge and fingerboard forces. This is a reduced model, not a validated acoustic double bass. """ import numpy as np from numba import njit from scipy.signal import resample_poly from scipy.io.wavfile import write import json from pathlib import Path @njit def simulate(pitch=38, amplitude=.002, contact=True, seconds=3., release_seconds=.003): sr=88200; dt=1/sr; f=440*2**((pitch-69)/12) open_pitch=43 if pitch>=43 else 38 if pitch>=38 else 33 if pitch>=33 else 28 tension=180.; speed=2*1.05*440*2**((open_pitch-69)/12); density=tension/speed**2 length=speed/(2*f); n=min(160,int(sr*.36/(2*f))) h=length/(n+1); mass=density*h; k=tension/h # Modal masses and frequencies are provisional, not measured responses. freq=np.array([68.,108.,175.,285.,460.,735.,1200.,2100.]) bm=np.array([.9,.65,.5,.35,.26,.18,.12,.08]) weight=np.array([.6,.8,.7,.5,.35,.24,.16,.1]) damping=np.array([18.,24.,32.,42.,58.,85.,130.,210.]) q=np.zeros(8);vbody=np.zeros(8);y=np.zeros(n);v=np.zeros(n) pluck_index=int(.76*(n+1))-1 pluck=(pluck_index+1)/(n+1) for j in range(n): x=(j+1)/(n+1);y[j]=amplitude*(x/pluck if x0: # Elastic distributed penalty spring; no random noise. cf=22000.*penetration force[j]+=cf;total_contact+=cf if half==0:contacts+=1 peak_penetration=max(peak_penetration,penetration) # Finite release of the force holding the string, not an audio envelope. time=(frame+half)*dt if time0];assert max(e)