[{"data":1,"prerenderedAt":4873},["ShallowReactive",2],{"article_list_juce_":3},[4,389,665,3177],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"image":11,"publishDate":12,"tags":13,"excerpt":10,"body":17,"_type":380,"_id":381,"_source":382,"_file":383,"_stem":384,"_extension":385,"author":386},"/bporter/2020-10/reanimated","2020-10",false,"","Re-animated","Last year, I posted here about an animation control framework called 'Friz' that works within the JUCE Application Framework.","/bporter/2020-10/img/module.png","2020-10-01",[14,15,16],"juce","ui","c++",{"type":18,"children":19,"toc":373},"root",[20,48,53,79,145,180,185,192,197,202,210,215,243,248,254,277,343,349,362,367],{"type":21,"tag":22,"props":23,"children":24},"element","p",{},[25,28,37,39,46],{"type":26,"value":27},"text","Last year, I ",{"type":21,"tag":29,"props":30,"children":34},"a",{"href":31,"rel":32},"https://artandlogic.com/2019/09/friz-and-the-illusion-of-life/",[33],"nofollow",[35],{"type":26,"value":36},"posted here",{"type":26,"value":38}," about an animation control framework called 'Friz' that works within the ",{"type":21,"tag":29,"props":40,"children":43},{"href":41,"rel":42},"https://www.juce.com",[33],[44],{"type":26,"value":45},"JUCE Application Framework",{"type":26,"value":47},".",{"type":21,"tag":22,"props":49,"children":50},{},[51],{"type":26,"value":52},"As I said in that post:",{"type":21,"tag":54,"props":55,"children":56},"blockquote",{},[57],{"type":21,"tag":22,"props":58,"children":59},{},[60,62,68,70,77],{"type":26,"value":61},"I sat down to sketch some things out and ended up making a small framework for generating dynamic data that can be used to animate pretty much any aspect of the user interface of a program written using the ",{"type":21,"tag":29,"props":63,"children":65},{"href":41,"rel":64},[33],[66],{"type":26,"value":67},"JUCE application framework",{"type":26,"value":69}," (which I've written about here ",{"type":21,"tag":29,"props":71,"children":74},{"href":72,"rel":73},"https://artandlogic.com/?s=juce",[33],[75],{"type":26,"value":76},"many times",{"type":26,"value":78}," before), and has the goals of being:",{"type":21,"tag":54,"props":80,"children":81},{},[82],{"type":21,"tag":83,"props":84,"children":85},"ul",{},[86,98,108,127],{"type":21,"tag":87,"props":88,"children":89},"li",{},[90,96],{"type":21,"tag":91,"props":92,"children":93},"strong",{},[94],{"type":26,"value":95},"Lightweight",{"type":26,"value":97},"—if there aren't any animations in progress, there's no runtime overhead.",{"type":21,"tag":87,"props":99,"children":100},{},[101,106],{"type":21,"tag":91,"props":102,"children":103},{},[104],{"type":26,"value":105},"Flexible",{"type":26,"value":107},"—it's easy to add new types of animation curves if you want to, or to chain multiple curves together into a more complex sequence.",{"type":21,"tag":87,"props":109,"children":110},{},[111,116,118,125],{"type":21,"tag":91,"props":112,"children":113},{},[114],{"type":26,"value":115},"Decoupled",{"type":26,"value":117},"—it doesn't need or want to know anything about your application. When your code creates an animation effect, you pass it a pair of ",{"type":21,"tag":119,"props":120,"children":122},"code",{"className":121},[],[123],{"type":26,"value":124},"std::function",{"type":26,"value":126}," objects; one to handle updates for each frame, and another one to handle the completion of the effect.",{"type":21,"tag":87,"props":128,"children":129},{},[130,135,137,144],{"type":21,"tag":91,"props":131,"children":132},{},[133],{"type":26,"value":134},"Modern",{"type":26,"value":136},"—written using current C++ techniques (defined for our purposes as C++11 and later). I spent some time away from C++ and have come back to find the language has undergone some seroius changes that require me to consciously update my habits, making me feel like the programmer version of SNL's ",{"type":21,"tag":29,"props":138,"children":141},{"href":139,"rel":140},"https://en.wikipedia.org/wiki/Unfrozen_Caveman_Lawyer",[33],[142],{"type":26,"value":143},"Unfrozen Caveman Lawyer",{"type":26,"value":47},{"type":21,"tag":54,"props":146,"children":147},{},[148],{"type":21,"tag":22,"props":149,"children":150},{},[151,153,160,162,169,171,178],{"type":26,"value":152},"As the code began to take shape, I decided to name it after the great Warner Bros. animator/director ",{"type":21,"tag":29,"props":154,"children":157},{"href":155,"rel":156},"https://en.wikipedia.org/wiki/Friz_Freleng",[33],[158],{"type":26,"value":159},"Friz Freling",{"type":26,"value":161},". I would like to have called it 'Chuck' after his colleague ",{"type":21,"tag":29,"props":163,"children":166},{"href":164,"rel":165},"https://en.wikipedia.org/wiki/Chuck_Jones",[33],[167],{"type":26,"value":168},"Chuck Jones",{"type":26,"value":170},", but \"",{"type":21,"tag":29,"props":172,"children":175},{"href":173,"rel":174},"https://chuck.cs.princeton.edu/",[33],[176],{"type":26,"value":177},"ChucK",{"type":26,"value":179},"\" already means something else in the electronic music software world.",{"type":21,"tag":22,"props":181,"children":182},{},[183],{"type":26,"value":184},"I found the time this past weekend to update the Friz code with two things that I've had on my list for a long time:",{"type":21,"tag":186,"props":187,"children":189},"h2",{"id":188},"juce-module",[190],{"type":26,"value":191},"JUCE Module",{"type":21,"tag":22,"props":193,"children":194},{},[195],{"type":26,"value":196},"The library is now packaged as a JUCE module, so it's straightforward to include in a JUCE project without needing to manually copy files around. In reality, I should have done this from day one (and now that I understand how trivial it is to work with JUCE modules, that's how I'll approach anything like this in the future).",{"type":21,"tag":22,"props":198,"children":199},{},[200],{"type":26,"value":201},"To use friz as a module, get it from the GitHub repo (see below) and then use the Projucer to add it to your project from that directory:",{"type":21,"tag":22,"props":203,"children":204},{},[205],{"type":21,"tag":206,"props":207,"children":209},"img",{"alt":208,"src":11},"projucer view",[],{"type":21,"tag":22,"props":211,"children":212},{},[213],{"type":26,"value":214},"Once you've done that, you can just add",{"type":21,"tag":216,"props":217,"children":221},"pre",{"className":218,"code":219,"language":220,"meta":8,"style":8},"language-cpp shiki shiki-themes github-light github-dark","#include \"friz.h\"\n","cpp",[222],{"type":21,"tag":119,"props":223,"children":224},{"__ignoreMap":8},[225],{"type":21,"tag":226,"props":227,"children":230},"span",{"class":228,"line":229},"line",1,[231,237],{"type":21,"tag":226,"props":232,"children":234},{"style":233},"--shiki-default:#D73A49;--shiki-dark:#F97583",[235],{"type":26,"value":236},"#include",{"type":21,"tag":226,"props":238,"children":240},{"style":239},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[241],{"type":26,"value":242}," \"friz.h\"\n",{"type":21,"tag":22,"props":244,"children":245},{},[246],{"type":26,"value":247},"...and start using it as described in my earlier post.",{"type":21,"tag":186,"props":249,"children":251},{"id":250},"new-parametric-easing-curves",[252],{"type":26,"value":253},"New 'parametric' easing curves",{"type":21,"tag":22,"props":255,"children":256},{},[257,259,266,268,275],{"type":26,"value":258},"Shortly after releasing the original version of this last year, I stumbled on an ",{"type":21,"tag":29,"props":260,"children":263},{"href":261,"rel":262},"https://easings.net",[33],[264],{"type":26,"value":265},"excellent site",{"type":26,"value":267}," that described itself as an \"Easing Curve Cheat Sheet\" that shows examples of a whole bunch of curves that are commonly found in the wild—libraries and frameworks like jQuery, Cinder, and Flutter all support a set of curves that all point back to functions defined by ",{"type":21,"tag":29,"props":269,"children":272},{"href":270,"rel":271},"http://robertpenner.com/easing/",[33],[273],{"type":26,"value":274},"Robert Penner",{"type":26,"value":276},", whose site you should check out.",{"type":21,"tag":22,"props":278,"children":279},{},[280,282,288,290,295,297,303,305,311,313,318,320,326,328,333,335,341],{"type":26,"value":281},"The new ",{"type":21,"tag":119,"props":283,"children":285},{"className":284},[],[286],{"type":26,"value":287},"Parametric",{"type":26,"value":289}," class may be created to implement any of those Penner curves, or you can use it to follow any curve that you design, by passing in a function (whether a ",{"type":21,"tag":119,"props":291,"children":293},{"className":292},[],[294],{"type":26,"value":124},{"type":26,"value":296},", lambda, or plain old C function) that accepts a single ",{"type":21,"tag":119,"props":298,"children":300},{"className":299},[],[301],{"type":26,"value":302},"float",{"type":26,"value":304}," parameter in the range ",{"type":21,"tag":119,"props":306,"children":308},{"className":307},[],[309],{"type":26,"value":310},"[0..1]",{"type":26,"value":312}," and returns a ",{"type":21,"tag":119,"props":314,"children":316},{"className":315},[],[317],{"type":26,"value":302},{"type":26,"value":319}," that is typically ",{"type":21,"tag":321,"props":322,"children":323},"em",{},[324],{"type":26,"value":325},"but not necessarily",{"type":26,"value":327}," also in the ",{"type":21,"tag":119,"props":329,"children":331},{"className":330},[],[332],{"type":26,"value":310},{"type":26,"value":334}," range. The animation framework will interpolate that output value between the start/end values used in your animation. Since these curves may have overshoot on either end (see for example, the ",{"type":21,"tag":119,"props":336,"children":338},{"className":337},[],[339],{"type":26,"value":340},"easeInOutElastic",{"type":26,"value":342}," curve, which has fairly complex under- and overshoot on both ends of the curve), the code that handles your animation must work correctly when passed values outside of your start/stop values (where only you can define what 'correctly' means other than 'don't crash').",{"type":21,"tag":186,"props":344,"children":346},{"id":345},"downloadlicenceetc",[347],{"type":26,"value":348},"Download/Licence/etc.",{"type":21,"tag":22,"props":350,"children":351},{},[352,354,360],{"type":26,"value":353},"You can get the library at ",{"type":21,"tag":29,"props":355,"children":358},{"href":356,"rel":357},"https://github.com/bgporter/animator",[33],[359],{"type":26,"value":356},{"type":26,"value":361},", and it's MIT licensed. Open source, closed source, it's all fine by me as long as you only use it for good and not evil.",{"type":21,"tag":22,"props":363,"children":364},{},[365],{"type":26,"value":366},"Go make cool stuff.",{"type":21,"tag":368,"props":369,"children":370},"style",{},[371],{"type":26,"value":372},"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);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":8,"searchDepth":374,"depth":374,"links":375},3,[376,378,379],{"id":188,"depth":377,"text":191},2,{"id":250,"depth":377,"text":253},{"id":345,"depth":377,"text":348},"markdown","content:bporter:2020-10:reanimated.md","content","bporter/2020-10/reanimated.md","bporter/2020-10/reanimated","md",{"user":387,"name":388},"bporter","Brett Porter",{"_path":390,"_dir":391,"_draft":7,"_partial":7,"_locale":8,"title":392,"description":393,"excerpt":393,"image":394,"publishDate":395,"tags":396,"body":400,"_type":380,"_id":659,"_source":382,"_file":660,"_stem":661,"_extension":385,"author":662},"/asherbrooke/2020-4/watchwah","2020-4","Bringing an Idea to Life: WatchWah Proof of Concept","\"Wouldn't it be cool if...\"","/asherbrooke/2020-4/img/Guitar_and_Watch.jpeg","2020-04-01",[397,398,399,14],"ios","apple","watch",{"type":18,"children":401,"toc":650},[402,409,414,420,425,430,457,462,468,473,478,483,490,504,516,521,527,532,538,543,557,562,567,572,577,582,588,601,606,620,642],{"type":21,"tag":22,"props":403,"children":404},{},[405],{"type":21,"tag":321,"props":406,"children":407},{},[408],{"type":26,"value":393},{"type":21,"tag":22,"props":410,"children":411},{},[412],{"type":26,"value":413},"We've all had that thought about something. How do you get from there to a product you can use and share with others? When I found myself playing with an idea that seemed exciting and new, I decided to capture some notes on the process, from the perspective of an Engineering Manager at a company that's all about implementing people's creative visions.",{"type":21,"tag":186,"props":415,"children":417},{"id":416},"the-idea",[418],{"type":26,"value":419},"The Idea",{"type":21,"tag":22,"props":421,"children":422},{},[423],{"type":26,"value":424},"In addition to working as Art+Logic's Director of Engineering I've been a performing musician for much of my life. A year ago I bought an Apple Watch and it occurred to me that this small computer, packed with sensors monitoring all sorts of interesting things, could be used to enhance some element of musical expression.",{"type":21,"tag":22,"props":426,"children":427},{},[428],{"type":26,"value":429},"As a professional software developer I've learned that a good idea includes these qualities:",{"type":21,"tag":83,"props":431,"children":432},{},[433,438,443],{"type":21,"tag":87,"props":434,"children":435},{},[436],{"type":26,"value":437},"It needs to spark your passion, or at least get you curious. If it's something you're personally neutral about, but think might make you money, it's much harder to stay with it over the long haul. My idea was fairly simple, and I felt curious enough to spend some time on it.",{"type":21,"tag":87,"props":439,"children":440},{},[441],{"type":26,"value":442},"It's framed generally enough to be resilient as challenges arise. I've seen clients cling to specific details of their projects, at the expense of the product, when a looser conception of their goals might have allowed them to pivot more gracefully. In my case, \"Can I use my watch to enhance my musical performance\" seemed sufficiently general.",{"type":21,"tag":87,"props":444,"children":445},{},[446,448,455],{"type":26,"value":447},"It's been researched enough to know what possible challenges or competitors exist. I found ",{"type":21,"tag":29,"props":449,"children":452},{"href":450,"rel":451},"https://www.synthanatomy.com/2019/02/midiwrist-apple-watch-midi-controller.html",[33],[453],{"type":26,"value":454},"an existing system",{"type":26,"value":456}," that allowed the user to send messages from the Apple Watch to audio applications, but it didn't use the sensors. I was pleased that there wasn't anything quite like my idea that was already available, but also wondered if it might be unexpectedly difficult to implement, since nobody seemed to have done this before.",{"type":21,"tag":22,"props":458,"children":459},{},[460],{"type":26,"value":461},"Interestingly, I'd say \"the idea is possible\" is not always an important question to ask. If it's not possible now it may be in the future, and indeed it's hard to say whether or not something can be done until you try to do it.",{"type":21,"tag":186,"props":463,"children":465},{"id":464},"development",[466],{"type":26,"value":467},"Development",{"type":21,"tag":22,"props":469,"children":470},{},[471],{"type":26,"value":472},"I love the idea of iterative development, and it's something we do a lot of at Art+Logic. With a new and unproven idea it's extremely valuable; how quickly (and cheaply) can you learn if your idea will work, and how can it be refined, step-wise, through experimentation?",{"type":21,"tag":22,"props":474,"children":475},{},[476],{"type":26,"value":477},"My idea seemed to have a few parts: getting sensor data from the watch, bringing that data to a guitar effect to control it's behavior, and then the effect itself. I had a vision of using my watch to control a physical \"stomp-box\" style guitar effects pedal, so I wouldn't need to be tethered to a computer, but to test and refine the basic, big-picture idea that seemed excessive, so I opted for something simpler.",{"type":21,"tag":22,"props":479,"children":480},{},[481],{"type":26,"value":482},"Not only is a fast proof-of-concept a more reasonable business proposition, but it can be hard to sustain the passion for an untested idea over the months it takes to build out a complete solution. Iterative development allows for a whole bunch of successes along the way, which keeps excitement and engagement high. Failures happen too, admittedly, but they're usually cheaper and less disappointing than seeing a lengthy project abandoned. Spoiler alert - there are no major failures in this article!",{"type":21,"tag":484,"props":485,"children":487},"h3",{"id":486},"the-watch",[488],{"type":26,"value":489},"The Watch",{"type":21,"tag":22,"props":491,"children":492},{},[493,495,502],{"type":26,"value":494},"A quick search turned up ",{"type":21,"tag":29,"props":496,"children":499},{"href":497,"rel":498},"http://sensorlog.berndthomas.net",[33],[500],{"type":26,"value":501},"SensorLog",{"type":26,"value":503}," for the iPhone and Apple Watch. It's an app that can write sensor data to file, or stream it via a network socket. I even approached testing out this app iteratively - I first logged my data to file to see what the sensor data looked like, so I could make a guess about which sensors might meed my needs, before investing time in reading a live stream of data.",{"type":21,"tag":22,"props":505,"children":506},{},[507,509,514],{"type":26,"value":508},"I had an initial misstep as I learned how to use the app. I was using my iPhone to log what I believed to be watch sensor data. I wiggled my wrist around to generate interesting watch information for the log, but the app was actually monitoring the sensors on the ",{"type":21,"tag":321,"props":510,"children":511},{},[512],{"type":26,"value":513},"phone",{"type":26,"value":515},"! I spent an hour trying to convince myself that I saw meaningful patterns in the accelerometer and other sensor data, when it was all being created by a device sitting motionless upon my table. After discovering my mistake and switching the system over to monitoring the watch I was delighted to find dramatically varying data that clearly correlated with my previous movements. I graphed data in a spreadsheet program from a bunch of different possible sensors, and saw lots of possibilities.",{"type":21,"tag":22,"props":517,"children":518},{},[519],{"type":26,"value":520},"Documenting the process is really valuable. When you're not completely sure what you're going for, having notes about the journey can provide additional options later on. I decided to start with accelerometer data, but added notes to a \"Future\" document, indicating that it might be worth trying the gyro sensor, or other CoreMotion data.",{"type":21,"tag":484,"props":522,"children":524},{"id":523},"the-effect",[525],{"type":26,"value":526},"The Effect",{"type":21,"tag":22,"props":528,"children":529},{},[530],{"type":26,"value":531},"It appeared that the natural movements of my wrist and body were indeed enough for the sensors to output interesting information. That had been my hope, since it meant that a player could add some nuance without having to learn a whole new approach to playing (as with a foot pedal). I decided that the first thing I would try would be controlling a wah effect. This is a movable band pass filter that adds a lot of character and expressiveness to a sound, but can also be relatively subtle and flexible as to when it's applied. In other words even a somewhat random movement of the filter might be kind of interesting. I decided to use my existing recording software (Logic Pro) to process the guitar sound. I set up a Logic project that featured Apple's Fuzz-Wah effect on my guitar channel. While my ultimate goal was to have a standalone effects processor, possibly based on an effect I would write myself, staying focused on testing the basic idea was the priority, and using Logic made this part of the test trivially easy.",{"type":21,"tag":484,"props":533,"children":535},{"id":534},"the-app",[536],{"type":26,"value":537},"The App",{"type":21,"tag":22,"props":539,"children":540},{},[541],{"type":26,"value":542},"The missing link between the watch and the wah effect was some piece of software that could listen for the incoming sensor data and convert it to a form that the wah could understand. MIDI seemed the obvious answer, since Logic has extensive capacity for remote control by MIDI, and standardized formats often provide the greatest flexibility. There was no obvious way to convert socket-based, comma-separated numbers to MIDI (aside from possibly using a fairly expensive 3rd-party product like Max/MSP), so I decided it was time to write some code.",{"type":21,"tag":22,"props":544,"children":545},{},[546,548,555],{"type":26,"value":547},"I've come to really appreciate the ",{"type":21,"tag":29,"props":549,"children":552},{"href":550,"rel":551},"https://juce.com/",[33],[553],{"type":26,"value":554},"JUCE C++ cross-platform development framework",{"type":26,"value":556},", particularly for audio and MIDI work. I consulted a colleague to learn what I might encounter as I set out to write my JUCE MIDI app. I was grateful for the support; it turns out that it's trivially easy to write a \"virtual MIDI port\" app with JUCE on macOS X, and extremely difficult on Windows. Knowing that in advance was great, as it confirmed my initial decision to work on macOS X. And again, as this was a proof-of-concept rather than a commercial product, one platform would be enough for now.",{"type":21,"tag":22,"props":558,"children":559},{},[560],{"type":26,"value":561},"I started with a skeleton MIDI / audio app, adding a slider to send MIDI Continuous Controller messages through a virtual MIDI port, so I could test the app without needing the watch. Within about an hour I was able to open my Logic project, launch my new app, and control the wah effect's \"pedal position\" parameter with the slider in my app.",{"type":21,"tag":22,"props":563,"children":564},{},[565],{"type":26,"value":566},"Adding JUCE's StreamingSocket class allowed me to monitor the port to which the watch would be sending data, wait for a connection, and start reading in values. This happens in a separate thread, and getting the thread performance to acceptable levels took a little fiddling.",{"type":21,"tag":22,"props":568,"children":569},{},[570],{"type":26,"value":571},"The most labor intensive portion came next; parsing the incoming sensor data and converting it to MIDI messages. SensorLog streams CSV values - comma-separated and then further delimited by line feed characters. I broke up the lines of data, and was able to convert the accelerometer readings to float values. Here too the learning was iterative: I was pleased to discover JUCE's NormalisableRange class, which appeared to let me map my accelerometer values to the 0-127 range MIDI requires, then startled when vigorous arm movements caused my program to crash!",{"type":21,"tag":22,"props":573,"children":574},{},[575],{"type":26,"value":576},"The problem was that I hadn't fully understood how the accelerometer values worked. I'd thought they might be ranging from -1.0 to 1.0, based on my cursory observations, but they're actually unbounded; 1.0 simply means acceleration equal to gravity, so quick movements can easily generate three or four times that value. I considered those movements to be outliers, and simply filtered them out. It was hard to imagine I'd be wildly swinging my fretting hand. If Pete Townsend wants this for his right arm, I can expand my range a bit! Accelerometer values also seem to depend upon the orientation within the gravitational field, so the watch at rest reports a non-zero value, and when upside down, a different value; roughly the inverse of the first.",{"type":21,"tag":22,"props":578,"children":579},{},[580],{"type":26,"value":581},"At this point I was ready to test - my iterative process had allowed me to check various components of the project along the way so I had some confidence that it would work pretty well.",{"type":21,"tag":186,"props":583,"children":585},{"id":584},"results",[586],{"type":26,"value":587},"Results",{"type":21,"tag":22,"props":589,"children":590},{},[591],{"type":21,"tag":29,"props":592,"children":595},{"href":593,"rel":594},"https://youtu.be/g5aaT5ytM3Q?si=mJVD1hVN3RHSJTWD",[33],[596],{"type":21,"tag":206,"props":597,"children":600},{"alt":598,"src":599},"Performance","/asherbrooke/2020-4/img/Performance.jpg",[],{"type":21,"tag":22,"props":602,"children":603},{},[604],{"type":26,"value":605},"The results can be seen in the linked videos, and are really exciting to me. After just a few evenings of effort I have a surprisingly musical tool. I see many possible next steps; trying different sensors (possibly simultaneously), using other effects, scaling the controller data in different ways, and simply jamming with it for a while to see how my relationship with the tool evolves. The next steps will also be iterative, and perhaps documented here. I can already see that one challenge will be balancing my urge to keep tweaking against the goal of actually building something that others can use.",{"type":21,"tag":22,"props":607,"children":608},{},[609,611,618],{"type":26,"value":610},"Here's a ",{"type":21,"tag":29,"props":612,"children":615},{"href":613,"rel":614},"https://youtu.be/jTOcGLFNTmo",[33],[616],{"type":26,"value":617},"quick video",{"type":26,"value":619}," of the sensor data changing the sound of a solo guitar.",{"type":21,"tag":22,"props":621,"children":622},{},[623,624,631,633,640],{"type":26,"value":610},{"type":21,"tag":29,"props":625,"children":628},{"href":626,"rel":627},"https://youtu.be/g5aaT5ytM3Q",[33],[629],{"type":26,"value":630},"longer performance demonstration",{"type":26,"value":632},", featuring a song I wrote and recorded ",{"type":21,"tag":29,"props":634,"children":637},{"href":635,"rel":636},"https://youtu.be/Jc-J-T5gQBQ",[33],[638],{"type":26,"value":639},"once before",{"type":26,"value":641}," without the wah effect.",{"type":21,"tag":22,"props":643,"children":644},{},[645],{"type":21,"tag":321,"props":646,"children":647},{},[648],{"type":26,"value":649},"Photos courtesy of the author.",{"title":8,"searchDepth":374,"depth":374,"links":651},[652,653,658],{"id":416,"depth":377,"text":419},{"id":464,"depth":377,"text":467,"children":654},[655,656,657],{"id":486,"depth":374,"text":489},{"id":523,"depth":374,"text":526},{"id":534,"depth":374,"text":537},{"id":584,"depth":377,"text":587},"content:asherbrooke:2020-4:watchwah.md","asherbrooke/2020-4/watchwah.md","asherbrooke/2020-4/watchwah",{"user":663,"name":664},"asherbrooke","Andrew Sherbrooke",{"_path":666,"_dir":667,"_draft":7,"_partial":7,"_locale":8,"title":668,"description":669,"tags":670,"excerpt":669,"image":672,"publishDate":673,"body":674,"_type":380,"_id":3171,"_source":382,"_file":3172,"_stem":3173,"_extension":385,"author":3174},"/jbagley/2019-4/makingspectrogramsinjuce","2019-4","Making Spectrograms in JUCE","Art+Logic's Incubator project has made a lot of progress. In a previous post I mentioned that Dr. Scott Hawley's technique to classify audio involved converting audio to an image and using a Convolution Neural Network (CNN) to classify the audio based on this image. That image is a spectrogram. I'm going to go into some detail about what we do to create one, and why to the best of my ability.",[14,16,671],"audio","/jbagley/2019-4/img/Fortissimo_Trumpet_Ensemble_Matrix_Swells_61.wav-2048x1700.png","2019-04-01",{"type":18,"children":675,"toc":3161},[676,690,696,717,723,735,748,769,788,793,988,994,1006,1707,1713,1718,1755,2008,2013,2279,2299,2304,2310,2324,2338,2391,2397,2417,2835,2841,2861,3109,3115,3120,3125,3157],{"type":21,"tag":22,"props":677,"children":678},{},[679,681,688],{"type":26,"value":680},"Art+Logic's Incubator project has made a lot of progress. In a ",{"type":21,"tag":29,"props":682,"children":685},{"href":683,"rel":684},"https://artandlogic.com/2018/12/incubator-kick-off/",[33],[686],{"type":26,"value":687},"previous post",{"type":26,"value":689}," I mentioned that Dr. Scott Hawley's technique to classify audio involved converting audio to an image and using a Convolution Neural Network (CNN) to classify the audio based on this image. That image is a spectrogram. I'm going to go into some detail about what we do to create one, and why to the best of my ability.",{"type":21,"tag":186,"props":691,"children":693},{"id":692},"basics",[694],{"type":26,"value":695},"Basics",{"type":21,"tag":22,"props":697,"children":698},{},[699,701,707,709,715],{"type":26,"value":700},"The spectrogram shows the energy within a set of frequency ranges. The vertical axis represents the frequencies, the horizontal axis represents time. When represented as a grayscale image, the brighter the pixel, the more energy in that frequency range (",{"type":21,"tag":119,"props":702,"children":704},{"className":703},[],[705],{"type":26,"value":706},"y",{"type":26,"value":708},") at that time range (",{"type":21,"tag":119,"props":710,"children":712},{"className":711},[],[713],{"type":26,"value":714},"x",{"type":26,"value":716},").",{"type":21,"tag":186,"props":718,"children":720},{"id":719},"the-base-the-fft",[721],{"type":26,"value":722},"The Base -- The FFT",{"type":21,"tag":22,"props":724,"children":725},{},[726,728,733],{"type":26,"value":727},"To create the spectrogram we have to extract that frequency data from the audio signal. This is done with a Fourier Transform algorithm, commonly implemented as the Fast Fourier Transform (FFT). The variation we need to use is the Short Time Fourier Transform (STFT) so we can see how the frequency information changes over the time. In general, a Fourier Transform takes a signal, for us a buffer full of discrete samples taken of a signal over time (i.e. audio), isolates contiguous frequency ranges into what are called bins, then measures the energy present in each bin over some length of time. More on ",{"type":21,"tag":226,"props":729,"children":730},{},[731],{"type":26,"value":732},"STFT",{"type":26,"value":734}," later.",{"type":21,"tag":22,"props":736,"children":737},{},[738,740,746],{"type":26,"value":739},"JUCE provides an implementation of an FFT that we can use to implement the STFT in its ",{"type":21,"tag":119,"props":741,"children":743},{"className":742},[],[744],{"type":26,"value":745},"dsp",{"type":26,"value":747}," module which it documents as not necessarily fast but good enough for many uses. So far it works for us. We may be lucky in that we've been developing on macOS, where JUCE uses Apple's Accelerate library.",{"type":21,"tag":22,"props":749,"children":750},{},[751,753,759,761,767],{"type":26,"value":752},"The FFT class is initialized with an ",{"type":21,"tag":119,"props":754,"children":756},{"className":755},[],[757],{"type":26,"value":758},"order",{"type":26,"value":760}," argument. The order is the exponent such that the number of samples processed by the FFT will be two raised to that number, called ",{"type":21,"tag":119,"props":762,"children":764},{"className":763},[],[765],{"type":26,"value":766},"fftSize",{"type":26,"value":768},". It wasn't immediately obvious to me what the size was when I started on this code. Is it the number of bins, or is it the number of samples the FFT will produce? For JUCE and Accelerate it's both.",{"type":21,"tag":22,"props":770,"children":771},{},[772,774,779,781,786],{"type":26,"value":773},"I thought I would be able to simply pass it the number of bins I wanted, and define the length separately. Using ",{"type":21,"tag":119,"props":775,"children":777},{"className":776},[],[778],{"type":26,"value":758},{"type":26,"value":780}," this way makes sense when you consider that the FFT works best when the number of samples it will process is a power of two. Additionally, the Inverse Fourier Transform can be performed, in which case the input is the bins and there would need to be ",{"type":21,"tag":119,"props":782,"children":784},{"className":783},[],[785],{"type":26,"value":766},{"type":26,"value":787}," bins.",{"type":21,"tag":22,"props":789,"children":790},{},[791],{"type":26,"value":792},"The total number of bins, in our case 2048, includes positive and negative frequency values. The two halves will be mirrored values, and we'll eventually only keep the positive half of them.",{"type":21,"tag":216,"props":794,"children":796},{"className":218,"code":795,"language":220,"meta":8,"style":8},"/// The class to make spectrograms from audio file data\nclass ASpectroMaker\n{\n   //...\n   dsp::FFT fFft;\n   //...\n};\n\nASpectroMaker::ASpectroMaker(int fftBins /*...*/)\n: fFft(std::log2(fftBins))\n// ...\n{\n}\n",[797],{"type":21,"tag":119,"props":798,"children":799},{"__ignoreMap":8},[800,809,823,832,841,855,863,872,882,925,962,971,979],{"type":21,"tag":226,"props":801,"children":802},{"class":228,"line":229},[803],{"type":21,"tag":226,"props":804,"children":806},{"style":805},"--shiki-default:#6A737D;--shiki-dark:#6A737D",[807],{"type":26,"value":808},"/// The class to make spectrograms from audio file data\n",{"type":21,"tag":226,"props":810,"children":811},{"class":228,"line":377},[812,817],{"type":21,"tag":226,"props":813,"children":814},{"style":233},[815],{"type":26,"value":816},"class",{"type":21,"tag":226,"props":818,"children":820},{"style":819},"--shiki-default:#6F42C1;--shiki-dark:#B392F0",[821],{"type":26,"value":822}," ASpectroMaker\n",{"type":21,"tag":226,"props":824,"children":825},{"class":228,"line":374},[826],{"type":21,"tag":226,"props":827,"children":829},{"style":828},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[830],{"type":26,"value":831},"{\n",{"type":21,"tag":226,"props":833,"children":835},{"class":228,"line":834},4,[836],{"type":21,"tag":226,"props":837,"children":838},{"style":805},[839],{"type":26,"value":840},"   //...\n",{"type":21,"tag":226,"props":842,"children":844},{"class":228,"line":843},5,[845,850],{"type":21,"tag":226,"props":846,"children":847},{"style":819},[848],{"type":26,"value":849},"   dsp",{"type":21,"tag":226,"props":851,"children":852},{"style":828},[853],{"type":26,"value":854},"::FFT fFft;\n",{"type":21,"tag":226,"props":856,"children":858},{"class":228,"line":857},6,[859],{"type":21,"tag":226,"props":860,"children":861},{"style":805},[862],{"type":26,"value":840},{"type":21,"tag":226,"props":864,"children":866},{"class":228,"line":865},7,[867],{"type":21,"tag":226,"props":868,"children":869},{"style":828},[870],{"type":26,"value":871},"};\n",{"type":21,"tag":226,"props":873,"children":875},{"class":228,"line":874},8,[876],{"type":21,"tag":226,"props":877,"children":879},{"emptyLinePlaceholder":878},true,[880],{"type":26,"value":881},"\n",{"type":21,"tag":226,"props":883,"children":885},{"class":228,"line":884},9,[886,891,896,900,905,910,915,920],{"type":21,"tag":226,"props":887,"children":888},{"style":819},[889],{"type":26,"value":890},"ASpectroMaker",{"type":21,"tag":226,"props":892,"children":893},{"style":828},[894],{"type":26,"value":895},"::",{"type":21,"tag":226,"props":897,"children":898},{"style":819},[899],{"type":26,"value":890},{"type":21,"tag":226,"props":901,"children":902},{"style":828},[903],{"type":26,"value":904},"(",{"type":21,"tag":226,"props":906,"children":907},{"style":233},[908],{"type":26,"value":909},"int",{"type":21,"tag":226,"props":911,"children":912},{"style":828},[913],{"type":26,"value":914}," fftBins",{"type":21,"tag":226,"props":916,"children":917},{"style":805},[918],{"type":26,"value":919}," /*...*/",{"type":21,"tag":226,"props":921,"children":922},{"style":828},[923],{"type":26,"value":924},")\n",{"type":21,"tag":226,"props":926,"children":928},{"class":228,"line":927},10,[929,934,939,943,948,952,957],{"type":21,"tag":226,"props":930,"children":931},{"style":828},[932],{"type":26,"value":933},": ",{"type":21,"tag":226,"props":935,"children":936},{"style":819},[937],{"type":26,"value":938},"fFft",{"type":21,"tag":226,"props":940,"children":941},{"style":828},[942],{"type":26,"value":904},{"type":21,"tag":226,"props":944,"children":945},{"style":819},[946],{"type":26,"value":947},"std",{"type":21,"tag":226,"props":949,"children":950},{"style":828},[951],{"type":26,"value":895},{"type":21,"tag":226,"props":953,"children":954},{"style":819},[955],{"type":26,"value":956},"log2",{"type":21,"tag":226,"props":958,"children":959},{"style":828},[960],{"type":26,"value":961},"(fftBins))\n",{"type":21,"tag":226,"props":963,"children":965},{"class":228,"line":964},11,[966],{"type":21,"tag":226,"props":967,"children":968},{"style":805},[969],{"type":26,"value":970},"// ...\n",{"type":21,"tag":226,"props":972,"children":974},{"class":228,"line":973},12,[975],{"type":21,"tag":226,"props":976,"children":977},{"style":828},[978],{"type":26,"value":831},{"type":21,"tag":226,"props":980,"children":982},{"class":228,"line":981},13,[983],{"type":21,"tag":226,"props":984,"children":985},{"style":828},[986],{"type":26,"value":987},"}\n",{"type":21,"tag":186,"props":989,"children":991},{"id":990},"the-iteration-the-stft",[992],{"type":26,"value":993},"The Iteration -- The STFT",{"type":21,"tag":22,"props":995,"children":996},{},[997,999,1004],{"type":26,"value":998},"In STFT The ",{"type":21,"tag":321,"props":1000,"children":1001},{},[1002],{"type":26,"value":1003},"Short Time",{"type":26,"value":1005}," part means we will take overlapping subsets of the signal and perform the transform on each chunk. So each column of the spectrogram represents the Fourier transform output from one of these chunks. The distance between chunks is called the hop size. The hop size should be less than the chunk size.",{"type":21,"tag":216,"props":1007,"children":1009},{"className":218,"code":1008,"language":220,"meta":8,"style":8},"using Spectrum = std::vector\u003Cfloat>; //!\u003C One audio channel of FFT data over time, really 2-dimensional\n\nASpectrogram ASpectroMaker::STFT(const AudioSampleBuffer& signal, size_t hop)\n{\n   const float* data = signal.getReadPointer(0);\n   const size_t dataCount = signal.getNumSamples();\n\n   // fftSize will be the number of bins we used to initialize the ASpectroMaker.\n   ptrdiff_t fftSize = fFft.getSize();\n\n   // See below for more about numHops\n   ptrdiff_t numHops = 1L + static_cast\u003Clong>((dataCount - fftSize) / hop);\n\n   //...\n\n   // Ignore the negative frequency information but leave the center bin.\n   size_t numRows = 1UL + (fftSize / 2UL);\n\n   // fFft works on the data in place, and needs twice as much space as the input size.\n   std::vector\u003Cfloat> fftBuffer(fftSize * 2UL);\n\n   // While data remains\n   {\n      std::memcpy(fftBuffer.data(), data, fftSize * sizeof(float));\n\n      // prepare fft data...\n\n      fFft.performFrequencyOnlyForwardTransform(fftBuffer.data());\n\n      // ...copy the frequency information from fftBuffer to the spectrum\n\n      // Next chunk\n      data += hop;\n   }\n   //...\n}\n",[1010],{"type":21,"tag":119,"props":1011,"children":1012},{"__ignoreMap":8},[1013,1064,1071,1136,1143,1191,1226,1233,1241,1272,1279,1287,1348,1355,1363,1371,1380,1433,1441,1450,1496,1504,1513,1522,1577,1585,1594,1602,1629,1637,1646,1654,1663,1682,1691,1699],{"type":21,"tag":226,"props":1014,"children":1015},{"class":228,"line":229},[1016,1021,1026,1031,1036,1040,1045,1050,1054,1059],{"type":21,"tag":226,"props":1017,"children":1018},{"style":233},[1019],{"type":26,"value":1020},"using",{"type":21,"tag":226,"props":1022,"children":1023},{"style":819},[1024],{"type":26,"value":1025}," Spectrum",{"type":21,"tag":226,"props":1027,"children":1028},{"style":233},[1029],{"type":26,"value":1030}," =",{"type":21,"tag":226,"props":1032,"children":1033},{"style":819},[1034],{"type":26,"value":1035}," std",{"type":21,"tag":226,"props":1037,"children":1038},{"style":828},[1039],{"type":26,"value":895},{"type":21,"tag":226,"props":1041,"children":1042},{"style":819},[1043],{"type":26,"value":1044},"vector",{"type":21,"tag":226,"props":1046,"children":1047},{"style":828},[1048],{"type":26,"value":1049},"\u003C",{"type":21,"tag":226,"props":1051,"children":1052},{"style":233},[1053],{"type":26,"value":302},{"type":21,"tag":226,"props":1055,"children":1056},{"style":828},[1057],{"type":26,"value":1058},">;",{"type":21,"tag":226,"props":1060,"children":1061},{"style":805},[1062],{"type":26,"value":1063}," //!\u003C One audio channel of FFT data over time, really 2-dimensional\n",{"type":21,"tag":226,"props":1065,"children":1066},{"class":228,"line":377},[1067],{"type":21,"tag":226,"props":1068,"children":1069},{"emptyLinePlaceholder":878},[1070],{"type":26,"value":881},{"type":21,"tag":226,"props":1072,"children":1073},{"class":228,"line":374},[1074,1079,1084,1088,1092,1096,1101,1106,1111,1117,1122,1127,1132],{"type":21,"tag":226,"props":1075,"children":1076},{"style":819},[1077],{"type":26,"value":1078},"ASpectrogram",{"type":21,"tag":226,"props":1080,"children":1081},{"style":819},[1082],{"type":26,"value":1083}," ASpectroMaker",{"type":21,"tag":226,"props":1085,"children":1086},{"style":828},[1087],{"type":26,"value":895},{"type":21,"tag":226,"props":1089,"children":1090},{"style":819},[1091],{"type":26,"value":732},{"type":21,"tag":226,"props":1093,"children":1094},{"style":828},[1095],{"type":26,"value":904},{"type":21,"tag":226,"props":1097,"children":1098},{"style":233},[1099],{"type":26,"value":1100},"const",{"type":21,"tag":226,"props":1102,"children":1103},{"style":819},[1104],{"type":26,"value":1105}," AudioSampleBuffer",{"type":21,"tag":226,"props":1107,"children":1108},{"style":233},[1109],{"type":26,"value":1110},"&",{"type":21,"tag":226,"props":1112,"children":1114},{"style":1113},"--shiki-default:#E36209;--shiki-dark:#FFAB70",[1115],{"type":26,"value":1116}," signal",{"type":21,"tag":226,"props":1118,"children":1119},{"style":828},[1120],{"type":26,"value":1121},", ",{"type":21,"tag":226,"props":1123,"children":1124},{"style":233},[1125],{"type":26,"value":1126},"size_t",{"type":21,"tag":226,"props":1128,"children":1129},{"style":1113},[1130],{"type":26,"value":1131}," hop",{"type":21,"tag":226,"props":1133,"children":1134},{"style":828},[1135],{"type":26,"value":924},{"type":21,"tag":226,"props":1137,"children":1138},{"class":228,"line":834},[1139],{"type":21,"tag":226,"props":1140,"children":1141},{"style":828},[1142],{"type":26,"value":831},{"type":21,"tag":226,"props":1144,"children":1145},{"class":228,"line":843},[1146,1151,1156,1161,1166,1171,1176,1180,1186],{"type":21,"tag":226,"props":1147,"children":1148},{"style":233},[1149],{"type":26,"value":1150},"   const",{"type":21,"tag":226,"props":1152,"children":1153},{"style":233},[1154],{"type":26,"value":1155}," float*",{"type":21,"tag":226,"props":1157,"children":1158},{"style":828},[1159],{"type":26,"value":1160}," data ",{"type":21,"tag":226,"props":1162,"children":1163},{"style":233},[1164],{"type":26,"value":1165},"=",{"type":21,"tag":226,"props":1167,"children":1168},{"style":828},[1169],{"type":26,"value":1170}," signal.",{"type":21,"tag":226,"props":1172,"children":1173},{"style":819},[1174],{"type":26,"value":1175},"getReadPointer",{"type":21,"tag":226,"props":1177,"children":1178},{"style":828},[1179],{"type":26,"value":904},{"type":21,"tag":226,"props":1181,"children":1183},{"style":1182},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[1184],{"type":26,"value":1185},"0",{"type":21,"tag":226,"props":1187,"children":1188},{"style":828},[1189],{"type":26,"value":1190},");\n",{"type":21,"tag":226,"props":1192,"children":1193},{"class":228,"line":857},[1194,1198,1203,1208,1212,1216,1221],{"type":21,"tag":226,"props":1195,"children":1196},{"style":233},[1197],{"type":26,"value":1150},{"type":21,"tag":226,"props":1199,"children":1200},{"style":233},[1201],{"type":26,"value":1202}," size_t",{"type":21,"tag":226,"props":1204,"children":1205},{"style":828},[1206],{"type":26,"value":1207}," dataCount ",{"type":21,"tag":226,"props":1209,"children":1210},{"style":233},[1211],{"type":26,"value":1165},{"type":21,"tag":226,"props":1213,"children":1214},{"style":828},[1215],{"type":26,"value":1170},{"type":21,"tag":226,"props":1217,"children":1218},{"style":819},[1219],{"type":26,"value":1220},"getNumSamples",{"type":21,"tag":226,"props":1222,"children":1223},{"style":828},[1224],{"type":26,"value":1225},"();\n",{"type":21,"tag":226,"props":1227,"children":1228},{"class":228,"line":865},[1229],{"type":21,"tag":226,"props":1230,"children":1231},{"emptyLinePlaceholder":878},[1232],{"type":26,"value":881},{"type":21,"tag":226,"props":1234,"children":1235},{"class":228,"line":874},[1236],{"type":21,"tag":226,"props":1237,"children":1238},{"style":805},[1239],{"type":26,"value":1240},"   // fftSize will be the number of bins we used to initialize the ASpectroMaker.\n",{"type":21,"tag":226,"props":1242,"children":1243},{"class":228,"line":884},[1244,1249,1254,1258,1263,1268],{"type":21,"tag":226,"props":1245,"children":1246},{"style":1182},[1247],{"type":26,"value":1248},"   ptrdiff_t",{"type":21,"tag":226,"props":1250,"children":1251},{"style":828},[1252],{"type":26,"value":1253}," fftSize ",{"type":21,"tag":226,"props":1255,"children":1256},{"style":233},[1257],{"type":26,"value":1165},{"type":21,"tag":226,"props":1259,"children":1260},{"style":828},[1261],{"type":26,"value":1262}," fFft.",{"type":21,"tag":226,"props":1264,"children":1265},{"style":819},[1266],{"type":26,"value":1267},"getSize",{"type":21,"tag":226,"props":1269,"children":1270},{"style":828},[1271],{"type":26,"value":1225},{"type":21,"tag":226,"props":1273,"children":1274},{"class":228,"line":927},[1275],{"type":21,"tag":226,"props":1276,"children":1277},{"emptyLinePlaceholder":878},[1278],{"type":26,"value":881},{"type":21,"tag":226,"props":1280,"children":1281},{"class":228,"line":964},[1282],{"type":21,"tag":226,"props":1283,"children":1284},{"style":805},[1285],{"type":26,"value":1286},"   // See below for more about numHops\n",{"type":21,"tag":226,"props":1288,"children":1289},{"class":228,"line":973},[1290,1294,1299,1303,1308,1313,1318,1323,1328,1333,1338,1343],{"type":21,"tag":226,"props":1291,"children":1292},{"style":1182},[1293],{"type":26,"value":1248},{"type":21,"tag":226,"props":1295,"children":1296},{"style":828},[1297],{"type":26,"value":1298}," numHops ",{"type":21,"tag":226,"props":1300,"children":1301},{"style":233},[1302],{"type":26,"value":1165},{"type":21,"tag":226,"props":1304,"children":1305},{"style":1182},[1306],{"type":26,"value":1307}," 1",{"type":21,"tag":226,"props":1309,"children":1310},{"style":233},[1311],{"type":26,"value":1312},"L",{"type":21,"tag":226,"props":1314,"children":1315},{"style":233},[1316],{"type":26,"value":1317}," +",{"type":21,"tag":226,"props":1319,"children":1320},{"style":233},[1321],{"type":26,"value":1322}," static_cast\u003Clong>",{"type":21,"tag":226,"props":1324,"children":1325},{"style":828},[1326],{"type":26,"value":1327},"((dataCount ",{"type":21,"tag":226,"props":1329,"children":1330},{"style":233},[1331],{"type":26,"value":1332},"-",{"type":21,"tag":226,"props":1334,"children":1335},{"style":828},[1336],{"type":26,"value":1337}," fftSize) ",{"type":21,"tag":226,"props":1339,"children":1340},{"style":233},[1341],{"type":26,"value":1342},"/",{"type":21,"tag":226,"props":1344,"children":1345},{"style":828},[1346],{"type":26,"value":1347}," hop);\n",{"type":21,"tag":226,"props":1349,"children":1350},{"class":228,"line":981},[1351],{"type":21,"tag":226,"props":1352,"children":1353},{"emptyLinePlaceholder":878},[1354],{"type":26,"value":881},{"type":21,"tag":226,"props":1356,"children":1358},{"class":228,"line":1357},14,[1359],{"type":21,"tag":226,"props":1360,"children":1361},{"style":805},[1362],{"type":26,"value":840},{"type":21,"tag":226,"props":1364,"children":1366},{"class":228,"line":1365},15,[1367],{"type":21,"tag":226,"props":1368,"children":1369},{"emptyLinePlaceholder":878},[1370],{"type":26,"value":881},{"type":21,"tag":226,"props":1372,"children":1374},{"class":228,"line":1373},16,[1375],{"type":21,"tag":226,"props":1376,"children":1377},{"style":805},[1378],{"type":26,"value":1379},"   // Ignore the negative frequency information but leave the center bin.\n",{"type":21,"tag":226,"props":1381,"children":1383},{"class":228,"line":1382},17,[1384,1389,1394,1398,1402,1407,1411,1416,1420,1425,1429],{"type":21,"tag":226,"props":1385,"children":1386},{"style":233},[1387],{"type":26,"value":1388},"   size_t",{"type":21,"tag":226,"props":1390,"children":1391},{"style":828},[1392],{"type":26,"value":1393}," numRows ",{"type":21,"tag":226,"props":1395,"children":1396},{"style":233},[1397],{"type":26,"value":1165},{"type":21,"tag":226,"props":1399,"children":1400},{"style":1182},[1401],{"type":26,"value":1307},{"type":21,"tag":226,"props":1403,"children":1404},{"style":233},[1405],{"type":26,"value":1406},"UL",{"type":21,"tag":226,"props":1408,"children":1409},{"style":233},[1410],{"type":26,"value":1317},{"type":21,"tag":226,"props":1412,"children":1413},{"style":828},[1414],{"type":26,"value":1415}," (fftSize ",{"type":21,"tag":226,"props":1417,"children":1418},{"style":233},[1419],{"type":26,"value":1342},{"type":21,"tag":226,"props":1421,"children":1422},{"style":1182},[1423],{"type":26,"value":1424}," 2",{"type":21,"tag":226,"props":1426,"children":1427},{"style":233},[1428],{"type":26,"value":1406},{"type":21,"tag":226,"props":1430,"children":1431},{"style":828},[1432],{"type":26,"value":1190},{"type":21,"tag":226,"props":1434,"children":1436},{"class":228,"line":1435},18,[1437],{"type":21,"tag":226,"props":1438,"children":1439},{"emptyLinePlaceholder":878},[1440],{"type":26,"value":881},{"type":21,"tag":226,"props":1442,"children":1444},{"class":228,"line":1443},19,[1445],{"type":21,"tag":226,"props":1446,"children":1447},{"style":805},[1448],{"type":26,"value":1449},"   // fFft works on the data in place, and needs twice as much space as the input size.\n",{"type":21,"tag":226,"props":1451,"children":1453},{"class":228,"line":1452},20,[1454,1459,1464,1469,1474,1479,1484,1488,1492],{"type":21,"tag":226,"props":1455,"children":1456},{"style":819},[1457],{"type":26,"value":1458},"   std",{"type":21,"tag":226,"props":1460,"children":1461},{"style":828},[1462],{"type":26,"value":1463},"::vector",{"type":21,"tag":226,"props":1465,"children":1466},{"style":233},[1467],{"type":26,"value":1468},"\u003Cfloat>",{"type":21,"tag":226,"props":1470,"children":1471},{"style":819},[1472],{"type":26,"value":1473}," fftBuffer",{"type":21,"tag":226,"props":1475,"children":1476},{"style":828},[1477],{"type":26,"value":1478},"(fftSize ",{"type":21,"tag":226,"props":1480,"children":1481},{"style":233},[1482],{"type":26,"value":1483},"*",{"type":21,"tag":226,"props":1485,"children":1486},{"style":1182},[1487],{"type":26,"value":1424},{"type":21,"tag":226,"props":1489,"children":1490},{"style":233},[1491],{"type":26,"value":1406},{"type":21,"tag":226,"props":1493,"children":1494},{"style":828},[1495],{"type":26,"value":1190},{"type":21,"tag":226,"props":1497,"children":1499},{"class":228,"line":1498},21,[1500],{"type":21,"tag":226,"props":1501,"children":1502},{"emptyLinePlaceholder":878},[1503],{"type":26,"value":881},{"type":21,"tag":226,"props":1505,"children":1507},{"class":228,"line":1506},22,[1508],{"type":21,"tag":226,"props":1509,"children":1510},{"style":805},[1511],{"type":26,"value":1512},"   // While data remains\n",{"type":21,"tag":226,"props":1514,"children":1516},{"class":228,"line":1515},23,[1517],{"type":21,"tag":226,"props":1518,"children":1519},{"style":828},[1520],{"type":26,"value":1521},"   {\n",{"type":21,"tag":226,"props":1523,"children":1525},{"class":228,"line":1524},24,[1526,1531,1535,1540,1545,1550,1555,1559,1564,1568,1572],{"type":21,"tag":226,"props":1527,"children":1528},{"style":819},[1529],{"type":26,"value":1530},"      std",{"type":21,"tag":226,"props":1532,"children":1533},{"style":828},[1534],{"type":26,"value":895},{"type":21,"tag":226,"props":1536,"children":1537},{"style":819},[1538],{"type":26,"value":1539},"memcpy",{"type":21,"tag":226,"props":1541,"children":1542},{"style":828},[1543],{"type":26,"value":1544},"(fftBuffer.",{"type":21,"tag":226,"props":1546,"children":1547},{"style":819},[1548],{"type":26,"value":1549},"data",{"type":21,"tag":226,"props":1551,"children":1552},{"style":828},[1553],{"type":26,"value":1554},"(), data, fftSize ",{"type":21,"tag":226,"props":1556,"children":1557},{"style":233},[1558],{"type":26,"value":1483},{"type":21,"tag":226,"props":1560,"children":1561},{"style":233},[1562],{"type":26,"value":1563}," sizeof",{"type":21,"tag":226,"props":1565,"children":1566},{"style":828},[1567],{"type":26,"value":904},{"type":21,"tag":226,"props":1569,"children":1570},{"style":233},[1571],{"type":26,"value":302},{"type":21,"tag":226,"props":1573,"children":1574},{"style":828},[1575],{"type":26,"value":1576},"));\n",{"type":21,"tag":226,"props":1578,"children":1580},{"class":228,"line":1579},25,[1581],{"type":21,"tag":226,"props":1582,"children":1583},{"emptyLinePlaceholder":878},[1584],{"type":26,"value":881},{"type":21,"tag":226,"props":1586,"children":1588},{"class":228,"line":1587},26,[1589],{"type":21,"tag":226,"props":1590,"children":1591},{"style":805},[1592],{"type":26,"value":1593},"      // prepare fft data...\n",{"type":21,"tag":226,"props":1595,"children":1597},{"class":228,"line":1596},27,[1598],{"type":21,"tag":226,"props":1599,"children":1600},{"emptyLinePlaceholder":878},[1601],{"type":26,"value":881},{"type":21,"tag":226,"props":1603,"children":1605},{"class":228,"line":1604},28,[1606,1611,1616,1620,1624],{"type":21,"tag":226,"props":1607,"children":1608},{"style":828},[1609],{"type":26,"value":1610},"      fFft.",{"type":21,"tag":226,"props":1612,"children":1613},{"style":819},[1614],{"type":26,"value":1615},"performFrequencyOnlyForwardTransform",{"type":21,"tag":226,"props":1617,"children":1618},{"style":828},[1619],{"type":26,"value":1544},{"type":21,"tag":226,"props":1621,"children":1622},{"style":819},[1623],{"type":26,"value":1549},{"type":21,"tag":226,"props":1625,"children":1626},{"style":828},[1627],{"type":26,"value":1628},"());\n",{"type":21,"tag":226,"props":1630,"children":1632},{"class":228,"line":1631},29,[1633],{"type":21,"tag":226,"props":1634,"children":1635},{"emptyLinePlaceholder":878},[1636],{"type":26,"value":881},{"type":21,"tag":226,"props":1638,"children":1640},{"class":228,"line":1639},30,[1641],{"type":21,"tag":226,"props":1642,"children":1643},{"style":805},[1644],{"type":26,"value":1645},"      // ...copy the frequency information from fftBuffer to the spectrum\n",{"type":21,"tag":226,"props":1647,"children":1649},{"class":228,"line":1648},31,[1650],{"type":21,"tag":226,"props":1651,"children":1652},{"emptyLinePlaceholder":878},[1653],{"type":26,"value":881},{"type":21,"tag":226,"props":1655,"children":1657},{"class":228,"line":1656},32,[1658],{"type":21,"tag":226,"props":1659,"children":1660},{"style":805},[1661],{"type":26,"value":1662},"      // Next chunk\n",{"type":21,"tag":226,"props":1664,"children":1666},{"class":228,"line":1665},33,[1667,1672,1677],{"type":21,"tag":226,"props":1668,"children":1669},{"style":828},[1670],{"type":26,"value":1671},"      data ",{"type":21,"tag":226,"props":1673,"children":1674},{"style":233},[1675],{"type":26,"value":1676},"+=",{"type":21,"tag":226,"props":1678,"children":1679},{"style":828},[1680],{"type":26,"value":1681}," hop;\n",{"type":21,"tag":226,"props":1683,"children":1685},{"class":228,"line":1684},34,[1686],{"type":21,"tag":226,"props":1687,"children":1688},{"style":828},[1689],{"type":26,"value":1690},"   }\n",{"type":21,"tag":226,"props":1692,"children":1694},{"class":228,"line":1693},35,[1695],{"type":21,"tag":226,"props":1696,"children":1697},{"style":805},[1698],{"type":26,"value":840},{"type":21,"tag":226,"props":1700,"children":1702},{"class":228,"line":1701},36,[1703],{"type":21,"tag":226,"props":1704,"children":1705},{"style":828},[1706],{"type":26,"value":987},{"type":21,"tag":186,"props":1708,"children":1710},{"id":1709},"windowing",[1711],{"type":26,"value":1712},"Windowing",{"type":21,"tag":22,"props":1714,"children":1715},{},[1716],{"type":26,"value":1717},"Because we are breaking apart our signal into chunks, the first and last values in each buffer will probably not be zero. When that's true, the FFT will produce noise in the output due to the discontinuity of the signal. Imagine a flat signal with just zero before and after the chunk. We need to smooth the begining and end to make sure they transition smoothly to the full amplitude of the signal. To do this we use a windowing function. There are many choices for windowing functions with various tradeoffs, and JUCE provides several options.",{"type":21,"tag":22,"props":1719,"children":1720},{},[1721,1723,1730,1732,1738,1740,1745,1747,1753],{"type":26,"value":1722},"The ",{"type":21,"tag":29,"props":1724,"children":1727},{"href":1725,"rel":1726},"https://en.wikipedia.org/wiki/Hann_function",[33],[1728],{"type":26,"value":1729},"Hann function",{"type":26,"value":1731}," provides good performance and meets our requirements (and it was what Dr. Hawley used). Notice that we initialize it with the ",{"type":21,"tag":119,"props":1733,"children":1735},{"className":1734},[],[1736],{"type":26,"value":1737},"fftSize + 1",{"type":26,"value":1739},", not the hop size. This threw me for a minute during development, but because we are windowing we have to overlap the chunks, moving by hop size, but processing ",{"type":21,"tag":119,"props":1741,"children":1743},{"className":1742},[],[1744],{"type":26,"value":766},{"type":26,"value":1746}," samples per chunk, where ",{"type":21,"tag":119,"props":1748,"children":1750},{"className":1749},[],[1751],{"type":26,"value":1752},"hop \u003C fftSize",{"type":26,"value":1754},". Looking at the Hann function it only allows the maximum signal amplitude near the center, the rest of the time it attenuates the signal. So to represent the original signal more accurately we overlap the chunks so that at some iteration the full amplitude will be well represented and in others it still contributes some to the calculations.",{"type":21,"tag":216,"props":1756,"children":1758},{"className":218,"code":1757,"language":220,"meta":8,"style":8},"/// The class to make spectrograms from audio file data\nclass ASpectroMaker\n{\n   //...\n   dsp::FFT fFft;\n   dsp::WindowingFunction\u003Cfloat> fWindow;\n   //...\n};\n\nASpectroMaker::ASpectroMaker(int fftBins /*...*/)\n: fFft(std::log2(fftBins))\n, fWindow(fFft.getSize() + 1, dsp::WindowingFunction\u003Cfloat>::hann, false)\n// ...\n{\n}\n",[1759],{"type":21,"tag":119,"props":1760,"children":1761},{"__ignoreMap":8},[1762,1769,1780,1787,1794,1805,1826,1833,1840,1847,1882,1913,1987,1994,2001],{"type":21,"tag":226,"props":1763,"children":1764},{"class":228,"line":229},[1765],{"type":21,"tag":226,"props":1766,"children":1767},{"style":805},[1768],{"type":26,"value":808},{"type":21,"tag":226,"props":1770,"children":1771},{"class":228,"line":377},[1772,1776],{"type":21,"tag":226,"props":1773,"children":1774},{"style":233},[1775],{"type":26,"value":816},{"type":21,"tag":226,"props":1777,"children":1778},{"style":819},[1779],{"type":26,"value":822},{"type":21,"tag":226,"props":1781,"children":1782},{"class":228,"line":374},[1783],{"type":21,"tag":226,"props":1784,"children":1785},{"style":828},[1786],{"type":26,"value":831},{"type":21,"tag":226,"props":1788,"children":1789},{"class":228,"line":834},[1790],{"type":21,"tag":226,"props":1791,"children":1792},{"style":805},[1793],{"type":26,"value":840},{"type":21,"tag":226,"props":1795,"children":1796},{"class":228,"line":843},[1797,1801],{"type":21,"tag":226,"props":1798,"children":1799},{"style":819},[1800],{"type":26,"value":849},{"type":21,"tag":226,"props":1802,"children":1803},{"style":828},[1804],{"type":26,"value":854},{"type":21,"tag":226,"props":1806,"children":1807},{"class":228,"line":857},[1808,1812,1817,1821],{"type":21,"tag":226,"props":1809,"children":1810},{"style":819},[1811],{"type":26,"value":849},{"type":21,"tag":226,"props":1813,"children":1814},{"style":828},[1815],{"type":26,"value":1816},"::WindowingFunction",{"type":21,"tag":226,"props":1818,"children":1819},{"style":233},[1820],{"type":26,"value":1468},{"type":21,"tag":226,"props":1822,"children":1823},{"style":828},[1824],{"type":26,"value":1825}," fWindow;\n",{"type":21,"tag":226,"props":1827,"children":1828},{"class":228,"line":865},[1829],{"type":21,"tag":226,"props":1830,"children":1831},{"style":805},[1832],{"type":26,"value":840},{"type":21,"tag":226,"props":1834,"children":1835},{"class":228,"line":874},[1836],{"type":21,"tag":226,"props":1837,"children":1838},{"style":828},[1839],{"type":26,"value":871},{"type":21,"tag":226,"props":1841,"children":1842},{"class":228,"line":884},[1843],{"type":21,"tag":226,"props":1844,"children":1845},{"emptyLinePlaceholder":878},[1846],{"type":26,"value":881},{"type":21,"tag":226,"props":1848,"children":1849},{"class":228,"line":927},[1850,1854,1858,1862,1866,1870,1874,1878],{"type":21,"tag":226,"props":1851,"children":1852},{"style":819},[1853],{"type":26,"value":890},{"type":21,"tag":226,"props":1855,"children":1856},{"style":828},[1857],{"type":26,"value":895},{"type":21,"tag":226,"props":1859,"children":1860},{"style":819},[1861],{"type":26,"value":890},{"type":21,"tag":226,"props":1863,"children":1864},{"style":828},[1865],{"type":26,"value":904},{"type":21,"tag":226,"props":1867,"children":1868},{"style":233},[1869],{"type":26,"value":909},{"type":21,"tag":226,"props":1871,"children":1872},{"style":828},[1873],{"type":26,"value":914},{"type":21,"tag":226,"props":1875,"children":1876},{"style":805},[1877],{"type":26,"value":919},{"type":21,"tag":226,"props":1879,"children":1880},{"style":828},[1881],{"type":26,"value":924},{"type":21,"tag":226,"props":1883,"children":1884},{"class":228,"line":964},[1885,1889,1893,1897,1901,1905,1909],{"type":21,"tag":226,"props":1886,"children":1887},{"style":828},[1888],{"type":26,"value":933},{"type":21,"tag":226,"props":1890,"children":1891},{"style":819},[1892],{"type":26,"value":938},{"type":21,"tag":226,"props":1894,"children":1895},{"style":828},[1896],{"type":26,"value":904},{"type":21,"tag":226,"props":1898,"children":1899},{"style":819},[1900],{"type":26,"value":947},{"type":21,"tag":226,"props":1902,"children":1903},{"style":828},[1904],{"type":26,"value":895},{"type":21,"tag":226,"props":1906,"children":1907},{"style":819},[1908],{"type":26,"value":956},{"type":21,"tag":226,"props":1910,"children":1911},{"style":828},[1912],{"type":26,"value":961},{"type":21,"tag":226,"props":1914,"children":1915},{"class":228,"line":973},[1916,1920,1925,1930,1934,1939,1944,1948,1952,1956,1960,1965,1969,1973,1978,1983],{"type":21,"tag":226,"props":1917,"children":1918},{"style":828},[1919],{"type":26,"value":1121},{"type":21,"tag":226,"props":1921,"children":1922},{"style":819},[1923],{"type":26,"value":1924},"fWindow",{"type":21,"tag":226,"props":1926,"children":1927},{"style":828},[1928],{"type":26,"value":1929},"(fFft.",{"type":21,"tag":226,"props":1931,"children":1932},{"style":819},[1933],{"type":26,"value":1267},{"type":21,"tag":226,"props":1935,"children":1936},{"style":828},[1937],{"type":26,"value":1938},"() ",{"type":21,"tag":226,"props":1940,"children":1941},{"style":233},[1942],{"type":26,"value":1943},"+",{"type":21,"tag":226,"props":1945,"children":1946},{"style":1182},[1947],{"type":26,"value":1307},{"type":21,"tag":226,"props":1949,"children":1950},{"style":828},[1951],{"type":26,"value":1121},{"type":21,"tag":226,"props":1953,"children":1954},{"style":819},[1955],{"type":26,"value":745},{"type":21,"tag":226,"props":1957,"children":1958},{"style":828},[1959],{"type":26,"value":895},{"type":21,"tag":226,"props":1961,"children":1962},{"style":819},[1963],{"type":26,"value":1964},"WindowingFunction",{"type":21,"tag":226,"props":1966,"children":1967},{"style":828},[1968],{"type":26,"value":1049},{"type":21,"tag":226,"props":1970,"children":1971},{"style":233},[1972],{"type":26,"value":302},{"type":21,"tag":226,"props":1974,"children":1975},{"style":828},[1976],{"type":26,"value":1977},">::hann, ",{"type":21,"tag":226,"props":1979,"children":1980},{"style":1182},[1981],{"type":26,"value":1982},"false",{"type":21,"tag":226,"props":1984,"children":1985},{"style":828},[1986],{"type":26,"value":924},{"type":21,"tag":226,"props":1988,"children":1989},{"class":228,"line":981},[1990],{"type":21,"tag":226,"props":1991,"children":1992},{"style":805},[1993],{"type":26,"value":970},{"type":21,"tag":226,"props":1995,"children":1996},{"class":228,"line":1357},[1997],{"type":21,"tag":226,"props":1998,"children":1999},{"style":828},[2000],{"type":26,"value":831},{"type":21,"tag":226,"props":2002,"children":2003},{"class":228,"line":1365},[2004],{"type":21,"tag":226,"props":2005,"children":2006},{"style":828},[2007],{"type":26,"value":987},{"type":21,"tag":22,"props":2009,"children":2010},{},[2011],{"type":26,"value":2012},"Now apply the windowing to the chunk of samples before passing it to the FFT.",{"type":21,"tag":216,"props":2014,"children":2016},{"className":218,"code":2015,"language":220,"meta":8,"style":8},"ASpectrogram ASpectroMaker::STFT(const AudioSampleBuffer& signal, size_t hop)\n{\n   // ...Initialize variables as before...\n\n   // While data remains\n   {\n      std::memcpy(fftBuffer.data(), data, fftSize * sizeof(float));\n\n      fWindow.multiplyWithWindowingTable(fftBuffer.data(), fftSize);\n      fFft.performFrequencyOnlyForwardTransform(fftBuffer.data());\n\n      // ...copy the frequency information from fftBuffer to the spectrum\n\n      // Next chunk start\n      data += hop;\n   }\n   //...\n}\n",[2017],{"type":21,"tag":119,"props":2018,"children":2019},{"__ignoreMap":8},[2020,2075,2082,2090,2097,2104,2111,2158,2165,2191,2214,2221,2228,2235,2243,2258,2265,2272],{"type":21,"tag":226,"props":2021,"children":2022},{"class":228,"line":229},[2023,2027,2031,2035,2039,2043,2047,2051,2055,2059,2063,2067,2071],{"type":21,"tag":226,"props":2024,"children":2025},{"style":819},[2026],{"type":26,"value":1078},{"type":21,"tag":226,"props":2028,"children":2029},{"style":819},[2030],{"type":26,"value":1083},{"type":21,"tag":226,"props":2032,"children":2033},{"style":828},[2034],{"type":26,"value":895},{"type":21,"tag":226,"props":2036,"children":2037},{"style":819},[2038],{"type":26,"value":732},{"type":21,"tag":226,"props":2040,"children":2041},{"style":828},[2042],{"type":26,"value":904},{"type":21,"tag":226,"props":2044,"children":2045},{"style":233},[2046],{"type":26,"value":1100},{"type":21,"tag":226,"props":2048,"children":2049},{"style":819},[2050],{"type":26,"value":1105},{"type":21,"tag":226,"props":2052,"children":2053},{"style":233},[2054],{"type":26,"value":1110},{"type":21,"tag":226,"props":2056,"children":2057},{"style":1113},[2058],{"type":26,"value":1116},{"type":21,"tag":226,"props":2060,"children":2061},{"style":828},[2062],{"type":26,"value":1121},{"type":21,"tag":226,"props":2064,"children":2065},{"style":233},[2066],{"type":26,"value":1126},{"type":21,"tag":226,"props":2068,"children":2069},{"style":1113},[2070],{"type":26,"value":1131},{"type":21,"tag":226,"props":2072,"children":2073},{"style":828},[2074],{"type":26,"value":924},{"type":21,"tag":226,"props":2076,"children":2077},{"class":228,"line":377},[2078],{"type":21,"tag":226,"props":2079,"children":2080},{"style":828},[2081],{"type":26,"value":831},{"type":21,"tag":226,"props":2083,"children":2084},{"class":228,"line":374},[2085],{"type":21,"tag":226,"props":2086,"children":2087},{"style":805},[2088],{"type":26,"value":2089},"   // ...Initialize variables as before...\n",{"type":21,"tag":226,"props":2091,"children":2092},{"class":228,"line":834},[2093],{"type":21,"tag":226,"props":2094,"children":2095},{"emptyLinePlaceholder":878},[2096],{"type":26,"value":881},{"type":21,"tag":226,"props":2098,"children":2099},{"class":228,"line":843},[2100],{"type":21,"tag":226,"props":2101,"children":2102},{"style":805},[2103],{"type":26,"value":1512},{"type":21,"tag":226,"props":2105,"children":2106},{"class":228,"line":857},[2107],{"type":21,"tag":226,"props":2108,"children":2109},{"style":828},[2110],{"type":26,"value":1521},{"type":21,"tag":226,"props":2112,"children":2113},{"class":228,"line":865},[2114,2118,2122,2126,2130,2134,2138,2142,2146,2150,2154],{"type":21,"tag":226,"props":2115,"children":2116},{"style":819},[2117],{"type":26,"value":1530},{"type":21,"tag":226,"props":2119,"children":2120},{"style":828},[2121],{"type":26,"value":895},{"type":21,"tag":226,"props":2123,"children":2124},{"style":819},[2125],{"type":26,"value":1539},{"type":21,"tag":226,"props":2127,"children":2128},{"style":828},[2129],{"type":26,"value":1544},{"type":21,"tag":226,"props":2131,"children":2132},{"style":819},[2133],{"type":26,"value":1549},{"type":21,"tag":226,"props":2135,"children":2136},{"style":828},[2137],{"type":26,"value":1554},{"type":21,"tag":226,"props":2139,"children":2140},{"style":233},[2141],{"type":26,"value":1483},{"type":21,"tag":226,"props":2143,"children":2144},{"style":233},[2145],{"type":26,"value":1563},{"type":21,"tag":226,"props":2147,"children":2148},{"style":828},[2149],{"type":26,"value":904},{"type":21,"tag":226,"props":2151,"children":2152},{"style":233},[2153],{"type":26,"value":302},{"type":21,"tag":226,"props":2155,"children":2156},{"style":828},[2157],{"type":26,"value":1576},{"type":21,"tag":226,"props":2159,"children":2160},{"class":228,"line":874},[2161],{"type":21,"tag":226,"props":2162,"children":2163},{"emptyLinePlaceholder":878},[2164],{"type":26,"value":881},{"type":21,"tag":226,"props":2166,"children":2167},{"class":228,"line":884},[2168,2173,2178,2182,2186],{"type":21,"tag":226,"props":2169,"children":2170},{"style":828},[2171],{"type":26,"value":2172},"      fWindow.",{"type":21,"tag":226,"props":2174,"children":2175},{"style":819},[2176],{"type":26,"value":2177},"multiplyWithWindowingTable",{"type":21,"tag":226,"props":2179,"children":2180},{"style":828},[2181],{"type":26,"value":1544},{"type":21,"tag":226,"props":2183,"children":2184},{"style":819},[2185],{"type":26,"value":1549},{"type":21,"tag":226,"props":2187,"children":2188},{"style":828},[2189],{"type":26,"value":2190},"(), fftSize);\n",{"type":21,"tag":226,"props":2192,"children":2193},{"class":228,"line":927},[2194,2198,2202,2206,2210],{"type":21,"tag":226,"props":2195,"children":2196},{"style":828},[2197],{"type":26,"value":1610},{"type":21,"tag":226,"props":2199,"children":2200},{"style":819},[2201],{"type":26,"value":1615},{"type":21,"tag":226,"props":2203,"children":2204},{"style":828},[2205],{"type":26,"value":1544},{"type":21,"tag":226,"props":2207,"children":2208},{"style":819},[2209],{"type":26,"value":1549},{"type":21,"tag":226,"props":2211,"children":2212},{"style":828},[2213],{"type":26,"value":1628},{"type":21,"tag":226,"props":2215,"children":2216},{"class":228,"line":964},[2217],{"type":21,"tag":226,"props":2218,"children":2219},{"emptyLinePlaceholder":878},[2220],{"type":26,"value":881},{"type":21,"tag":226,"props":2222,"children":2223},{"class":228,"line":973},[2224],{"type":21,"tag":226,"props":2225,"children":2226},{"style":805},[2227],{"type":26,"value":1645},{"type":21,"tag":226,"props":2229,"children":2230},{"class":228,"line":981},[2231],{"type":21,"tag":226,"props":2232,"children":2233},{"emptyLinePlaceholder":878},[2234],{"type":26,"value":881},{"type":21,"tag":226,"props":2236,"children":2237},{"class":228,"line":1357},[2238],{"type":21,"tag":226,"props":2239,"children":2240},{"style":805},[2241],{"type":26,"value":2242},"      // Next chunk start\n",{"type":21,"tag":226,"props":2244,"children":2245},{"class":228,"line":1365},[2246,2250,2254],{"type":21,"tag":226,"props":2247,"children":2248},{"style":828},[2249],{"type":26,"value":1671},{"type":21,"tag":226,"props":2251,"children":2252},{"style":233},[2253],{"type":26,"value":1676},{"type":21,"tag":226,"props":2255,"children":2256},{"style":828},[2257],{"type":26,"value":1681},{"type":21,"tag":226,"props":2259,"children":2260},{"class":228,"line":1373},[2261],{"type":21,"tag":226,"props":2262,"children":2263},{"style":828},[2264],{"type":26,"value":1690},{"type":21,"tag":226,"props":2266,"children":2267},{"class":228,"line":1382},[2268],{"type":21,"tag":226,"props":2269,"children":2270},{"style":805},[2271],{"type":26,"value":840},{"type":21,"tag":226,"props":2273,"children":2274},{"class":228,"line":1435},[2275],{"type":21,"tag":226,"props":2276,"children":2277},{"style":828},[2278],{"type":26,"value":987},{"type":21,"tag":22,"props":2280,"children":2281},{},[2282,2284,2290,2292,2297],{"type":26,"value":2283},"I'll skip how the data is read into the ",{"type":21,"tag":119,"props":2285,"children":2287},{"className":2286},[],[2288],{"type":26,"value":2289},"Spectrum",{"type":26,"value":2291}," where ",{"type":21,"tag":119,"props":2293,"children":2295},{"className":2294},[],[2296],{"type":26,"value":1078},{"type":26,"value":2298}," keeps per-channel information, but it is straightforward. Just remember to discard the bins corresponding to negative frequencies. And we flip the data vertically so that higher frequencies are at the top, lower frequencies are at the bottom.",{"type":21,"tag":22,"props":2300,"children":2301},{},[2302],{"type":26,"value":2303},"There's a more interesting thing we need to do after that.",{"type":21,"tag":186,"props":2305,"children":2307},{"id":2306},"we-dont-hear-hertz",[2308],{"type":26,"value":2309},"We Don't Hear Hertz",{"type":21,"tag":22,"props":2311,"children":2312},{},[2313,2315,2322],{"type":26,"value":2314},"The FFT bins are ranges of Hertz (Hz). Hertz are a linear representation of frequency, but our ",{"type":21,"tag":29,"props":2316,"children":2319},{"href":2317,"rel":2318},"https://en.wikipedia.org/wiki/Psychoacoustics#/media/File:Perceived_Human_Hearing.svg",[33],[2320],{"type":26,"value":2321},"hearing's perception",{"type":26,"value":2323}," is logarithmic. That means we don't perceive much difference between 10,000 Hz and 10,500 Hz, but by 20,000 Hz we will. It also means we hear a lot of change between 20 Hz and 500 Hz. A lot of the resolution in the linear spacing of the Hz bins is wasted given our perception.",{"type":21,"tag":22,"props":2325,"children":2326},{},[2327,2329,2336],{"type":26,"value":2328},"In order that our classification system finds features in the spectrogram that are more likely to correspond to the way we hear the sound, we should convert from Hertz to another measurement that matches our hearing. For the method Dr. Hawley brought us that is... well it's commonly called ",{"type":21,"tag":29,"props":2330,"children":2333},{"href":2331,"rel":2332},"https://en.wikipedia.org/wiki/Mel-frequency_cepstrum",[33],[2334],{"type":26,"value":2335},"Mels",{"type":26,"value":2337},". I won't presume to understand or explain everything that is going on in the conversion to Mels, but it boils down to a matrix multiplication with a specially constructed conversion matrix that stretches and squeezes information from our evenly spaced linear Hertz bins into bins that better represent our perception of the sound's frequencies.",{"type":21,"tag":22,"props":2339,"children":2340},{},[2341,2343,2349,2351,2357,2359,2365,2367,2373,2375,2381,2383,2389],{"type":26,"value":2342},"The conversion matrix ends up being ",{"type":21,"tag":119,"props":2344,"children":2346},{"className":2345},[],[2347],{"type":26,"value":2348},"fftSize x mels",{"type":26,"value":2350},". Multiplying the frequency data which is ",{"type":21,"tag":119,"props":2352,"children":2354},{"className":2353},[],[2355],{"type":26,"value":2356},"n x fftsize",{"type":26,"value":2358}," with it will produce the ",{"type":21,"tag":119,"props":2360,"children":2362},{"className":2361},[],[2363],{"type":26,"value":2364},"n x mels",{"type":26,"value":2366}," desired output where ",{"type":21,"tag":119,"props":2368,"children":2370},{"className":2369},[],[2371],{"type":26,"value":2372},"n",{"type":26,"value":2374}," is the width of our spectrogram. And we reduce the total size of the spectrogram data by as much. In our application we go from the Hz representation ",{"type":21,"tag":119,"props":2376,"children":2378},{"className":2377},[],[2379],{"type":26,"value":2380},"1025 x 259",{"type":26,"value":2382}," to ",{"type":21,"tag":119,"props":2384,"children":2386},{"className":2385},[],[2387],{"type":26,"value":2388},"96 x 259",{"type":26,"value":2390}," in the Mels representation.",{"type":21,"tag":186,"props":2392,"children":2394},{"id":2393},"the-final-transforms",[2395],{"type":26,"value":2396},"The Final Transforms",{"type":21,"tag":22,"props":2398,"children":2399},{},[2400,2402,2407,2409,2415],{"type":26,"value":2401},"Once we have converted to Mels, we make a final transformation from amplitudes to deciBels (db) while normalizing and clamping to a maximum dB. To use the ",{"type":21,"tag":321,"props":2403,"children":2404},{},[2405],{"type":26,"value":2406},"power to dB",{"type":26,"value":2408}," formula we need powers rather than amplitudes. This just means squaring the signal, i.e. multiply it by itself. Notice that in the ",{"type":21,"tag":119,"props":2410,"children":2412},{"className":2411},[],[2413],{"type":26,"value":2414},"power-to-db",{"type":26,"value":2416}," code it again squares the values. It needs both.",{"type":21,"tag":216,"props":2418,"children":2420},{"className":218,"code":2419,"language":220,"meta":8,"style":8},"   // TransformChannels will modify the data in each channel (aka Spectrum) in the spectrogram in place.\n   // Power to dB while limiting to a minimum magnitude.\n   TransformChannels(magnitudes, [](float mag)\n    {\n       constexpr double kMin = 1e-5 * 1e-5;\n       constexpr double kReference = 1.0f * 1.0f;\n       return 10.0 * std::log10(std::max(kMin, (double)mag * mag))\n            - 10.0 * std::log10(std::max(kMin, kReference));\n    });\n\n   // Limit to maximum dB\n   auto maxDb = magnitudes.MinMax().second - kMaxDb;\n   TransformChannels(magnitudes, [maxDb](float dB)\n    {\n       return std::max(dB, maxDb);\n    });\n",[2421],{"type":21,"tag":119,"props":2422,"children":2423},{"__ignoreMap":8},[2424,2432,2440,2466,2474,2532,2578,2649,2698,2706,2713,2721,2762,2797,2804,2828],{"type":21,"tag":226,"props":2425,"children":2426},{"class":228,"line":229},[2427],{"type":21,"tag":226,"props":2428,"children":2429},{"style":805},[2430],{"type":26,"value":2431},"   // TransformChannels will modify the data in each channel (aka Spectrum) in the spectrogram in place.\n",{"type":21,"tag":226,"props":2433,"children":2434},{"class":228,"line":377},[2435],{"type":21,"tag":226,"props":2436,"children":2437},{"style":805},[2438],{"type":26,"value":2439},"   // Power to dB while limiting to a minimum magnitude.\n",{"type":21,"tag":226,"props":2441,"children":2442},{"class":228,"line":374},[2443,2448,2453,2457,2462],{"type":21,"tag":226,"props":2444,"children":2445},{"style":819},[2446],{"type":26,"value":2447},"   TransformChannels",{"type":21,"tag":226,"props":2449,"children":2450},{"style":828},[2451],{"type":26,"value":2452},"(magnitudes, [](",{"type":21,"tag":226,"props":2454,"children":2455},{"style":233},[2456],{"type":26,"value":302},{"type":21,"tag":226,"props":2458,"children":2459},{"style":1113},[2460],{"type":26,"value":2461}," mag",{"type":21,"tag":226,"props":2463,"children":2464},{"style":828},[2465],{"type":26,"value":924},{"type":21,"tag":226,"props":2467,"children":2468},{"class":228,"line":834},[2469],{"type":21,"tag":226,"props":2470,"children":2471},{"style":828},[2472],{"type":26,"value":2473},"    {\n",{"type":21,"tag":226,"props":2475,"children":2476},{"class":228,"line":843},[2477,2482,2487,2492,2496,2500,2505,2510,2515,2519,2523,2527],{"type":21,"tag":226,"props":2478,"children":2479},{"style":233},[2480],{"type":26,"value":2481},"       constexpr",{"type":21,"tag":226,"props":2483,"children":2484},{"style":233},[2485],{"type":26,"value":2486}," double",{"type":21,"tag":226,"props":2488,"children":2489},{"style":828},[2490],{"type":26,"value":2491}," kMin ",{"type":21,"tag":226,"props":2493,"children":2494},{"style":233},[2495],{"type":26,"value":1165},{"type":21,"tag":226,"props":2497,"children":2498},{"style":1182},[2499],{"type":26,"value":1307},{"type":21,"tag":226,"props":2501,"children":2502},{"style":233},[2503],{"type":26,"value":2504},"e-",{"type":21,"tag":226,"props":2506,"children":2507},{"style":1182},[2508],{"type":26,"value":2509},"5",{"type":21,"tag":226,"props":2511,"children":2512},{"style":233},[2513],{"type":26,"value":2514}," *",{"type":21,"tag":226,"props":2516,"children":2517},{"style":1182},[2518],{"type":26,"value":1307},{"type":21,"tag":226,"props":2520,"children":2521},{"style":233},[2522],{"type":26,"value":2504},{"type":21,"tag":226,"props":2524,"children":2525},{"style":1182},[2526],{"type":26,"value":2509},{"type":21,"tag":226,"props":2528,"children":2529},{"style":828},[2530],{"type":26,"value":2531},";\n",{"type":21,"tag":226,"props":2533,"children":2534},{"class":228,"line":857},[2535,2539,2543,2548,2552,2557,2562,2566,2570,2574],{"type":21,"tag":226,"props":2536,"children":2537},{"style":233},[2538],{"type":26,"value":2481},{"type":21,"tag":226,"props":2540,"children":2541},{"style":233},[2542],{"type":26,"value":2486},{"type":21,"tag":226,"props":2544,"children":2545},{"style":828},[2546],{"type":26,"value":2547}," kReference ",{"type":21,"tag":226,"props":2549,"children":2550},{"style":233},[2551],{"type":26,"value":1165},{"type":21,"tag":226,"props":2553,"children":2554},{"style":1182},[2555],{"type":26,"value":2556}," 1.0",{"type":21,"tag":226,"props":2558,"children":2559},{"style":233},[2560],{"type":26,"value":2561},"f",{"type":21,"tag":226,"props":2563,"children":2564},{"style":233},[2565],{"type":26,"value":2514},{"type":21,"tag":226,"props":2567,"children":2568},{"style":1182},[2569],{"type":26,"value":2556},{"type":21,"tag":226,"props":2571,"children":2572},{"style":233},[2573],{"type":26,"value":2561},{"type":21,"tag":226,"props":2575,"children":2576},{"style":828},[2577],{"type":26,"value":2531},{"type":21,"tag":226,"props":2579,"children":2580},{"class":228,"line":865},[2581,2586,2591,2595,2599,2603,2608,2612,2616,2620,2625,2630,2635,2640,2644],{"type":21,"tag":226,"props":2582,"children":2583},{"style":233},[2584],{"type":26,"value":2585},"       return",{"type":21,"tag":226,"props":2587,"children":2588},{"style":1182},[2589],{"type":26,"value":2590}," 10.0",{"type":21,"tag":226,"props":2592,"children":2593},{"style":233},[2594],{"type":26,"value":2514},{"type":21,"tag":226,"props":2596,"children":2597},{"style":819},[2598],{"type":26,"value":1035},{"type":21,"tag":226,"props":2600,"children":2601},{"style":828},[2602],{"type":26,"value":895},{"type":21,"tag":226,"props":2604,"children":2605},{"style":819},[2606],{"type":26,"value":2607},"log10",{"type":21,"tag":226,"props":2609,"children":2610},{"style":828},[2611],{"type":26,"value":904},{"type":21,"tag":226,"props":2613,"children":2614},{"style":819},[2615],{"type":26,"value":947},{"type":21,"tag":226,"props":2617,"children":2618},{"style":828},[2619],{"type":26,"value":895},{"type":21,"tag":226,"props":2621,"children":2622},{"style":819},[2623],{"type":26,"value":2624},"max",{"type":21,"tag":226,"props":2626,"children":2627},{"style":828},[2628],{"type":26,"value":2629},"(kMin, (",{"type":21,"tag":226,"props":2631,"children":2632},{"style":233},[2633],{"type":26,"value":2634},"double",{"type":21,"tag":226,"props":2636,"children":2637},{"style":828},[2638],{"type":26,"value":2639},")mag ",{"type":21,"tag":226,"props":2641,"children":2642},{"style":233},[2643],{"type":26,"value":1483},{"type":21,"tag":226,"props":2645,"children":2646},{"style":828},[2647],{"type":26,"value":2648}," mag))\n",{"type":21,"tag":226,"props":2650,"children":2651},{"class":228,"line":874},[2652,2657,2661,2665,2669,2673,2677,2681,2685,2689,2693],{"type":21,"tag":226,"props":2653,"children":2654},{"style":233},[2655],{"type":26,"value":2656},"            -",{"type":21,"tag":226,"props":2658,"children":2659},{"style":1182},[2660],{"type":26,"value":2590},{"type":21,"tag":226,"props":2662,"children":2663},{"style":233},[2664],{"type":26,"value":2514},{"type":21,"tag":226,"props":2666,"children":2667},{"style":819},[2668],{"type":26,"value":1035},{"type":21,"tag":226,"props":2670,"children":2671},{"style":828},[2672],{"type":26,"value":895},{"type":21,"tag":226,"props":2674,"children":2675},{"style":819},[2676],{"type":26,"value":2607},{"type":21,"tag":226,"props":2678,"children":2679},{"style":828},[2680],{"type":26,"value":904},{"type":21,"tag":226,"props":2682,"children":2683},{"style":819},[2684],{"type":26,"value":947},{"type":21,"tag":226,"props":2686,"children":2687},{"style":828},[2688],{"type":26,"value":895},{"type":21,"tag":226,"props":2690,"children":2691},{"style":819},[2692],{"type":26,"value":2624},{"type":21,"tag":226,"props":2694,"children":2695},{"style":828},[2696],{"type":26,"value":2697},"(kMin, kReference));\n",{"type":21,"tag":226,"props":2699,"children":2700},{"class":228,"line":884},[2701],{"type":21,"tag":226,"props":2702,"children":2703},{"style":828},[2704],{"type":26,"value":2705},"    });\n",{"type":21,"tag":226,"props":2707,"children":2708},{"class":228,"line":927},[2709],{"type":21,"tag":226,"props":2710,"children":2711},{"emptyLinePlaceholder":878},[2712],{"type":26,"value":881},{"type":21,"tag":226,"props":2714,"children":2715},{"class":228,"line":964},[2716],{"type":21,"tag":226,"props":2717,"children":2718},{"style":805},[2719],{"type":26,"value":2720},"   // Limit to maximum dB\n",{"type":21,"tag":226,"props":2722,"children":2723},{"class":228,"line":973},[2724,2729,2734,2738,2743,2748,2753,2757],{"type":21,"tag":226,"props":2725,"children":2726},{"style":233},[2727],{"type":26,"value":2728},"   auto",{"type":21,"tag":226,"props":2730,"children":2731},{"style":828},[2732],{"type":26,"value":2733}," maxDb ",{"type":21,"tag":226,"props":2735,"children":2736},{"style":233},[2737],{"type":26,"value":1165},{"type":21,"tag":226,"props":2739,"children":2740},{"style":828},[2741],{"type":26,"value":2742}," magnitudes.",{"type":21,"tag":226,"props":2744,"children":2745},{"style":819},[2746],{"type":26,"value":2747},"MinMax",{"type":21,"tag":226,"props":2749,"children":2750},{"style":828},[2751],{"type":26,"value":2752},"().second ",{"type":21,"tag":226,"props":2754,"children":2755},{"style":233},[2756],{"type":26,"value":1332},{"type":21,"tag":226,"props":2758,"children":2759},{"style":828},[2760],{"type":26,"value":2761}," kMaxDb;\n",{"type":21,"tag":226,"props":2763,"children":2764},{"class":228,"line":981},[2765,2769,2774,2779,2784,2788,2793],{"type":21,"tag":226,"props":2766,"children":2767},{"style":819},[2768],{"type":26,"value":2447},{"type":21,"tag":226,"props":2770,"children":2771},{"style":828},[2772],{"type":26,"value":2773},"(magnitudes, [",{"type":21,"tag":226,"props":2775,"children":2776},{"style":1113},[2777],{"type":26,"value":2778},"maxDb",{"type":21,"tag":226,"props":2780,"children":2781},{"style":828},[2782],{"type":26,"value":2783},"](",{"type":21,"tag":226,"props":2785,"children":2786},{"style":233},[2787],{"type":26,"value":302},{"type":21,"tag":226,"props":2789,"children":2790},{"style":1113},[2791],{"type":26,"value":2792}," dB",{"type":21,"tag":226,"props":2794,"children":2795},{"style":828},[2796],{"type":26,"value":924},{"type":21,"tag":226,"props":2798,"children":2799},{"class":228,"line":1357},[2800],{"type":21,"tag":226,"props":2801,"children":2802},{"style":828},[2803],{"type":26,"value":2473},{"type":21,"tag":226,"props":2805,"children":2806},{"class":228,"line":1365},[2807,2811,2815,2819,2823],{"type":21,"tag":226,"props":2808,"children":2809},{"style":233},[2810],{"type":26,"value":2585},{"type":21,"tag":226,"props":2812,"children":2813},{"style":819},[2814],{"type":26,"value":1035},{"type":21,"tag":226,"props":2816,"children":2817},{"style":828},[2818],{"type":26,"value":895},{"type":21,"tag":226,"props":2820,"children":2821},{"style":819},[2822],{"type":26,"value":2624},{"type":21,"tag":226,"props":2824,"children":2825},{"style":828},[2826],{"type":26,"value":2827},"(dB, maxDb);\n",{"type":21,"tag":226,"props":2829,"children":2830},{"class":228,"line":1373},[2831],{"type":21,"tag":226,"props":2832,"children":2833},{"style":828},[2834],{"type":26,"value":2705},{"type":21,"tag":186,"props":2836,"children":2838},{"id":2837},"view-from-the-top",[2839],{"type":26,"value":2840},"View From The Top",{"type":21,"tag":22,"props":2842,"children":2843},{},[2844,2846,2851,2853,2859],{"type":26,"value":2845},"Here is the top-level ",{"type":21,"tag":119,"props":2847,"children":2849},{"className":2848},[],[2850],{"type":26,"value":890},{"type":26,"value":2852}," code snippet that goes from audio file to the final spectrogram. When it finishes ",{"type":21,"tag":119,"props":2854,"children":2856},{"className":2855},[],[2857],{"type":26,"value":2858},"spectrogram",{"type":26,"value":2860}," contains the final Mel frequency information.",{"type":21,"tag":216,"props":2862,"children":2864},{"className":218,"code":2863,"language":220,"meta":8,"style":8},"   // Read a little more than 3 seconds to get the amount of data needed to produce 259 columns in the spectrogram\n   auto signal = this->ReadAudioSignal(file, 3.1);\n   auto hzFreqs = this->STFT(ToMono(signal).buffer, kHopLength);\n   TransformChannels(hzFreqs, [](auto x) { return x * x; });\n\n   spectrogram.AddChannel(ASpectroMaker::HzToMels(hzFreqs, 0, signal.sampleRate, fNumMelBins));\n   spectrogram.Reshape(fNumMelBins, hzFreqs.Shape().second);\n   ASpectroMaker::AmplitudesToDbs(spectrogram);\n",[2865],{"type":21,"tag":119,"props":2866,"children":2867},{"__ignoreMap":8},[2868,2876,2921,2963,3009,3016,3060,3087],{"type":21,"tag":226,"props":2869,"children":2870},{"class":228,"line":229},[2871],{"type":21,"tag":226,"props":2872,"children":2873},{"style":805},[2874],{"type":26,"value":2875},"   // Read a little more than 3 seconds to get the amount of data needed to produce 259 columns in the spectrogram\n",{"type":21,"tag":226,"props":2877,"children":2878},{"class":228,"line":377},[2879,2883,2888,2892,2897,2902,2907,2912,2917],{"type":21,"tag":226,"props":2880,"children":2881},{"style":233},[2882],{"type":26,"value":2728},{"type":21,"tag":226,"props":2884,"children":2885},{"style":828},[2886],{"type":26,"value":2887}," signal ",{"type":21,"tag":226,"props":2889,"children":2890},{"style":233},[2891],{"type":26,"value":1165},{"type":21,"tag":226,"props":2893,"children":2894},{"style":1182},[2895],{"type":26,"value":2896}," this",{"type":21,"tag":226,"props":2898,"children":2899},{"style":828},[2900],{"type":26,"value":2901},"->",{"type":21,"tag":226,"props":2903,"children":2904},{"style":819},[2905],{"type":26,"value":2906},"ReadAudioSignal",{"type":21,"tag":226,"props":2908,"children":2909},{"style":828},[2910],{"type":26,"value":2911},"(file, ",{"type":21,"tag":226,"props":2913,"children":2914},{"style":1182},[2915],{"type":26,"value":2916},"3.1",{"type":21,"tag":226,"props":2918,"children":2919},{"style":828},[2920],{"type":26,"value":1190},{"type":21,"tag":226,"props":2922,"children":2923},{"class":228,"line":374},[2924,2928,2933,2937,2941,2945,2949,2953,2958],{"type":21,"tag":226,"props":2925,"children":2926},{"style":233},[2927],{"type":26,"value":2728},{"type":21,"tag":226,"props":2929,"children":2930},{"style":828},[2931],{"type":26,"value":2932}," hzFreqs ",{"type":21,"tag":226,"props":2934,"children":2935},{"style":233},[2936],{"type":26,"value":1165},{"type":21,"tag":226,"props":2938,"children":2939},{"style":1182},[2940],{"type":26,"value":2896},{"type":21,"tag":226,"props":2942,"children":2943},{"style":828},[2944],{"type":26,"value":2901},{"type":21,"tag":226,"props":2946,"children":2947},{"style":819},[2948],{"type":26,"value":732},{"type":21,"tag":226,"props":2950,"children":2951},{"style":828},[2952],{"type":26,"value":904},{"type":21,"tag":226,"props":2954,"children":2955},{"style":819},[2956],{"type":26,"value":2957},"ToMono",{"type":21,"tag":226,"props":2959,"children":2960},{"style":828},[2961],{"type":26,"value":2962},"(signal).buffer, kHopLength);\n",{"type":21,"tag":226,"props":2964,"children":2965},{"class":228,"line":834},[2966,2970,2975,2980,2985,2990,2995,3000,3004],{"type":21,"tag":226,"props":2967,"children":2968},{"style":819},[2969],{"type":26,"value":2447},{"type":21,"tag":226,"props":2971,"children":2972},{"style":828},[2973],{"type":26,"value":2974},"(hzFreqs, [](",{"type":21,"tag":226,"props":2976,"children":2977},{"style":233},[2978],{"type":26,"value":2979},"auto",{"type":21,"tag":226,"props":2981,"children":2982},{"style":1113},[2983],{"type":26,"value":2984}," x",{"type":21,"tag":226,"props":2986,"children":2987},{"style":828},[2988],{"type":26,"value":2989},") { ",{"type":21,"tag":226,"props":2991,"children":2992},{"style":233},[2993],{"type":26,"value":2994},"return",{"type":21,"tag":226,"props":2996,"children":2997},{"style":828},[2998],{"type":26,"value":2999}," x ",{"type":21,"tag":226,"props":3001,"children":3002},{"style":233},[3003],{"type":26,"value":1483},{"type":21,"tag":226,"props":3005,"children":3006},{"style":828},[3007],{"type":26,"value":3008}," x; });\n",{"type":21,"tag":226,"props":3010,"children":3011},{"class":228,"line":843},[3012],{"type":21,"tag":226,"props":3013,"children":3014},{"emptyLinePlaceholder":878},[3015],{"type":26,"value":881},{"type":21,"tag":226,"props":3017,"children":3018},{"class":228,"line":857},[3019,3024,3029,3033,3037,3041,3046,3051,3055],{"type":21,"tag":226,"props":3020,"children":3021},{"style":828},[3022],{"type":26,"value":3023},"   spectrogram.",{"type":21,"tag":226,"props":3025,"children":3026},{"style":819},[3027],{"type":26,"value":3028},"AddChannel",{"type":21,"tag":226,"props":3030,"children":3031},{"style":828},[3032],{"type":26,"value":904},{"type":21,"tag":226,"props":3034,"children":3035},{"style":819},[3036],{"type":26,"value":890},{"type":21,"tag":226,"props":3038,"children":3039},{"style":828},[3040],{"type":26,"value":895},{"type":21,"tag":226,"props":3042,"children":3043},{"style":819},[3044],{"type":26,"value":3045},"HzToMels",{"type":21,"tag":226,"props":3047,"children":3048},{"style":828},[3049],{"type":26,"value":3050},"(hzFreqs, ",{"type":21,"tag":226,"props":3052,"children":3053},{"style":1182},[3054],{"type":26,"value":1185},{"type":21,"tag":226,"props":3056,"children":3057},{"style":828},[3058],{"type":26,"value":3059},", signal.sampleRate, fNumMelBins));\n",{"type":21,"tag":226,"props":3061,"children":3062},{"class":228,"line":865},[3063,3067,3072,3077,3082],{"type":21,"tag":226,"props":3064,"children":3065},{"style":828},[3066],{"type":26,"value":3023},{"type":21,"tag":226,"props":3068,"children":3069},{"style":819},[3070],{"type":26,"value":3071},"Reshape",{"type":21,"tag":226,"props":3073,"children":3074},{"style":828},[3075],{"type":26,"value":3076},"(fNumMelBins, hzFreqs.",{"type":21,"tag":226,"props":3078,"children":3079},{"style":819},[3080],{"type":26,"value":3081},"Shape",{"type":21,"tag":226,"props":3083,"children":3084},{"style":828},[3085],{"type":26,"value":3086},"().second);\n",{"type":21,"tag":226,"props":3088,"children":3089},{"class":228,"line":874},[3090,3095,3099,3104],{"type":21,"tag":226,"props":3091,"children":3092},{"style":819},[3093],{"type":26,"value":3094},"   ASpectroMaker",{"type":21,"tag":226,"props":3096,"children":3097},{"style":828},[3098],{"type":26,"value":895},{"type":21,"tag":226,"props":3100,"children":3101},{"style":819},[3102],{"type":26,"value":3103},"AmplitudesToDbs",{"type":21,"tag":226,"props":3105,"children":3106},{"style":828},[3107],{"type":26,"value":3108},"(spectrogram);\n",{"type":21,"tag":186,"props":3110,"children":3112},{"id":3111},"concluding",[3113],{"type":26,"value":3114},"Concluding",{"type":21,"tag":22,"props":3116,"children":3117},{},[3118],{"type":26,"value":3119},"You now have a two-dimensional spectrogram represented as a set of floating point values in a vector. This is suitable to export, say as a NumPy array, or conversion to an actual image representation in color. We do both.",{"type":21,"tag":22,"props":3121,"children":3122},{},[3123],{"type":26,"value":3124},"Hopefully this article has let you get a little better understanding of the how and the why, and you can avoid some of the confusion I experienced during development.",{"type":21,"tag":22,"props":3126,"children":3127},{},[3128,3130,3137,3139,3146,3148,3155],{"type":26,"value":3129},"Some notes.\n* We discard the phase information the FFT can produce; this means we cannot reliably convert back to audio from the spectrogram\n* Dr. Hawley's Python implementation used ",{"type":21,"tag":29,"props":3131,"children":3134},{"href":3132,"rel":3133},"https://librosa.github.io",[33],[3135],{"type":26,"value":3136},"librosa",{"type":26,"value":3138}," and ",{"type":21,"tag":29,"props":3140,"children":3143},{"href":3141,"rel":3142},"https://numpy.org",[33],[3144],{"type":26,"value":3145},"NumPy",{"type":26,"value":3147}," for much of the audio processing; we based our implementation on that code, and used his parameters\n* The JUCE ",{"type":21,"tag":29,"props":3149,"children":3152},{"href":3150,"rel":3151},"https://docs.juce.com/master/tutorial_simple_fft.html",[33],[3153],{"type":26,"value":3154},"spectrogram demo",{"type":26,"value":3156}," was also very helpful",{"type":21,"tag":368,"props":3158,"children":3159},{},[3160],{"type":26,"value":372},{"title":8,"searchDepth":374,"depth":374,"links":3162},[3163,3164,3165,3166,3167,3168,3169,3170],{"id":692,"depth":377,"text":695},{"id":719,"depth":377,"text":722},{"id":990,"depth":377,"text":993},{"id":1709,"depth":377,"text":1712},{"id":2306,"depth":377,"text":2309},{"id":2393,"depth":377,"text":2396},{"id":2837,"depth":377,"text":2840},{"id":3111,"depth":377,"text":3114},"content:jbagley:2019-4:MakingSpectrogramsInJUCE.md","jbagley/2019-4/MakingSpectrogramsInJUCE.md","jbagley/2019-4/MakingSpectrogramsInJUCE",{"user":3175,"name":3176},"jbagley","Jason Bagley",{"_path":3178,"_dir":3179,"_draft":7,"_partial":7,"_locale":8,"title":3180,"description":3181,"excerpt":3181,"image":3182,"publishDate":3183,"tags":3184,"body":3185,"_type":380,"_id":4869,"_source":382,"_file":4870,"_stem":4871,"_extension":385,"author":4872},"/bporter/2019-3/animator","2019-3","Friz: A flexible animation controller for JUCE","As is often the case, I found myself working on a personal project and had some UI elements that really wanted to have some life to them on the screen.","/bporter/2019-3/img/animator.png","2019-03-01",[14,15,16],{"type":18,"children":3186,"toc":4848},[3187,3199,3212,3217,3231,3236,3241,3246,3255,3260,3276,3323,3345,3351,3356,3381,3386,3393,3398,3403,3411,3417,3438,3448,3453,3463,3468,3475,3485,3490,3503,3511,3521,3526,3534,3544,3549,3554,3561,3568,3578,3583,3596,3601,3679,3684,4447,4460,4465,4475,4488,4503,4516,4527,4548,4553,4564,4583,4588,4616,4626,4638,4643,4648,4694,4699,4746,4758,4764,4782,4804,4818,4823,4829,4834,4839,4844],{"type":21,"tag":22,"props":3188,"children":3189},{},[3190,3192,3197],{"type":26,"value":3191},"As is often the case, I found myself working on a personal project and had some UI elements that ",{"type":21,"tag":321,"props":3193,"children":3194},{},[3195],{"type":26,"value":3196},"really",{"type":26,"value":3198}," wanted to have some life to them on the screen.",{"type":21,"tag":22,"props":3200,"children":3201},{},[3202,3204,3210],{"type":26,"value":3203},"I tucked into the JUCE documentation expecting to see something that I could use to easily add some personality to the interface, and...didn't find what I was looking for. There's a ",{"type":21,"tag":119,"props":3205,"children":3207},{"className":3206},[],[3208],{"type":26,"value":3209},"ComponentAnimator",{"type":26,"value":3211}," class that supports moving a single component from one set of coordinates/bounds to another linearly, and also modify the component's alpha value to have it fade in or out.",{"type":21,"tag":22,"props":3213,"children":3214},{},[3215],{"type":26,"value":3216},"I was looking for something...more expressive.",{"type":21,"tag":22,"props":3218,"children":3219},{},[3220,3222,3229],{"type":26,"value":3221},"Earlier this year, I read Ge Wang's book \"Artful Design\" (see my review ",{"type":21,"tag":29,"props":3223,"children":3226},{"href":3224,"rel":3225},"https://artandlogic.com/2019/02/book-review-artful-design/",[33],[3227],{"type":26,"value":3228},"here",{"type":26,"value":3230},") and it's occupied a larger than expected chunk of my brain for a long time, especially some of the topics covered there in Chapter 3 on Visual Design.",{"type":21,"tag":22,"props":3232,"children":3233},{},[3234],{"type":26,"value":3235},"The ideas presented there on animation and design and interactivity resonated so strongly with me at least partly because I've always been a student and fan of animation as an art form, whether classic-era hand drawn or eventually computer-created. I have vivid memories from my time in college studying to become a composer of the look of shock and disappointment on my professor's face when I told him that most of my thinking about musical structure had its roots in the Warner Bros. cartoons of Chuck Jones.",{"type":21,"tag":22,"props":3237,"children":3238},{},[3239],{"type":26,"value":3240},"Somewhere in my house I still have an old copy of the book \"The Illusion of Life,\" written by Frank Thomas and Ollie Johnston, two of Disney's \"Nine Old Men,\" the animators who led the creation of Disney's post-war feature films. In that book they call out some principles that I wanted to be able to express at least in some small ways in my software.",{"type":21,"tag":22,"props":3242,"children":3243},{},[3244],{"type":26,"value":3245},"This video is a quick look at them:",{"type":21,"tag":22,"props":3247,"children":3248},{},[3249],{"type":21,"tag":29,"props":3250,"children":3253},{"href":3251,"rel":3252},"https://www.youtube.com/watch?v=yiGY0qiy8fY",[33],[3254],{"type":26,"value":3251},{"type":21,"tag":22,"props":3256,"children":3257},{},[3258],{"type":26,"value":3259},"Not all of them are directly applicable to software that's restricting itself to living in a 2D plane, but let's take them as an inspiration, and something to aspire to.",{"type":21,"tag":22,"props":3261,"children":3262},{},[3263,3264,3269,3270,3275],{"type":26,"value":61},{"type":21,"tag":29,"props":3265,"children":3267},{"href":41,"rel":3266},[33],[3268],{"type":26,"value":67},{"type":26,"value":69},{"type":21,"tag":29,"props":3271,"children":3273},{"href":72,"rel":3272},[33],[3274],{"type":26,"value":76},{"type":26,"value":78},{"type":21,"tag":83,"props":3277,"children":3278},{},[3279,3287,3295,3309],{"type":21,"tag":87,"props":3280,"children":3281},{},[3282,3286],{"type":21,"tag":91,"props":3283,"children":3284},{},[3285],{"type":26,"value":95},{"type":26,"value":97},{"type":21,"tag":87,"props":3288,"children":3289},{},[3290,3294],{"type":21,"tag":91,"props":3291,"children":3292},{},[3293],{"type":26,"value":105},{"type":26,"value":107},{"type":21,"tag":87,"props":3296,"children":3297},{},[3298,3302,3303,3308],{"type":21,"tag":91,"props":3299,"children":3300},{},[3301],{"type":26,"value":115},{"type":26,"value":117},{"type":21,"tag":119,"props":3304,"children":3306},{"className":3305},[],[3307],{"type":26,"value":124},{"type":26,"value":126},{"type":21,"tag":87,"props":3310,"children":3311},{},[3312,3316,3317,3322],{"type":21,"tag":91,"props":3313,"children":3314},{},[3315],{"type":26,"value":134},{"type":26,"value":136},{"type":21,"tag":29,"props":3318,"children":3320},{"href":139,"rel":3319},[33],[3321],{"type":26,"value":143},{"type":26,"value":47},{"type":21,"tag":22,"props":3324,"children":3325},{},[3326,3327,3332,3333,3338,3339,3344],{"type":26,"value":152},{"type":21,"tag":29,"props":3328,"children":3330},{"href":155,"rel":3329},[33],[3331],{"type":26,"value":159},{"type":26,"value":161},{"type":21,"tag":29,"props":3334,"children":3336},{"href":164,"rel":3335},[33],[3337],{"type":26,"value":168},{"type":26,"value":170},{"type":21,"tag":29,"props":3340,"children":3342},{"href":173,"rel":3341},[33],[3343],{"type":26,"value":177},{"type":26,"value":179},{"type":21,"tag":186,"props":3346,"children":3348},{"id":3347},"orbital-view",[3349],{"type":26,"value":3350},"Orbital View",{"type":21,"tag":22,"props":3352,"children":3353},{},[3354],{"type":26,"value":3355},"At the outermost level, all we want to be able to do is:",{"type":21,"tag":83,"props":3357,"children":3358},{},[3359,3364,3369],{"type":21,"tag":87,"props":3360,"children":3361},{},[3362],{"type":26,"value":3363},"define one or more values that will change from one value to another value...",{"type":21,"tag":87,"props":3365,"children":3366},{},[3367],{"type":26,"value":3368},"...over some period of time...",{"type":21,"tag":87,"props":3370,"children":3371},{},[3372,3374,3379],{"type":26,"value":3373},"...using some set of curves that describe ",{"type":21,"tag":321,"props":3375,"children":3376},{},[3377],{"type":26,"value":3378},"how",{"type":26,"value":3380}," the values change over time.",{"type":21,"tag":22,"props":3382,"children":3383},{},[3384],{"type":26,"value":3385},"It's just a question of how to create those pieces and plug them together. We'll start looking at the pieces from the bottom up, with a look at the curves we support, using a demo application that lets us play with things.",{"type":21,"tag":22,"props":3387,"children":3388},{},[3389],{"type":21,"tag":206,"props":3390,"children":3392},{"alt":3391,"src":3182},"The Animator",[],{"type":21,"tag":22,"props":3394,"children":3395},{},[3396],{"type":26,"value":3397},"The Animator is mostly a blank window that you can click inside to create randomly colored squares that will animate away from the mouse using one of the pre-defined curves. After the animation of a square completes, the square will animate its color to fade away, until it disappears.",{"type":21,"tag":22,"props":3399,"children":3400},{},[3401],{"type":26,"value":3402},"The control panel on the right lets you tweak parameters for each of the animation types to explore the different behaviors. The control panel is itself animated:",{"type":21,"tag":22,"props":3404,"children":3405},{},[3406],{"type":21,"tag":206,"props":3407,"children":3410},{"alt":3408,"src":3409},"panel","/bporter/2019-3/img/panel.mp4",[],{"type":21,"tag":186,"props":3412,"children":3414},{"id":3413},"available-curves",[3415],{"type":26,"value":3416},"Available Curves",{"type":21,"tag":22,"props":3418,"children":3419},{},[3420,3422,3428,3430,3436],{"type":26,"value":3421},"All of the classes that are used to generate animated data are derived from the ",{"type":21,"tag":119,"props":3423,"children":3425},{"className":3424},[],[3426],{"type":26,"value":3427},"friz::AnimatedValue",{"type":26,"value":3429}," class (which you can also derive classes from to define any behavior makes sense for your application). Right now, the following are available as part of the ",{"type":21,"tag":119,"props":3431,"children":3433},{"className":3432},[],[3434],{"type":26,"value":3435},"friz",{"type":26,"value":3437}," module:",{"type":21,"tag":484,"props":3439,"children":3441},{"id":3440},"constant",[3442],{"type":21,"tag":119,"props":3443,"children":3445},{"className":3444},[],[3446],{"type":26,"value":3447},"Constant",{"type":21,"tag":22,"props":3449,"children":3450},{},[3451],{"type":26,"value":3452},"Generates a single value for the duration of the effect. Perhaps it's not obviously useful to be able to generate unchanging values in an animation, this can be useful when building up sequences of multiple effects or when controlling the regeneration of cyclical effects.",{"type":21,"tag":484,"props":3454,"children":3456},{"id":3455},"linear",[3457],{"type":21,"tag":119,"props":3458,"children":3460},{"className":3459},[],[3461],{"type":26,"value":3462},"Linear",{"type":21,"tag":22,"props":3464,"children":3465},{},[3466],{"type":26,"value":3467},"Performs a simple linear interpolation between two values over a specified amount of time.",{"type":21,"tag":22,"props":3469,"children":3470},{},[3471],{"type":21,"tag":206,"props":3472,"children":3474},{"alt":3455,"src":3473},"/bporter/2019-3/img/linear.mp4",[],{"type":21,"tag":484,"props":3476,"children":3478},{"id":3477},"easein",[3479],{"type":21,"tag":119,"props":3480,"children":3482},{"className":3481},[],[3483],{"type":26,"value":3484},"EaseIn",{"type":21,"tag":22,"props":3486,"children":3487},{},[3488],{"type":26,"value":3489},"Accelerates quickly away from the start value, decelerates smoothly into the end value. The amount of time for the animation is not specified, instead your code specifies a tolerance of how close to the end value it needs to be before the effect is complete.",{"type":21,"tag":22,"props":3491,"children":3492},{},[3493,3495,3501],{"type":26,"value":3494},"In his ",{"type":21,"tag":119,"props":3496,"children":3498},{"className":3497},[],[3499],{"type":26,"value":3500},"Artful Design",{"type":26,"value":3502}," book, Dr Wang refers to this as \"Zeno's Interpolator\" because it keeps moving some fraction of the way between the current value and the end value, never quite getting there. Our tolerance argument keeps us from calculating ever smaller slices.",{"type":21,"tag":22,"props":3504,"children":3505},{},[3506],{"type":21,"tag":206,"props":3507,"children":3510},{"alt":3508,"src":3509},"easeIn","/bporter/2019-3/img/easeIn.mp4",[],{"type":21,"tag":484,"props":3512,"children":3514},{"id":3513},"easeout",[3515],{"type":21,"tag":119,"props":3516,"children":3518},{"className":3517},[],[3519],{"type":26,"value":3520},"EaseOut",{"type":21,"tag":22,"props":3522,"children":3523},{},[3524],{"type":26,"value":3525},"Accelerates slowly away from the start values, accelerates into the end value. Also tolerance-based.",{"type":21,"tag":22,"props":3527,"children":3528},{},[3529],{"type":21,"tag":206,"props":3530,"children":3533},{"alt":3531,"src":3532},"easeOut","/bporter/2019-3/img/easeOut.mp4",[],{"type":21,"tag":484,"props":3535,"children":3537},{"id":3536},"spring",[3538],{"type":21,"tag":119,"props":3539,"children":3541},{"className":3540},[],[3542],{"type":26,"value":3543},"Spring",{"type":21,"tag":22,"props":3545,"children":3546},{},[3547],{"type":26,"value":3548},"Accelerates away from the start value, and may go past the end value; if so, it does a simplified model of the oscillation of a  dampened spring (...and if I wasn't worried that there might be physics professors reading this I'd insert some text here about Young's Modulus and how that affects the behavior here but instead will point out that this is in no way intended as a realistic physics modeling engine).",{"type":21,"tag":22,"props":3550,"children":3551},{},[3552],{"type":26,"value":3553},"A few examples of different damping values:",{"type":21,"tag":22,"props":3555,"children":3556},{},[3557],{"type":21,"tag":206,"props":3558,"children":3560},{"alt":3536,"src":3559},"/bporter/2019-3/img/spring1.mp4",[],{"type":21,"tag":22,"props":3562,"children":3563},{},[3564],{"type":21,"tag":206,"props":3565,"children":3567},{"alt":3536,"src":3566},"/bporter/2019-3/img/spring2.mp4",[],{"type":21,"tag":484,"props":3569,"children":3571},{"id":3570},"sinusoid",[3572],{"type":21,"tag":119,"props":3573,"children":3575},{"className":3574},[],[3576],{"type":26,"value":3577},"Sinusoid",{"type":21,"tag":22,"props":3579,"children":3580},{},[3581],{"type":26,"value":3582},"Generates sin/cos wave values between any two arbitrary phase points. The data generated is always in the natural (-1.0..+1.0) range of those functions.",{"type":21,"tag":186,"props":3584,"children":3586},{"id":3585},"the-animation-class",[3587,3588,3594],{"type":26,"value":1722},{"type":21,"tag":119,"props":3589,"children":3591},{"className":3590},[],[3592],{"type":26,"value":3593},"Animation",{"type":26,"value":3595}," class",{"type":21,"tag":22,"props":3597,"children":3598},{},[3599],{"type":26,"value":3600},"In code, the basic process is to",{"type":21,"tag":3602,"props":3603,"children":3604},"ol",{},[3605,3610,3623,3635,3647,3667],{"type":21,"tag":87,"props":3606,"children":3607},{},[3608],{"type":26,"value":3609},"decide how many curves your animation needs -- perhaps one for a control panel opening/closing or a color/alpha change, two for moving a component around, more for changing location/size of a component",{"type":21,"tag":87,"props":3611,"children":3612},{},[3613,3615,3621],{"type":26,"value":3614},"Create ",{"type":21,"tag":119,"props":3616,"children":3618},{"className":3617},[],[3619],{"type":26,"value":3620},"AnimatedValue",{"type":26,"value":3622}," objects of the appropriate types and parameters",{"type":21,"tag":87,"props":3624,"children":3625},{},[3626,3628,3633],{"type":26,"value":3627},"Add those value objects to a new instance of the ",{"type":21,"tag":119,"props":3629,"children":3631},{"className":3630},[],[3632],{"type":26,"value":3593},{"type":26,"value":3634}," class.",{"type":21,"tag":87,"props":3636,"children":3637},{},[3638,3640,3645],{"type":26,"value":3639},"Configure the ",{"type":21,"tag":119,"props":3641,"children":3643},{"className":3642},[],[3644],{"type":26,"value":3593},{"type":26,"value":3646}," appropriately -- usually, this will require you to at least provide a function to call on each frame of the animation.",{"type":21,"tag":87,"props":3648,"children":3649},{},[3650,3652,3657,3659,3665],{"type":26,"value":3651},"Add that ",{"type":21,"tag":119,"props":3653,"children":3655},{"className":3654},[],[3656],{"type":26,"value":3593},{"type":26,"value":3658}," object to the appropriate ",{"type":21,"tag":119,"props":3660,"children":3662},{"className":3661},[],[3663],{"type":26,"value":3664},"Animator",{"type":26,"value":3666}," object, which will start executing the animation.",{"type":21,"tag":87,"props":3668,"children":3669},{},[3670,3672,3677],{"type":26,"value":3671},"When all of the values in the animation are complete, your completion function (if any) will be called, and the ",{"type":21,"tag":119,"props":3673,"children":3675},{"className":3674},[],[3676],{"type":26,"value":3593},{"type":26,"value":3678}," will be deleted.",{"type":21,"tag":22,"props":3680,"children":3681},{},[3682],{"type":26,"value":3683},"An example—this is the code inside the animator demo application that closes the control panel when the mouse is clicked:",{"type":21,"tag":216,"props":3685,"children":3687},{"className":218,"code":3686,"language":220,"meta":8,"style":8},"void MainComponent::ClosePanel()\n{\n   // 1. get parameters in place \n   jassert(PanelState::kOpen == fPanelState);\n   int width = this->getWidth();\n   \n   int startX = fControls->getX();\n   int endX = width - kClosedPanelWidth;\n   \n   float accel = 1.4f;\n   float dampen = 0.4f;\n   \n   // 2. Create a Spring value\n   auto curve = std::make_unique\u003Cfriz::Spring>(startX, endX, 0.5, accel, dampen);\n   \n   // 3. Create an animation and add the curve to it. \n   auto animation = std::make_unique\u003Cfriz::Animation\u003C1>>(\n      friz::Animation\u003C1>::SourceList{std::move(curve)}, 0);\n   \n   // 4. Add a lambda to the animation that will move the panel\n   animation->OnUpdate([=] (int id, const friz::Animation\u003C1>::ValueList& val){\n      fControls->setTopLeftPosition(val[0], 0);\n   });\n   \n   // 5. Add a lambda to call when the effect is complete. \n   animation->OnCompletion([=] (int id) {\n      fPanelState = PanelState::kClosed;\n   });\n   \n   fPanelState = PanelState::kClosing;\n   // 6. Add the animation to the animator, starting it.  \n   fPanelAnimator.AddAnimation(std::move(animation));\n   \n}\n\n",[3688],{"type":21,"tag":119,"props":3689,"children":3690},{"__ignoreMap":8},[3691,3718,3725,3733,3765,3799,3807,3837,3866,3873,3903,3932,3939,3947,4007,4014,4022,4080,4135,4142,4150,4239,4274,4282,4289,4297,4334,4356,4363,4370,4391,4399,4433,4440],{"type":21,"tag":226,"props":3692,"children":3693},{"class":228,"line":229},[3694,3699,3704,3708,3713],{"type":21,"tag":226,"props":3695,"children":3696},{"style":233},[3697],{"type":26,"value":3698},"void",{"type":21,"tag":226,"props":3700,"children":3701},{"style":819},[3702],{"type":26,"value":3703}," MainComponent",{"type":21,"tag":226,"props":3705,"children":3706},{"style":828},[3707],{"type":26,"value":895},{"type":21,"tag":226,"props":3709,"children":3710},{"style":819},[3711],{"type":26,"value":3712},"ClosePanel",{"type":21,"tag":226,"props":3714,"children":3715},{"style":828},[3716],{"type":26,"value":3717},"()\n",{"type":21,"tag":226,"props":3719,"children":3720},{"class":228,"line":377},[3721],{"type":21,"tag":226,"props":3722,"children":3723},{"style":828},[3724],{"type":26,"value":831},{"type":21,"tag":226,"props":3726,"children":3727},{"class":228,"line":374},[3728],{"type":21,"tag":226,"props":3729,"children":3730},{"style":805},[3731],{"type":26,"value":3732},"   // 1. get parameters in place \n",{"type":21,"tag":226,"props":3734,"children":3735},{"class":228,"line":834},[3736,3741,3745,3750,3755,3760],{"type":21,"tag":226,"props":3737,"children":3738},{"style":819},[3739],{"type":26,"value":3740},"   jassert",{"type":21,"tag":226,"props":3742,"children":3743},{"style":828},[3744],{"type":26,"value":904},{"type":21,"tag":226,"props":3746,"children":3747},{"style":819},[3748],{"type":26,"value":3749},"PanelState",{"type":21,"tag":226,"props":3751,"children":3752},{"style":828},[3753],{"type":26,"value":3754},"::kOpen ",{"type":21,"tag":226,"props":3756,"children":3757},{"style":233},[3758],{"type":26,"value":3759},"==",{"type":21,"tag":226,"props":3761,"children":3762},{"style":828},[3763],{"type":26,"value":3764}," fPanelState);\n",{"type":21,"tag":226,"props":3766,"children":3767},{"class":228,"line":843},[3768,3773,3778,3782,3786,3790,3795],{"type":21,"tag":226,"props":3769,"children":3770},{"style":233},[3771],{"type":26,"value":3772},"   int",{"type":21,"tag":226,"props":3774,"children":3775},{"style":828},[3776],{"type":26,"value":3777}," width ",{"type":21,"tag":226,"props":3779,"children":3780},{"style":233},[3781],{"type":26,"value":1165},{"type":21,"tag":226,"props":3783,"children":3784},{"style":1182},[3785],{"type":26,"value":2896},{"type":21,"tag":226,"props":3787,"children":3788},{"style":828},[3789],{"type":26,"value":2901},{"type":21,"tag":226,"props":3791,"children":3792},{"style":819},[3793],{"type":26,"value":3794},"getWidth",{"type":21,"tag":226,"props":3796,"children":3797},{"style":828},[3798],{"type":26,"value":1225},{"type":21,"tag":226,"props":3800,"children":3801},{"class":228,"line":857},[3802],{"type":21,"tag":226,"props":3803,"children":3804},{"style":828},[3805],{"type":26,"value":3806},"   \n",{"type":21,"tag":226,"props":3808,"children":3809},{"class":228,"line":865},[3810,3814,3819,3823,3828,3833],{"type":21,"tag":226,"props":3811,"children":3812},{"style":233},[3813],{"type":26,"value":3772},{"type":21,"tag":226,"props":3815,"children":3816},{"style":828},[3817],{"type":26,"value":3818}," startX ",{"type":21,"tag":226,"props":3820,"children":3821},{"style":233},[3822],{"type":26,"value":1165},{"type":21,"tag":226,"props":3824,"children":3825},{"style":828},[3826],{"type":26,"value":3827}," fControls->",{"type":21,"tag":226,"props":3829,"children":3830},{"style":819},[3831],{"type":26,"value":3832},"getX",{"type":21,"tag":226,"props":3834,"children":3835},{"style":828},[3836],{"type":26,"value":1225},{"type":21,"tag":226,"props":3838,"children":3839},{"class":228,"line":874},[3840,3844,3849,3853,3857,3861],{"type":21,"tag":226,"props":3841,"children":3842},{"style":233},[3843],{"type":26,"value":3772},{"type":21,"tag":226,"props":3845,"children":3846},{"style":828},[3847],{"type":26,"value":3848}," endX ",{"type":21,"tag":226,"props":3850,"children":3851},{"style":233},[3852],{"type":26,"value":1165},{"type":21,"tag":226,"props":3854,"children":3855},{"style":828},[3856],{"type":26,"value":3777},{"type":21,"tag":226,"props":3858,"children":3859},{"style":233},[3860],{"type":26,"value":1332},{"type":21,"tag":226,"props":3862,"children":3863},{"style":828},[3864],{"type":26,"value":3865}," kClosedPanelWidth;\n",{"type":21,"tag":226,"props":3867,"children":3868},{"class":228,"line":884},[3869],{"type":21,"tag":226,"props":3870,"children":3871},{"style":828},[3872],{"type":26,"value":3806},{"type":21,"tag":226,"props":3874,"children":3875},{"class":228,"line":927},[3876,3881,3886,3890,3895,3899],{"type":21,"tag":226,"props":3877,"children":3878},{"style":233},[3879],{"type":26,"value":3880},"   float",{"type":21,"tag":226,"props":3882,"children":3883},{"style":828},[3884],{"type":26,"value":3885}," accel ",{"type":21,"tag":226,"props":3887,"children":3888},{"style":233},[3889],{"type":26,"value":1165},{"type":21,"tag":226,"props":3891,"children":3892},{"style":1182},[3893],{"type":26,"value":3894}," 1.4",{"type":21,"tag":226,"props":3896,"children":3897},{"style":233},[3898],{"type":26,"value":2561},{"type":21,"tag":226,"props":3900,"children":3901},{"style":828},[3902],{"type":26,"value":2531},{"type":21,"tag":226,"props":3904,"children":3905},{"class":228,"line":964},[3906,3910,3915,3919,3924,3928],{"type":21,"tag":226,"props":3907,"children":3908},{"style":233},[3909],{"type":26,"value":3880},{"type":21,"tag":226,"props":3911,"children":3912},{"style":828},[3913],{"type":26,"value":3914}," dampen ",{"type":21,"tag":226,"props":3916,"children":3917},{"style":233},[3918],{"type":26,"value":1165},{"type":21,"tag":226,"props":3920,"children":3921},{"style":1182},[3922],{"type":26,"value":3923}," 0.4",{"type":21,"tag":226,"props":3925,"children":3926},{"style":233},[3927],{"type":26,"value":2561},{"type":21,"tag":226,"props":3929,"children":3930},{"style":828},[3931],{"type":26,"value":2531},{"type":21,"tag":226,"props":3933,"children":3934},{"class":228,"line":973},[3935],{"type":21,"tag":226,"props":3936,"children":3937},{"style":828},[3938],{"type":26,"value":3806},{"type":21,"tag":226,"props":3940,"children":3941},{"class":228,"line":981},[3942],{"type":21,"tag":226,"props":3943,"children":3944},{"style":805},[3945],{"type":26,"value":3946},"   // 2. Create a Spring value\n",{"type":21,"tag":226,"props":3948,"children":3949},{"class":228,"line":1357},[3950,3954,3959,3963,3967,3971,3976,3980,3984,3988,3992,3997,4002],{"type":21,"tag":226,"props":3951,"children":3952},{"style":233},[3953],{"type":26,"value":2728},{"type":21,"tag":226,"props":3955,"children":3956},{"style":828},[3957],{"type":26,"value":3958}," curve ",{"type":21,"tag":226,"props":3960,"children":3961},{"style":233},[3962],{"type":26,"value":1165},{"type":21,"tag":226,"props":3964,"children":3965},{"style":819},[3966],{"type":26,"value":1035},{"type":21,"tag":226,"props":3968,"children":3969},{"style":828},[3970],{"type":26,"value":895},{"type":21,"tag":226,"props":3972,"children":3973},{"style":819},[3974],{"type":26,"value":3975},"make_unique",{"type":21,"tag":226,"props":3977,"children":3978},{"style":828},[3979],{"type":26,"value":1049},{"type":21,"tag":226,"props":3981,"children":3982},{"style":819},[3983],{"type":26,"value":3435},{"type":21,"tag":226,"props":3985,"children":3986},{"style":828},[3987],{"type":26,"value":895},{"type":21,"tag":226,"props":3989,"children":3990},{"style":819},[3991],{"type":26,"value":3543},{"type":21,"tag":226,"props":3993,"children":3994},{"style":828},[3995],{"type":26,"value":3996},">(startX, endX, ",{"type":21,"tag":226,"props":3998,"children":3999},{"style":1182},[4000],{"type":26,"value":4001},"0.5",{"type":21,"tag":226,"props":4003,"children":4004},{"style":828},[4005],{"type":26,"value":4006},", accel, dampen);\n",{"type":21,"tag":226,"props":4008,"children":4009},{"class":228,"line":1365},[4010],{"type":21,"tag":226,"props":4011,"children":4012},{"style":828},[4013],{"type":26,"value":3806},{"type":21,"tag":226,"props":4015,"children":4016},{"class":228,"line":1373},[4017],{"type":21,"tag":226,"props":4018,"children":4019},{"style":805},[4020],{"type":26,"value":4021},"   // 3. Create an animation and add the curve to it. \n",{"type":21,"tag":226,"props":4023,"children":4024},{"class":228,"line":1382},[4025,4029,4034,4038,4042,4046,4050,4054,4058,4062,4066,4070,4075],{"type":21,"tag":226,"props":4026,"children":4027},{"style":233},[4028],{"type":26,"value":2728},{"type":21,"tag":226,"props":4030,"children":4031},{"style":828},[4032],{"type":26,"value":4033}," animation ",{"type":21,"tag":226,"props":4035,"children":4036},{"style":233},[4037],{"type":26,"value":1165},{"type":21,"tag":226,"props":4039,"children":4040},{"style":819},[4041],{"type":26,"value":1035},{"type":21,"tag":226,"props":4043,"children":4044},{"style":828},[4045],{"type":26,"value":895},{"type":21,"tag":226,"props":4047,"children":4048},{"style":819},[4049],{"type":26,"value":3975},{"type":21,"tag":226,"props":4051,"children":4052},{"style":828},[4053],{"type":26,"value":1049},{"type":21,"tag":226,"props":4055,"children":4056},{"style":819},[4057],{"type":26,"value":3435},{"type":21,"tag":226,"props":4059,"children":4060},{"style":828},[4061],{"type":26,"value":895},{"type":21,"tag":226,"props":4063,"children":4064},{"style":819},[4065],{"type":26,"value":3593},{"type":21,"tag":226,"props":4067,"children":4068},{"style":828},[4069],{"type":26,"value":1049},{"type":21,"tag":226,"props":4071,"children":4072},{"style":1182},[4073],{"type":26,"value":4074},"1",{"type":21,"tag":226,"props":4076,"children":4077},{"style":828},[4078],{"type":26,"value":4079},">>(\n",{"type":21,"tag":226,"props":4081,"children":4082},{"class":228,"line":1435},[4083,4088,4092,4096,4100,4104,4109,4113,4117,4122,4127,4131],{"type":21,"tag":226,"props":4084,"children":4085},{"style":819},[4086],{"type":26,"value":4087},"      friz",{"type":21,"tag":226,"props":4089,"children":4090},{"style":828},[4091],{"type":26,"value":895},{"type":21,"tag":226,"props":4093,"children":4094},{"style":819},[4095],{"type":26,"value":3593},{"type":21,"tag":226,"props":4097,"children":4098},{"style":828},[4099],{"type":26,"value":1049},{"type":21,"tag":226,"props":4101,"children":4102},{"style":1182},[4103],{"type":26,"value":4074},{"type":21,"tag":226,"props":4105,"children":4106},{"style":828},[4107],{"type":26,"value":4108},">::SourceList{",{"type":21,"tag":226,"props":4110,"children":4111},{"style":819},[4112],{"type":26,"value":947},{"type":21,"tag":226,"props":4114,"children":4115},{"style":828},[4116],{"type":26,"value":895},{"type":21,"tag":226,"props":4118,"children":4119},{"style":819},[4120],{"type":26,"value":4121},"move",{"type":21,"tag":226,"props":4123,"children":4124},{"style":828},[4125],{"type":26,"value":4126},"(curve)}, ",{"type":21,"tag":226,"props":4128,"children":4129},{"style":1182},[4130],{"type":26,"value":1185},{"type":21,"tag":226,"props":4132,"children":4133},{"style":828},[4134],{"type":26,"value":1190},{"type":21,"tag":226,"props":4136,"children":4137},{"class":228,"line":1443},[4138],{"type":21,"tag":226,"props":4139,"children":4140},{"style":828},[4141],{"type":26,"value":3806},{"type":21,"tag":226,"props":4143,"children":4144},{"class":228,"line":1452},[4145],{"type":21,"tag":226,"props":4146,"children":4147},{"style":805},[4148],{"type":26,"value":4149},"   // 4. Add a lambda to the animation that will move the panel\n",{"type":21,"tag":226,"props":4151,"children":4152},{"class":228,"line":1498},[4153,4158,4163,4168,4172,4177,4181,4186,4190,4194,4199,4203,4207,4211,4215,4220,4225,4229,4234],{"type":21,"tag":226,"props":4154,"children":4155},{"style":828},[4156],{"type":26,"value":4157},"   animation->",{"type":21,"tag":226,"props":4159,"children":4160},{"style":819},[4161],{"type":26,"value":4162},"OnUpdate",{"type":21,"tag":226,"props":4164,"children":4165},{"style":828},[4166],{"type":26,"value":4167},"([",{"type":21,"tag":226,"props":4169,"children":4170},{"style":233},[4171],{"type":26,"value":1165},{"type":21,"tag":226,"props":4173,"children":4174},{"style":828},[4175],{"type":26,"value":4176},"] (",{"type":21,"tag":226,"props":4178,"children":4179},{"style":233},[4180],{"type":26,"value":909},{"type":21,"tag":226,"props":4182,"children":4183},{"style":1113},[4184],{"type":26,"value":4185}," id",{"type":21,"tag":226,"props":4187,"children":4188},{"style":828},[4189],{"type":26,"value":1121},{"type":21,"tag":226,"props":4191,"children":4192},{"style":233},[4193],{"type":26,"value":1100},{"type":21,"tag":226,"props":4195,"children":4196},{"style":819},[4197],{"type":26,"value":4198}," friz",{"type":21,"tag":226,"props":4200,"children":4201},{"style":828},[4202],{"type":26,"value":895},{"type":21,"tag":226,"props":4204,"children":4205},{"style":819},[4206],{"type":26,"value":3593},{"type":21,"tag":226,"props":4208,"children":4209},{"style":828},[4210],{"type":26,"value":1049},{"type":21,"tag":226,"props":4212,"children":4213},{"style":1182},[4214],{"type":26,"value":4074},{"type":21,"tag":226,"props":4216,"children":4217},{"style":828},[4218],{"type":26,"value":4219},">::",{"type":21,"tag":226,"props":4221,"children":4222},{"style":819},[4223],{"type":26,"value":4224},"ValueList",{"type":21,"tag":226,"props":4226,"children":4227},{"style":233},[4228],{"type":26,"value":1110},{"type":21,"tag":226,"props":4230,"children":4231},{"style":1113},[4232],{"type":26,"value":4233}," val",{"type":21,"tag":226,"props":4235,"children":4236},{"style":828},[4237],{"type":26,"value":4238},"){\n",{"type":21,"tag":226,"props":4240,"children":4241},{"class":228,"line":1506},[4242,4247,4252,4257,4261,4266,4270],{"type":21,"tag":226,"props":4243,"children":4244},{"style":828},[4245],{"type":26,"value":4246},"      fControls->",{"type":21,"tag":226,"props":4248,"children":4249},{"style":819},[4250],{"type":26,"value":4251},"setTopLeftPosition",{"type":21,"tag":226,"props":4253,"children":4254},{"style":828},[4255],{"type":26,"value":4256},"(val[",{"type":21,"tag":226,"props":4258,"children":4259},{"style":1182},[4260],{"type":26,"value":1185},{"type":21,"tag":226,"props":4262,"children":4263},{"style":828},[4264],{"type":26,"value":4265},"], ",{"type":21,"tag":226,"props":4267,"children":4268},{"style":1182},[4269],{"type":26,"value":1185},{"type":21,"tag":226,"props":4271,"children":4272},{"style":828},[4273],{"type":26,"value":1190},{"type":21,"tag":226,"props":4275,"children":4276},{"class":228,"line":1515},[4277],{"type":21,"tag":226,"props":4278,"children":4279},{"style":828},[4280],{"type":26,"value":4281},"   });\n",{"type":21,"tag":226,"props":4283,"children":4284},{"class":228,"line":1524},[4285],{"type":21,"tag":226,"props":4286,"children":4287},{"style":828},[4288],{"type":26,"value":3806},{"type":21,"tag":226,"props":4290,"children":4291},{"class":228,"line":1579},[4292],{"type":21,"tag":226,"props":4293,"children":4294},{"style":805},[4295],{"type":26,"value":4296},"   // 5. Add a lambda to call when the effect is complete. \n",{"type":21,"tag":226,"props":4298,"children":4299},{"class":228,"line":1587},[4300,4304,4309,4313,4317,4321,4325,4329],{"type":21,"tag":226,"props":4301,"children":4302},{"style":828},[4303],{"type":26,"value":4157},{"type":21,"tag":226,"props":4305,"children":4306},{"style":819},[4307],{"type":26,"value":4308},"OnCompletion",{"type":21,"tag":226,"props":4310,"children":4311},{"style":828},[4312],{"type":26,"value":4167},{"type":21,"tag":226,"props":4314,"children":4315},{"style":233},[4316],{"type":26,"value":1165},{"type":21,"tag":226,"props":4318,"children":4319},{"style":828},[4320],{"type":26,"value":4176},{"type":21,"tag":226,"props":4322,"children":4323},{"style":233},[4324],{"type":26,"value":909},{"type":21,"tag":226,"props":4326,"children":4327},{"style":1113},[4328],{"type":26,"value":4185},{"type":21,"tag":226,"props":4330,"children":4331},{"style":828},[4332],{"type":26,"value":4333},") {\n",{"type":21,"tag":226,"props":4335,"children":4336},{"class":228,"line":1596},[4337,4342,4346,4351],{"type":21,"tag":226,"props":4338,"children":4339},{"style":828},[4340],{"type":26,"value":4341},"      fPanelState ",{"type":21,"tag":226,"props":4343,"children":4344},{"style":233},[4345],{"type":26,"value":1165},{"type":21,"tag":226,"props":4347,"children":4348},{"style":819},[4349],{"type":26,"value":4350}," PanelState",{"type":21,"tag":226,"props":4352,"children":4353},{"style":828},[4354],{"type":26,"value":4355},"::kClosed;\n",{"type":21,"tag":226,"props":4357,"children":4358},{"class":228,"line":1604},[4359],{"type":21,"tag":226,"props":4360,"children":4361},{"style":828},[4362],{"type":26,"value":4281},{"type":21,"tag":226,"props":4364,"children":4365},{"class":228,"line":1631},[4366],{"type":21,"tag":226,"props":4367,"children":4368},{"style":828},[4369],{"type":26,"value":3806},{"type":21,"tag":226,"props":4371,"children":4372},{"class":228,"line":1639},[4373,4378,4382,4386],{"type":21,"tag":226,"props":4374,"children":4375},{"style":828},[4376],{"type":26,"value":4377},"   fPanelState ",{"type":21,"tag":226,"props":4379,"children":4380},{"style":233},[4381],{"type":26,"value":1165},{"type":21,"tag":226,"props":4383,"children":4384},{"style":819},[4385],{"type":26,"value":4350},{"type":21,"tag":226,"props":4387,"children":4388},{"style":828},[4389],{"type":26,"value":4390},"::kClosing;\n",{"type":21,"tag":226,"props":4392,"children":4393},{"class":228,"line":1648},[4394],{"type":21,"tag":226,"props":4395,"children":4396},{"style":805},[4397],{"type":26,"value":4398},"   // 6. Add the animation to the animator, starting it.  \n",{"type":21,"tag":226,"props":4400,"children":4401},{"class":228,"line":1656},[4402,4407,4412,4416,4420,4424,4428],{"type":21,"tag":226,"props":4403,"children":4404},{"style":828},[4405],{"type":26,"value":4406},"   fPanelAnimator.",{"type":21,"tag":226,"props":4408,"children":4409},{"style":819},[4410],{"type":26,"value":4411},"AddAnimation",{"type":21,"tag":226,"props":4413,"children":4414},{"style":828},[4415],{"type":26,"value":904},{"type":21,"tag":226,"props":4417,"children":4418},{"style":819},[4419],{"type":26,"value":947},{"type":21,"tag":226,"props":4421,"children":4422},{"style":828},[4423],{"type":26,"value":895},{"type":21,"tag":226,"props":4425,"children":4426},{"style":819},[4427],{"type":26,"value":4121},{"type":21,"tag":226,"props":4429,"children":4430},{"style":828},[4431],{"type":26,"value":4432},"(animation));\n",{"type":21,"tag":226,"props":4434,"children":4435},{"class":228,"line":1665},[4436],{"type":21,"tag":226,"props":4437,"children":4438},{"style":828},[4439],{"type":26,"value":3806},{"type":21,"tag":226,"props":4441,"children":4442},{"class":228,"line":1684},[4443],{"type":21,"tag":226,"props":4444,"children":4445},{"style":828},[4446],{"type":26,"value":987},{"type":21,"tag":22,"props":4448,"children":4449},{},[4450,4452,4458],{"type":26,"value":4451},"Each animation may also be given an optional ",{"type":21,"tag":119,"props":4453,"children":4455},{"className":4454},[],[4456],{"type":26,"value":4457},"id",{"type":26,"value":4459}," value.",{"type":21,"tag":22,"props":4461,"children":4462},{},[4463],{"type":26,"value":4464},"This id will be passed as an argument to the Update and Completion functions registered with the animation, and may also be used to cancel an animation that's in progress.",{"type":21,"tag":484,"props":4466,"children":4468},{"id":4467},"sequence",[4469],{"type":21,"tag":119,"props":4470,"children":4472},{"className":4471},[],[4473],{"type":26,"value":4474},"Sequence",{"type":21,"tag":22,"props":4476,"children":4477},{},[4478,4480,4486],{"type":26,"value":4479},"There's also a separate ",{"type":21,"tag":119,"props":4481,"children":4483},{"className":4482},[],[4484],{"type":26,"value":4485},"friz::Sequence",{"type":26,"value":4487}," class that can hold a series of Animation objects and execute them in order as if they were a single long animation. Here's an example of EaseIn and EaseOut combined into a single effect:",{"type":21,"tag":4489,"props":4490,"children":4492},"h4",{"id":4491},"about-those-lambda-callbacks",[4493,4495,4501],{"type":26,"value":4494},"About Those ",{"type":21,"tag":119,"props":4496,"children":4498},{"className":4497},[],[4499],{"type":26,"value":4500},"lambda",{"type":26,"value":4502}," Callbacks",{"type":21,"tag":22,"props":4504,"children":4505},{},[4506,4508,4515],{"type":26,"value":4507},"You will want to provide at least one (a per-frame callback) and possibly two (an animation complete callback) lambda(s) to each animation you create. The example code in the demo app gives several models for how these functions should be declared and used. If you, like me, have a bit of Unfrozen Caveman Programmer in you, there are tons of references out there on using lambdas in modern C++; my favorite in terms of depth can be found in Meyers' ",{"type":21,"tag":29,"props":4509,"children":4512},{"href":4510,"rel":4511},"https://www.aristeia.com/books.html",[33],[4513],{"type":26,"value":4514},"EffectiveModern C++",{"type":26,"value":47},{"type":21,"tag":22,"props":4517,"children":4518},{},[4519,4520,4525],{"type":26,"value":1722},{"type":21,"tag":91,"props":4521,"children":4522},{},[4523],{"type":26,"value":4524},"per-frame update callback",{"type":26,"value":4526}," will always have two arguments:",{"type":21,"tag":83,"props":4528,"children":4529},{},[4530,4535],{"type":21,"tag":87,"props":4531,"children":4532},{},[4533],{"type":26,"value":4534},"The ID for the animation being performed",{"type":21,"tag":87,"props":4536,"children":4537},{},[4538,4540,4546],{"type":26,"value":4539},"an ",{"type":21,"tag":119,"props":4541,"children":4543},{"className":4542},[],[4544],{"type":26,"value":4545},"std::array",{"type":26,"value":4547}," of float values for this frame.",{"type":21,"tag":22,"props":4549,"children":4550},{},[4551],{"type":26,"value":4552},"Your callback should do whatever it needs to do to implement the effect as quickly as it can—remember that another callback will be coming when the timer elapses again.",{"type":21,"tag":22,"props":4554,"children":4555},{},[4556,4557,4562],{"type":26,"value":1722},{"type":21,"tag":91,"props":4558,"children":4559},{},[4560],{"type":26,"value":4561},"completion",{"type":26,"value":4563}," callback will have a single argument, the ID of the animation being completed.",{"type":21,"tag":22,"props":4565,"children":4566},{},[4567,4569,4574,4576,4581],{"type":26,"value":4568},"Note that if you are combining multiple ",{"type":21,"tag":119,"props":4570,"children":4572},{"className":4571},[],[4573],{"type":26,"value":3593},{"type":26,"value":4575}," objects together into a single ",{"type":21,"tag":119,"props":4577,"children":4579},{"className":4578},[],[4580],{"type":26,"value":4474},{"type":26,"value":4582},", your callbacks will receive updates using the ID assigned to the sequence, not to the individual animation objects.",{"type":21,"tag":22,"props":4584,"children":4585},{},[4586],{"type":26,"value":4587},"You should also be very careful to watch the lifetimes of variables carried inside of the lambda objects via capture. It's tempting to think that \"hey, we're using smart pointers because we're all modern here so object lifetime is handled for us!\"",{"type":21,"tag":22,"props":4589,"children":4590},{},[4591,4593,4598,4600,4606,4608,4614],{"type":26,"value":4592},"That's not always true with constructs like these -- make sure that you puzzle out all the ownership and lifetime issues (and test, test, test!). A good pattern to follow is to make sure that the object that owns the ",{"type":21,"tag":119,"props":4594,"children":4596},{"className":4595},[],[4597],{"type":26,"value":3664},{"type":26,"value":4599}," object driving an animation also owns any ",{"type":21,"tag":119,"props":4601,"children":4603},{"className":4602},[],[4604],{"type":26,"value":4605},"Component",{"type":26,"value":4607}," objects that will be controlled by an animation, and that in the destructor of that object, you ",{"type":21,"tag":119,"props":4609,"children":4611},{"className":4610},[],[4612],{"type":26,"value":4613},"CancelAllAnimations()",{"type":26,"value":4615}," to prevent stray timer updates from trying to update a component that's about to be deleted.",{"type":21,"tag":186,"props":4617,"children":4619},{"id":4618},"the-animator",[4620,4621],{"type":26,"value":1722},{"type":21,"tag":119,"props":4622,"children":4624},{"className":4623},[],[4625],{"type":26,"value":3664},{"type":21,"tag":22,"props":4627,"children":4628},{},[4629,4631,4636],{"type":26,"value":4630},"Your app will need one or more instances of the ",{"type":21,"tag":119,"props":4632,"children":4634},{"className":4633},[],[4635],{"type":26,"value":3664},{"type":26,"value":4637}," object. The easiest approach is likely to be having any component that wants to control some aspect of one or more child components own an Animator.",{"type":21,"tag":22,"props":4639,"children":4640},{},[4641],{"type":26,"value":4642},"To start an animation, pass it to an Animator, which will take ownership of it and control its lifecycle from there. Ordinarily, the animation will run to its completion and be destroyed.",{"type":21,"tag":22,"props":4644,"children":4645},{},[4646],{"type":26,"value":4647},"You may, however, want to explicitly cancel a single animation that's in progress (or perhaps a group of animations). The Animator exposes two methods to cancel in-progress animations:",{"type":21,"tag":216,"props":4649,"children":4651},{"className":218,"code":4650,"language":220,"meta":8,"style":8},"bool Animator::CancelAllAnimations(bool moveToEndPosition)\n",[4652],{"type":21,"tag":119,"props":4653,"children":4654},{"__ignoreMap":8},[4655],{"type":21,"tag":226,"props":4656,"children":4657},{"class":228,"line":229},[4658,4663,4668,4672,4677,4681,4685,4690],{"type":21,"tag":226,"props":4659,"children":4660},{"style":233},[4661],{"type":26,"value":4662},"bool",{"type":21,"tag":226,"props":4664,"children":4665},{"style":819},[4666],{"type":26,"value":4667}," Animator",{"type":21,"tag":226,"props":4669,"children":4670},{"style":828},[4671],{"type":26,"value":895},{"type":21,"tag":226,"props":4673,"children":4674},{"style":819},[4675],{"type":26,"value":4676},"CancelAllAnimations",{"type":21,"tag":226,"props":4678,"children":4679},{"style":828},[4680],{"type":26,"value":904},{"type":21,"tag":226,"props":4682,"children":4683},{"style":233},[4684],{"type":26,"value":4662},{"type":21,"tag":226,"props":4686,"children":4687},{"style":1113},[4688],{"type":26,"value":4689}," moveToEndPosition",{"type":21,"tag":226,"props":4691,"children":4692},{"style":828},[4693],{"type":26,"value":924},{"type":21,"tag":22,"props":4695,"children":4696},{},[4697],{"type":26,"value":4698},"will cancel all animations that are currently executing, optionally advancing them all to their final positions.",{"type":21,"tag":216,"props":4700,"children":4702},{"className":218,"code":4701,"language":220,"meta":8,"style":8},"bool CancelAnimation(int id, bool moveToEndPosition);\n",[4703],{"type":21,"tag":119,"props":4704,"children":4705},{"__ignoreMap":8},[4706],{"type":21,"tag":226,"props":4707,"children":4708},{"class":228,"line":229},[4709,4713,4718,4722,4726,4730,4734,4738,4742],{"type":21,"tag":226,"props":4710,"children":4711},{"style":233},[4712],{"type":26,"value":4662},{"type":21,"tag":226,"props":4714,"children":4715},{"style":819},[4716],{"type":26,"value":4717}," CancelAnimation",{"type":21,"tag":226,"props":4719,"children":4720},{"style":828},[4721],{"type":26,"value":904},{"type":21,"tag":226,"props":4723,"children":4724},{"style":233},[4725],{"type":26,"value":909},{"type":21,"tag":226,"props":4727,"children":4728},{"style":1113},[4729],{"type":26,"value":4185},{"type":21,"tag":226,"props":4731,"children":4732},{"style":828},[4733],{"type":26,"value":1121},{"type":21,"tag":226,"props":4735,"children":4736},{"style":233},[4737],{"type":26,"value":4662},{"type":21,"tag":226,"props":4739,"children":4740},{"style":1113},[4741],{"type":26,"value":4689},{"type":21,"tag":226,"props":4743,"children":4744},{"style":828},[4745],{"type":26,"value":1190},{"type":21,"tag":22,"props":4747,"children":4748},{},[4749,4751,4756],{"type":26,"value":4750},"cancels any animations with an ",{"type":21,"tag":119,"props":4752,"children":4754},{"className":4753},[],[4755],{"type":26,"value":4457},{"type":26,"value":4757}," value that matches the argument. Since there's no requirement that animations use unique id values (or any id at all), you can use this knowledge to group animations together with a shared id value if that is useful to you.",{"type":21,"tag":186,"props":4759,"children":4761},{"id":4760},"technical-details",[4762],{"type":26,"value":4763},"Technical Details",{"type":21,"tag":22,"props":4765,"children":4766},{},[4767,4769,4774,4776,4781],{"type":26,"value":4768},"You can grab the demo application that includes the ",{"type":21,"tag":119,"props":4770,"children":4772},{"className":4771},[],[4773],{"type":26,"value":3435},{"type":26,"value":4775}," code from GitHub at ",{"type":21,"tag":29,"props":4777,"children":4779},{"href":356,"rel":4778},[33],[4780],{"type":26,"value":356},{"type":26,"value":47},{"type":21,"tag":22,"props":4783,"children":4784},{},[4785,4787,4794,4796,4802],{"type":26,"value":4786},"If you have ",{"type":21,"tag":29,"props":4788,"children":4791},{"href":4789,"rel":4790},"http://www.doxygen.nl/",[33],[4792],{"type":26,"value":4793},"Doxygen",{"type":26,"value":4795}," installed, you can use the included ",{"type":21,"tag":119,"props":4797,"children":4799},{"className":4798},[],[4800],{"type":26,"value":4801},"doxygen/Doxyfile",{"type":26,"value":4803}," to generate a local copy of HTML documentation that should prove useful.",{"type":21,"tag":22,"props":4805,"children":4806},{},[4807,4809,4816],{"type":26,"value":4808},"It's licensed under the terms of the ",{"type":21,"tag":29,"props":4810,"children":4813},{"href":4811,"rel":4812},"https://github.com/bgporter/animator/blob/master/LICENSE",[33],[4814],{"type":26,"value":4815},"MIT license",{"type":26,"value":4817},"—if it's useful to you, use it as you like. Open source, closed source, commercial, free, whatever, as long as the terms of that license are amenable to you (and perhaps to whoever writes the checks funding your project).",{"type":21,"tag":22,"props":4819,"children":4820},{},[4821],{"type":26,"value":4822},"At some point I will package it up as a JUCE user module to make it easier to work with.",{"type":21,"tag":186,"props":4824,"children":4826},{"id":4825},"aspirations",[4827],{"type":26,"value":4828},"Aspirations",{"type":21,"tag":22,"props":4830,"children":4831},{},[4832],{"type":26,"value":4833},"Part of me felt a strange obligation to say something about how animation is like a seasoning where a little goes a long way, and about the value and importance of subtlety. Part of me actually believes those things.",{"type":21,"tag":22,"props":4835,"children":4836},{},[4837],{"type":26,"value":4838},"A different part of me walks around the show floor at NAMM every January, checking out new music software releases and dies a little inside from the sameness of everything. That part of me is hoping that someone will use this and create something that's way outside the lines.",{"type":21,"tag":22,"props":4840,"children":4841},{},[4842],{"type":26,"value":4843},"Make cool things.",{"type":21,"tag":368,"props":4845,"children":4846},{},[4847],{"type":26,"value":372},{"title":8,"searchDepth":374,"depth":374,"links":4849},[4850,4851,4859,4866,4867,4868],{"id":3347,"depth":377,"text":3350},{"id":3413,"depth":377,"text":3416,"children":4852},[4853,4854,4855,4856,4857,4858],{"id":3440,"depth":374,"text":3447},{"id":3455,"depth":374,"text":3462},{"id":3477,"depth":374,"text":3484},{"id":3513,"depth":374,"text":3520},{"id":3536,"depth":374,"text":3543},{"id":3570,"depth":374,"text":3577},{"id":3585,"depth":377,"text":4860,"children":4861},"The Animation class",[4862],{"id":4467,"depth":374,"text":4474,"children":4863},[4864],{"id":4491,"depth":834,"text":4865},"About Those lambda Callbacks",{"id":4618,"depth":377,"text":3391},{"id":4760,"depth":377,"text":4763},{"id":4825,"depth":377,"text":4828},"content:bporter:2019-3:animator.md","bporter/2019-3/animator.md","bporter/2019-3/animator",{"user":387,"name":388},1780330268743]