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