1
2//
3//    SAOL Resonator-Based Physical Model Library
4//    This file: Plucked string
5//
6//    This license also covers the SASL file in this directory.
7//    The MIDI file is public domain, thanks to http://www.mutopiaproject.org
8//
9//
10// Copyright (c) 1999-2006, Regents of the University of California
11// All rights reserved.
12//
13// Redistribution and use in source and binary forms, with or without
14// modification, are permitted provided that the following conditions are
15// met:
16//
17//  Redistributions of source code must retain the above copyright
18//  notice, this list of conditions and the following disclaimer.
19//
20//  Redistributions in binary form must reproduce the above copyright
21//  notice, this list of conditions and the following disclaimer in the
22//  documentation and/or other materials provided with the distribution.
23//
24//  Neither the name of the University of California, Berkeley nor the
25//  names of its contributors may be used to endorse or promote products
26//  derived from this software without specific prior written permission.
27//
28// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39//
40//    Original Author: John Wawrzynek
41//    Maintainer: John Lazzaro, lazzaro@cs.berkeley.edu
42//
43
44
45//
46// If you're interested in extending this model, don't modify this
47// file directly; see the README in sfront/lib/reson/ instead.
48//
49
50//
51// Global block for scaling string prototype
52//
53
54global {
55
56  srate 44100;
57  krate 1050;
58
59  // hand-tuned version of qscale = 0.0393432*exp(0.0615256*x) for q scaling
60
61  table string_qscale(expseg, -1,
62			 0,  0.5*0.0393432,
63                        128, 0.5*0.0393432*exp(0.0615256*128));
64
65
66  // implements gscale = 0.04*(1/70.0337)*exp(0.0735272*rms_x) for g scaling
67
68  table string_gscale(expseg, -1, 0, 0.04*(1/70.0337), 128,
69			 0.04*(1/70.0337)*exp(0.0735272*128));
70
71
72  // vel table holds status for each note
73  //
74  //  -1  -- no instr active for this note
75  //   0  -- instr active, no new strikes
76  // > 0  -- new note strike, value of MIDIvel
77
78  table string_vel(step, 128 , 0, -1, 128);
79
80  ksig string_poly;               // number of active notes at once
81
82  sequence(string_kbd, string_audio);
83
84}
85
86//
87// The resinit iopcode initializes the resonance model for the thing being
88// struck or plucked.
89//
90
91iopcode string_resinit(ivar a[10], ivar b[10],
92		ivar g[10], ivar notenum)
93
94{
95  ivar r[10], freq[10], q[10];
96  ivar j, scale, norm;
97  imports exports table string_qscale;
98  imports exports table string_gscale;
99
100  // set f/q/g for prototype bar
101
102  norm = tableread(string_qscale, int(notenum));
103  scale = cpsmidi(notenum)/220;
104
105  freq[0] = 440*scale;  q[0] = 300*norm;
106  freq[1] = 880*scale;  q[1] = 300*norm;
107  freq[2] = 1320*scale; q[2] = 300*norm;
108  freq[3] = 1760*scale; q[3] = 300*norm;
109  freq[4] = 2200*scale; q[4] = 300*norm;
110  freq[5] = 2640*scale; q[5] = 300*norm;
111  freq[6] = 3080*scale; q[6] = 320*norm;
112  freq[7] = 3520*scale; q[7] = 300*norm;
113  freq[8] = 3960*scale; q[8] = 190*norm;
114  freq[9] = 4400*scale; q[9] = 300*norm;
115
116  norm = tableread(string_gscale, int(notenum));
117
118  g[0] = (freq[0] < s_rate/2) ? norm*0.7  : 0.0;
119  g[1] = (freq[1] < s_rate/2) ? norm*0.8  : 0.0;
120  g[2] = (freq[2] < s_rate/2) ? norm*0.6  : 0.0;
121  g[3] = (freq[3] < s_rate/2) ? norm*0.7  : 0.0;
122  g[4] = (freq[4] < s_rate/2) ? norm*0.7  : 0.0;
123  g[5] = (freq[5] < s_rate/2) ? norm*0.8  : 0.0;
124  g[6] = (freq[6] < s_rate/2) ? norm*0.95 : 0.0;
125  g[7] = (freq[7] < s_rate/2) ? norm*0.76 : 0.0;
126  g[8] = (freq[8] < s_rate/2) ? norm*0.87 : 0.0;
127  g[9] = (freq[9] < s_rate/2) ? norm*0.76 : 0.0;
128
129  // compute actual resonator coefficients
130
131  j = 0;
132  while ( j < 10)
133    {
134      r[j] = exp(-freq[j]/(s_rate*q[j]));
135      a[j] = 2*r[j]*cos(2* 3.14159265358979323846 *(freq[j]/s_rate));
136      b[j] = - r[j]*r[j];
137      j = j + 1;
138    }
139
140}
141
142//
143// The strikeinit iopcode initializes the pluck model.
144//
145
146iopcode string_strikeinit(ivar aa, ivar ab, ivar sg, ivar vw,
147			     ivar vwn, ivar notenum)
148
149{
150  ivar ar, afreq;
151
152  afreq = 2000;  // attack resonator frequency
153
154  // Compute resonator bank coefficients
155
156  ar = exp(-2* 3.14159265358979323846 *(afreq/s_rate));
157  aa = 2*ar;
158  ab = -ar*ar;
159
160  vw = (1/127) ; // keyboard normalization curve
161  sg = 0.004;      // "signal gain" empirical constant (should not scale).
162  vwn = 0.02;      // velocity scaling for nm
163
164}
165
166//
167//
168// k-pass semantics for the pluck model
169//
170
171kopcode string_strikeupdate(ksig ky[1], ksig nm, ksig silent,
172	                 ksig notenum, ivar vw, ivar vwn)
173
174{
175  imports exports table string_vel;
176  imports exports ksig string_poly;
177  ksig exit, count;
178
179  count = silent ? (count + 1) : max(count - 1, 0);
180  if (((count > 5) && (itime > 0.25)) || exit)
181    {
182      if (!exit)
183	{
184	  turnoff;
185	  exit = 1;
186	  tablewrite(string_vel, int(notenum), -1);
187	  string_poly   = string_poly   - 1;
188	}
189      ky[0] = 0;
190    }
191  else
192    {
193      if (tableread(string_vel, int(notenum)) > 0)
194	{
195	  ky[0] = vw*tableread(string_vel, int(notenum));
196	  nm = ky[0]*vwn;
197	  tablewrite(string_vel, int(notenum), 0);
198	}
199      else
200	{
201	  ky[0] = 0;
202	}
203    }
204}
205
206//
207// Instr for creating audio output.
208//
209
210instr string_audio(notenum) {
211
212  ivar a[10], b[10], g[10];
213  ivar aa, ab, sg, vw, vwn;
214  ksig nm, ky[1], silent;
215  asig out;
216
217  asig y[10], y1[10], y2[10];
218  asig sy[10];
219  asig ay, ay1, ay2, dummy, x;
220
221  // happens at i-rate
222
223  string_resinit(a, b, g, notenum);
224  string_strikeinit(aa, ab, sg, vw, vwn, notenum);
225
226  // happens at k-rate
227
228  silent = (rms(out) < 8e-4);
229  string_strikeupdate(ky, nm, silent, notenum, vw, vwn);
230
231  // happens at a-rate
232
233  dummy = 0;                          // until optimizer improves
234
235  ay = aa*ay1 + ab*ay2 + ky[dummy];   // attack resonator
236  x = (arand(nm) + sg)*ay;
237
238  y = a*y1 + b*y2 + x;                // resonator bank
239
240  ay2 = ay1;                          // update filter state
241  ay1 = (abs(ay)>1e-30) ? ay : 0.0;
242
243  ky[dummy] = 0;
244  y2 = y1;
245  y1 = y;
246
247  sy = g*y;                           // gain adjust
248  out = (sy[0]+sy[1]+sy[2]+sy[3]+sy[4]+sy[5]+sy[6]+sy[7]+sy[8]+sy[9]) ;                 // sum over sy[]
249
250  output(out);
251
252}
253
254
255//
256// Instr for handling MIDI control input. Updates string_vel table.
257//
258
259instr string_kbd(pitch, velocity) preset 1
260
261{
262  imports exports table string_vel;
263  imports exports ksig string_poly;
264  ksig vval, kpitch;
265
266  // happens at k-rate
267
268  vval = velocity;
269  kpitch = pitch;
270  if (tableread(string_vel, int(kpitch)) == -1)
271    {
272      if (string_poly   < 24)
273	{
274	  tablewrite(string_vel, int(pitch), vval);
275	  instr string_audio(0, -1, pitch);
276	  string_poly   = string_poly   + 1;
277	}
278    }
279  else
280    {
281      tablewrite(string_vel, int(pitch), vval);
282    }
283
284  turnoff;
285}
286