[{"data":1,"prerenderedAt":2112},["ShallowReactive",2],{"posts:\u002Fposts\u002F2019-06-26-sympathetic-resonance-experimentation":3},{"id":4,"title":5,"body":6,"categories":2095,"date":2097,"description":2098,"extension":2099,"image":2100,"lang":2098,"meta":2101,"navigation":226,"path":2102,"seo":2103,"stem":2104,"tags":2105,"titleInPicture":2110,"__hash__":2111},"posts\u002Fposts\u002F2019-06-26-sympathetic-resonance-experimentation.md","Sympathetic resonance experimentation",{"type":7,"value":8,"toc":2090},"minimark",[9,17,20,23,26,29,34,37,809,812,816,819,822,827,838,841,845,853,2074,2077,2080,2083,2086],[10,11,12],"p",{},[13,14],"img",{"alt":15,"src":16},"","\u002Fimages\u002F2019\u002F06\u002FIMG_20190624_205922-1024x576.jpg",[10,18,19],{},"Using a Bela board, I set out to create a synthesizer that applied the sympathetic resonance feature present in piano physical models to other waves than piano strings. Sine waves, in this case.",[10,21,22],{},"If you are in front of a real piano, you can try this experiment: press one key slowly enough, so that it does not get triggered. Without releasing that key, press the same note in a different octave, as loud as you can, and release it fast. I will spoil the surprise in advantage of those who cannot do this test: the previously pressed note will have picked up some sound, which came from the short key stroke on a different octave. This phenomenon is a manifestation of sympathetic resonance.",[10,24,25],{},"For each note on the piano, you can learn which other notes are the most and least harmonic with respect to that note by trying the same exercise with different pairs. The sympathetic resonance, hence, is not the same for pair of frequencies.",[10,27,28],{},"For a long time I have been interested in modelling this in a program, in order to be able to change the parameters of the sound while having the resonance. Beside this, I have a strong attachment to the sound of real pianos, partly because of this feature. Note, however, that there are many software pianos that implement this too.",[30,31,33],"h2",{"id":32},"basic-synth-code","Basic synth code",[10,35,36],{},"The first step, was to create a simple sine-wave synth with an ADSR envelope, capable of polyphony. The design is simple, and largely based on Bela's example code:",[38,39,43],"pre",{"className":40,"code":41,"language":42,"meta":15,"style":15},"language-cpp shiki shiki-themes github-dark","namespace global{\n    unsigned int sampleRate=0;\n    unsigned int controlInterval=10;\u002F\u002Fhow many samples it takes to update a control signal\n    double secondsPerSample=0;\n}\nstruct envelope{\n    float attack=0;\n    float decay=0;\n    float sustain=0;\n    float release=0;\n    \n    float attack_delta=0;\n    float decay_delta=0;\n    float sustain_delta=0;\n    float release_delta=0;\n    \n    void set(float a, float d,float s, float r){\n        float fps=(float)global::sampleRate\u002Fglobal::controlInterval;\n        if(fps==0) throw \"sample rate or control interval is zero\";\n        attack=a;\n        decay=d;\n        sustain=s;\n        release=r;\n        attack_delta=1\u002F(attack*fps);\n        decay_delta=1\u002F(decay*fps);\n        sustain_delta=1\u002F(sustain*fps);\n        release_delta=1\u002F(release*fps);\n    }\n};\n\nenvelope synthEnvelope;\n\nclass Voice {\n    public:\n        static int count;\n        unsigned int id;\n        float currentPhase;\n        float currentVelocity;\n        float currentPhaseIncrement;\n        int envState;\n        float envLevel;\n        int updateCount;\n        bool isTriggered=false;\n        bool restartPhase=true;\n        float velocityToAttack=0.3;\n        \n        Voice() {\n            id = count++;\n            currentPhase = 0;\n            currentVelocity = 0;\n            currentPhaseIncrement = 0;\n            envState = 0;\n            envLevel = 0;\n            updateCount = 0;\n            \n        }\n        \n        void triggerNoteOn(int note, int velocity){\n            if(restartPhase)currentPhase=0;\n            isTriggered=true;\n            float gFreq = powf(2, note \u002F 12.f) * 440.f;\n            currentVelocity = velocity\u002F127.f;\n            currentPhaseIncrement = 2.f * (float) M_PI * gFreq \u002F global::sampleRate;\n            rt_printf(\"id: %d freq:%f, phaseInc:%6.5f, currentVelocity: %f\\n\", id, gFreq, currentPhaseIncrement, currentVelocity);\n            envState=1;\n        }\n        void triggerNoteOff(){\n            isTriggered=false;\n        }\n        float sample(){\n            if(envState==0) return 0;\n            \n            currentPhase += currentPhaseIncrement;\n            if(currentPhase > M_PI)\n                currentPhase -= 2.f * (float)M_PI;\n            if(updateCount>=global::controlInterval){\n                envUpdate();\n                updateCount=0;\n            }\n            \n            float result=envLevel * sinf(currentPhase) * currentVelocity;\n            \u002F*\n            float scope1=0;\n            float scope2=0;\n            float scope3=envState;\n            \n            scope.log(scope1, scope2, scope3);\n            *\u002F\n            updateCount++;\n            return result;\n        }\n        \n        void envUpdate(){\n            switch (envState){\n                case 0:\n                    envLevel=0;\n                    break;\n                case 1:\u002F\u002Fattack started\n                    if(envLevel\u003C1){\n                        envLevel+=synthEnvelope.attack_delta+(currentVelocity*velocityToAttack);\n                    }else{\n                        envState++;\n                    }\n                    break;\n                case 2:\u002F\u002Fdecay started\n                    if(envLevel>synthEnvelope.sustain){\n                        envLevel-=synthEnvelope.decay_delta;\n                    }else{\n                        envState++;\n                    }\n                    break;\n                case 3:\u002F\u002Fsustain started\n                    if(isTriggered){\n                        \u002F\u002Fsustainiiing\n                    }else{\n                        envState++;\n                    }\n                    break;\n                case 4:\u002F\u002Frelease started\n                    if(envLevel>0){\n                        envLevel-=synthEnvelope.release_delta;\n                    }else{\n                        envState++;\n                    }\n                    break;\n                default:\u002F\u002Frelease ended, or state is out of range.\n                    envState=0;\n            }\n        }\n};\nint Voice::count=0;\n","cpp",[44,45,46,54,60,66,72,78,84,90,96,102,108,114,120,126,132,138,143,149,155,161,167,173,179,185,191,197,203,209,215,221,228,234,239,245,251,257,263,269,275,281,287,293,299,305,311,317,323,329,335,341,347,353,359,365,371,377,383,388,394,400,406,412,418,424,430,436,441,447,453,458,464,470,475,481,487,493,499,505,511,517,522,528,534,540,546,552,557,563,569,575,581,586,591,597,603,609,615,621,627,633,639,645,651,657,662,668,674,680,685,690,695,700,706,712,718,723,728,733,738,744,750,756,761,766,771,776,782,788,793,798,803],"code",{"__ignoreMap":15},[47,48,51],"span",{"class":49,"line":50},"line",1,[47,52,53],{},"namespace global{\n",[47,55,57],{"class":49,"line":56},2,[47,58,59],{},"    unsigned int sampleRate=0;\n",[47,61,63],{"class":49,"line":62},3,[47,64,65],{},"    unsigned int controlInterval=10;\u002F\u002Fhow many samples it takes to update a control signal\n",[47,67,69],{"class":49,"line":68},4,[47,70,71],{},"    double secondsPerSample=0;\n",[47,73,75],{"class":49,"line":74},5,[47,76,77],{},"}\n",[47,79,81],{"class":49,"line":80},6,[47,82,83],{},"struct envelope{\n",[47,85,87],{"class":49,"line":86},7,[47,88,89],{},"    float attack=0;\n",[47,91,93],{"class":49,"line":92},8,[47,94,95],{},"    float decay=0;\n",[47,97,99],{"class":49,"line":98},9,[47,100,101],{},"    float sustain=0;\n",[47,103,105],{"class":49,"line":104},10,[47,106,107],{},"    float release=0;\n",[47,109,111],{"class":49,"line":110},11,[47,112,113],{},"    \n",[47,115,117],{"class":49,"line":116},12,[47,118,119],{},"    float attack_delta=0;\n",[47,121,123],{"class":49,"line":122},13,[47,124,125],{},"    float decay_delta=0;\n",[47,127,129],{"class":49,"line":128},14,[47,130,131],{},"    float sustain_delta=0;\n",[47,133,135],{"class":49,"line":134},15,[47,136,137],{},"    float release_delta=0;\n",[47,139,141],{"class":49,"line":140},16,[47,142,113],{},[47,144,146],{"class":49,"line":145},17,[47,147,148],{},"    void set(float a, float d,float s, float r){\n",[47,150,152],{"class":49,"line":151},18,[47,153,154],{},"        float fps=(float)global::sampleRate\u002Fglobal::controlInterval;\n",[47,156,158],{"class":49,"line":157},19,[47,159,160],{},"        if(fps==0) throw \"sample rate or control interval is zero\";\n",[47,162,164],{"class":49,"line":163},20,[47,165,166],{},"        attack=a;\n",[47,168,170],{"class":49,"line":169},21,[47,171,172],{},"        decay=d;\n",[47,174,176],{"class":49,"line":175},22,[47,177,178],{},"        sustain=s;\n",[47,180,182],{"class":49,"line":181},23,[47,183,184],{},"        release=r;\n",[47,186,188],{"class":49,"line":187},24,[47,189,190],{},"        attack_delta=1\u002F(attack*fps);\n",[47,192,194],{"class":49,"line":193},25,[47,195,196],{},"        decay_delta=1\u002F(decay*fps);\n",[47,198,200],{"class":49,"line":199},26,[47,201,202],{},"        sustain_delta=1\u002F(sustain*fps);\n",[47,204,206],{"class":49,"line":205},27,[47,207,208],{},"        release_delta=1\u002F(release*fps);\n",[47,210,212],{"class":49,"line":211},28,[47,213,214],{},"    }\n",[47,216,218],{"class":49,"line":217},29,[47,219,220],{},"};\n",[47,222,224],{"class":49,"line":223},30,[47,225,227],{"emptyLinePlaceholder":226},true,"\n",[47,229,231],{"class":49,"line":230},31,[47,232,233],{},"envelope synthEnvelope;\n",[47,235,237],{"class":49,"line":236},32,[47,238,227],{"emptyLinePlaceholder":226},[47,240,242],{"class":49,"line":241},33,[47,243,244],{},"class Voice {\n",[47,246,248],{"class":49,"line":247},34,[47,249,250],{},"    public:\n",[47,252,254],{"class":49,"line":253},35,[47,255,256],{},"        static int count;\n",[47,258,260],{"class":49,"line":259},36,[47,261,262],{},"        unsigned int id;\n",[47,264,266],{"class":49,"line":265},37,[47,267,268],{},"        float currentPhase;\n",[47,270,272],{"class":49,"line":271},38,[47,273,274],{},"        float currentVelocity;\n",[47,276,278],{"class":49,"line":277},39,[47,279,280],{},"        float currentPhaseIncrement;\n",[47,282,284],{"class":49,"line":283},40,[47,285,286],{},"        int envState;\n",[47,288,290],{"class":49,"line":289},41,[47,291,292],{},"        float envLevel;\n",[47,294,296],{"class":49,"line":295},42,[47,297,298],{},"        int updateCount;\n",[47,300,302],{"class":49,"line":301},43,[47,303,304],{},"        bool isTriggered=false;\n",[47,306,308],{"class":49,"line":307},44,[47,309,310],{},"        bool restartPhase=true;\n",[47,312,314],{"class":49,"line":313},45,[47,315,316],{},"        float velocityToAttack=0.3;\n",[47,318,320],{"class":49,"line":319},46,[47,321,322],{},"        \n",[47,324,326],{"class":49,"line":325},47,[47,327,328],{},"        Voice() {\n",[47,330,332],{"class":49,"line":331},48,[47,333,334],{},"            id = count++;\n",[47,336,338],{"class":49,"line":337},49,[47,339,340],{},"            currentPhase = 0;\n",[47,342,344],{"class":49,"line":343},50,[47,345,346],{},"            currentVelocity = 0;\n",[47,348,350],{"class":49,"line":349},51,[47,351,352],{},"            currentPhaseIncrement = 0;\n",[47,354,356],{"class":49,"line":355},52,[47,357,358],{},"            envState = 0;\n",[47,360,362],{"class":49,"line":361},53,[47,363,364],{},"            envLevel = 0;\n",[47,366,368],{"class":49,"line":367},54,[47,369,370],{},"            updateCount = 0;\n",[47,372,374],{"class":49,"line":373},55,[47,375,376],{},"            \n",[47,378,380],{"class":49,"line":379},56,[47,381,382],{},"        }\n",[47,384,386],{"class":49,"line":385},57,[47,387,322],{},[47,389,391],{"class":49,"line":390},58,[47,392,393],{},"        void triggerNoteOn(int note, int velocity){\n",[47,395,397],{"class":49,"line":396},59,[47,398,399],{},"            if(restartPhase)currentPhase=0;\n",[47,401,403],{"class":49,"line":402},60,[47,404,405],{},"            isTriggered=true;\n",[47,407,409],{"class":49,"line":408},61,[47,410,411],{},"            float gFreq = powf(2, note \u002F 12.f) * 440.f;\n",[47,413,415],{"class":49,"line":414},62,[47,416,417],{},"            currentVelocity = velocity\u002F127.f;\n",[47,419,421],{"class":49,"line":420},63,[47,422,423],{},"            currentPhaseIncrement = 2.f * (float) M_PI * gFreq \u002F global::sampleRate;\n",[47,425,427],{"class":49,"line":426},64,[47,428,429],{},"            rt_printf(\"id: %d freq:%f, phaseInc:%6.5f, currentVelocity: %f\\n\", id, gFreq, currentPhaseIncrement, currentVelocity);\n",[47,431,433],{"class":49,"line":432},65,[47,434,435],{},"            envState=1;\n",[47,437,439],{"class":49,"line":438},66,[47,440,382],{},[47,442,444],{"class":49,"line":443},67,[47,445,446],{},"        void triggerNoteOff(){\n",[47,448,450],{"class":49,"line":449},68,[47,451,452],{},"            isTriggered=false;\n",[47,454,456],{"class":49,"line":455},69,[47,457,382],{},[47,459,461],{"class":49,"line":460},70,[47,462,463],{},"        float sample(){\n",[47,465,467],{"class":49,"line":466},71,[47,468,469],{},"            if(envState==0) return 0;\n",[47,471,473],{"class":49,"line":472},72,[47,474,376],{},[47,476,478],{"class":49,"line":477},73,[47,479,480],{},"            currentPhase += currentPhaseIncrement;\n",[47,482,484],{"class":49,"line":483},74,[47,485,486],{},"            if(currentPhase > M_PI)\n",[47,488,490],{"class":49,"line":489},75,[47,491,492],{},"                currentPhase -= 2.f * (float)M_PI;\n",[47,494,496],{"class":49,"line":495},76,[47,497,498],{},"            if(updateCount>=global::controlInterval){\n",[47,500,502],{"class":49,"line":501},77,[47,503,504],{},"                envUpdate();\n",[47,506,508],{"class":49,"line":507},78,[47,509,510],{},"                updateCount=0;\n",[47,512,514],{"class":49,"line":513},79,[47,515,516],{},"            }\n",[47,518,520],{"class":49,"line":519},80,[47,521,376],{},[47,523,525],{"class":49,"line":524},81,[47,526,527],{},"            float result=envLevel * sinf(currentPhase) * currentVelocity;\n",[47,529,531],{"class":49,"line":530},82,[47,532,533],{},"            \u002F*\n",[47,535,537],{"class":49,"line":536},83,[47,538,539],{},"            float scope1=0;\n",[47,541,543],{"class":49,"line":542},84,[47,544,545],{},"            float scope2=0;\n",[47,547,549],{"class":49,"line":548},85,[47,550,551],{},"            float scope3=envState;\n",[47,553,555],{"class":49,"line":554},86,[47,556,376],{},[47,558,560],{"class":49,"line":559},87,[47,561,562],{},"            scope.log(scope1, scope2, scope3);\n",[47,564,566],{"class":49,"line":565},88,[47,567,568],{},"            *\u002F\n",[47,570,572],{"class":49,"line":571},89,[47,573,574],{},"            updateCount++;\n",[47,576,578],{"class":49,"line":577},90,[47,579,580],{},"            return result;\n",[47,582,584],{"class":49,"line":583},91,[47,585,382],{},[47,587,589],{"class":49,"line":588},92,[47,590,322],{},[47,592,594],{"class":49,"line":593},93,[47,595,596],{},"        void envUpdate(){\n",[47,598,600],{"class":49,"line":599},94,[47,601,602],{},"            switch (envState){\n",[47,604,606],{"class":49,"line":605},95,[47,607,608],{},"                case 0:\n",[47,610,612],{"class":49,"line":611},96,[47,613,614],{},"                    envLevel=0;\n",[47,616,618],{"class":49,"line":617},97,[47,619,620],{},"                    break;\n",[47,622,624],{"class":49,"line":623},98,[47,625,626],{},"                case 1:\u002F\u002Fattack started\n",[47,628,630],{"class":49,"line":629},99,[47,631,632],{},"                    if(envLevel\u003C1){\n",[47,634,636],{"class":49,"line":635},100,[47,637,638],{},"                        envLevel+=synthEnvelope.attack_delta+(currentVelocity*velocityToAttack);\n",[47,640,642],{"class":49,"line":641},101,[47,643,644],{},"                    }else{\n",[47,646,648],{"class":49,"line":647},102,[47,649,650],{},"                        envState++;\n",[47,652,654],{"class":49,"line":653},103,[47,655,656],{},"                    }\n",[47,658,660],{"class":49,"line":659},104,[47,661,620],{},[47,663,665],{"class":49,"line":664},105,[47,666,667],{},"                case 2:\u002F\u002Fdecay started\n",[47,669,671],{"class":49,"line":670},106,[47,672,673],{},"                    if(envLevel>synthEnvelope.sustain){\n",[47,675,677],{"class":49,"line":676},107,[47,678,679],{},"                        envLevel-=synthEnvelope.decay_delta;\n",[47,681,683],{"class":49,"line":682},108,[47,684,644],{},[47,686,688],{"class":49,"line":687},109,[47,689,650],{},[47,691,693],{"class":49,"line":692},110,[47,694,656],{},[47,696,698],{"class":49,"line":697},111,[47,699,620],{},[47,701,703],{"class":49,"line":702},112,[47,704,705],{},"                case 3:\u002F\u002Fsustain started\n",[47,707,709],{"class":49,"line":708},113,[47,710,711],{},"                    if(isTriggered){\n",[47,713,715],{"class":49,"line":714},114,[47,716,717],{},"                        \u002F\u002Fsustainiiing\n",[47,719,721],{"class":49,"line":720},115,[47,722,644],{},[47,724,726],{"class":49,"line":725},116,[47,727,650],{},[47,729,731],{"class":49,"line":730},117,[47,732,656],{},[47,734,736],{"class":49,"line":735},118,[47,737,620],{},[47,739,741],{"class":49,"line":740},119,[47,742,743],{},"                case 4:\u002F\u002Frelease started\n",[47,745,747],{"class":49,"line":746},120,[47,748,749],{},"                    if(envLevel>0){\n",[47,751,753],{"class":49,"line":752},121,[47,754,755],{},"                        envLevel-=synthEnvelope.release_delta;\n",[47,757,759],{"class":49,"line":758},122,[47,760,644],{},[47,762,764],{"class":49,"line":763},123,[47,765,650],{},[47,767,769],{"class":49,"line":768},124,[47,770,656],{},[47,772,774],{"class":49,"line":773},125,[47,775,620],{},[47,777,779],{"class":49,"line":778},126,[47,780,781],{},"                default:\u002F\u002Frelease ended, or state is out of range.\n",[47,783,785],{"class":49,"line":784},127,[47,786,787],{},"                    envState=0;\n",[47,789,791],{"class":49,"line":790},128,[47,792,516],{},[47,794,796],{"class":49,"line":795},129,[47,797,382],{},[47,799,801],{"class":49,"line":800},130,[47,802,220],{},[47,804,806],{"class":49,"line":805},131,[47,807,808],{},"int Voice::count=0;\n",[10,810,811],{},"Besides this code, the only needed things is triggering the voice attacks, and requesting the samples. This will be present in the code at the end. The Voice class manages a sine wave whose volume is controlled by an envelope, and the velocity. The envelope is a global, since voices represent polyphony on one same synthesizer, and I was not interested in generating a different envelope for each voice.",[30,813,815],{"id":814},"the-wrong-sympathetic-resonance-function","The wrong sympathetic resonance function",[10,817,818],{},"In order to generate the desired sympathetic resonance, I needed a graph that indicated how much to raise the volume of a certain \"triggered\" note according to it's frequency relation to some other note. I used a simple sin formula, multiplied by the x axis, which is also called a sync function. The idea would be to divide the two frequencies, and use this relation as function parameter.",[10,820,821],{},"The chosen sync function is not the correct formula to use: it will consider as \"resonant\" the closer the value is to an integer. This will provide resonance to the octave relation between notes, but will fail for fifths, thirds, etc. Additionally, it considers neighboring frequencies to be more resonant, which in the real model is not true. It was useful, however as a way to test the resonance, having these limitations in mind.",[10,823,824],{},[13,825],{"alt":15,"src":826},"images\u002Fimage-266x300.png",[10,828,829,830,837],{},"The resulting curve of the graph was produced using ",[831,832,836],"a",{"href":833,"rel":834},"https:\u002F\u002Fwww.desmos.com\u002Fcalculator\u002Fugh5b3n9n8",[835],"nofollow","desmos graphing calculator",".",[10,839,840],{},"The correct resonance function to use, I suspect, would be the result of doing FFT in a sine wave. This would take a bit more of time to program. The interesting thing that derivates from this idea, if true, is that other resonance models could be tested by doing FFT to different samples. As a matter of fact, the FFT could be applied to the very timbre that presumably could be the voice of the synthesizer; being able theoretically to make resonance models to any natural sound that is not resonant in nature.",[30,842,844],{"id":843},"synthesizer-code-with-resonance-applied","Synthesizer code with resonance applied",[10,846,847,848,852],{},"With the ",[849,850,851],"em",{},"wrong"," formula applied, the whole synthesizer code for a Bela board is the following:",[38,854,856],{"className":40,"code":855,"language":42,"meta":15,"style":15},"#include \u003CBela.h>\n#include \u003CMidi.h>\n#include \u003Cstdlib.h>\n#include \u003CScope.h>\n#include \u003Ccmath>\n\n\nScope scope;\n\nnamespace global{\n    unsigned int sampleRate=0;\n    unsigned int controlInterval=10;\u002F\u002Fhow many samples it takes to udpate a control signal\n    double secondsPerSample=0;\n}\n\nfloat sympathicResonanceTable[5000];\u002F\u002Fusing step of 1\u002F1000, hence getting to 5 times freq\n\u002F\u002Fto make a lookup table of this function:https:\u002F\u002Fwww.desmos.com\u002Fcalculator\u002Fcfu9rxzx1o\n\u002F\u002Fwhich can be used to calculate how much a wave sympathizes with another.\n\u002F\u002Fformula needs improvement. cirrently only octaves resonate\nvoid fillSympathicResonanceTable(int positions, float hertzPerStep){\n    for(unsigned int step=0; step\u003Cpositions; step++){\n        float x=step*hertzPerStep;\n        sympathicResonanceTable[step]=(cosf(x*M_PI)\u002F(x))+(1\u002Fx);\n    }\n}\n\nstruct envelope{\n    float attack=0;\n    float decay=0;\n    float sustain=0;\n    float release=0;\n    \n    float attack_delta=0;\n    float decay_delta=0;\n    float sustain_delta=0;\n    float release_delta=0;\n    \n    void set(float a, float d,float s, float r){\n        float fps=(float)global::sampleRate\u002Fglobal::controlInterval;\n        if(fps==0) throw \"sample rate or control interval is zero\";\n        attack=a;\n        decay=d;\n        sustain=s;\n        release=r;\n        attack_delta=1\u002F(attack*fps);\n        decay_delta=1\u002F(decay*fps);\n        sustain_delta=1\u002F(sustain*fps);\n        release_delta=1\u002F(release*fps);\n    }\n};\n\nenvelope synthEnvelope;\n\n\u002F\u002Fworks weirdly. only some voices sound.\nclass Voice {\n    public:\n        static int count;\n        unsigned int id;\n        float currentPhase;\n        float currentVelocity;\n        float currentPhaseIncrement;\n        int envState;\n        float envLevel;\n        int updateCount;\n        bool isTriggered=false;\n        bool restartPhase=true;\n        float velocityToAttack=0.3;\n        \n        Voice() {\n            id = count++;\n            currentPhase = 0;\n            currentVelocity = 0;\n            currentPhaseIncrement = 0;\n            envState = 0;\n            envLevel = 0;\n            updateCount = 0;\n            \n        }\n        \n        void triggerNoteOn(int note, int velocity){\n            if(restartPhase)currentPhase=0;\n            isTriggered=true;\n            float gFreq = powf(2, note \u002F 12.f) * 440.f;\n            currentVelocity = velocity\u002F127.f;\n            currentPhaseIncrement = 2.f * (float) M_PI * gFreq \u002F global::sampleRate;\n            rt_printf(\"id: %d freq:%f, phaseInc:%6.5f, currentVelocity: %f\\n\", id, gFreq, currentPhaseIncrement, currentVelocity);\n            envState=1;\n        }\n        \n        void triggerNoteOff(){\n            isTriggered=false;\n        }\n        \n        float sample(){\n            if(envState==0) return 0;\n            currentPhase += currentPhaseIncrement;\n            if(currentPhase > M_PI)\n                currentPhase -= 2.f * (float)M_PI;\n            if(updateCount>=global::controlInterval){\n                envUpdate();\n                updateCount=0;\n            }\n            \n            float result=envLevel * sinf(currentPhase) ;\n            \u002F*\n            float scope1=0;\n            float scope2=0;\n            float scope3=envState;\n            \n            scope.log(scope1, scope2, scope3);\n            *\u002F\n            updateCount++;\n            return result;\n        }\n        \n        void simpathize(Voice & otherVoice){\n            float ratio=currentPhaseIncrement\u002FotherVoice.currentPhaseIncrement;\n            ratio*=1000;\u002F\u002Fmapping constant, has to be the inverse to hertzPerStep when map was built.\n            envLevel+=sympathicResonanceTable[(int)floor(ratio)]\u002F32;\n            if(envLevel>1.2)envLevel=1.2;\u002F\u002Fprevent crazy resonance\n        }\n        \n        void envUpdate(){\n            switch (envState){\n                case 0:\n                    envLevel=0;\n                    break;\n                case 1:\u002F\u002Fattack started\n                    if(envLevel \u003C 1 * currentVelocity){\n                        \u002F\u002F*currentVelocity compensates the fact that it'd take shorter to achieve 1*currentVelocity\n                        \u002F\u002F+(currentVelocity*velocityToAttack) actually makes attacks faster when more velocity\n                        envLevel+=synthEnvelope.attack_delta*currentVelocity+(currentVelocity*velocityToAttack);\n                    }else{\n                        envState++;\n                    }\n                    break;\n                case 2:\u002F\u002Fdecay started\n                    if(!isTriggered){\n                        envState++;\n                    }else if(envLevel>synthEnvelope.sustain){\n                        envLevel-=synthEnvelope.decay_delta;\n                    }\n                    break;\n                case 3:\u002F\u002Fsustain started\n                    if(envLevel>synthEnvelope.sustain){\n                        envLevel-=synthEnvelope.release_delta;\n                    }else if(isTriggered){\n                        \u002F\u002Fsustainiiing\n                    }else{\n                        envState++;\n                    }\n                    break;\n                case 4:\u002F\u002Frelease started\n                    if(envLevel>0){\n                        envLevel-=synthEnvelope.release_delta;\n                    }else{\n                        envState++;\n                    }\n                    break;\n                default:\u002F\u002Frelease ended, or state is out of range.\n                    envState=0;\n            }\n        }\n};\nint Voice::count=0;\n\n#define POLYPHONY 12\nVoice testVoice [POLYPHONY];\nVoice * noteOwners [128]; \u002F\u002Fcontains which voice should be affected by a trigger off. there should be a better way.\n\nvoid midiMessageCallback(MidiChannelMessage message, void* arg){\n    \u002F\u002F Note that this is called in a different thread than the audio processing one.\n    static unsigned int useVoice=0;\n    if(arg != NULL){\n        rt_printf(\"Message from midi port %s \", (const char*) arg);\n    }\n    message.prettyPrint();\n    if(message.getType() == kmmNoteOn){\n        useVoice=0;\n        unsigned int secondChoice=0;\n        while(testVoice[useVoice].isTriggered || testVoice[useVoice].envState!=0){\n            if(!testVoice[useVoice].isTriggered){\n                \u002F\u002Fthis voice is in release stage, could be used as a last resort\n                secondChoice=useVoice;\n            }\n            useVoice++;\n            if(useVoice>=POLYPHONY) {\n                useVoice=secondChoice;\n                break;\n            }\n        }\n        \n        int noteNumber=message.getDataByte(0);\n        testVoice[useVoice].triggerNoteOn(noteNumber-69,message.getDataByte(1));\n        noteOwners[noteNumber]=& testVoice[useVoice];\n        \n        \n        \u002F\u002Fresonate!\n        for(unsigned int vy=0; vy \u003C POLYPHONY; vy++){\n            if(testVoice[vy].isTriggered)\n                testVoice[vy].simpathize(testVoice[useVoice]);\n        }\n        \n    }\n    if(message.getType() == kmmNoteOff){\n        int noteNumber=message.getDataByte(0);\n        noteOwners[noteNumber]->triggerNoteOff();\n        \u002F\u002FtestVoice[useVoice].triggerNoteOff();\n    }\n}\n\n\nMidi midi;\n\nconst char* gMidiPort0 = \"hw:1,0,0\";\n\nbool setup(BelaContext *context, void *userData){\n    midi.readFrom(gMidiPort0);\n    midi.writeTo(gMidiPort0);\n    midi.enableParser(true);\n    midi.setParserCallback(midiMessageCallback, (void*) gMidiPort0);\n    global::sampleRate = context->audioSampleRate;\n    global::secondsPerSample = 1.00\u002Fcontext->audioSampleRate;\n    synthEnvelope.set(1,10,0,0.7);\n    scope.setup(3, context->audioSampleRate);\n    \n    \n    fillSympathicResonanceTable(5000, 1.f\u002F1000);\n    \n    \n    return true;\n}\n\nenum {kVelocity, kNoteOn, kNoteNumber};\nvoid render(BelaContext *context, void *userData)\n{\n    for(unsigned int n = 0; n \u003C context->audioFrames; n++){\n        float value=0;\n        for(unsigned int vn=0; vn \u003C POLYPHONY; vn++){\n            value+=testVoice[vn].sample()\u002F(POLYPHONY\u002F2);\n            \n        }\n        \n        \n        for(unsigned int ch = 0; ch \u003C context->audioOutChannels; ++ch)\n            audioWrite(context, n, ch, value);\n    }\n}\n\nvoid cleanup(BelaContext *context, void *userData)\n{\n\n}\n",[44,857,858,863,868,873,878,883,887,891,896,900,904,908,913,917,921,925,930,935,940,945,950,955,960,965,969,973,977,981,985,989,993,997,1001,1005,1009,1013,1017,1021,1025,1029,1033,1037,1041,1045,1049,1053,1057,1061,1065,1069,1073,1077,1081,1085,1090,1094,1098,1102,1106,1110,1114,1118,1122,1126,1130,1134,1138,1142,1146,1150,1154,1158,1162,1166,1170,1174,1178,1182,1186,1190,1194,1198,1202,1206,1210,1214,1218,1222,1226,1230,1234,1238,1242,1246,1250,1254,1258,1262,1266,1270,1274,1278,1282,1286,1291,1295,1299,1303,1307,1311,1315,1319,1323,1327,1331,1335,1340,1345,1350,1355,1360,1364,1368,1372,1376,1380,1384,1388,1392,1397,1402,1407,1413,1418,1423,1428,1433,1438,1444,1449,1455,1460,1465,1470,1475,1480,1485,1491,1496,1501,1506,1511,1516,1521,1526,1531,1536,1541,1546,1551,1556,1561,1566,1571,1576,1581,1586,1592,1598,1604,1609,1615,1621,1627,1633,1639,1644,1650,1656,1662,1668,1674,1680,1686,1692,1697,1703,1709,1715,1721,1726,1731,1736,1742,1748,1754,1759,1764,1770,1776,1782,1788,1793,1798,1803,1809,1814,1820,1826,1831,1836,1841,1846,1852,1857,1863,1868,1874,1880,1886,1892,1898,1904,1910,1916,1922,1927,1932,1938,1943,1948,1954,1959,1964,1970,1976,1982,1988,1994,2000,2006,2011,2016,2021,2026,2032,2038,2043,2048,2053,2059,2064,2069],{"__ignoreMap":15},[47,859,860],{"class":49,"line":50},[47,861,862],{},"#include \u003CBela.h>\n",[47,864,865],{"class":49,"line":56},[47,866,867],{},"#include \u003CMidi.h>\n",[47,869,870],{"class":49,"line":62},[47,871,872],{},"#include \u003Cstdlib.h>\n",[47,874,875],{"class":49,"line":68},[47,876,877],{},"#include \u003CScope.h>\n",[47,879,880],{"class":49,"line":74},[47,881,882],{},"#include \u003Ccmath>\n",[47,884,885],{"class":49,"line":80},[47,886,227],{"emptyLinePlaceholder":226},[47,888,889],{"class":49,"line":86},[47,890,227],{"emptyLinePlaceholder":226},[47,892,893],{"class":49,"line":92},[47,894,895],{},"Scope scope;\n",[47,897,898],{"class":49,"line":98},[47,899,227],{"emptyLinePlaceholder":226},[47,901,902],{"class":49,"line":104},[47,903,53],{},[47,905,906],{"class":49,"line":110},[47,907,59],{},[47,909,910],{"class":49,"line":116},[47,911,912],{},"    unsigned int controlInterval=10;\u002F\u002Fhow many samples it takes to udpate a control signal\n",[47,914,915],{"class":49,"line":122},[47,916,71],{},[47,918,919],{"class":49,"line":128},[47,920,77],{},[47,922,923],{"class":49,"line":134},[47,924,227],{"emptyLinePlaceholder":226},[47,926,927],{"class":49,"line":140},[47,928,929],{},"float sympathicResonanceTable[5000];\u002F\u002Fusing step of 1\u002F1000, hence getting to 5 times freq\n",[47,931,932],{"class":49,"line":145},[47,933,934],{},"\u002F\u002Fto make a lookup table of this function:https:\u002F\u002Fwww.desmos.com\u002Fcalculator\u002Fcfu9rxzx1o\n",[47,936,937],{"class":49,"line":151},[47,938,939],{},"\u002F\u002Fwhich can be used to calculate how much a wave sympathizes with another.\n",[47,941,942],{"class":49,"line":157},[47,943,944],{},"\u002F\u002Fformula needs improvement. cirrently only octaves resonate\n",[47,946,947],{"class":49,"line":163},[47,948,949],{},"void fillSympathicResonanceTable(int positions, float hertzPerStep){\n",[47,951,952],{"class":49,"line":169},[47,953,954],{},"    for(unsigned int step=0; step\u003Cpositions; step++){\n",[47,956,957],{"class":49,"line":175},[47,958,959],{},"        float x=step*hertzPerStep;\n",[47,961,962],{"class":49,"line":181},[47,963,964],{},"        sympathicResonanceTable[step]=(cosf(x*M_PI)\u002F(x))+(1\u002Fx);\n",[47,966,967],{"class":49,"line":187},[47,968,214],{},[47,970,971],{"class":49,"line":193},[47,972,77],{},[47,974,975],{"class":49,"line":199},[47,976,227],{"emptyLinePlaceholder":226},[47,978,979],{"class":49,"line":205},[47,980,83],{},[47,982,983],{"class":49,"line":211},[47,984,89],{},[47,986,987],{"class":49,"line":217},[47,988,95],{},[47,990,991],{"class":49,"line":223},[47,992,101],{},[47,994,995],{"class":49,"line":230},[47,996,107],{},[47,998,999],{"class":49,"line":236},[47,1000,113],{},[47,1002,1003],{"class":49,"line":241},[47,1004,119],{},[47,1006,1007],{"class":49,"line":247},[47,1008,125],{},[47,1010,1011],{"class":49,"line":253},[47,1012,131],{},[47,1014,1015],{"class":49,"line":259},[47,1016,137],{},[47,1018,1019],{"class":49,"line":265},[47,1020,113],{},[47,1022,1023],{"class":49,"line":271},[47,1024,148],{},[47,1026,1027],{"class":49,"line":277},[47,1028,154],{},[47,1030,1031],{"class":49,"line":283},[47,1032,160],{},[47,1034,1035],{"class":49,"line":289},[47,1036,166],{},[47,1038,1039],{"class":49,"line":295},[47,1040,172],{},[47,1042,1043],{"class":49,"line":301},[47,1044,178],{},[47,1046,1047],{"class":49,"line":307},[47,1048,184],{},[47,1050,1051],{"class":49,"line":313},[47,1052,190],{},[47,1054,1055],{"class":49,"line":319},[47,1056,196],{},[47,1058,1059],{"class":49,"line":325},[47,1060,202],{},[47,1062,1063],{"class":49,"line":331},[47,1064,208],{},[47,1066,1067],{"class":49,"line":337},[47,1068,214],{},[47,1070,1071],{"class":49,"line":343},[47,1072,220],{},[47,1074,1075],{"class":49,"line":349},[47,1076,227],{"emptyLinePlaceholder":226},[47,1078,1079],{"class":49,"line":355},[47,1080,233],{},[47,1082,1083],{"class":49,"line":361},[47,1084,227],{"emptyLinePlaceholder":226},[47,1086,1087],{"class":49,"line":367},[47,1088,1089],{},"\u002F\u002Fworks weirdly. only some voices sound.\n",[47,1091,1092],{"class":49,"line":373},[47,1093,244],{},[47,1095,1096],{"class":49,"line":379},[47,1097,250],{},[47,1099,1100],{"class":49,"line":385},[47,1101,256],{},[47,1103,1104],{"class":49,"line":390},[47,1105,262],{},[47,1107,1108],{"class":49,"line":396},[47,1109,268],{},[47,1111,1112],{"class":49,"line":402},[47,1113,274],{},[47,1115,1116],{"class":49,"line":408},[47,1117,280],{},[47,1119,1120],{"class":49,"line":414},[47,1121,286],{},[47,1123,1124],{"class":49,"line":420},[47,1125,292],{},[47,1127,1128],{"class":49,"line":426},[47,1129,298],{},[47,1131,1132],{"class":49,"line":432},[47,1133,304],{},[47,1135,1136],{"class":49,"line":438},[47,1137,310],{},[47,1139,1140],{"class":49,"line":443},[47,1141,316],{},[47,1143,1144],{"class":49,"line":449},[47,1145,322],{},[47,1147,1148],{"class":49,"line":455},[47,1149,328],{},[47,1151,1152],{"class":49,"line":460},[47,1153,334],{},[47,1155,1156],{"class":49,"line":466},[47,1157,340],{},[47,1159,1160],{"class":49,"line":472},[47,1161,346],{},[47,1163,1164],{"class":49,"line":477},[47,1165,352],{},[47,1167,1168],{"class":49,"line":483},[47,1169,358],{},[47,1171,1172],{"class":49,"line":489},[47,1173,364],{},[47,1175,1176],{"class":49,"line":495},[47,1177,370],{},[47,1179,1180],{"class":49,"line":501},[47,1181,376],{},[47,1183,1184],{"class":49,"line":507},[47,1185,382],{},[47,1187,1188],{"class":49,"line":513},[47,1189,322],{},[47,1191,1192],{"class":49,"line":519},[47,1193,393],{},[47,1195,1196],{"class":49,"line":524},[47,1197,399],{},[47,1199,1200],{"class":49,"line":530},[47,1201,405],{},[47,1203,1204],{"class":49,"line":536},[47,1205,411],{},[47,1207,1208],{"class":49,"line":542},[47,1209,417],{},[47,1211,1212],{"class":49,"line":548},[47,1213,423],{},[47,1215,1216],{"class":49,"line":554},[47,1217,429],{},[47,1219,1220],{"class":49,"line":559},[47,1221,435],{},[47,1223,1224],{"class":49,"line":565},[47,1225,382],{},[47,1227,1228],{"class":49,"line":571},[47,1229,322],{},[47,1231,1232],{"class":49,"line":577},[47,1233,446],{},[47,1235,1236],{"class":49,"line":583},[47,1237,452],{},[47,1239,1240],{"class":49,"line":588},[47,1241,382],{},[47,1243,1244],{"class":49,"line":593},[47,1245,322],{},[47,1247,1248],{"class":49,"line":599},[47,1249,463],{},[47,1251,1252],{"class":49,"line":605},[47,1253,469],{},[47,1255,1256],{"class":49,"line":611},[47,1257,480],{},[47,1259,1260],{"class":49,"line":617},[47,1261,486],{},[47,1263,1264],{"class":49,"line":623},[47,1265,492],{},[47,1267,1268],{"class":49,"line":629},[47,1269,498],{},[47,1271,1272],{"class":49,"line":635},[47,1273,504],{},[47,1275,1276],{"class":49,"line":641},[47,1277,510],{},[47,1279,1280],{"class":49,"line":647},[47,1281,516],{},[47,1283,1284],{"class":49,"line":653},[47,1285,376],{},[47,1287,1288],{"class":49,"line":659},[47,1289,1290],{},"            float result=envLevel * sinf(currentPhase) ;\n",[47,1292,1293],{"class":49,"line":664},[47,1294,533],{},[47,1296,1297],{"class":49,"line":670},[47,1298,539],{},[47,1300,1301],{"class":49,"line":676},[47,1302,545],{},[47,1304,1305],{"class":49,"line":682},[47,1306,551],{},[47,1308,1309],{"class":49,"line":687},[47,1310,376],{},[47,1312,1313],{"class":49,"line":692},[47,1314,562],{},[47,1316,1317],{"class":49,"line":697},[47,1318,568],{},[47,1320,1321],{"class":49,"line":702},[47,1322,574],{},[47,1324,1325],{"class":49,"line":708},[47,1326,580],{},[47,1328,1329],{"class":49,"line":714},[47,1330,382],{},[47,1332,1333],{"class":49,"line":720},[47,1334,322],{},[47,1336,1337],{"class":49,"line":725},[47,1338,1339],{},"        void simpathize(Voice & otherVoice){\n",[47,1341,1342],{"class":49,"line":730},[47,1343,1344],{},"            float ratio=currentPhaseIncrement\u002FotherVoice.currentPhaseIncrement;\n",[47,1346,1347],{"class":49,"line":735},[47,1348,1349],{},"            ratio*=1000;\u002F\u002Fmapping constant, has to be the inverse to hertzPerStep when map was built.\n",[47,1351,1352],{"class":49,"line":740},[47,1353,1354],{},"            envLevel+=sympathicResonanceTable[(int)floor(ratio)]\u002F32;\n",[47,1356,1357],{"class":49,"line":746},[47,1358,1359],{},"            if(envLevel>1.2)envLevel=1.2;\u002F\u002Fprevent crazy resonance\n",[47,1361,1362],{"class":49,"line":752},[47,1363,382],{},[47,1365,1366],{"class":49,"line":758},[47,1367,322],{},[47,1369,1370],{"class":49,"line":763},[47,1371,596],{},[47,1373,1374],{"class":49,"line":768},[47,1375,602],{},[47,1377,1378],{"class":49,"line":773},[47,1379,608],{},[47,1381,1382],{"class":49,"line":778},[47,1383,614],{},[47,1385,1386],{"class":49,"line":784},[47,1387,620],{},[47,1389,1390],{"class":49,"line":790},[47,1391,626],{},[47,1393,1394],{"class":49,"line":795},[47,1395,1396],{},"                    if(envLevel \u003C 1 * currentVelocity){\n",[47,1398,1399],{"class":49,"line":800},[47,1400,1401],{},"                        \u002F\u002F*currentVelocity compensates the fact that it'd take shorter to achieve 1*currentVelocity\n",[47,1403,1404],{"class":49,"line":805},[47,1405,1406],{},"                        \u002F\u002F+(currentVelocity*velocityToAttack) actually makes attacks faster when more velocity\n",[47,1408,1410],{"class":49,"line":1409},132,[47,1411,1412],{},"                        envLevel+=synthEnvelope.attack_delta*currentVelocity+(currentVelocity*velocityToAttack);\n",[47,1414,1416],{"class":49,"line":1415},133,[47,1417,644],{},[47,1419,1421],{"class":49,"line":1420},134,[47,1422,650],{},[47,1424,1426],{"class":49,"line":1425},135,[47,1427,656],{},[47,1429,1431],{"class":49,"line":1430},136,[47,1432,620],{},[47,1434,1436],{"class":49,"line":1435},137,[47,1437,667],{},[47,1439,1441],{"class":49,"line":1440},138,[47,1442,1443],{},"                    if(!isTriggered){\n",[47,1445,1447],{"class":49,"line":1446},139,[47,1448,650],{},[47,1450,1452],{"class":49,"line":1451},140,[47,1453,1454],{},"                    }else if(envLevel>synthEnvelope.sustain){\n",[47,1456,1458],{"class":49,"line":1457},141,[47,1459,679],{},[47,1461,1463],{"class":49,"line":1462},142,[47,1464,656],{},[47,1466,1468],{"class":49,"line":1467},143,[47,1469,620],{},[47,1471,1473],{"class":49,"line":1472},144,[47,1474,705],{},[47,1476,1478],{"class":49,"line":1477},145,[47,1479,673],{},[47,1481,1483],{"class":49,"line":1482},146,[47,1484,755],{},[47,1486,1488],{"class":49,"line":1487},147,[47,1489,1490],{},"                    }else if(isTriggered){\n",[47,1492,1494],{"class":49,"line":1493},148,[47,1495,717],{},[47,1497,1499],{"class":49,"line":1498},149,[47,1500,644],{},[47,1502,1504],{"class":49,"line":1503},150,[47,1505,650],{},[47,1507,1509],{"class":49,"line":1508},151,[47,1510,656],{},[47,1512,1514],{"class":49,"line":1513},152,[47,1515,620],{},[47,1517,1519],{"class":49,"line":1518},153,[47,1520,743],{},[47,1522,1524],{"class":49,"line":1523},154,[47,1525,749],{},[47,1527,1529],{"class":49,"line":1528},155,[47,1530,755],{},[47,1532,1534],{"class":49,"line":1533},156,[47,1535,644],{},[47,1537,1539],{"class":49,"line":1538},157,[47,1540,650],{},[47,1542,1544],{"class":49,"line":1543},158,[47,1545,656],{},[47,1547,1549],{"class":49,"line":1548},159,[47,1550,620],{},[47,1552,1554],{"class":49,"line":1553},160,[47,1555,781],{},[47,1557,1559],{"class":49,"line":1558},161,[47,1560,787],{},[47,1562,1564],{"class":49,"line":1563},162,[47,1565,516],{},[47,1567,1569],{"class":49,"line":1568},163,[47,1570,382],{},[47,1572,1574],{"class":49,"line":1573},164,[47,1575,220],{},[47,1577,1579],{"class":49,"line":1578},165,[47,1580,808],{},[47,1582,1584],{"class":49,"line":1583},166,[47,1585,227],{"emptyLinePlaceholder":226},[47,1587,1589],{"class":49,"line":1588},167,[47,1590,1591],{},"#define POLYPHONY 12\n",[47,1593,1595],{"class":49,"line":1594},168,[47,1596,1597],{},"Voice testVoice [POLYPHONY];\n",[47,1599,1601],{"class":49,"line":1600},169,[47,1602,1603],{},"Voice * noteOwners [128]; \u002F\u002Fcontains which voice should be affected by a trigger off. there should be a better way.\n",[47,1605,1607],{"class":49,"line":1606},170,[47,1608,227],{"emptyLinePlaceholder":226},[47,1610,1612],{"class":49,"line":1611},171,[47,1613,1614],{},"void midiMessageCallback(MidiChannelMessage message, void* arg){\n",[47,1616,1618],{"class":49,"line":1617},172,[47,1619,1620],{},"    \u002F\u002F Note that this is called in a different thread than the audio processing one.\n",[47,1622,1624],{"class":49,"line":1623},173,[47,1625,1626],{},"    static unsigned int useVoice=0;\n",[47,1628,1630],{"class":49,"line":1629},174,[47,1631,1632],{},"    if(arg != NULL){\n",[47,1634,1636],{"class":49,"line":1635},175,[47,1637,1638],{},"        rt_printf(\"Message from midi port %s \", (const char*) arg);\n",[47,1640,1642],{"class":49,"line":1641},176,[47,1643,214],{},[47,1645,1647],{"class":49,"line":1646},177,[47,1648,1649],{},"    message.prettyPrint();\n",[47,1651,1653],{"class":49,"line":1652},178,[47,1654,1655],{},"    if(message.getType() == kmmNoteOn){\n",[47,1657,1659],{"class":49,"line":1658},179,[47,1660,1661],{},"        useVoice=0;\n",[47,1663,1665],{"class":49,"line":1664},180,[47,1666,1667],{},"        unsigned int secondChoice=0;\n",[47,1669,1671],{"class":49,"line":1670},181,[47,1672,1673],{},"        while(testVoice[useVoice].isTriggered || testVoice[useVoice].envState!=0){\n",[47,1675,1677],{"class":49,"line":1676},182,[47,1678,1679],{},"            if(!testVoice[useVoice].isTriggered){\n",[47,1681,1683],{"class":49,"line":1682},183,[47,1684,1685],{},"                \u002F\u002Fthis voice is in release stage, could be used as a last resort\n",[47,1687,1689],{"class":49,"line":1688},184,[47,1690,1691],{},"                secondChoice=useVoice;\n",[47,1693,1695],{"class":49,"line":1694},185,[47,1696,516],{},[47,1698,1700],{"class":49,"line":1699},186,[47,1701,1702],{},"            useVoice++;\n",[47,1704,1706],{"class":49,"line":1705},187,[47,1707,1708],{},"            if(useVoice>=POLYPHONY) {\n",[47,1710,1712],{"class":49,"line":1711},188,[47,1713,1714],{},"                useVoice=secondChoice;\n",[47,1716,1718],{"class":49,"line":1717},189,[47,1719,1720],{},"                break;\n",[47,1722,1724],{"class":49,"line":1723},190,[47,1725,516],{},[47,1727,1729],{"class":49,"line":1728},191,[47,1730,382],{},[47,1732,1734],{"class":49,"line":1733},192,[47,1735,322],{},[47,1737,1739],{"class":49,"line":1738},193,[47,1740,1741],{},"        int noteNumber=message.getDataByte(0);\n",[47,1743,1745],{"class":49,"line":1744},194,[47,1746,1747],{},"        testVoice[useVoice].triggerNoteOn(noteNumber-69,message.getDataByte(1));\n",[47,1749,1751],{"class":49,"line":1750},195,[47,1752,1753],{},"        noteOwners[noteNumber]=& testVoice[useVoice];\n",[47,1755,1757],{"class":49,"line":1756},196,[47,1758,322],{},[47,1760,1762],{"class":49,"line":1761},197,[47,1763,322],{},[47,1765,1767],{"class":49,"line":1766},198,[47,1768,1769],{},"        \u002F\u002Fresonate!\n",[47,1771,1773],{"class":49,"line":1772},199,[47,1774,1775],{},"        for(unsigned int vy=0; vy \u003C POLYPHONY; vy++){\n",[47,1777,1779],{"class":49,"line":1778},200,[47,1780,1781],{},"            if(testVoice[vy].isTriggered)\n",[47,1783,1785],{"class":49,"line":1784},201,[47,1786,1787],{},"                testVoice[vy].simpathize(testVoice[useVoice]);\n",[47,1789,1791],{"class":49,"line":1790},202,[47,1792,382],{},[47,1794,1796],{"class":49,"line":1795},203,[47,1797,322],{},[47,1799,1801],{"class":49,"line":1800},204,[47,1802,214],{},[47,1804,1806],{"class":49,"line":1805},205,[47,1807,1808],{},"    if(message.getType() == kmmNoteOff){\n",[47,1810,1812],{"class":49,"line":1811},206,[47,1813,1741],{},[47,1815,1817],{"class":49,"line":1816},207,[47,1818,1819],{},"        noteOwners[noteNumber]->triggerNoteOff();\n",[47,1821,1823],{"class":49,"line":1822},208,[47,1824,1825],{},"        \u002F\u002FtestVoice[useVoice].triggerNoteOff();\n",[47,1827,1829],{"class":49,"line":1828},209,[47,1830,214],{},[47,1832,1834],{"class":49,"line":1833},210,[47,1835,77],{},[47,1837,1839],{"class":49,"line":1838},211,[47,1840,227],{"emptyLinePlaceholder":226},[47,1842,1844],{"class":49,"line":1843},212,[47,1845,227],{"emptyLinePlaceholder":226},[47,1847,1849],{"class":49,"line":1848},213,[47,1850,1851],{},"Midi midi;\n",[47,1853,1855],{"class":49,"line":1854},214,[47,1856,227],{"emptyLinePlaceholder":226},[47,1858,1860],{"class":49,"line":1859},215,[47,1861,1862],{},"const char* gMidiPort0 = \"hw:1,0,0\";\n",[47,1864,1866],{"class":49,"line":1865},216,[47,1867,227],{"emptyLinePlaceholder":226},[47,1869,1871],{"class":49,"line":1870},217,[47,1872,1873],{},"bool setup(BelaContext *context, void *userData){\n",[47,1875,1877],{"class":49,"line":1876},218,[47,1878,1879],{},"    midi.readFrom(gMidiPort0);\n",[47,1881,1883],{"class":49,"line":1882},219,[47,1884,1885],{},"    midi.writeTo(gMidiPort0);\n",[47,1887,1889],{"class":49,"line":1888},220,[47,1890,1891],{},"    midi.enableParser(true);\n",[47,1893,1895],{"class":49,"line":1894},221,[47,1896,1897],{},"    midi.setParserCallback(midiMessageCallback, (void*) gMidiPort0);\n",[47,1899,1901],{"class":49,"line":1900},222,[47,1902,1903],{},"    global::sampleRate = context->audioSampleRate;\n",[47,1905,1907],{"class":49,"line":1906},223,[47,1908,1909],{},"    global::secondsPerSample = 1.00\u002Fcontext->audioSampleRate;\n",[47,1911,1913],{"class":49,"line":1912},224,[47,1914,1915],{},"    synthEnvelope.set(1,10,0,0.7);\n",[47,1917,1919],{"class":49,"line":1918},225,[47,1920,1921],{},"    scope.setup(3, context->audioSampleRate);\n",[47,1923,1925],{"class":49,"line":1924},226,[47,1926,113],{},[47,1928,1930],{"class":49,"line":1929},227,[47,1931,113],{},[47,1933,1935],{"class":49,"line":1934},228,[47,1936,1937],{},"    fillSympathicResonanceTable(5000, 1.f\u002F1000);\n",[47,1939,1941],{"class":49,"line":1940},229,[47,1942,113],{},[47,1944,1946],{"class":49,"line":1945},230,[47,1947,113],{},[47,1949,1951],{"class":49,"line":1950},231,[47,1952,1953],{},"    return true;\n",[47,1955,1957],{"class":49,"line":1956},232,[47,1958,77],{},[47,1960,1962],{"class":49,"line":1961},233,[47,1963,227],{"emptyLinePlaceholder":226},[47,1965,1967],{"class":49,"line":1966},234,[47,1968,1969],{},"enum {kVelocity, kNoteOn, kNoteNumber};\n",[47,1971,1973],{"class":49,"line":1972},235,[47,1974,1975],{},"void render(BelaContext *context, void *userData)\n",[47,1977,1979],{"class":49,"line":1978},236,[47,1980,1981],{},"{\n",[47,1983,1985],{"class":49,"line":1984},237,[47,1986,1987],{},"    for(unsigned int n = 0; n \u003C context->audioFrames; n++){\n",[47,1989,1991],{"class":49,"line":1990},238,[47,1992,1993],{},"        float value=0;\n",[47,1995,1997],{"class":49,"line":1996},239,[47,1998,1999],{},"        for(unsigned int vn=0; vn \u003C POLYPHONY; vn++){\n",[47,2001,2003],{"class":49,"line":2002},240,[47,2004,2005],{},"            value+=testVoice[vn].sample()\u002F(POLYPHONY\u002F2);\n",[47,2007,2009],{"class":49,"line":2008},241,[47,2010,376],{},[47,2012,2014],{"class":49,"line":2013},242,[47,2015,382],{},[47,2017,2019],{"class":49,"line":2018},243,[47,2020,322],{},[47,2022,2024],{"class":49,"line":2023},244,[47,2025,322],{},[47,2027,2029],{"class":49,"line":2028},245,[47,2030,2031],{},"        for(unsigned int ch = 0; ch \u003C context->audioOutChannels; ++ch)\n",[47,2033,2035],{"class":49,"line":2034},246,[47,2036,2037],{},"            audioWrite(context, n, ch, value);\n",[47,2039,2041],{"class":49,"line":2040},247,[47,2042,214],{},[47,2044,2046],{"class":49,"line":2045},248,[47,2047,77],{},[47,2049,2051],{"class":49,"line":2050},249,[47,2052,227],{"emptyLinePlaceholder":226},[47,2054,2056],{"class":49,"line":2055},250,[47,2057,2058],{},"void cleanup(BelaContext *context, void *userData)\n",[47,2060,2062],{"class":49,"line":2061},251,[47,2063,1981],{},[47,2065,2067],{"class":49,"line":2066},252,[47,2068,227],{"emptyLinePlaceholder":226},[47,2070,2072],{"class":49,"line":2071},253,[47,2073,77],{},[10,2075,2076],{},"As a side note, the ADSR in this version is slightly different than in the last version, because the decay needed to be cancelled in case the note was released. This allows to use zero sustain and a very long decay, in the same way as it happens on a piano. This helps the model to work more accurately; applying the decay delta value to make the resonating waves fade out.",[10,2078,2079],{},"The code sometimes produces a segfault. I suspect this is because there are no thread-safety measures taken, although the MIDI event callbacks happen in another thread, as indicated. Implementing this is also simple, it only takes to isolate the event into a single variable that is thread protected, and delegating the noteOn() call to the main thread.",[10,2081,2082],{},"Also note, in the sympathize function, that I don't convert phase delta to actual frequency because we need to obtain a relation between the two. I considered the delta to be interchangeable with frequency in this case. I did not either multiply these deltas by the sampling rate (to convert the sample time to real time) for the same reason; we need the relative relation between the two frequencies rather than an absolute frequency.",[10,2084,2085],{},"The resulting synthesizer, although not physically accurate is quite fun and inspiring to play with. In reality this is more to the fact of the sound of sine waves than because of the sympathetic resonance. The latter feature only adds value in the longer run, to provide a richer spectrum of expressiveness.",[2087,2088,2089],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":15,"searchDepth":56,"depth":56,"links":2091},[2092,2093,2094],{"id":32,"depth":56,"text":33},{"id":814,"depth":56,"text":815},{"id":843,"depth":56,"text":844},[2096],"blog","2019-06-26",null,"md","\u002Fimages\u002F2019\u002F06\u002FIMG_20190624_205922-scaled.jpg",{},"\u002Fposts\u002F2019-06-26-sympathetic-resonance-experimentation",{"title":5,"description":15},"posts\u002F2019-06-26-sympathetic-resonance-experimentation",[2106,2107,2108,2109],"tool_belaboard","tool_cpp","area_rogramming","process-notes",false,"hhjwCtmSxdOuNnglr56fYg2NM9t96ylE8HasAt-_6RI",1791578987281]