{"id":384,"date":"2017-12-12T23:40:14","date_gmt":"2017-12-13T04:40:14","guid":{"rendered":"http:\/\/magneticpiano.com\/?page_id=384"},"modified":"2017-12-27T10:32:20","modified_gmt":"2017-12-27T15:32:20","slug":"ircam-phase-1b","status":"publish","type":"page","link":"http:\/\/magneticpiano.com\/?page_id=384","title":{"rendered":"Phase 1b-Modalys piano"},"content":{"rendered":"<h3>Modeling Piano Acoustics in Modalys<\/h3>\n<p>The primary goal of this phase was to create a reasonable physical model of the response of a grand piano bichord (two strings tuned to the same pitch, struck by a single hammer) using Modalys, IRCAM\u2019s physical modeling software. This model served as a foundation during testing of the induction algorithm.<\/p>\n<p><strong>Research<\/strong><\/p>\n<p>My research into piano acoustics focused on two aspects:<\/p>\n<ul>\n<li>string parameters (density, diameter, stiffness, etc.),<\/li>\n<li>the physics of \u201cdouble decay\u201d<\/li>\n<\/ul>\n<p>Several resources proved indispensable to this:<\/p>\n<p style=\"padding-left: 30px;\">Bensa, Julien, et al. \u201cParameter fitting for piano sound synthesis by physical modeling.\u201d <em>Journal of the Acoustical Society of America.<\/em> vol. 118, no. 1, July 2005, pp 495-504.<\/p>\n<p style=\"padding-left: 30px;\">Burred, Juan Jos\u00e9. \u00a0<em>The Acoustics of the Piano<\/em>. Translated by David Ripplinger, Professional Conservatory of Music <em>Arturo Soria<\/em>, Revised version, September 2004.<\/p>\n<p style=\"padding-left: 30px;\">Delacour, John. \u201cFormul\u00e6 for Piano String Calculation.\u201d\u00a0<em>Delacour Pianos<\/em>, 2006, <a href=\"http:\/\/pianomaker.co.uk\/technical\/string_formulae\/\" target=\"_blank\" rel=\"noopener\">pianomaker.co.uk\/technical\/string_formulae\/<\/a>.<\/p>\n<p style=\"padding-left: 30px;\">Hall, Donald E. \u201cThe hammer and the string.\u201d <em>Five Lectures on the Acoustics of the Piano.<\/em> Royal Swedish Academy of Music, 1990. <a href=\"http:\/\/www.speech.kth.se\/music\/5_lectures\/hall\/hall.html\" target=\"_blank\" rel=\"noopener\">www.speech.kth.se\/music\/5_lectures\/hall\/hall.html<\/a>.<\/p>\n<p style=\"padding-left: 30px;\">Weinreich, Gabriel. \u201cThe coupled motion of piano strings.\u201d <em>Five Lectures on the Acoustics of the Piano.<\/em> Royal Swedish Academy of Music, 1990. <a href=\"http:\/\/www.speech.kth.se\/music\/5_lectures\/weinreic\/weinreic.html\" target=\"_blank\" rel=\"noopener\">www.speech.kth.se\/music\/5_lectures\/weinreic\/weinreic.html<\/a>.<\/p>\n<p>Many thanks to <a href=\"http:\/\/hyvibe.audio\/about-us\/\" target=\"_blank\" rel=\"noopener\">Adrien Mamou-Mani<\/a> and <a href=\"http:\/\/www.cemes.fr\/Seminaire-Rene-Causse?lang=en\" target=\"_blank\" rel=\"noopener\">Ren\u00e9 Causs\u00e9<\/a> for their help during this phase.<\/p>\n<p><strong>Implementation in Modalys<\/strong><\/p>\n<p>I hope to soon publish a paper providing details on how these the physical parameters were adapted to string creation in Modalys. Until then, I hope the following brief explanation, followed by example code, will suffice.<\/p>\n<p>In creating this physical model, I worked primarily in ModaLisp, the Modalys textual environment based on Lisp. The use of this environment, which functions out of real-time, allowed me to build my initial model with minimal concern for processing power limitations. Once I had achieved a reasonable model I built an interface in Max\/MSP to allow for real-time control. In doing so I used the ModaLisp code to generate a .mlys script file. A modalys~ object in Max then read that script file. In doing so I found that the initial model had to be simplified somewhat, particularly with regard to my experimentation with string double decay.<\/p>\n<p>ModaLisp code for a physical model of a set of tuned piano strings attached to a bridge (also available as a <a href=\"http:\/\/www.perbloland.com\/userfiles\/file\/magnetic-piano\/30-Piano-With-Hammers.lisp\">download<\/a>).<\/p>\n<pre><span class=\"inner-pre\" style=\"font-size: 10px;\">\n;;;-*-Mode: Lisp; Package: MODALYS -*-\n\n;;;----------------------------------------------------------------------\n;;; Modalys, Piano String\n;;; Physical model of piano bichord (two strings tuned to the same pitch,\n;;; struck by a single hammer. \n;;;----------------------------------------------------------------------\n\n;; string names: str1a, str1b, etc.\n;; string pitch controller labels: STR1A-PITCH, STR1B-PITCH, etc. (labels in Max must be capitalized)\n;; string on-off controller labels: STR1A-ON, STR1B-ON, etc.\n;; string freq-loss controller labels: STR1A-DAMP, STR1B-DAMP, etc.\n;; string access names:\n;;  str1a-hammer, str1a-bridge, str1a-listen, str1a-damper\n\n;; hammer names: hammer1, hammer2 etc.\n;; hammer access names: hammer1-hit, hammer1-mov, hammer2-hit, hammer2-mov, etc.\n;; hammer controller labels: hammer1-move, hammer2-move, etc \n;;    (manipulated with a Max signal rather than a message, so case doesn't matter)\n\n;;; **NOTE: \n;;;   1) the *env* parameter several lines below determines the output - an audio file or mlys script\n;;;   2) if generating a script, the file path must be reset to your own. This is controlled in the final \"if\" statement at the bottom\n\n(new)\n(set-precision 'float)\n(set-message-level 3)\n\n;; output - nil=generate audio, t=generate mlys script\n(defparameter *env* nil)\n\n;; set string parameters\n\n(setq *string-freqs* (list 261.62 329.628 391.995)) ; a major triad: C4 (middle C), E, G\n;; note that the number of frequencies listed determines the number of bichords created\n(setq *detune-range* (list 0.5 3)); (min max) detune in cents - controls the variation in tuning between strings of the bichord\n\n(setf *hammer-loc-onstring* 0.7) ; between 0 and 1\n(setf *listen-loc-onstring* 0.293)\n(setf *bridge-loc-onstring* 0.95)\n\n;; make bridge\n(setf small-mass-ctl (make-controller 'dynamic 1 0 .0015 \"small-mass\")) ; must be low to allow the mass to move at same freq as string\n(setf large-mass-ctl (make-controller 'dynamic 1 0 100000 \"large-mass\"))\n(setf stiffness-ctl (make-controller 'dynamic 1 0 291000 \"stiffness\")) ; high stiffness allows more transfer to soundboard\n(setf freq-loss-ctl (make-controller 'dynamic 1 0 4 \"freq-loss\"))\n(setf const-loss-ctl (make-controller 'dynamic 1 0 13 \"const-loss\"))\n\n(setq bridge (make-object 'mono-two-mass\n                          (small-mass small-mass-ctl) ; mass 1\n                          (large-mass large-mass-ctl) ; mass 0\n                          (stiffness0 stiffness-ctl)\n                          (freq-loss0 freq-loss-ctl)\n                          (const-loss0 const-loss-ctl)))\n; actual bridge objects exist in Modalys, however the 'mono-two-mass was selected as it is more efficient\n\n(setq bridge-string-access (make-access bridge (const 1) 'trans0)) ; small mass connected to strings\n(setq bridge-position-access (make-access bridge (const 0) 'trans0)) ; large mass locked in place\n(setq bridge-pos1 (make-controller 'access-position 1 bridge-string-access)) ; small mass (connected to strings) for graph\n(setq bridge-pos0 (make-controller 'access-position 1 bridge-position-access)); large mass locked in place\n(make-connection 'position bridge-position-access (const 0)) ; locks large mass to \"soundboard\"\n(setq bridge-rigidity (make-controller 'dynamic 1 0 '(.5) \"bridge-rigidity\")) ; sets amount of resonance\n(make-connection 'position bridge-string-access (const 0) bridge-rigidity) ; use this to lock bridge in place, so no resonance\n\n;; build strings\n\n(defun name-string (string-num ab)\n  (read-from-string\n   (format nil \"str~A~A\" string-num ab)))\n\n(setf string-names ; creates a list of strings, grouped into bichords: ((str1a str1b) (str2a str2b) etc.)\n      (loop for x from 1 to (length *string-freqs*)\n            collect (list (name-string x 'a)\n                          (name-string x 'b))))\n\n(defun detune (pitch)\n  (let* ((min (float (first *detune-range*)))\n         (max (second *detune-range*))\n         (range (- max min)))\n    (midi-to-freq (+ (freq-to-midi pitch)\n                     (\/ (+ min \n                           (if (= 0 range) 0 ; if min=max, just add min to freq\n                             (random range))) ; otherwise, pick a random # w\/in range and add it\n                        100)))))\n\n(defun bridge-adjust (string-freq) ; compensates for the change in string pitch caused by attachment to bridge\n  (* string-freq *bridge-loc-onstring*))\n\n(defun highest-mode (string-freq)\n  (+ 1 (\/ 22050.0 string-freq)))\n\n;====================================================================\n; string creation formulas - based on measurements from a grand piano\n\n; exponential scaling formula: (+ minout (* (expt (* (- n minin) (\/ 1 (- maxin minin))) (exp curve)) (- maxout minout)))))\n(defun string-length (string-freq)\n  (if (&gt; string-freq 82.4)\n      (realpart (\/ (+ 47 (* (expt (* (- (freq-to-midi string-freq) 108) -0.01492537313433)\n                                  2.0137527) 1428)) 1000))\n    (\/ (+ 1505 (* (- (freq-to-midi string-freq) 40) -78.6842105263158)) 1000))) ; result in mm, converted to meters\n\n; linear scaling formula: (+ minout (* (- self minin) (\/ (- maxout minout) (- maxin minin))))\n; provides a linear scaling from inputs 41-108 to outputs 0.0005625-0.000375 (radius in meters)\n(defun string-radius (string-freq)\n  (\/ (\/ (+ 1.125 (* (- (freq-to-midi string-freq) 41) -0.00559701492537)) ; delivers diameter in mm\n        2) 1000)) ; converts to radius, then to meters\n\n; inharmonicity used to compute youngs modulus\n(defun inharmonicity (string-freq)\n  (if (&gt; string-freq 110)\n      (realpart (+ 3.761e-5 (* (expt (* (- string-freq 130.813) 0.00024690157047) \n                                     1.5372576) 0.009433391)))\n    (realpart (+ 1e-16 (* (expt (* (- string-freq 27.5) \n                                   0.01212121212121) 54.59815) 3.761e-5)))))\n\n(defun youngs (string-freq)\n  (\/ (* (inharmonicity string-freq) 134400 (expt (string-length string-freq) 2))\n     (* 31.006276680299827 (expt (* 2 (string-radius string-freq)) 4))))\n\n; freq-loss and const-loss formulas done by ear...\n(defun freq-loss-adjust (string-freq)\n  (realpart (+ 0.01 (* (sin (* (expt (* (- (freq-to-midi string-freq) 45) \n                                        0.01666666666667) 2.2255409) pi)) 0.03))))\n\n(defun const-loss-adjust (string-freq)\n  (realpart (+ 0.1 (* (expt (* (- string-freq 110) 0.00029325513196) \n                               3.320117) 4.9))))\n;====================================================================\n\n\n; create single string, with a controller for freq-loss (used for damping in Max)\n(defun create-string (name string-freq freq-loss-ctl)\n  (set name (make-object 'bi-string\n                         (modes      (highest-mode string-freq))\n                         (length     (string-length string-freq))\n                         (density     5000) ; I cheated with this - should be 7850\n                         (radius     (string-radius string-freq))\n                         (young      (youngs string-freq))\n                         (freq-loss  freq-loss-ctl)\n                         (const-loss (const-loss-adjust string-freq))\n                         )))\n\n(defun create-name-string (string-name type)\n  (format nil \"~A-~A\" string-name type))\n\n; create both strings of a bichord (double strings for a single pitch),\n; and controllers for damping and pitch adjustment\n(defun create-bichord (name-a name-b string-freq)\n  (let* ((string-freq2 (detune string-freq))\n         (actual-string-freq (bridge-adjust string-freq)) ; adjust pitch for bridge location\n         (actual-string-freq2 (bridge-adjust string-freq2))\n         (controller-name (read-from-string (create-name-string name-a 'pitch))) ; pitch adjustment in Max\n         (controller-name2 (read-from-string (create-name-string name-b 'pitch)))\n         (controller-string (create-name-string name-a 'pitch))\n         (controller-string2 (create-name-string name-b 'pitch))\n         (controller-name3 (read-from-string (create-name-string name-a 'damp))) ; imitates dampers by raising freq-loss (in Max)\n         (controller-name4 (read-from-string (create-name-string name-b 'damp)))\n         (controller-string3 (create-name-string name-a 'damp))\n         (controller-string4 (create-name-string name-b 'damp))\n         )\n    (list (setf controller-name3\n                (make-controller 'dynamic 1 -1 (freq-loss-adjust string-freq) controller-string3))\n          (setf controller-name4\n                (make-controller 'dynamic 1 -1 (freq-loss-adjust string-freq) controller-string4))\n          (create-string name-a string-freq controller-name3) ; create the strings...\n          (create-string name-b string-freq2 controller-name4)\n          (setf controller-name \n                (make-controller 'dynamic 1 -1 (list actual-string-freq) controller-string))\n          (setf controller-name2\n                (make-controller 'dynamic 1 -1 (list actual-string-freq2) controller-string2))\n          (set-pitch (eval name-a) 'tension controller-name) ; ...and set their pitch\n          (set-pitch (eval name-b) 'tension controller-name2)\n          )))\n\n; create all the strings my applying \"create-bichord\" to string name list\n(mapcar #'(lambda (string-name pitch) \n            (create-bichord (first string-name) (second string-name) pitch))\n        string-names *string-freqs*)\n\n;; make hammers, accesses, and connections to movers\n\n(defun hammer-name (string-num)\n  (read-from-string\n   (format nil \"hammer~A\" string-num)))\n\n; create a list of hammers: ((hammer1 1) (hammer2 2) etc.)\n(setf hammer-names\n      (loop for x from 1 to (length *string-freqs*)\n            collect (list (hammer-name x) x)))\n\n; create all hammers, their accesses, and connect them to mover-controllers\n; mover-controllers are either envelopes, if generating a file, or signal inputs is generating a script\n(mapcar #'(lambda (hammer-name-num)\n            (let* ((hammer-name (first hammer-name-num))\n                   (hammer-num (- (second hammer-name-num) 1))\n                   (hammer-access-name1 (read-from-string (create-name-string hammer-name 'hit))) ; access for string strike\n                   (hammer-access-name2 (read-from-string (create-name-string hammer-name 'mov))) ; access for position controller\n                   (mover-name (create-name-string hammer-name 'move))\n                   )\n              (list\n               (set hammer-name (make-object 'mono-two-mass))\n               (set hammer-access-name1 (make-access (eval hammer-name) (const 1) 'trans0)) ; connected to strings\n               (set hammer-access-name2 (make-access (eval hammer-name) (const 0) 'trans0)) ; connected to position controller\n               (if *env*\n                   (make-connection 'position (eval hammer-access-name2) ; if generating a script, use an input signal to move hammer\n                                    (make-controller 'signal 1 (make-point-input hammer-num (const 1))))\n                 (make-connection 'position (eval hammer-access-name2) ; if generating an audio file, use envelope to move hammer\n                                  (make-controller 'envelope 1\n                                                   (list (list 0.00 .1) (list 0.025 -.001) (list 0.05 .1)))) \n                                                   ; this envelope controls the speed of the hammer. currently mf\n                 ))))\n            hammer-names)\n\n;; some different dynamics to plug into envelope, above:\n;(list (list 0.00   .1) (list 0.0025  -.001) (list 0.005   .1)) ;; f\n;(list (list 0.00   .1) (list 0.025  -.001) (list 0.05   .1)) ;; mf\n;(list (list 0.00   .1) (list 0.045  -.001) (list 0.1   .1)) ;; mp\n;(list (list 0.00   .1) (list .1  0.001) (list .17   .1)) ;; p\n\n;; make all string accesses and connections to hammers\n\n(defun create-access-name (string-name access-type)\n  (read-from-string\n   (format nil \"~A-~A\" string-name access-type)))\n\n(defun create-accesses (string-name)\n  (let* ((obj-name (eval string-name))\n         (hammer-access (create-access-name string-name 'hammer)) ; contact point with hammer\n         (bridge-access (create-access-name string-name 'bridge)) ; contact point with bridge\n         (listen-access (create-access-name string-name 'listen)) ; output point\n         )\n    (set hammer-access (make-access obj-name *hammer-loc-onstring* 'trans0))\n    (set bridge-access (make-access obj-name *bridge-loc-onstring* 'trans0))\n    (set listen-access (make-access obj-name *listen-loc-onstring* 'trans0))\n    ))\n\n\n(mapcar #'(lambda (string-name hammer-name-num) \n            (let* ((name-a (first string-name)) ; reads through list of strings, extracting individual string names\n                   (name-b (second string-name))\n                   (hammer-name (first hammer-name-num)) ; extracts hammer names\n                   (hammer-acc-a (create-access-name name-a 'hammer)) ; create the string access, 3 per string\n                   (hammer-acc-b (create-access-name name-b 'hammer))\n                   (listen-acc-a (create-access-name name-a 'listen))\n                   (listen-acc-b (create-access-name name-b 'listen))\n                   (bridge-acc-a (create-access-name name-a 'bridge))\n                   (bridge-acc-b (create-access-name name-b 'bridge))\n                   (hammer-hit-acc (read-from-string (create-name-string hammer-name 'hit))) ; just get the name of the access on the hammer\n                   (controller-name (read-from-string (create-name-string name-a 'on))) ; controller to mute string - turns off point-output \n                   (controller-string (create-name-string name-a 'on))\n                   )\n              (list (create-accesses name-a)\n                    (create-accesses name-b)\n                    (make-connection 'felt (eval hammer-hit-acc) 0  (eval hammer-acc-a) -0.1 ; string init location is -0.1\n                                     (const .01)     ;; thickness - so actual hammer init location is -0.01\n                                     (const 1e+11)     ;;  F0\n                                     (const 2.5)      ;;  alpha\n                                     (const .6)     ;;  epsilon\n                                     (const 15e-05)   ;;  tau\n                                     )\n                    (make-connection 'felt (eval hammer-hit-acc) 0  (eval hammer-acc-b) -0.1 \n                                     (const .01)     ;; thickness\n                                     (const 1e+11)     ;;  F0\n                                     (const 2.5)      ;;  alpha\n                                     (const .6)     ;;  epsilon\n                                     (const 15e-05)   ;;  tau\n                                     )\n                    (make-connection 'adhere (eval bridge-acc-a) bridge-string-access) ; connect strings to bridge\n                    (make-connection 'adhere (eval bridge-acc-b) bridge-string-access)\n                    (setf controller-name \n                          (make-controller 'dynamic 1 -1 (list 1) controller-string)) ; string mute\n                    (make-point-output (eval listen-acc-a) 0 controller-name) ; outputs for strings\n                    (make-point-output (eval listen-acc-b) 1 controller-name)\n                    )))\n        string-names hammer-names)\n\n(setq str1a-pos0 (make-controller 'access-position 1 str1a-listen)) ; these 3 are for the graph\n(setq hammer1-hit-pos (make-controller 'access-position 1 hammer1-hit))\n(setq hammer1-mov-pos (make-controller 'access-position 1 hammer1-mov))\n\n(if *env* ; either make a script for Max -or- generate an audio file and a graph\n    (save-script \"\/Users\/mus\/Documents\/Data\/-in\\ progress\/ircam-Project\/final-file-summaries\/5-piano\/Piano-With-Hammers.mlys\")\n  (list (setq graph (make-plot))\n        (plot-value graph \"string1\" str1a-pos0)\n;        (plot-value graph \"bridge0\" bridge-pos0) ; this is locked in place, but graph it to check\n        (plot-value graph \"bridge1\" bridge-pos1) ; connected to strings\n;        (plot-value graph \"hammer0\" hammer1-hit-pos) ; connected to strings\n;        (plot-value graph \"hammer1\" hammer1-mov-pos) ; connected to position controller\n        (run 15)\n        (plot graph \"My Plotted Controller Data\")\n        (play)))\n<\/span><\/pre>\n<p>Continue on to <a href=\"http:\/\/magneticpiano.com\/?page_id=440\">Phase 2 &#8211; Induction<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Modeling Piano Acoustics in Modalys The primary goal of this phase was to create a reasonable physical model of the response of a grand piano bichord (two strings tuned to the same pitch, struck by a single hammer) using Modalys, IRCAM\u2019s physical modeling software. This model served as a foundation &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":46,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-full-width.php","meta":{"jetpack_post_was_ever_published":false},"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/P41WFm-6c","_links":{"self":[{"href":"http:\/\/magneticpiano.com\/index.php?rest_route=\/wp\/v2\/pages\/384"}],"collection":[{"href":"http:\/\/magneticpiano.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"http:\/\/magneticpiano.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"http:\/\/magneticpiano.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/magneticpiano.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=384"}],"version-history":[{"count":37,"href":"http:\/\/magneticpiano.com\/index.php?rest_route=\/wp\/v2\/pages\/384\/revisions"}],"predecessor-version":[{"id":512,"href":"http:\/\/magneticpiano.com\/index.php?rest_route=\/wp\/v2\/pages\/384\/revisions\/512"}],"up":[{"embeddable":true,"href":"http:\/\/magneticpiano.com\/index.php?rest_route=\/wp\/v2\/pages\/46"}],"wp:attachment":[{"href":"http:\/\/magneticpiano.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=384"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}