1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """ Waveguides.py - Contains classes defining the transmission matrices for
25 simple geometries and functions to generate them.
26
27 """
28
29 __all__ = ['Cylinder','Cone','CurvedCone',
30 'ConeApproximatedWithCylinders','EquationApproximatedWithCones',
31 'CreateCylindersAndConesFromList', 'Jump', 'ToneHole']
32
33 from scipy import special
34 from scipy.integrate import quad
35
36 import numpy
37 from numpy import size, sqrt, cosh, sinh, tanh, ones
38
39 import Radiation
40 from wiat.util import air_properties
41
43 """ Calculate the complex propagation constant in an acoustic waveguide
44
45 See Allan D. Pierce p. 531-534
46
47 @param f: frequency array
48 @param a: tube radius
49 @param air: Air instance
50 @param beta: factor to take into account wall roughness
51 @return: S{Gamma} = S{alpha} + 1j * S{omega} / v_S{phi}
52 where S{alpha} is the attenuation, S{omega} is 2S{pi}f and
53 v_S{phi} is the phase velocity
54
55 """
56 omega = 2*numpy.pi*f
57
58
59 alpha = beta * (1/a) * sqrt(omega*air['mu']/(2*air['rho']*air['c']**2)) *\
60 (1 + (air['gamma']-1)/air['nu'])
61
62
63 G = 1j*(omega/air['c']) + (1+1j)*alpha
64 return G
65
66
68 " Base class for waveguides "
70 self.a1 = 0.
71 self.L = 0.
72
76
78 " return the length of the section. "
79 return self.L
80
82 assert -1e-6 <= x <= self.L+1e-6,\
83 "value of x outside the waveguide by %.4f"%(x-self.L)
84
86 " Class representing a cylinder "
88 """ Cylinder transmission matrix calculation class
89
90 d: diameter
91 L: length
92 """
93 self.a1 = d/2.
94 self.L = L
95
97 return "Cylinder(d=%.4f,L=%.4f)"%(2*self.a1,self.L)
98
100 self.checkx(x)
101 return self.a1
102
104 " Transmission matrix of the complete cylinder "
105 TM = self.subTM(f,T,0.,self.L)
106 return TM
107
108 - def subTM(self,f,T,xi,xf):
109 """ Transmission matrix of a section of the cylinder
110 f: array of frequencies
111 T: temperature
112 xi: beginning of the section
113 xf: end of the section
114 """
115 assert 0. <= xi <= self.L,\
116 "cylinder subsection out of range %f <= %f <= %f"%(0.,xi,self.L)
117 assert xi <= xf <= self.L+0.00001,\
118 "cylinder subsection out of range %f <= %f <= %f"%(xi,xf,self.L)
119 L = xf - xi
120 air = air_properties(T)
121 Gamma = PropagationConstant(f,self.a1,air,1)
122 sinhL = numpy.sinh(Gamma*L)
123 coshL = numpy.cosh(Gamma*L)
124 TM = numpy.array([[coshL,sinhL],[sinhL,coshL]])
125 return TM
126
127 -class Cone(Waveguides):
128 """ Truncated cone transmission matrix calculation class
129
130 This is an implementation of the formulation presented in [1]
131
132 References:
133
134 1. Transfer matrix of conical waveguides with any geometric
135 parameters for increased precision in computer modeling,
136 The Journal of the Acoustical Society of America,
137 vol. 122, pp. EL179-EL184, November 2007.
138 """
139
141 """ Initialize the conical waveguide data structure
142
143 @param d0: diameter at the beginning [m]
144 @type d0: float
145 @param de: diameter at the end [m]
146 @type de: float
147 @param L: length of the cone [m]
148 @type L: float
149
150
151 """
152
153 self.a1 = d0/2.
154 self.ae = de/2.
155 self.L = L
156
157 self.x_in = self.a1 * L / ( self.ae - self.a1 )
158
159
160 self.x_out = self.x_in + L
161
162
164 return "Cone(d0=%.4f,de=%.4f,L=%.4f)"%(2*self.a1,2*self.ae,self.L)
165
167 " Return the half angle of the cone"
168 return numpy.arcsin(self.a1/self.x_in)
169
171 """ Radius of the cone at distance x from its beginning """
172 self.checkx(x)
173 return (self.x_in + x) * self.a1 / self.x_in
174
176 """ Local propagation constant in the conical waveguide
177 at distance z from the apex
178 """
179 self.checkx(z - self.x_in)
180 a = self.radius(z - self.x_in)
181 air = air_properties(self.T)
182 beta = 1.
183 return -1j*PropagationConstant(f,a,air,beta)
184
185 - def _kbarL(self,freqs,xi,xf):
186 """ equivalent propagation constant multiply by length of cone
187 see Kulik eq.8
188 """
189 kbL = numpy.empty(numpy.size(freqs),dtype=complex)
190 for i,f in enumerate(freqs):
191
192
193 kbarLr = quad(lambda x: numpy.real(self._k(f,x)), xi, xf,
194 epsabs=1e-10,epsrel=1e-8)
195 kbarLi = quad(lambda x: numpy.imag(self._k(f,x)), xi, xf,
196 epsabs=1e-10,epsrel=1e-8)
197 kbL[i] = kbarLr[0] +1j*kbarLi[0]
198 return kbL
199
201 " z is the distance from the cone apex "
202 return numpy.arctan( self._k(f, z) * z )
203
205 self.T = T
206 TM = self.subTM(f,T,0.,self.x_out-self.x_in)
207 return TM
208
209 - def subTM(self,f,T,xi,xf):
210 self.T = T
211
212 x_in = self.x_in + xi
213 x_out = self.x_in + xf
214
215 theta_in = self._theta(f,x_in)
216 t_in = 1 / numpy.sin(theta_in);
217
218 theta_out = self._theta(f,x_out)
219 t_out = 1 / numpy.sin(theta_out);
220
221 r = x_out / x_in
222 kbL = self._kbarL(f,x_in,x_out)
223
224 TM = numpy.array([[-r*t_out*numpy.sin(kbL-theta_out),
225 1j*r*numpy.sin(kbL)],
226 [1j*r*t_in*t_out*numpy.sin(kbL+theta_in-theta_out),
227 r*t_in*numpy.sin(kbL+theta_in)]])
228 return TM
229
231 """ Class representing a curved cone
232
233 The idea is to construct an equivalent straight cone.
234 The theory behind this have to be verify.
235
236 References:
237
238 1. Nederveen, C. J. (1998).
239 Influence of a toroidal bend on wind instrument tuning.
240 JASA, 104(3), 1616-1626.
241
242 """
244 """ Initialize the CurvedCone data structure
245
246 @param d0: diameter at the beginning [m]
247 @type d0: float
248 @param de: diameter at the end [m]
249 @type de: float
250 @param L: length of the cone [m]
251 @type L: float
252 @param R: the radius of curvature of the section
253 @type R: float
254 """
255 B0 = d0/(2*R)
256 Be = de/(2*R)
257 B = (B0+Be)/2.
258
259
260 rhoB_rho = B**2/2 / (1 - sqrt(1 - B**2))
261 LB = L*sqrt(rhoB_rho)
262 d0B = 2*sqrt( (d0/2)**2 * sqrt(1/rhoB_rho) )
263 deB = 2*sqrt( (de/2)**2 * sqrt(1/rhoB_rho) )
264
265 Cone.__init__(self,d0B,deB,LB)
266
268 """ Build a list of Cylinder or Cone from a list of tuples containing
269 position and diameters.
270
271 Example::
272 >>> import wiat
273 >>> FluteBody = wiat.TM.CreateCylindersAndConesFromList(
274 ... [(0.,0.0188),(0.132,0.0188),(0.340,0.0152),
275 ... (0.452,0.0135),(0.583,0.0112)])
276 >>> for s in FluteBody: print s
277 Cylinder(d=0.0188,L=0.1320)
278 Cone(d0=0.0188,de=0.0152,L=0.2080)
279 Cone(d0=0.0152,de=0.0135,L=0.1120)
280 Cone(d0=0.0135,de=0.0112,L=0.1310)
281
282 @param list: a list of tuples (position,diameter) where the
283 position is the distance along the centerline of the instrument
284 from the mouthpiece.
285
286 """
287 sections = []
288 p0,d0 = list[0]
289 for (p,d) in list[1:]:
290 if d==d0:
291 sections.append( Cylinder(d, p-p0) )
292 else:
293 sections.append( Cone(d0,d,p-p0) )
294 p0,d0 = p,d
295 return sections
296
298 c = Cone(d0,de,L)
299 lc = L / float(N)
300 cylinders = []
301 dp = d0
302 for i in range(N):
303 d = 2*c.radius(lc*(2*i+1)/2)
304
305
306 cylinders.append( Jump(dp,d) )
307 cylinders.append( Cylinder(d,lc) )
308 dp = d
309 cylinders.append( Jump(d, de) )
310 return cylinders
311
313 """ Discretization in conical segments of an equation giving the radius of
314 the waveguide as a function of its argument.
315
316 @param equation: an equation of one argument. It will be evaluated from
317 0 to L.
318 @param L: the length of the waveguide.
319 """
320 lc = L / float(N)
321 cones = []
322 d1 = 2*equation(0)
323 for i in range(N):
324 x = (i+1)*lc
325 d2 = 2*equation(x)
326 cones.append( Cone(d1,d2,lc) )
327 d1 = d2
328 return cones
329
330 -class Jump(Waveguides):
331 " Class representing a diameter jump "
333 """ Transfer Matrix of a diameter jump
334 d1: diameter 1
335 d2: diameter 2
336 """
337 self.a1 = d1/2.
338 self.a2 = d2/2.
339 self.L = 0.
340
341
342
343
344
345
346
347 - def subTM(self,f,T,xi,xf):
349
351 S1 = numpy.pi * self.a1**2
352 S2 = numpy.pi * self.a2**2
353 n = numpy.size(f)
354 TM = numpy.array([[numpy.ones(n),numpy.zeros(n)],
355 [numpy.zeros(n),numpy.ones(n)*S2/S1]])
356 return TM
357
359 """ This class implements the transmission matrix of tone holes
360 References:
361 1. J. Dalmont et al.,
362 "Experimental Determination of the Equivalent Circuit of an
363 Open Side Hole: Linear and Non Linear Behaviour,"
364 Acta Acustica united with Acustica, vol. 88, pp. 567-575, 2002.
365
366 2. D.H. Keefe,
367 "Theory of the single woodwind tonehole,"
368 J. Acoust. Soc. Am., vol. 72, pp. 676-687, 1982.
369
370 3. D.H. Keefe,
371 "Experiments on the single woodwind tonehole,"
372 J. Acoust. Soc. Am., vol. 72, pp. 688-699, 1982.
373
374 4. van Walstijn, M., & Campbell, M. (2003).
375 "Discrete-time modeling of woodwind instrument bores using wave
376 variables,"
377 J. Acoust. Soc. Am., vol. 113(1), 575-585.
378
379 5. Nederveen, C. J. (1998).
380 "Acoustical Aspects of Woodwind Instruments.", revised edition.
381 147 pages. Northern Illinois University Press.
382
383 """
384 - def __init__(self,d,t,p,d_pad=0.,h_pad=0.):
385 """ Initialization of a tonehole data structure
386 @param d: diameter of the tonehole
387 @param t: height of the tonehole (minimum)
388 @param p: position of the center of the tonehole with respect to
389 the beginning of the instrument.
390 """
391 self.b = d/2.
392 self.t = t
393 self.p = p
394
395 self.r_pad = d_pad/2.
396 self.h_pad = h_pad
397
398
399
400
401
402 self.ToneholeOpenEnd = Radiation.FlangedOpenEnd(self.b)
403
405 return "ToneHole(d=%.4f,t=%.4f,p=%.4f,d_pad=%.4f,h_pad=%.4f)"%\
406 (2*self.b,self.t,self.p,2*self.r_pad,self.h_pad)
407
409 """ Change the position of the tonehole.
410 @param new_p: new position of the center of the tonehole with respect
411 to the beginning of the instrument.
412 """
413 self.p = new_p
414
416 """ Change the diameter of the tonehole.
417 @param new_d: new diameter of the tonehole
418 """
419 self.b = new_d/2.
420 self.ToneholeOpenEnd = Radiation.FlangedOpenEnd(self.b)
421
424
426 """ Ratio of tone hole radius to body radius
427 a: body radius
428
429 The tone hole radius was set at initialization
430 """
431 return self.b/a
432
434 """ Cross sectional area of the tonehole """
435 return numpy.pi * self.b**2
436
438 " equation 4 from [1] "
439 return self.b * ( 0.82 - 1.4*delta**2 + 0.75*pow(delta, 2.7) )
440
442 """ correction for matching volume, valid for cylindical bores
443 ([1] eq. 6)
444 """
445 return (self.b * delta / 8) * (1 + 0.207*delta**3)
446
448 """ from [5]
449 """
450
451
452
453 if self.r_pad < self.b:
454 td = 0.
455 else:
456 td = self.PadLengthCorrection()
457
458 Rc = self.b
459 return self.b*(0.831 - 0.135*(self.b/Rc) -0.073*(self.b/Rc)**4) + td
460
461
462
464 " Nederveen (1998) eq. 38.6 "
465
466
467 if self.r_pad == 0.:
468 return 0.
469
470
471 if not (1. <= (self.r_pad/self.b) <= 4.):
472 print "Warning, the r_pad/b ratio is out of range: %.2f"%\
473 (self.r_pad/self.b)
474
475 tp = 0.613*self.b*((self.r_pad/self.b)**0.18 * \
476 (self.r_pad/self.h_pad)**0.39 - 1)
477
478 if tp < 0.: tp = 0.
479
480 return tp
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
519
532
534 """ inertance
535
536 see ref [1] eq. 2, 3 and 4
537 """
538
539
540
541 t_a = -self.b * delta**2 * (2.72+0.540*delta+0.285*delta**2)**-1
542
543
544 return t_a
545
546
548 """ Series impedance of the tone hole
549 """
550 air = air_properties(T)
551 k = 2*numpy.pi*f/air['c']
552 Zc_hole = air['rho'] * air['c'] / self.area()
553 return -1j * Zc_hole * k * self.SeriesLengthCorrection(self.delta(a))
554
555 - def TM(self,f,a,T,state=0):
556 """ Transmission matrix of the tone hole
557
558 f: frequency array
559 a: radius of the bore at tonehole location p
560 T: temperature
561 state: open or closed
562 """
563
564 assert a > self.b, "Bore smaller than tonehole (a = %.4f, b = %.4f)"%\
565 (a,self.b)
566
567 Za = self.SeriesImpedance(f,a,T)
568 if state == 1:
569 Zs = self.OpenShuntImpedance(f,a,T)
570 else:
571 Zs = self.ClosedShuntImpedance(f,a,T)
572
573
574
575
576
577
578 air = air_properties(T)
579 Zc_tube = air['rho'] * air['c'] / (numpy.pi * a**2)
580 TM = numpy.array([ [ ones(size(f)), (Za/Zc_tube)*ones(size(f))],
581 [Zc_tube/Zs, ones(size(f))] ])
582 return TM
583