[{"data":1,"prerenderedAt":5459},["ShallowReactive",2],{"article_list_audio_":3},[4,2544],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"tags":11,"excerpt":10,"image":15,"publishDate":16,"body":17,"_type":2535,"_id":2536,"_source":2537,"_file":2538,"_stem":2539,"_extension":2540,"author":2541},"/jbagley/2019-4/makingspectrogramsinjuce","2019-4",false,"","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.",[12,13,14],"juce","c++","audio","/jbagley/2019-4/img/Fortissimo_Trumpet_Ensemble_Matrix_Swells_61.wav-2048x1700.png","2019-04-01",{"type":18,"children":19,"toc":2525},"root",[20,39,46,68,74,87,100,121,140,145,348,354,367,1069,1075,1080,1117,1370,1375,1641,1661,1666,1672,1686,1700,1753,1759,1779,2197,2203,2223,2471,2477,2482,2487,2519],{"type":21,"tag":22,"props":23,"children":24},"element","p",{},[25,28,37],{"type":26,"value":27},"text","Art+Logic's Incubator project has made a lot of progress. In a ",{"type":21,"tag":29,"props":30,"children":34},"a",{"href":31,"rel":32},"https://artandlogic.com/2018/12/incubator-kick-off/",[33],"nofollow",[35],{"type":26,"value":36},"previous post",{"type":26,"value":38}," 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":40,"props":41,"children":43},"h2",{"id":42},"basics",[44],{"type":26,"value":45},"Basics",{"type":21,"tag":22,"props":47,"children":48},{},[49,51,58,60,66],{"type":26,"value":50},"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":52,"props":53,"children":55},"code",{"className":54},[],[56],{"type":26,"value":57},"y",{"type":26,"value":59},") at that time range (",{"type":21,"tag":52,"props":61,"children":63},{"className":62},[],[64],{"type":26,"value":65},"x",{"type":26,"value":67},").",{"type":21,"tag":40,"props":69,"children":71},{"id":70},"the-base-the-fft",[72],{"type":26,"value":73},"The Base -- The FFT",{"type":21,"tag":22,"props":75,"children":76},{},[77,79,85],{"type":26,"value":78},"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":80,"props":81,"children":82},"span",{},[83],{"type":26,"value":84},"STFT",{"type":26,"value":86}," later.",{"type":21,"tag":22,"props":88,"children":89},{},[90,92,98],{"type":26,"value":91},"JUCE provides an implementation of an FFT that we can use to implement the STFT in its ",{"type":21,"tag":52,"props":93,"children":95},{"className":94},[],[96],{"type":26,"value":97},"dsp",{"type":26,"value":99}," 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":101,"children":102},{},[103,105,111,113,119],{"type":26,"value":104},"The FFT class is initialized with an ",{"type":21,"tag":52,"props":106,"children":108},{"className":107},[],[109],{"type":26,"value":110},"order",{"type":26,"value":112}," 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":52,"props":114,"children":116},{"className":115},[],[117],{"type":26,"value":118},"fftSize",{"type":26,"value":120},". 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":122,"children":123},{},[124,126,131,133,138],{"type":26,"value":125},"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":52,"props":127,"children":129},{"className":128},[],[130],{"type":26,"value":110},{"type":26,"value":132}," 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":52,"props":134,"children":136},{"className":135},[],[137],{"type":26,"value":118},{"type":26,"value":139}," bins.",{"type":21,"tag":22,"props":141,"children":142},{},[143],{"type":26,"value":144},"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":146,"props":147,"children":151},"pre",{"className":148,"code":149,"language":150,"meta":8,"style":8},"language-cpp shiki shiki-themes github-light github-dark","/// 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","cpp",[152],{"type":21,"tag":52,"props":153,"children":154},{"__ignoreMap":8},[155,166,182,192,201,215,223,232,242,285,322,331,339],{"type":21,"tag":80,"props":156,"children":159},{"class":157,"line":158},"line",1,[160],{"type":21,"tag":80,"props":161,"children":163},{"style":162},"--shiki-default:#6A737D;--shiki-dark:#6A737D",[164],{"type":26,"value":165},"/// The class to make spectrograms from audio file data\n",{"type":21,"tag":80,"props":167,"children":169},{"class":157,"line":168},2,[170,176],{"type":21,"tag":80,"props":171,"children":173},{"style":172},"--shiki-default:#D73A49;--shiki-dark:#F97583",[174],{"type":26,"value":175},"class",{"type":21,"tag":80,"props":177,"children":179},{"style":178},"--shiki-default:#6F42C1;--shiki-dark:#B392F0",[180],{"type":26,"value":181}," ASpectroMaker\n",{"type":21,"tag":80,"props":183,"children":185},{"class":157,"line":184},3,[186],{"type":21,"tag":80,"props":187,"children":189},{"style":188},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[190],{"type":26,"value":191},"{\n",{"type":21,"tag":80,"props":193,"children":195},{"class":157,"line":194},4,[196],{"type":21,"tag":80,"props":197,"children":198},{"style":162},[199],{"type":26,"value":200},"   //...\n",{"type":21,"tag":80,"props":202,"children":204},{"class":157,"line":203},5,[205,210],{"type":21,"tag":80,"props":206,"children":207},{"style":178},[208],{"type":26,"value":209},"   dsp",{"type":21,"tag":80,"props":211,"children":212},{"style":188},[213],{"type":26,"value":214},"::FFT fFft;\n",{"type":21,"tag":80,"props":216,"children":218},{"class":157,"line":217},6,[219],{"type":21,"tag":80,"props":220,"children":221},{"style":162},[222],{"type":26,"value":200},{"type":21,"tag":80,"props":224,"children":226},{"class":157,"line":225},7,[227],{"type":21,"tag":80,"props":228,"children":229},{"style":188},[230],{"type":26,"value":231},"};\n",{"type":21,"tag":80,"props":233,"children":235},{"class":157,"line":234},8,[236],{"type":21,"tag":80,"props":237,"children":239},{"emptyLinePlaceholder":238},true,[240],{"type":26,"value":241},"\n",{"type":21,"tag":80,"props":243,"children":245},{"class":157,"line":244},9,[246,251,256,260,265,270,275,280],{"type":21,"tag":80,"props":247,"children":248},{"style":178},[249],{"type":26,"value":250},"ASpectroMaker",{"type":21,"tag":80,"props":252,"children":253},{"style":188},[254],{"type":26,"value":255},"::",{"type":21,"tag":80,"props":257,"children":258},{"style":178},[259],{"type":26,"value":250},{"type":21,"tag":80,"props":261,"children":262},{"style":188},[263],{"type":26,"value":264},"(",{"type":21,"tag":80,"props":266,"children":267},{"style":172},[268],{"type":26,"value":269},"int",{"type":21,"tag":80,"props":271,"children":272},{"style":188},[273],{"type":26,"value":274}," fftBins",{"type":21,"tag":80,"props":276,"children":277},{"style":162},[278],{"type":26,"value":279}," /*...*/",{"type":21,"tag":80,"props":281,"children":282},{"style":188},[283],{"type":26,"value":284},")\n",{"type":21,"tag":80,"props":286,"children":288},{"class":157,"line":287},10,[289,294,299,303,308,312,317],{"type":21,"tag":80,"props":290,"children":291},{"style":188},[292],{"type":26,"value":293},": ",{"type":21,"tag":80,"props":295,"children":296},{"style":178},[297],{"type":26,"value":298},"fFft",{"type":21,"tag":80,"props":300,"children":301},{"style":188},[302],{"type":26,"value":264},{"type":21,"tag":80,"props":304,"children":305},{"style":178},[306],{"type":26,"value":307},"std",{"type":21,"tag":80,"props":309,"children":310},{"style":188},[311],{"type":26,"value":255},{"type":21,"tag":80,"props":313,"children":314},{"style":178},[315],{"type":26,"value":316},"log2",{"type":21,"tag":80,"props":318,"children":319},{"style":188},[320],{"type":26,"value":321},"(fftBins))\n",{"type":21,"tag":80,"props":323,"children":325},{"class":157,"line":324},11,[326],{"type":21,"tag":80,"props":327,"children":328},{"style":162},[329],{"type":26,"value":330},"// ...\n",{"type":21,"tag":80,"props":332,"children":334},{"class":157,"line":333},12,[335],{"type":21,"tag":80,"props":336,"children":337},{"style":188},[338],{"type":26,"value":191},{"type":21,"tag":80,"props":340,"children":342},{"class":157,"line":341},13,[343],{"type":21,"tag":80,"props":344,"children":345},{"style":188},[346],{"type":26,"value":347},"}\n",{"type":21,"tag":40,"props":349,"children":351},{"id":350},"the-iteration-the-stft",[352],{"type":26,"value":353},"The Iteration -- The STFT",{"type":21,"tag":22,"props":355,"children":356},{},[357,359,365],{"type":26,"value":358},"In STFT The ",{"type":21,"tag":360,"props":361,"children":362},"em",{},[363],{"type":26,"value":364},"Short Time",{"type":26,"value":366}," 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":146,"props":368,"children":370},{"className":148,"code":369,"language":150,"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",[371],{"type":21,"tag":52,"props":372,"children":373},{"__ignoreMap":8},[374,426,433,498,505,553,588,595,603,634,641,649,710,717,725,733,742,795,803,812,858,866,875,884,939,947,956,964,991,999,1008,1016,1025,1044,1053,1061],{"type":21,"tag":80,"props":375,"children":376},{"class":157,"line":158},[377,382,387,392,397,401,406,411,416,421],{"type":21,"tag":80,"props":378,"children":379},{"style":172},[380],{"type":26,"value":381},"using",{"type":21,"tag":80,"props":383,"children":384},{"style":178},[385],{"type":26,"value":386}," Spectrum",{"type":21,"tag":80,"props":388,"children":389},{"style":172},[390],{"type":26,"value":391}," =",{"type":21,"tag":80,"props":393,"children":394},{"style":178},[395],{"type":26,"value":396}," std",{"type":21,"tag":80,"props":398,"children":399},{"style":188},[400],{"type":26,"value":255},{"type":21,"tag":80,"props":402,"children":403},{"style":178},[404],{"type":26,"value":405},"vector",{"type":21,"tag":80,"props":407,"children":408},{"style":188},[409],{"type":26,"value":410},"\u003C",{"type":21,"tag":80,"props":412,"children":413},{"style":172},[414],{"type":26,"value":415},"float",{"type":21,"tag":80,"props":417,"children":418},{"style":188},[419],{"type":26,"value":420},">;",{"type":21,"tag":80,"props":422,"children":423},{"style":162},[424],{"type":26,"value":425}," //!\u003C One audio channel of FFT data over time, really 2-dimensional\n",{"type":21,"tag":80,"props":427,"children":428},{"class":157,"line":168},[429],{"type":21,"tag":80,"props":430,"children":431},{"emptyLinePlaceholder":238},[432],{"type":26,"value":241},{"type":21,"tag":80,"props":434,"children":435},{"class":157,"line":184},[436,441,446,450,454,458,463,468,473,479,484,489,494],{"type":21,"tag":80,"props":437,"children":438},{"style":178},[439],{"type":26,"value":440},"ASpectrogram",{"type":21,"tag":80,"props":442,"children":443},{"style":178},[444],{"type":26,"value":445}," ASpectroMaker",{"type":21,"tag":80,"props":447,"children":448},{"style":188},[449],{"type":26,"value":255},{"type":21,"tag":80,"props":451,"children":452},{"style":178},[453],{"type":26,"value":84},{"type":21,"tag":80,"props":455,"children":456},{"style":188},[457],{"type":26,"value":264},{"type":21,"tag":80,"props":459,"children":460},{"style":172},[461],{"type":26,"value":462},"const",{"type":21,"tag":80,"props":464,"children":465},{"style":178},[466],{"type":26,"value":467}," AudioSampleBuffer",{"type":21,"tag":80,"props":469,"children":470},{"style":172},[471],{"type":26,"value":472},"&",{"type":21,"tag":80,"props":474,"children":476},{"style":475},"--shiki-default:#E36209;--shiki-dark:#FFAB70",[477],{"type":26,"value":478}," signal",{"type":21,"tag":80,"props":480,"children":481},{"style":188},[482],{"type":26,"value":483},", ",{"type":21,"tag":80,"props":485,"children":486},{"style":172},[487],{"type":26,"value":488},"size_t",{"type":21,"tag":80,"props":490,"children":491},{"style":475},[492],{"type":26,"value":493}," hop",{"type":21,"tag":80,"props":495,"children":496},{"style":188},[497],{"type":26,"value":284},{"type":21,"tag":80,"props":499,"children":500},{"class":157,"line":194},[501],{"type":21,"tag":80,"props":502,"children":503},{"style":188},[504],{"type":26,"value":191},{"type":21,"tag":80,"props":506,"children":507},{"class":157,"line":203},[508,513,518,523,528,533,538,542,548],{"type":21,"tag":80,"props":509,"children":510},{"style":172},[511],{"type":26,"value":512},"   const",{"type":21,"tag":80,"props":514,"children":515},{"style":172},[516],{"type":26,"value":517}," float*",{"type":21,"tag":80,"props":519,"children":520},{"style":188},[521],{"type":26,"value":522}," data ",{"type":21,"tag":80,"props":524,"children":525},{"style":172},[526],{"type":26,"value":527},"=",{"type":21,"tag":80,"props":529,"children":530},{"style":188},[531],{"type":26,"value":532}," signal.",{"type":21,"tag":80,"props":534,"children":535},{"style":178},[536],{"type":26,"value":537},"getReadPointer",{"type":21,"tag":80,"props":539,"children":540},{"style":188},[541],{"type":26,"value":264},{"type":21,"tag":80,"props":543,"children":545},{"style":544},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[546],{"type":26,"value":547},"0",{"type":21,"tag":80,"props":549,"children":550},{"style":188},[551],{"type":26,"value":552},");\n",{"type":21,"tag":80,"props":554,"children":555},{"class":157,"line":217},[556,560,565,570,574,578,583],{"type":21,"tag":80,"props":557,"children":558},{"style":172},[559],{"type":26,"value":512},{"type":21,"tag":80,"props":561,"children":562},{"style":172},[563],{"type":26,"value":564}," size_t",{"type":21,"tag":80,"props":566,"children":567},{"style":188},[568],{"type":26,"value":569}," dataCount ",{"type":21,"tag":80,"props":571,"children":572},{"style":172},[573],{"type":26,"value":527},{"type":21,"tag":80,"props":575,"children":576},{"style":188},[577],{"type":26,"value":532},{"type":21,"tag":80,"props":579,"children":580},{"style":178},[581],{"type":26,"value":582},"getNumSamples",{"type":21,"tag":80,"props":584,"children":585},{"style":188},[586],{"type":26,"value":587},"();\n",{"type":21,"tag":80,"props":589,"children":590},{"class":157,"line":225},[591],{"type":21,"tag":80,"props":592,"children":593},{"emptyLinePlaceholder":238},[594],{"type":26,"value":241},{"type":21,"tag":80,"props":596,"children":597},{"class":157,"line":234},[598],{"type":21,"tag":80,"props":599,"children":600},{"style":162},[601],{"type":26,"value":602},"   // fftSize will be the number of bins we used to initialize the ASpectroMaker.\n",{"type":21,"tag":80,"props":604,"children":605},{"class":157,"line":244},[606,611,616,620,625,630],{"type":21,"tag":80,"props":607,"children":608},{"style":544},[609],{"type":26,"value":610},"   ptrdiff_t",{"type":21,"tag":80,"props":612,"children":613},{"style":188},[614],{"type":26,"value":615}," fftSize ",{"type":21,"tag":80,"props":617,"children":618},{"style":172},[619],{"type":26,"value":527},{"type":21,"tag":80,"props":621,"children":622},{"style":188},[623],{"type":26,"value":624}," fFft.",{"type":21,"tag":80,"props":626,"children":627},{"style":178},[628],{"type":26,"value":629},"getSize",{"type":21,"tag":80,"props":631,"children":632},{"style":188},[633],{"type":26,"value":587},{"type":21,"tag":80,"props":635,"children":636},{"class":157,"line":287},[637],{"type":21,"tag":80,"props":638,"children":639},{"emptyLinePlaceholder":238},[640],{"type":26,"value":241},{"type":21,"tag":80,"props":642,"children":643},{"class":157,"line":324},[644],{"type":21,"tag":80,"props":645,"children":646},{"style":162},[647],{"type":26,"value":648},"   // See below for more about numHops\n",{"type":21,"tag":80,"props":650,"children":651},{"class":157,"line":333},[652,656,661,665,670,675,680,685,690,695,700,705],{"type":21,"tag":80,"props":653,"children":654},{"style":544},[655],{"type":26,"value":610},{"type":21,"tag":80,"props":657,"children":658},{"style":188},[659],{"type":26,"value":660}," numHops ",{"type":21,"tag":80,"props":662,"children":663},{"style":172},[664],{"type":26,"value":527},{"type":21,"tag":80,"props":666,"children":667},{"style":544},[668],{"type":26,"value":669}," 1",{"type":21,"tag":80,"props":671,"children":672},{"style":172},[673],{"type":26,"value":674},"L",{"type":21,"tag":80,"props":676,"children":677},{"style":172},[678],{"type":26,"value":679}," +",{"type":21,"tag":80,"props":681,"children":682},{"style":172},[683],{"type":26,"value":684}," static_cast\u003Clong>",{"type":21,"tag":80,"props":686,"children":687},{"style":188},[688],{"type":26,"value":689},"((dataCount ",{"type":21,"tag":80,"props":691,"children":692},{"style":172},[693],{"type":26,"value":694},"-",{"type":21,"tag":80,"props":696,"children":697},{"style":188},[698],{"type":26,"value":699}," fftSize) ",{"type":21,"tag":80,"props":701,"children":702},{"style":172},[703],{"type":26,"value":704},"/",{"type":21,"tag":80,"props":706,"children":707},{"style":188},[708],{"type":26,"value":709}," hop);\n",{"type":21,"tag":80,"props":711,"children":712},{"class":157,"line":341},[713],{"type":21,"tag":80,"props":714,"children":715},{"emptyLinePlaceholder":238},[716],{"type":26,"value":241},{"type":21,"tag":80,"props":718,"children":720},{"class":157,"line":719},14,[721],{"type":21,"tag":80,"props":722,"children":723},{"style":162},[724],{"type":26,"value":200},{"type":21,"tag":80,"props":726,"children":728},{"class":157,"line":727},15,[729],{"type":21,"tag":80,"props":730,"children":731},{"emptyLinePlaceholder":238},[732],{"type":26,"value":241},{"type":21,"tag":80,"props":734,"children":736},{"class":157,"line":735},16,[737],{"type":21,"tag":80,"props":738,"children":739},{"style":162},[740],{"type":26,"value":741},"   // Ignore the negative frequency information but leave the center bin.\n",{"type":21,"tag":80,"props":743,"children":745},{"class":157,"line":744},17,[746,751,756,760,764,769,773,778,782,787,791],{"type":21,"tag":80,"props":747,"children":748},{"style":172},[749],{"type":26,"value":750},"   size_t",{"type":21,"tag":80,"props":752,"children":753},{"style":188},[754],{"type":26,"value":755}," numRows ",{"type":21,"tag":80,"props":757,"children":758},{"style":172},[759],{"type":26,"value":527},{"type":21,"tag":80,"props":761,"children":762},{"style":544},[763],{"type":26,"value":669},{"type":21,"tag":80,"props":765,"children":766},{"style":172},[767],{"type":26,"value":768},"UL",{"type":21,"tag":80,"props":770,"children":771},{"style":172},[772],{"type":26,"value":679},{"type":21,"tag":80,"props":774,"children":775},{"style":188},[776],{"type":26,"value":777}," (fftSize ",{"type":21,"tag":80,"props":779,"children":780},{"style":172},[781],{"type":26,"value":704},{"type":21,"tag":80,"props":783,"children":784},{"style":544},[785],{"type":26,"value":786}," 2",{"type":21,"tag":80,"props":788,"children":789},{"style":172},[790],{"type":26,"value":768},{"type":21,"tag":80,"props":792,"children":793},{"style":188},[794],{"type":26,"value":552},{"type":21,"tag":80,"props":796,"children":798},{"class":157,"line":797},18,[799],{"type":21,"tag":80,"props":800,"children":801},{"emptyLinePlaceholder":238},[802],{"type":26,"value":241},{"type":21,"tag":80,"props":804,"children":806},{"class":157,"line":805},19,[807],{"type":21,"tag":80,"props":808,"children":809},{"style":162},[810],{"type":26,"value":811},"   // fFft works on the data in place, and needs twice as much space as the input size.\n",{"type":21,"tag":80,"props":813,"children":815},{"class":157,"line":814},20,[816,821,826,831,836,841,846,850,854],{"type":21,"tag":80,"props":817,"children":818},{"style":178},[819],{"type":26,"value":820},"   std",{"type":21,"tag":80,"props":822,"children":823},{"style":188},[824],{"type":26,"value":825},"::vector",{"type":21,"tag":80,"props":827,"children":828},{"style":172},[829],{"type":26,"value":830},"\u003Cfloat>",{"type":21,"tag":80,"props":832,"children":833},{"style":178},[834],{"type":26,"value":835}," fftBuffer",{"type":21,"tag":80,"props":837,"children":838},{"style":188},[839],{"type":26,"value":840},"(fftSize ",{"type":21,"tag":80,"props":842,"children":843},{"style":172},[844],{"type":26,"value":845},"*",{"type":21,"tag":80,"props":847,"children":848},{"style":544},[849],{"type":26,"value":786},{"type":21,"tag":80,"props":851,"children":852},{"style":172},[853],{"type":26,"value":768},{"type":21,"tag":80,"props":855,"children":856},{"style":188},[857],{"type":26,"value":552},{"type":21,"tag":80,"props":859,"children":861},{"class":157,"line":860},21,[862],{"type":21,"tag":80,"props":863,"children":864},{"emptyLinePlaceholder":238},[865],{"type":26,"value":241},{"type":21,"tag":80,"props":867,"children":869},{"class":157,"line":868},22,[870],{"type":21,"tag":80,"props":871,"children":872},{"style":162},[873],{"type":26,"value":874},"   // While data remains\n",{"type":21,"tag":80,"props":876,"children":878},{"class":157,"line":877},23,[879],{"type":21,"tag":80,"props":880,"children":881},{"style":188},[882],{"type":26,"value":883},"   {\n",{"type":21,"tag":80,"props":885,"children":887},{"class":157,"line":886},24,[888,893,897,902,907,912,917,921,926,930,934],{"type":21,"tag":80,"props":889,"children":890},{"style":178},[891],{"type":26,"value":892},"      std",{"type":21,"tag":80,"props":894,"children":895},{"style":188},[896],{"type":26,"value":255},{"type":21,"tag":80,"props":898,"children":899},{"style":178},[900],{"type":26,"value":901},"memcpy",{"type":21,"tag":80,"props":903,"children":904},{"style":188},[905],{"type":26,"value":906},"(fftBuffer.",{"type":21,"tag":80,"props":908,"children":909},{"style":178},[910],{"type":26,"value":911},"data",{"type":21,"tag":80,"props":913,"children":914},{"style":188},[915],{"type":26,"value":916},"(), data, fftSize ",{"type":21,"tag":80,"props":918,"children":919},{"style":172},[920],{"type":26,"value":845},{"type":21,"tag":80,"props":922,"children":923},{"style":172},[924],{"type":26,"value":925}," sizeof",{"type":21,"tag":80,"props":927,"children":928},{"style":188},[929],{"type":26,"value":264},{"type":21,"tag":80,"props":931,"children":932},{"style":172},[933],{"type":26,"value":415},{"type":21,"tag":80,"props":935,"children":936},{"style":188},[937],{"type":26,"value":938},"));\n",{"type":21,"tag":80,"props":940,"children":942},{"class":157,"line":941},25,[943],{"type":21,"tag":80,"props":944,"children":945},{"emptyLinePlaceholder":238},[946],{"type":26,"value":241},{"type":21,"tag":80,"props":948,"children":950},{"class":157,"line":949},26,[951],{"type":21,"tag":80,"props":952,"children":953},{"style":162},[954],{"type":26,"value":955},"      // prepare fft data...\n",{"type":21,"tag":80,"props":957,"children":959},{"class":157,"line":958},27,[960],{"type":21,"tag":80,"props":961,"children":962},{"emptyLinePlaceholder":238},[963],{"type":26,"value":241},{"type":21,"tag":80,"props":965,"children":967},{"class":157,"line":966},28,[968,973,978,982,986],{"type":21,"tag":80,"props":969,"children":970},{"style":188},[971],{"type":26,"value":972},"      fFft.",{"type":21,"tag":80,"props":974,"children":975},{"style":178},[976],{"type":26,"value":977},"performFrequencyOnlyForwardTransform",{"type":21,"tag":80,"props":979,"children":980},{"style":188},[981],{"type":26,"value":906},{"type":21,"tag":80,"props":983,"children":984},{"style":178},[985],{"type":26,"value":911},{"type":21,"tag":80,"props":987,"children":988},{"style":188},[989],{"type":26,"value":990},"());\n",{"type":21,"tag":80,"props":992,"children":994},{"class":157,"line":993},29,[995],{"type":21,"tag":80,"props":996,"children":997},{"emptyLinePlaceholder":238},[998],{"type":26,"value":241},{"type":21,"tag":80,"props":1000,"children":1002},{"class":157,"line":1001},30,[1003],{"type":21,"tag":80,"props":1004,"children":1005},{"style":162},[1006],{"type":26,"value":1007},"      // ...copy the frequency information from fftBuffer to the spectrum\n",{"type":21,"tag":80,"props":1009,"children":1011},{"class":157,"line":1010},31,[1012],{"type":21,"tag":80,"props":1013,"children":1014},{"emptyLinePlaceholder":238},[1015],{"type":26,"value":241},{"type":21,"tag":80,"props":1017,"children":1019},{"class":157,"line":1018},32,[1020],{"type":21,"tag":80,"props":1021,"children":1022},{"style":162},[1023],{"type":26,"value":1024},"      // Next chunk\n",{"type":21,"tag":80,"props":1026,"children":1028},{"class":157,"line":1027},33,[1029,1034,1039],{"type":21,"tag":80,"props":1030,"children":1031},{"style":188},[1032],{"type":26,"value":1033},"      data ",{"type":21,"tag":80,"props":1035,"children":1036},{"style":172},[1037],{"type":26,"value":1038},"+=",{"type":21,"tag":80,"props":1040,"children":1041},{"style":188},[1042],{"type":26,"value":1043}," hop;\n",{"type":21,"tag":80,"props":1045,"children":1047},{"class":157,"line":1046},34,[1048],{"type":21,"tag":80,"props":1049,"children":1050},{"style":188},[1051],{"type":26,"value":1052},"   }\n",{"type":21,"tag":80,"props":1054,"children":1056},{"class":157,"line":1055},35,[1057],{"type":21,"tag":80,"props":1058,"children":1059},{"style":162},[1060],{"type":26,"value":200},{"type":21,"tag":80,"props":1062,"children":1064},{"class":157,"line":1063},36,[1065],{"type":21,"tag":80,"props":1066,"children":1067},{"style":188},[1068],{"type":26,"value":347},{"type":21,"tag":40,"props":1070,"children":1072},{"id":1071},"windowing",[1073],{"type":26,"value":1074},"Windowing",{"type":21,"tag":22,"props":1076,"children":1077},{},[1078],{"type":26,"value":1079},"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":1081,"children":1082},{},[1083,1085,1092,1094,1100,1102,1107,1109,1115],{"type":26,"value":1084},"The ",{"type":21,"tag":29,"props":1086,"children":1089},{"href":1087,"rel":1088},"https://en.wikipedia.org/wiki/Hann_function",[33],[1090],{"type":26,"value":1091},"Hann function",{"type":26,"value":1093}," provides good performance and meets our requirements (and it was what Dr. Hawley used). Notice that we initialize it with the ",{"type":21,"tag":52,"props":1095,"children":1097},{"className":1096},[],[1098],{"type":26,"value":1099},"fftSize + 1",{"type":26,"value":1101},", 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":52,"props":1103,"children":1105},{"className":1104},[],[1106],{"type":26,"value":118},{"type":26,"value":1108}," samples per chunk, where ",{"type":21,"tag":52,"props":1110,"children":1112},{"className":1111},[],[1113],{"type":26,"value":1114},"hop \u003C fftSize",{"type":26,"value":1116},". 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":146,"props":1118,"children":1120},{"className":148,"code":1119,"language":150,"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",[1121],{"type":21,"tag":52,"props":1122,"children":1123},{"__ignoreMap":8},[1124,1131,1142,1149,1156,1167,1188,1195,1202,1209,1244,1275,1349,1356,1363],{"type":21,"tag":80,"props":1125,"children":1126},{"class":157,"line":158},[1127],{"type":21,"tag":80,"props":1128,"children":1129},{"style":162},[1130],{"type":26,"value":165},{"type":21,"tag":80,"props":1132,"children":1133},{"class":157,"line":168},[1134,1138],{"type":21,"tag":80,"props":1135,"children":1136},{"style":172},[1137],{"type":26,"value":175},{"type":21,"tag":80,"props":1139,"children":1140},{"style":178},[1141],{"type":26,"value":181},{"type":21,"tag":80,"props":1143,"children":1144},{"class":157,"line":184},[1145],{"type":21,"tag":80,"props":1146,"children":1147},{"style":188},[1148],{"type":26,"value":191},{"type":21,"tag":80,"props":1150,"children":1151},{"class":157,"line":194},[1152],{"type":21,"tag":80,"props":1153,"children":1154},{"style":162},[1155],{"type":26,"value":200},{"type":21,"tag":80,"props":1157,"children":1158},{"class":157,"line":203},[1159,1163],{"type":21,"tag":80,"props":1160,"children":1161},{"style":178},[1162],{"type":26,"value":209},{"type":21,"tag":80,"props":1164,"children":1165},{"style":188},[1166],{"type":26,"value":214},{"type":21,"tag":80,"props":1168,"children":1169},{"class":157,"line":217},[1170,1174,1179,1183],{"type":21,"tag":80,"props":1171,"children":1172},{"style":178},[1173],{"type":26,"value":209},{"type":21,"tag":80,"props":1175,"children":1176},{"style":188},[1177],{"type":26,"value":1178},"::WindowingFunction",{"type":21,"tag":80,"props":1180,"children":1181},{"style":172},[1182],{"type":26,"value":830},{"type":21,"tag":80,"props":1184,"children":1185},{"style":188},[1186],{"type":26,"value":1187}," fWindow;\n",{"type":21,"tag":80,"props":1189,"children":1190},{"class":157,"line":225},[1191],{"type":21,"tag":80,"props":1192,"children":1193},{"style":162},[1194],{"type":26,"value":200},{"type":21,"tag":80,"props":1196,"children":1197},{"class":157,"line":234},[1198],{"type":21,"tag":80,"props":1199,"children":1200},{"style":188},[1201],{"type":26,"value":231},{"type":21,"tag":80,"props":1203,"children":1204},{"class":157,"line":244},[1205],{"type":21,"tag":80,"props":1206,"children":1207},{"emptyLinePlaceholder":238},[1208],{"type":26,"value":241},{"type":21,"tag":80,"props":1210,"children":1211},{"class":157,"line":287},[1212,1216,1220,1224,1228,1232,1236,1240],{"type":21,"tag":80,"props":1213,"children":1214},{"style":178},[1215],{"type":26,"value":250},{"type":21,"tag":80,"props":1217,"children":1218},{"style":188},[1219],{"type":26,"value":255},{"type":21,"tag":80,"props":1221,"children":1222},{"style":178},[1223],{"type":26,"value":250},{"type":21,"tag":80,"props":1225,"children":1226},{"style":188},[1227],{"type":26,"value":264},{"type":21,"tag":80,"props":1229,"children":1230},{"style":172},[1231],{"type":26,"value":269},{"type":21,"tag":80,"props":1233,"children":1234},{"style":188},[1235],{"type":26,"value":274},{"type":21,"tag":80,"props":1237,"children":1238},{"style":162},[1239],{"type":26,"value":279},{"type":21,"tag":80,"props":1241,"children":1242},{"style":188},[1243],{"type":26,"value":284},{"type":21,"tag":80,"props":1245,"children":1246},{"class":157,"line":324},[1247,1251,1255,1259,1263,1267,1271],{"type":21,"tag":80,"props":1248,"children":1249},{"style":188},[1250],{"type":26,"value":293},{"type":21,"tag":80,"props":1252,"children":1253},{"style":178},[1254],{"type":26,"value":298},{"type":21,"tag":80,"props":1256,"children":1257},{"style":188},[1258],{"type":26,"value":264},{"type":21,"tag":80,"props":1260,"children":1261},{"style":178},[1262],{"type":26,"value":307},{"type":21,"tag":80,"props":1264,"children":1265},{"style":188},[1266],{"type":26,"value":255},{"type":21,"tag":80,"props":1268,"children":1269},{"style":178},[1270],{"type":26,"value":316},{"type":21,"tag":80,"props":1272,"children":1273},{"style":188},[1274],{"type":26,"value":321},{"type":21,"tag":80,"props":1276,"children":1277},{"class":157,"line":333},[1278,1282,1287,1292,1296,1301,1306,1310,1314,1318,1322,1327,1331,1335,1340,1345],{"type":21,"tag":80,"props":1279,"children":1280},{"style":188},[1281],{"type":26,"value":483},{"type":21,"tag":80,"props":1283,"children":1284},{"style":178},[1285],{"type":26,"value":1286},"fWindow",{"type":21,"tag":80,"props":1288,"children":1289},{"style":188},[1290],{"type":26,"value":1291},"(fFft.",{"type":21,"tag":80,"props":1293,"children":1294},{"style":178},[1295],{"type":26,"value":629},{"type":21,"tag":80,"props":1297,"children":1298},{"style":188},[1299],{"type":26,"value":1300},"() ",{"type":21,"tag":80,"props":1302,"children":1303},{"style":172},[1304],{"type":26,"value":1305},"+",{"type":21,"tag":80,"props":1307,"children":1308},{"style":544},[1309],{"type":26,"value":669},{"type":21,"tag":80,"props":1311,"children":1312},{"style":188},[1313],{"type":26,"value":483},{"type":21,"tag":80,"props":1315,"children":1316},{"style":178},[1317],{"type":26,"value":97},{"type":21,"tag":80,"props":1319,"children":1320},{"style":188},[1321],{"type":26,"value":255},{"type":21,"tag":80,"props":1323,"children":1324},{"style":178},[1325],{"type":26,"value":1326},"WindowingFunction",{"type":21,"tag":80,"props":1328,"children":1329},{"style":188},[1330],{"type":26,"value":410},{"type":21,"tag":80,"props":1332,"children":1333},{"style":172},[1334],{"type":26,"value":415},{"type":21,"tag":80,"props":1336,"children":1337},{"style":188},[1338],{"type":26,"value":1339},">::hann, ",{"type":21,"tag":80,"props":1341,"children":1342},{"style":544},[1343],{"type":26,"value":1344},"false",{"type":21,"tag":80,"props":1346,"children":1347},{"style":188},[1348],{"type":26,"value":284},{"type":21,"tag":80,"props":1350,"children":1351},{"class":157,"line":341},[1352],{"type":21,"tag":80,"props":1353,"children":1354},{"style":162},[1355],{"type":26,"value":330},{"type":21,"tag":80,"props":1357,"children":1358},{"class":157,"line":719},[1359],{"type":21,"tag":80,"props":1360,"children":1361},{"style":188},[1362],{"type":26,"value":191},{"type":21,"tag":80,"props":1364,"children":1365},{"class":157,"line":727},[1366],{"type":21,"tag":80,"props":1367,"children":1368},{"style":188},[1369],{"type":26,"value":347},{"type":21,"tag":22,"props":1371,"children":1372},{},[1373],{"type":26,"value":1374},"Now apply the windowing to the chunk of samples before passing it to the FFT.",{"type":21,"tag":146,"props":1376,"children":1378},{"className":148,"code":1377,"language":150,"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",[1379],{"type":21,"tag":52,"props":1380,"children":1381},{"__ignoreMap":8},[1382,1437,1444,1452,1459,1466,1473,1520,1527,1553,1576,1583,1590,1597,1605,1620,1627,1634],{"type":21,"tag":80,"props":1383,"children":1384},{"class":157,"line":158},[1385,1389,1393,1397,1401,1405,1409,1413,1417,1421,1425,1429,1433],{"type":21,"tag":80,"props":1386,"children":1387},{"style":178},[1388],{"type":26,"value":440},{"type":21,"tag":80,"props":1390,"children":1391},{"style":178},[1392],{"type":26,"value":445},{"type":21,"tag":80,"props":1394,"children":1395},{"style":188},[1396],{"type":26,"value":255},{"type":21,"tag":80,"props":1398,"children":1399},{"style":178},[1400],{"type":26,"value":84},{"type":21,"tag":80,"props":1402,"children":1403},{"style":188},[1404],{"type":26,"value":264},{"type":21,"tag":80,"props":1406,"children":1407},{"style":172},[1408],{"type":26,"value":462},{"type":21,"tag":80,"props":1410,"children":1411},{"style":178},[1412],{"type":26,"value":467},{"type":21,"tag":80,"props":1414,"children":1415},{"style":172},[1416],{"type":26,"value":472},{"type":21,"tag":80,"props":1418,"children":1419},{"style":475},[1420],{"type":26,"value":478},{"type":21,"tag":80,"props":1422,"children":1423},{"style":188},[1424],{"type":26,"value":483},{"type":21,"tag":80,"props":1426,"children":1427},{"style":172},[1428],{"type":26,"value":488},{"type":21,"tag":80,"props":1430,"children":1431},{"style":475},[1432],{"type":26,"value":493},{"type":21,"tag":80,"props":1434,"children":1435},{"style":188},[1436],{"type":26,"value":284},{"type":21,"tag":80,"props":1438,"children":1439},{"class":157,"line":168},[1440],{"type":21,"tag":80,"props":1441,"children":1442},{"style":188},[1443],{"type":26,"value":191},{"type":21,"tag":80,"props":1445,"children":1446},{"class":157,"line":184},[1447],{"type":21,"tag":80,"props":1448,"children":1449},{"style":162},[1450],{"type":26,"value":1451},"   // ...Initialize variables as before...\n",{"type":21,"tag":80,"props":1453,"children":1454},{"class":157,"line":194},[1455],{"type":21,"tag":80,"props":1456,"children":1457},{"emptyLinePlaceholder":238},[1458],{"type":26,"value":241},{"type":21,"tag":80,"props":1460,"children":1461},{"class":157,"line":203},[1462],{"type":21,"tag":80,"props":1463,"children":1464},{"style":162},[1465],{"type":26,"value":874},{"type":21,"tag":80,"props":1467,"children":1468},{"class":157,"line":217},[1469],{"type":21,"tag":80,"props":1470,"children":1471},{"style":188},[1472],{"type":26,"value":883},{"type":21,"tag":80,"props":1474,"children":1475},{"class":157,"line":225},[1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516],{"type":21,"tag":80,"props":1477,"children":1478},{"style":178},[1479],{"type":26,"value":892},{"type":21,"tag":80,"props":1481,"children":1482},{"style":188},[1483],{"type":26,"value":255},{"type":21,"tag":80,"props":1485,"children":1486},{"style":178},[1487],{"type":26,"value":901},{"type":21,"tag":80,"props":1489,"children":1490},{"style":188},[1491],{"type":26,"value":906},{"type":21,"tag":80,"props":1493,"children":1494},{"style":178},[1495],{"type":26,"value":911},{"type":21,"tag":80,"props":1497,"children":1498},{"style":188},[1499],{"type":26,"value":916},{"type":21,"tag":80,"props":1501,"children":1502},{"style":172},[1503],{"type":26,"value":845},{"type":21,"tag":80,"props":1505,"children":1506},{"style":172},[1507],{"type":26,"value":925},{"type":21,"tag":80,"props":1509,"children":1510},{"style":188},[1511],{"type":26,"value":264},{"type":21,"tag":80,"props":1513,"children":1514},{"style":172},[1515],{"type":26,"value":415},{"type":21,"tag":80,"props":1517,"children":1518},{"style":188},[1519],{"type":26,"value":938},{"type":21,"tag":80,"props":1521,"children":1522},{"class":157,"line":234},[1523],{"type":21,"tag":80,"props":1524,"children":1525},{"emptyLinePlaceholder":238},[1526],{"type":26,"value":241},{"type":21,"tag":80,"props":1528,"children":1529},{"class":157,"line":244},[1530,1535,1540,1544,1548],{"type":21,"tag":80,"props":1531,"children":1532},{"style":188},[1533],{"type":26,"value":1534},"      fWindow.",{"type":21,"tag":80,"props":1536,"children":1537},{"style":178},[1538],{"type":26,"value":1539},"multiplyWithWindowingTable",{"type":21,"tag":80,"props":1541,"children":1542},{"style":188},[1543],{"type":26,"value":906},{"type":21,"tag":80,"props":1545,"children":1546},{"style":178},[1547],{"type":26,"value":911},{"type":21,"tag":80,"props":1549,"children":1550},{"style":188},[1551],{"type":26,"value":1552},"(), fftSize);\n",{"type":21,"tag":80,"props":1554,"children":1555},{"class":157,"line":287},[1556,1560,1564,1568,1572],{"type":21,"tag":80,"props":1557,"children":1558},{"style":188},[1559],{"type":26,"value":972},{"type":21,"tag":80,"props":1561,"children":1562},{"style":178},[1563],{"type":26,"value":977},{"type":21,"tag":80,"props":1565,"children":1566},{"style":188},[1567],{"type":26,"value":906},{"type":21,"tag":80,"props":1569,"children":1570},{"style":178},[1571],{"type":26,"value":911},{"type":21,"tag":80,"props":1573,"children":1574},{"style":188},[1575],{"type":26,"value":990},{"type":21,"tag":80,"props":1577,"children":1578},{"class":157,"line":324},[1579],{"type":21,"tag":80,"props":1580,"children":1581},{"emptyLinePlaceholder":238},[1582],{"type":26,"value":241},{"type":21,"tag":80,"props":1584,"children":1585},{"class":157,"line":333},[1586],{"type":21,"tag":80,"props":1587,"children":1588},{"style":162},[1589],{"type":26,"value":1007},{"type":21,"tag":80,"props":1591,"children":1592},{"class":157,"line":341},[1593],{"type":21,"tag":80,"props":1594,"children":1595},{"emptyLinePlaceholder":238},[1596],{"type":26,"value":241},{"type":21,"tag":80,"props":1598,"children":1599},{"class":157,"line":719},[1600],{"type":21,"tag":80,"props":1601,"children":1602},{"style":162},[1603],{"type":26,"value":1604},"      // Next chunk start\n",{"type":21,"tag":80,"props":1606,"children":1607},{"class":157,"line":727},[1608,1612,1616],{"type":21,"tag":80,"props":1609,"children":1610},{"style":188},[1611],{"type":26,"value":1033},{"type":21,"tag":80,"props":1613,"children":1614},{"style":172},[1615],{"type":26,"value":1038},{"type":21,"tag":80,"props":1617,"children":1618},{"style":188},[1619],{"type":26,"value":1043},{"type":21,"tag":80,"props":1621,"children":1622},{"class":157,"line":735},[1623],{"type":21,"tag":80,"props":1624,"children":1625},{"style":188},[1626],{"type":26,"value":1052},{"type":21,"tag":80,"props":1628,"children":1629},{"class":157,"line":744},[1630],{"type":21,"tag":80,"props":1631,"children":1632},{"style":162},[1633],{"type":26,"value":200},{"type":21,"tag":80,"props":1635,"children":1636},{"class":157,"line":797},[1637],{"type":21,"tag":80,"props":1638,"children":1639},{"style":188},[1640],{"type":26,"value":347},{"type":21,"tag":22,"props":1642,"children":1643},{},[1644,1646,1652,1654,1659],{"type":26,"value":1645},"I'll skip how the data is read into the ",{"type":21,"tag":52,"props":1647,"children":1649},{"className":1648},[],[1650],{"type":26,"value":1651},"Spectrum",{"type":26,"value":1653}," where ",{"type":21,"tag":52,"props":1655,"children":1657},{"className":1656},[],[1658],{"type":26,"value":440},{"type":26,"value":1660}," 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":1662,"children":1663},{},[1664],{"type":26,"value":1665},"There's a more interesting thing we need to do after that.",{"type":21,"tag":40,"props":1667,"children":1669},{"id":1668},"we-dont-hear-hertz",[1670],{"type":26,"value":1671},"We Don't Hear Hertz",{"type":21,"tag":22,"props":1673,"children":1674},{},[1675,1677,1684],{"type":26,"value":1676},"The FFT bins are ranges of Hertz (Hz). Hertz are a linear representation of frequency, but our ",{"type":21,"tag":29,"props":1678,"children":1681},{"href":1679,"rel":1680},"https://en.wikipedia.org/wiki/Psychoacoustics#/media/File:Perceived_Human_Hearing.svg",[33],[1682],{"type":26,"value":1683},"hearing's perception",{"type":26,"value":1685}," 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":1687,"children":1688},{},[1689,1691,1698],{"type":26,"value":1690},"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":1692,"children":1695},{"href":1693,"rel":1694},"https://en.wikipedia.org/wiki/Mel-frequency_cepstrum",[33],[1696],{"type":26,"value":1697},"Mels",{"type":26,"value":1699},". 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":1701,"children":1702},{},[1703,1705,1711,1713,1719,1721,1727,1729,1735,1737,1743,1745,1751],{"type":26,"value":1704},"The conversion matrix ends up being ",{"type":21,"tag":52,"props":1706,"children":1708},{"className":1707},[],[1709],{"type":26,"value":1710},"fftSize x mels",{"type":26,"value":1712},". Multiplying the frequency data which is ",{"type":21,"tag":52,"props":1714,"children":1716},{"className":1715},[],[1717],{"type":26,"value":1718},"n x fftsize",{"type":26,"value":1720}," with it will produce the ",{"type":21,"tag":52,"props":1722,"children":1724},{"className":1723},[],[1725],{"type":26,"value":1726},"n x mels",{"type":26,"value":1728}," desired output where ",{"type":21,"tag":52,"props":1730,"children":1732},{"className":1731},[],[1733],{"type":26,"value":1734},"n",{"type":26,"value":1736}," 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":52,"props":1738,"children":1740},{"className":1739},[],[1741],{"type":26,"value":1742},"1025 x 259",{"type":26,"value":1744}," to ",{"type":21,"tag":52,"props":1746,"children":1748},{"className":1747},[],[1749],{"type":26,"value":1750},"96 x 259",{"type":26,"value":1752}," in the Mels representation.",{"type":21,"tag":40,"props":1754,"children":1756},{"id":1755},"the-final-transforms",[1757],{"type":26,"value":1758},"The Final Transforms",{"type":21,"tag":22,"props":1760,"children":1761},{},[1762,1764,1769,1771,1777],{"type":26,"value":1763},"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":360,"props":1765,"children":1766},{},[1767],{"type":26,"value":1768},"power to dB",{"type":26,"value":1770}," 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":52,"props":1772,"children":1774},{"className":1773},[],[1775],{"type":26,"value":1776},"power-to-db",{"type":26,"value":1778}," code it again squares the values. It needs both.",{"type":21,"tag":146,"props":1780,"children":1782},{"className":148,"code":1781,"language":150,"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",[1783],{"type":21,"tag":52,"props":1784,"children":1785},{"__ignoreMap":8},[1786,1794,1802,1828,1836,1894,1940,2011,2060,2068,2075,2083,2124,2159,2166,2190],{"type":21,"tag":80,"props":1787,"children":1788},{"class":157,"line":158},[1789],{"type":21,"tag":80,"props":1790,"children":1791},{"style":162},[1792],{"type":26,"value":1793},"   // TransformChannels will modify the data in each channel (aka Spectrum) in the spectrogram in place.\n",{"type":21,"tag":80,"props":1795,"children":1796},{"class":157,"line":168},[1797],{"type":21,"tag":80,"props":1798,"children":1799},{"style":162},[1800],{"type":26,"value":1801},"   // Power to dB while limiting to a minimum magnitude.\n",{"type":21,"tag":80,"props":1803,"children":1804},{"class":157,"line":184},[1805,1810,1815,1819,1824],{"type":21,"tag":80,"props":1806,"children":1807},{"style":178},[1808],{"type":26,"value":1809},"   TransformChannels",{"type":21,"tag":80,"props":1811,"children":1812},{"style":188},[1813],{"type":26,"value":1814},"(magnitudes, [](",{"type":21,"tag":80,"props":1816,"children":1817},{"style":172},[1818],{"type":26,"value":415},{"type":21,"tag":80,"props":1820,"children":1821},{"style":475},[1822],{"type":26,"value":1823}," mag",{"type":21,"tag":80,"props":1825,"children":1826},{"style":188},[1827],{"type":26,"value":284},{"type":21,"tag":80,"props":1829,"children":1830},{"class":157,"line":194},[1831],{"type":21,"tag":80,"props":1832,"children":1833},{"style":188},[1834],{"type":26,"value":1835},"    {\n",{"type":21,"tag":80,"props":1837,"children":1838},{"class":157,"line":203},[1839,1844,1849,1854,1858,1862,1867,1872,1877,1881,1885,1889],{"type":21,"tag":80,"props":1840,"children":1841},{"style":172},[1842],{"type":26,"value":1843},"       constexpr",{"type":21,"tag":80,"props":1845,"children":1846},{"style":172},[1847],{"type":26,"value":1848}," double",{"type":21,"tag":80,"props":1850,"children":1851},{"style":188},[1852],{"type":26,"value":1853}," kMin ",{"type":21,"tag":80,"props":1855,"children":1856},{"style":172},[1857],{"type":26,"value":527},{"type":21,"tag":80,"props":1859,"children":1860},{"style":544},[1861],{"type":26,"value":669},{"type":21,"tag":80,"props":1863,"children":1864},{"style":172},[1865],{"type":26,"value":1866},"e-",{"type":21,"tag":80,"props":1868,"children":1869},{"style":544},[1870],{"type":26,"value":1871},"5",{"type":21,"tag":80,"props":1873,"children":1874},{"style":172},[1875],{"type":26,"value":1876}," *",{"type":21,"tag":80,"props":1878,"children":1879},{"style":544},[1880],{"type":26,"value":669},{"type":21,"tag":80,"props":1882,"children":1883},{"style":172},[1884],{"type":26,"value":1866},{"type":21,"tag":80,"props":1886,"children":1887},{"style":544},[1888],{"type":26,"value":1871},{"type":21,"tag":80,"props":1890,"children":1891},{"style":188},[1892],{"type":26,"value":1893},";\n",{"type":21,"tag":80,"props":1895,"children":1896},{"class":157,"line":217},[1897,1901,1905,1910,1914,1919,1924,1928,1932,1936],{"type":21,"tag":80,"props":1898,"children":1899},{"style":172},[1900],{"type":26,"value":1843},{"type":21,"tag":80,"props":1902,"children":1903},{"style":172},[1904],{"type":26,"value":1848},{"type":21,"tag":80,"props":1906,"children":1907},{"style":188},[1908],{"type":26,"value":1909}," kReference ",{"type":21,"tag":80,"props":1911,"children":1912},{"style":172},[1913],{"type":26,"value":527},{"type":21,"tag":80,"props":1915,"children":1916},{"style":544},[1917],{"type":26,"value":1918}," 1.0",{"type":21,"tag":80,"props":1920,"children":1921},{"style":172},[1922],{"type":26,"value":1923},"f",{"type":21,"tag":80,"props":1925,"children":1926},{"style":172},[1927],{"type":26,"value":1876},{"type":21,"tag":80,"props":1929,"children":1930},{"style":544},[1931],{"type":26,"value":1918},{"type":21,"tag":80,"props":1933,"children":1934},{"style":172},[1935],{"type":26,"value":1923},{"type":21,"tag":80,"props":1937,"children":1938},{"style":188},[1939],{"type":26,"value":1893},{"type":21,"tag":80,"props":1941,"children":1942},{"class":157,"line":225},[1943,1948,1953,1957,1961,1965,1970,1974,1978,1982,1987,1992,1997,2002,2006],{"type":21,"tag":80,"props":1944,"children":1945},{"style":172},[1946],{"type":26,"value":1947},"       return",{"type":21,"tag":80,"props":1949,"children":1950},{"style":544},[1951],{"type":26,"value":1952}," 10.0",{"type":21,"tag":80,"props":1954,"children":1955},{"style":172},[1956],{"type":26,"value":1876},{"type":21,"tag":80,"props":1958,"children":1959},{"style":178},[1960],{"type":26,"value":396},{"type":21,"tag":80,"props":1962,"children":1963},{"style":188},[1964],{"type":26,"value":255},{"type":21,"tag":80,"props":1966,"children":1967},{"style":178},[1968],{"type":26,"value":1969},"log10",{"type":21,"tag":80,"props":1971,"children":1972},{"style":188},[1973],{"type":26,"value":264},{"type":21,"tag":80,"props":1975,"children":1976},{"style":178},[1977],{"type":26,"value":307},{"type":21,"tag":80,"props":1979,"children":1980},{"style":188},[1981],{"type":26,"value":255},{"type":21,"tag":80,"props":1983,"children":1984},{"style":178},[1985],{"type":26,"value":1986},"max",{"type":21,"tag":80,"props":1988,"children":1989},{"style":188},[1990],{"type":26,"value":1991},"(kMin, (",{"type":21,"tag":80,"props":1993,"children":1994},{"style":172},[1995],{"type":26,"value":1996},"double",{"type":21,"tag":80,"props":1998,"children":1999},{"style":188},[2000],{"type":26,"value":2001},")mag ",{"type":21,"tag":80,"props":2003,"children":2004},{"style":172},[2005],{"type":26,"value":845},{"type":21,"tag":80,"props":2007,"children":2008},{"style":188},[2009],{"type":26,"value":2010}," mag))\n",{"type":21,"tag":80,"props":2012,"children":2013},{"class":157,"line":234},[2014,2019,2023,2027,2031,2035,2039,2043,2047,2051,2055],{"type":21,"tag":80,"props":2015,"children":2016},{"style":172},[2017],{"type":26,"value":2018},"            -",{"type":21,"tag":80,"props":2020,"children":2021},{"style":544},[2022],{"type":26,"value":1952},{"type":21,"tag":80,"props":2024,"children":2025},{"style":172},[2026],{"type":26,"value":1876},{"type":21,"tag":80,"props":2028,"children":2029},{"style":178},[2030],{"type":26,"value":396},{"type":21,"tag":80,"props":2032,"children":2033},{"style":188},[2034],{"type":26,"value":255},{"type":21,"tag":80,"props":2036,"children":2037},{"style":178},[2038],{"type":26,"value":1969},{"type":21,"tag":80,"props":2040,"children":2041},{"style":188},[2042],{"type":26,"value":264},{"type":21,"tag":80,"props":2044,"children":2045},{"style":178},[2046],{"type":26,"value":307},{"type":21,"tag":80,"props":2048,"children":2049},{"style":188},[2050],{"type":26,"value":255},{"type":21,"tag":80,"props":2052,"children":2053},{"style":178},[2054],{"type":26,"value":1986},{"type":21,"tag":80,"props":2056,"children":2057},{"style":188},[2058],{"type":26,"value":2059},"(kMin, kReference));\n",{"type":21,"tag":80,"props":2061,"children":2062},{"class":157,"line":244},[2063],{"type":21,"tag":80,"props":2064,"children":2065},{"style":188},[2066],{"type":26,"value":2067},"    });\n",{"type":21,"tag":80,"props":2069,"children":2070},{"class":157,"line":287},[2071],{"type":21,"tag":80,"props":2072,"children":2073},{"emptyLinePlaceholder":238},[2074],{"type":26,"value":241},{"type":21,"tag":80,"props":2076,"children":2077},{"class":157,"line":324},[2078],{"type":21,"tag":80,"props":2079,"children":2080},{"style":162},[2081],{"type":26,"value":2082},"   // Limit to maximum dB\n",{"type":21,"tag":80,"props":2084,"children":2085},{"class":157,"line":333},[2086,2091,2096,2100,2105,2110,2115,2119],{"type":21,"tag":80,"props":2087,"children":2088},{"style":172},[2089],{"type":26,"value":2090},"   auto",{"type":21,"tag":80,"props":2092,"children":2093},{"style":188},[2094],{"type":26,"value":2095}," maxDb ",{"type":21,"tag":80,"props":2097,"children":2098},{"style":172},[2099],{"type":26,"value":527},{"type":21,"tag":80,"props":2101,"children":2102},{"style":188},[2103],{"type":26,"value":2104}," magnitudes.",{"type":21,"tag":80,"props":2106,"children":2107},{"style":178},[2108],{"type":26,"value":2109},"MinMax",{"type":21,"tag":80,"props":2111,"children":2112},{"style":188},[2113],{"type":26,"value":2114},"().second ",{"type":21,"tag":80,"props":2116,"children":2117},{"style":172},[2118],{"type":26,"value":694},{"type":21,"tag":80,"props":2120,"children":2121},{"style":188},[2122],{"type":26,"value":2123}," kMaxDb;\n",{"type":21,"tag":80,"props":2125,"children":2126},{"class":157,"line":341},[2127,2131,2136,2141,2146,2150,2155],{"type":21,"tag":80,"props":2128,"children":2129},{"style":178},[2130],{"type":26,"value":1809},{"type":21,"tag":80,"props":2132,"children":2133},{"style":188},[2134],{"type":26,"value":2135},"(magnitudes, [",{"type":21,"tag":80,"props":2137,"children":2138},{"style":475},[2139],{"type":26,"value":2140},"maxDb",{"type":21,"tag":80,"props":2142,"children":2143},{"style":188},[2144],{"type":26,"value":2145},"](",{"type":21,"tag":80,"props":2147,"children":2148},{"style":172},[2149],{"type":26,"value":415},{"type":21,"tag":80,"props":2151,"children":2152},{"style":475},[2153],{"type":26,"value":2154}," dB",{"type":21,"tag":80,"props":2156,"children":2157},{"style":188},[2158],{"type":26,"value":284},{"type":21,"tag":80,"props":2160,"children":2161},{"class":157,"line":719},[2162],{"type":21,"tag":80,"props":2163,"children":2164},{"style":188},[2165],{"type":26,"value":1835},{"type":21,"tag":80,"props":2167,"children":2168},{"class":157,"line":727},[2169,2173,2177,2181,2185],{"type":21,"tag":80,"props":2170,"children":2171},{"style":172},[2172],{"type":26,"value":1947},{"type":21,"tag":80,"props":2174,"children":2175},{"style":178},[2176],{"type":26,"value":396},{"type":21,"tag":80,"props":2178,"children":2179},{"style":188},[2180],{"type":26,"value":255},{"type":21,"tag":80,"props":2182,"children":2183},{"style":178},[2184],{"type":26,"value":1986},{"type":21,"tag":80,"props":2186,"children":2187},{"style":188},[2188],{"type":26,"value":2189},"(dB, maxDb);\n",{"type":21,"tag":80,"props":2191,"children":2192},{"class":157,"line":735},[2193],{"type":21,"tag":80,"props":2194,"children":2195},{"style":188},[2196],{"type":26,"value":2067},{"type":21,"tag":40,"props":2198,"children":2200},{"id":2199},"view-from-the-top",[2201],{"type":26,"value":2202},"View From The Top",{"type":21,"tag":22,"props":2204,"children":2205},{},[2206,2208,2213,2215,2221],{"type":26,"value":2207},"Here is the top-level ",{"type":21,"tag":52,"props":2209,"children":2211},{"className":2210},[],[2212],{"type":26,"value":250},{"type":26,"value":2214}," code snippet that goes from audio file to the final spectrogram. When it finishes ",{"type":21,"tag":52,"props":2216,"children":2218},{"className":2217},[],[2219],{"type":26,"value":2220},"spectrogram",{"type":26,"value":2222}," contains the final Mel frequency information.",{"type":21,"tag":146,"props":2224,"children":2226},{"className":148,"code":2225,"language":150,"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",[2227],{"type":21,"tag":52,"props":2228,"children":2229},{"__ignoreMap":8},[2230,2238,2283,2325,2371,2378,2422,2449],{"type":21,"tag":80,"props":2231,"children":2232},{"class":157,"line":158},[2233],{"type":21,"tag":80,"props":2234,"children":2235},{"style":162},[2236],{"type":26,"value":2237},"   // 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":80,"props":2239,"children":2240},{"class":157,"line":168},[2241,2245,2250,2254,2259,2264,2269,2274,2279],{"type":21,"tag":80,"props":2242,"children":2243},{"style":172},[2244],{"type":26,"value":2090},{"type":21,"tag":80,"props":2246,"children":2247},{"style":188},[2248],{"type":26,"value":2249}," signal ",{"type":21,"tag":80,"props":2251,"children":2252},{"style":172},[2253],{"type":26,"value":527},{"type":21,"tag":80,"props":2255,"children":2256},{"style":544},[2257],{"type":26,"value":2258}," this",{"type":21,"tag":80,"props":2260,"children":2261},{"style":188},[2262],{"type":26,"value":2263},"->",{"type":21,"tag":80,"props":2265,"children":2266},{"style":178},[2267],{"type":26,"value":2268},"ReadAudioSignal",{"type":21,"tag":80,"props":2270,"children":2271},{"style":188},[2272],{"type":26,"value":2273},"(file, ",{"type":21,"tag":80,"props":2275,"children":2276},{"style":544},[2277],{"type":26,"value":2278},"3.1",{"type":21,"tag":80,"props":2280,"children":2281},{"style":188},[2282],{"type":26,"value":552},{"type":21,"tag":80,"props":2284,"children":2285},{"class":157,"line":184},[2286,2290,2295,2299,2303,2307,2311,2315,2320],{"type":21,"tag":80,"props":2287,"children":2288},{"style":172},[2289],{"type":26,"value":2090},{"type":21,"tag":80,"props":2291,"children":2292},{"style":188},[2293],{"type":26,"value":2294}," hzFreqs ",{"type":21,"tag":80,"props":2296,"children":2297},{"style":172},[2298],{"type":26,"value":527},{"type":21,"tag":80,"props":2300,"children":2301},{"style":544},[2302],{"type":26,"value":2258},{"type":21,"tag":80,"props":2304,"children":2305},{"style":188},[2306],{"type":26,"value":2263},{"type":21,"tag":80,"props":2308,"children":2309},{"style":178},[2310],{"type":26,"value":84},{"type":21,"tag":80,"props":2312,"children":2313},{"style":188},[2314],{"type":26,"value":264},{"type":21,"tag":80,"props":2316,"children":2317},{"style":178},[2318],{"type":26,"value":2319},"ToMono",{"type":21,"tag":80,"props":2321,"children":2322},{"style":188},[2323],{"type":26,"value":2324},"(signal).buffer, kHopLength);\n",{"type":21,"tag":80,"props":2326,"children":2327},{"class":157,"line":194},[2328,2332,2337,2342,2347,2352,2357,2362,2366],{"type":21,"tag":80,"props":2329,"children":2330},{"style":178},[2331],{"type":26,"value":1809},{"type":21,"tag":80,"props":2333,"children":2334},{"style":188},[2335],{"type":26,"value":2336},"(hzFreqs, [](",{"type":21,"tag":80,"props":2338,"children":2339},{"style":172},[2340],{"type":26,"value":2341},"auto",{"type":21,"tag":80,"props":2343,"children":2344},{"style":475},[2345],{"type":26,"value":2346}," x",{"type":21,"tag":80,"props":2348,"children":2349},{"style":188},[2350],{"type":26,"value":2351},") { ",{"type":21,"tag":80,"props":2353,"children":2354},{"style":172},[2355],{"type":26,"value":2356},"return",{"type":21,"tag":80,"props":2358,"children":2359},{"style":188},[2360],{"type":26,"value":2361}," x ",{"type":21,"tag":80,"props":2363,"children":2364},{"style":172},[2365],{"type":26,"value":845},{"type":21,"tag":80,"props":2367,"children":2368},{"style":188},[2369],{"type":26,"value":2370}," x; });\n",{"type":21,"tag":80,"props":2372,"children":2373},{"class":157,"line":203},[2374],{"type":21,"tag":80,"props":2375,"children":2376},{"emptyLinePlaceholder":238},[2377],{"type":26,"value":241},{"type":21,"tag":80,"props":2379,"children":2380},{"class":157,"line":217},[2381,2386,2391,2395,2399,2403,2408,2413,2417],{"type":21,"tag":80,"props":2382,"children":2383},{"style":188},[2384],{"type":26,"value":2385},"   spectrogram.",{"type":21,"tag":80,"props":2387,"children":2388},{"style":178},[2389],{"type":26,"value":2390},"AddChannel",{"type":21,"tag":80,"props":2392,"children":2393},{"style":188},[2394],{"type":26,"value":264},{"type":21,"tag":80,"props":2396,"children":2397},{"style":178},[2398],{"type":26,"value":250},{"type":21,"tag":80,"props":2400,"children":2401},{"style":188},[2402],{"type":26,"value":255},{"type":21,"tag":80,"props":2404,"children":2405},{"style":178},[2406],{"type":26,"value":2407},"HzToMels",{"type":21,"tag":80,"props":2409,"children":2410},{"style":188},[2411],{"type":26,"value":2412},"(hzFreqs, ",{"type":21,"tag":80,"props":2414,"children":2415},{"style":544},[2416],{"type":26,"value":547},{"type":21,"tag":80,"props":2418,"children":2419},{"style":188},[2420],{"type":26,"value":2421},", signal.sampleRate, fNumMelBins));\n",{"type":21,"tag":80,"props":2423,"children":2424},{"class":157,"line":225},[2425,2429,2434,2439,2444],{"type":21,"tag":80,"props":2426,"children":2427},{"style":188},[2428],{"type":26,"value":2385},{"type":21,"tag":80,"props":2430,"children":2431},{"style":178},[2432],{"type":26,"value":2433},"Reshape",{"type":21,"tag":80,"props":2435,"children":2436},{"style":188},[2437],{"type":26,"value":2438},"(fNumMelBins, hzFreqs.",{"type":21,"tag":80,"props":2440,"children":2441},{"style":178},[2442],{"type":26,"value":2443},"Shape",{"type":21,"tag":80,"props":2445,"children":2446},{"style":188},[2447],{"type":26,"value":2448},"().second);\n",{"type":21,"tag":80,"props":2450,"children":2451},{"class":157,"line":234},[2452,2457,2461,2466],{"type":21,"tag":80,"props":2453,"children":2454},{"style":178},[2455],{"type":26,"value":2456},"   ASpectroMaker",{"type":21,"tag":80,"props":2458,"children":2459},{"style":188},[2460],{"type":26,"value":255},{"type":21,"tag":80,"props":2462,"children":2463},{"style":178},[2464],{"type":26,"value":2465},"AmplitudesToDbs",{"type":21,"tag":80,"props":2467,"children":2468},{"style":188},[2469],{"type":26,"value":2470},"(spectrogram);\n",{"type":21,"tag":40,"props":2472,"children":2474},{"id":2473},"concluding",[2475],{"type":26,"value":2476},"Concluding",{"type":21,"tag":22,"props":2478,"children":2479},{},[2480],{"type":26,"value":2481},"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":2483,"children":2484},{},[2485],{"type":26,"value":2486},"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":2488,"children":2489},{},[2490,2492,2499,2501,2508,2510,2517],{"type":26,"value":2491},"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":2493,"children":2496},{"href":2494,"rel":2495},"https://librosa.github.io",[33],[2497],{"type":26,"value":2498},"librosa",{"type":26,"value":2500}," and ",{"type":21,"tag":29,"props":2502,"children":2505},{"href":2503,"rel":2504},"https://numpy.org",[33],[2506],{"type":26,"value":2507},"NumPy",{"type":26,"value":2509}," 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":2511,"children":2514},{"href":2512,"rel":2513},"https://docs.juce.com/master/tutorial_simple_fft.html",[33],[2515],{"type":26,"value":2516},"spectrogram demo",{"type":26,"value":2518}," was also very helpful",{"type":21,"tag":2520,"props":2521,"children":2522},"style",{},[2523],{"type":26,"value":2524},"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":184,"depth":184,"links":2526},[2527,2528,2529,2530,2531,2532,2533,2534],{"id":42,"depth":168,"text":45},{"id":70,"depth":168,"text":73},{"id":350,"depth":168,"text":353},{"id":1071,"depth":168,"text":1074},{"id":1668,"depth":168,"text":1671},{"id":1755,"depth":168,"text":1758},{"id":2199,"depth":168,"text":2202},{"id":2473,"depth":168,"text":2476},"markdown","content:jbagley:2019-4:MakingSpectrogramsInJUCE.md","content","jbagley/2019-4/MakingSpectrogramsInJUCE.md","jbagley/2019-4/MakingSpectrogramsInJUCE","md",{"user":2542,"name":2543},"jbagley","Jason Bagley",{"_path":2545,"_dir":2546,"_draft":7,"_partial":7,"_locale":8,"title":2547,"description":2548,"excerpt":2548,"image":2549,"publishDate":2550,"tags":2551,"body":2553,"_type":2535,"_id":5453,"_source":2537,"_file":5454,"_stem":5455,"_extension":2540,"author":5456},"/ckeefer/2019-1/unlockingwebaudio","2019-1","Unlocking Web Audio","\"It's going to be the coolest thing ever.\"","/ckeefer/2019-1/img/featured_image.jpg","2019-01-01",[2552,14],"js",{"type":18,"children":2554,"toc":5446},[2555,2563,2568,2576,2581,2613,2627,2632,2637,2651,2657,2671,2694,2707,2730,2735,3226,3231,3270,3284,3306,3320,3363,3390,3395,3407,3413,3427,3432,3437,3442,3447,3453,3458,5337,5356,5367,5379,5392,5414,5419,5442],{"type":21,"tag":2556,"props":2557,"children":2558},"blockquote",{},[2559],{"type":21,"tag":22,"props":2560,"children":2561},{},[2562],{"type":26,"value":2548},{"type":21,"tag":22,"props":2564,"children":2565},{},[2566],{"type":26,"value":2567},"You know enough by now to be doubtful when a client makes this statement, but you're willing to entertain the idea that this may not, in fact, be a tragedy in the making.",{"type":21,"tag":2556,"props":2569,"children":2570},{},[2571],{"type":21,"tag":22,"props":2572,"children":2573},{},[2574],{"type":26,"value":2575},"\"It's going to be a music machine - like, full keyboard and everything - but each of the keys is going to be mapped to - wait for it - cat sounds! We'll call it the 'Meowsic Machine'! Oh, and we need it to be accessible to everyone via the Web. Which is easy, right?",{"type":21,"tag":22,"props":2577,"children":2578},{},[2579],{"type":26,"value":2580},"You are reminded that the universe can be a cruel place.",{"type":21,"tag":22,"props":2582,"children":2583},{},[2584,2586,2593,2595,2602,2604,2611],{"type":26,"value":2585},"It's now your job to make this happen. Over the course of a few posts, we're going to look at the ",{"type":21,"tag":29,"props":2587,"children":2590},{"href":2588,"rel":2589},"https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API",[33],[2591],{"type":26,"value":2592},"Web Audio API",{"type":26,"value":2594},", and build the Meowsic Machine together. In the process, we'll also enjoy a dalliance with ",{"type":21,"tag":29,"props":2596,"children":2599},{"href":2597,"rel":2598},"https://vuejs.org/",[33],[2600],{"type":26,"value":2601},"Vue.js",{"type":26,"value":2603},", and dip our toes into the deep-end with ",{"type":21,"tag":29,"props":2605,"children":2608},{"href":2606,"rel":2607},"https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers",[33],[2609],{"type":26,"value":2610},"Web Workers",{"type":26,"value":2612},". Today, we take the first step in this historic journey - convincing the browser to actually let us play sounds.",{"type":21,"tag":2614,"props":2615,"children":2617},"h3",{"id":2616},"periodicwave-isnt-that-something-the-crowd-does-at-a-sports-game",[2618,2625],{"type":21,"tag":29,"props":2619,"children":2622},{"href":2620,"rel":2621},"https://developer.mozilla.org/en-US/docs/Web/API/PeriodicWave",[33],[2623],{"type":26,"value":2624},"PeriodicWave",{"type":26,"value":2626},"... Isn't that something the crowd does at a sports game?",{"type":21,"tag":22,"props":2628,"children":2629},{},[2630],{"type":26,"value":2631},"The Web Audio API is rather intimidating at first glance if you're not used to dealing with the low-level vagaries of audio - you feed in an appropriate file to the right OS API, it gets converted to PCM via magic, cat sounds come out of the speakers, right?",{"type":21,"tag":22,"props":2633,"children":2634},{},[2635],{"type":26,"value":2636},"(If you're coming from the world of dealing regularly with said vagaries, you're probably thinking \"this API is weird - there's a mix of high-level and low-level constructs, and why is setting up a standard ring buffer so hard?\" We'll get to those details/criticisms/workarounds in a future post).",{"type":21,"tag":22,"props":2638,"children":2639},{},[2640,2642,2649],{"type":26,"value":2641},"It is, however, an unquestionably better API for playing sounds - especially dynamically created sounds - than any web standard to date. It's also ",{"type":21,"tag":29,"props":2643,"children":2646},{"href":2644,"rel":2645},"https://caniuse.com/#search=web%20audio%20api",[33],[2647],{"type":26,"value":2648},"fairly well supported across the board",{"type":26,"value":2650}," by browsers, so there's no reason not to dig in and get to work.",{"type":21,"tag":2614,"props":2652,"children":2654},{"id":2653},"kiss",[2655],{"type":26,"value":2656},"KISS",{"type":21,"tag":22,"props":2658,"children":2659},{},[2660,2662,2669],{"type":26,"value":2661},"So, let's start at the very beginning. If you haven't already, check out MDN's ",{"type":21,"tag":29,"props":2663,"children":2666},{"href":2664,"rel":2665},"https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API",[33],[2667],{"type":26,"value":2668},"Basic concepts behind Web Audio API",{"type":26,"value":2670}," article. It's a great introduction into some of the basics of not just the API, but playing sounds in general.",{"type":21,"tag":22,"props":2672,"children":2673},{},[2674,2676,2683,2685,2692],{"type":26,"value":2675},"We just want to play some cat sounds, though, not delve into the ",{"type":21,"tag":29,"props":2677,"children":2680},{"href":2678,"rel":2679},"https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem",[33],[2681],{"type":26,"value":2682},"Nyquist-Shannon Sampling Theorem",{"type":26,"value":2684},", or reprise the history of the ",{"type":21,"tag":29,"props":2686,"children":2689},{"href":2687,"rel":2688},"https://en.wikipedia.org/wiki/U-matic#Digital_audio",[33],[2690],{"type":26,"value":2691},"44.1kHz sampling frequency",{"type":26,"value":2693},". Can't we just skip to the good stuff?",{"type":21,"tag":22,"props":2695,"children":2696},{},[2697,2699,2705],{"type":26,"value":2698},"So, let's assume we have a sound file, ",{"type":21,"tag":52,"props":2700,"children":2702},{"className":2701},[],[2703],{"type":26,"value":2704},"meow.mp3",{"type":26,"value":2706},". We're going to rely on the browser have the right codec to decode this file, and we're not going to try and loop it, alter its gain, or perform any transformations of it—we're just going to play it.",{"type":21,"tag":22,"props":2708,"children":2709},{},[2710,2712,2719,2721,2728],{"type":26,"value":2711},"We could do something this simple with the ",{"type":21,"tag":29,"props":2713,"children":2716},{"href":2714,"rel":2715},"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio",[33],[2717],{"type":26,"value":2718},"Audio Element",{"type":26,"value":2720},"—but we want to do bigger and cooler things in the future. It is worth noting, however, that an audio element ",{"type":21,"tag":29,"props":2722,"children":2725},{"href":2723,"rel":2724},"https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaElementSource",[33],[2726],{"type":26,"value":2727},"can be used as the source for an Web Audio Context",{"type":26,"value":2729}," - we may delve more into this in the future.",{"type":21,"tag":22,"props":2731,"children":2732},{},[2733],{"type":26,"value":2734},"For now, let's get this party started:",{"type":21,"tag":146,"props":2736,"children":2739},{"className":2737,"code":2738,"language":2552,"meta":8,"style":8},"language-js shiki shiki-themes github-light github-dark","const _audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n\n    /**\n     * Allow the requester to load a new sfx, specifying a file to load.\n     * @param {string} sfxFile\n     * @returns {Promise\u003CArrayBuffer>}\n     */\n    async function load (sfxFile) {\n        const _sfxFile = await fetch(sfxFile);\n        return await _sfxFile.arrayBuffer();\n    };\n\n    /**\n     * Load and play the specified file.\n     * @param sfxFile\n     * @returns {Promise\u003CAudioBufferSourceNode>}\n     */\n    function play (sfxFile) {\n        return load(sfxFile).then((arrayBuffer) => {\n            const audioBuffer = _audioCtx.decodeAudioData(arrayBuffer);\n            const sourceNode = _audioCtx.createBufferSource();\n            sourceNode.buffer = audioBuffer;\n            sourceNode.connect(_audioCtx.destination);\n            sourceNode.start();\n\n            return sourceNode;\n        });\n    };\n",[2740],{"type":21,"tag":52,"props":2741,"children":2742},{"__ignoreMap":8},[2743,2779,2786,2794,2802,2825,2842,2850,2883,2915,2941,2949,2956,2963,2971,2986,3002,3009,3034,3079,3111,3140,3157,3175,3191,3198,3211,3219],{"type":21,"tag":80,"props":2744,"children":2745},{"class":157,"line":158},[2746,2750,2755,2759,2764,2769,2774],{"type":21,"tag":80,"props":2747,"children":2748},{"style":172},[2749],{"type":26,"value":462},{"type":21,"tag":80,"props":2751,"children":2752},{"style":544},[2753],{"type":26,"value":2754}," _audioCtx",{"type":21,"tag":80,"props":2756,"children":2757},{"style":172},[2758],{"type":26,"value":391},{"type":21,"tag":80,"props":2760,"children":2761},{"style":172},[2762],{"type":26,"value":2763}," new",{"type":21,"tag":80,"props":2765,"children":2766},{"style":188},[2767],{"type":26,"value":2768}," (window.AudioContext ",{"type":21,"tag":80,"props":2770,"children":2771},{"style":172},[2772],{"type":26,"value":2773},"||",{"type":21,"tag":80,"props":2775,"children":2776},{"style":188},[2777],{"type":26,"value":2778}," window.webkitAudioContext)();\n",{"type":21,"tag":80,"props":2780,"children":2781},{"class":157,"line":168},[2782],{"type":21,"tag":80,"props":2783,"children":2784},{"emptyLinePlaceholder":238},[2785],{"type":26,"value":241},{"type":21,"tag":80,"props":2787,"children":2788},{"class":157,"line":184},[2789],{"type":21,"tag":80,"props":2790,"children":2791},{"style":162},[2792],{"type":26,"value":2793},"    /**\n",{"type":21,"tag":80,"props":2795,"children":2796},{"class":157,"line":194},[2797],{"type":21,"tag":80,"props":2798,"children":2799},{"style":162},[2800],{"type":26,"value":2801},"     * Allow the requester to load a new sfx, specifying a file to load.\n",{"type":21,"tag":80,"props":2803,"children":2804},{"class":157,"line":203},[2805,2810,2815,2820],{"type":21,"tag":80,"props":2806,"children":2807},{"style":162},[2808],{"type":26,"value":2809},"     * ",{"type":21,"tag":80,"props":2811,"children":2812},{"style":172},[2813],{"type":26,"value":2814},"@param",{"type":21,"tag":80,"props":2816,"children":2817},{"style":178},[2818],{"type":26,"value":2819}," {string}",{"type":21,"tag":80,"props":2821,"children":2822},{"style":188},[2823],{"type":26,"value":2824}," sfxFile\n",{"type":21,"tag":80,"props":2826,"children":2827},{"class":157,"line":217},[2828,2832,2837],{"type":21,"tag":80,"props":2829,"children":2830},{"style":162},[2831],{"type":26,"value":2809},{"type":21,"tag":80,"props":2833,"children":2834},{"style":172},[2835],{"type":26,"value":2836},"@returns",{"type":21,"tag":80,"props":2838,"children":2839},{"style":178},[2840],{"type":26,"value":2841}," {Promise\u003CArrayBuffer>}\n",{"type":21,"tag":80,"props":2843,"children":2844},{"class":157,"line":225},[2845],{"type":21,"tag":80,"props":2846,"children":2847},{"style":162},[2848],{"type":26,"value":2849},"     */\n",{"type":21,"tag":80,"props":2851,"children":2852},{"class":157,"line":234},[2853,2858,2863,2868,2873,2878],{"type":21,"tag":80,"props":2854,"children":2855},{"style":172},[2856],{"type":26,"value":2857},"    async",{"type":21,"tag":80,"props":2859,"children":2860},{"style":172},[2861],{"type":26,"value":2862}," function",{"type":21,"tag":80,"props":2864,"children":2865},{"style":178},[2866],{"type":26,"value":2867}," load",{"type":21,"tag":80,"props":2869,"children":2870},{"style":188},[2871],{"type":26,"value":2872}," (",{"type":21,"tag":80,"props":2874,"children":2875},{"style":475},[2876],{"type":26,"value":2877},"sfxFile",{"type":21,"tag":80,"props":2879,"children":2880},{"style":188},[2881],{"type":26,"value":2882},") {\n",{"type":21,"tag":80,"props":2884,"children":2885},{"class":157,"line":244},[2886,2891,2896,2900,2905,2910],{"type":21,"tag":80,"props":2887,"children":2888},{"style":172},[2889],{"type":26,"value":2890},"        const",{"type":21,"tag":80,"props":2892,"children":2893},{"style":544},[2894],{"type":26,"value":2895}," _sfxFile",{"type":21,"tag":80,"props":2897,"children":2898},{"style":172},[2899],{"type":26,"value":391},{"type":21,"tag":80,"props":2901,"children":2902},{"style":172},[2903],{"type":26,"value":2904}," await",{"type":21,"tag":80,"props":2906,"children":2907},{"style":178},[2908],{"type":26,"value":2909}," fetch",{"type":21,"tag":80,"props":2911,"children":2912},{"style":188},[2913],{"type":26,"value":2914},"(sfxFile);\n",{"type":21,"tag":80,"props":2916,"children":2917},{"class":157,"line":287},[2918,2923,2927,2932,2937],{"type":21,"tag":80,"props":2919,"children":2920},{"style":172},[2921],{"type":26,"value":2922},"        return",{"type":21,"tag":80,"props":2924,"children":2925},{"style":172},[2926],{"type":26,"value":2904},{"type":21,"tag":80,"props":2928,"children":2929},{"style":188},[2930],{"type":26,"value":2931}," _sfxFile.",{"type":21,"tag":80,"props":2933,"children":2934},{"style":178},[2935],{"type":26,"value":2936},"arrayBuffer",{"type":21,"tag":80,"props":2938,"children":2939},{"style":188},[2940],{"type":26,"value":587},{"type":21,"tag":80,"props":2942,"children":2943},{"class":157,"line":324},[2944],{"type":21,"tag":80,"props":2945,"children":2946},{"style":188},[2947],{"type":26,"value":2948},"    };\n",{"type":21,"tag":80,"props":2950,"children":2951},{"class":157,"line":333},[2952],{"type":21,"tag":80,"props":2953,"children":2954},{"emptyLinePlaceholder":238},[2955],{"type":26,"value":241},{"type":21,"tag":80,"props":2957,"children":2958},{"class":157,"line":341},[2959],{"type":21,"tag":80,"props":2960,"children":2961},{"style":162},[2962],{"type":26,"value":2793},{"type":21,"tag":80,"props":2964,"children":2965},{"class":157,"line":719},[2966],{"type":21,"tag":80,"props":2967,"children":2968},{"style":162},[2969],{"type":26,"value":2970},"     * Load and play the specified file.\n",{"type":21,"tag":80,"props":2972,"children":2973},{"class":157,"line":727},[2974,2978,2982],{"type":21,"tag":80,"props":2975,"children":2976},{"style":162},[2977],{"type":26,"value":2809},{"type":21,"tag":80,"props":2979,"children":2980},{"style":172},[2981],{"type":26,"value":2814},{"type":21,"tag":80,"props":2983,"children":2984},{"style":188},[2985],{"type":26,"value":2824},{"type":21,"tag":80,"props":2987,"children":2988},{"class":157,"line":735},[2989,2993,2997],{"type":21,"tag":80,"props":2990,"children":2991},{"style":162},[2992],{"type":26,"value":2809},{"type":21,"tag":80,"props":2994,"children":2995},{"style":172},[2996],{"type":26,"value":2836},{"type":21,"tag":80,"props":2998,"children":2999},{"style":178},[3000],{"type":26,"value":3001}," {Promise\u003CAudioBufferSourceNode>}\n",{"type":21,"tag":80,"props":3003,"children":3004},{"class":157,"line":744},[3005],{"type":21,"tag":80,"props":3006,"children":3007},{"style":162},[3008],{"type":26,"value":2849},{"type":21,"tag":80,"props":3010,"children":3011},{"class":157,"line":797},[3012,3017,3022,3026,3030],{"type":21,"tag":80,"props":3013,"children":3014},{"style":172},[3015],{"type":26,"value":3016},"    function",{"type":21,"tag":80,"props":3018,"children":3019},{"style":178},[3020],{"type":26,"value":3021}," play",{"type":21,"tag":80,"props":3023,"children":3024},{"style":188},[3025],{"type":26,"value":2872},{"type":21,"tag":80,"props":3027,"children":3028},{"style":475},[3029],{"type":26,"value":2877},{"type":21,"tag":80,"props":3031,"children":3032},{"style":188},[3033],{"type":26,"value":2882},{"type":21,"tag":80,"props":3035,"children":3036},{"class":157,"line":805},[3037,3041,3045,3050,3055,3060,3064,3069,3074],{"type":21,"tag":80,"props":3038,"children":3039},{"style":172},[3040],{"type":26,"value":2922},{"type":21,"tag":80,"props":3042,"children":3043},{"style":178},[3044],{"type":26,"value":2867},{"type":21,"tag":80,"props":3046,"children":3047},{"style":188},[3048],{"type":26,"value":3049},"(sfxFile).",{"type":21,"tag":80,"props":3051,"children":3052},{"style":178},[3053],{"type":26,"value":3054},"then",{"type":21,"tag":80,"props":3056,"children":3057},{"style":188},[3058],{"type":26,"value":3059},"((",{"type":21,"tag":80,"props":3061,"children":3062},{"style":475},[3063],{"type":26,"value":2936},{"type":21,"tag":80,"props":3065,"children":3066},{"style":188},[3067],{"type":26,"value":3068},") ",{"type":21,"tag":80,"props":3070,"children":3071},{"style":172},[3072],{"type":26,"value":3073},"=>",{"type":21,"tag":80,"props":3075,"children":3076},{"style":188},[3077],{"type":26,"value":3078}," {\n",{"type":21,"tag":80,"props":3080,"children":3081},{"class":157,"line":814},[3082,3087,3092,3096,3101,3106],{"type":21,"tag":80,"props":3083,"children":3084},{"style":172},[3085],{"type":26,"value":3086},"            const",{"type":21,"tag":80,"props":3088,"children":3089},{"style":544},[3090],{"type":26,"value":3091}," audioBuffer",{"type":21,"tag":80,"props":3093,"children":3094},{"style":172},[3095],{"type":26,"value":391},{"type":21,"tag":80,"props":3097,"children":3098},{"style":188},[3099],{"type":26,"value":3100}," _audioCtx.",{"type":21,"tag":80,"props":3102,"children":3103},{"style":178},[3104],{"type":26,"value":3105},"decodeAudioData",{"type":21,"tag":80,"props":3107,"children":3108},{"style":188},[3109],{"type":26,"value":3110},"(arrayBuffer);\n",{"type":21,"tag":80,"props":3112,"children":3113},{"class":157,"line":860},[3114,3118,3123,3127,3131,3136],{"type":21,"tag":80,"props":3115,"children":3116},{"style":172},[3117],{"type":26,"value":3086},{"type":21,"tag":80,"props":3119,"children":3120},{"style":544},[3121],{"type":26,"value":3122}," sourceNode",{"type":21,"tag":80,"props":3124,"children":3125},{"style":172},[3126],{"type":26,"value":391},{"type":21,"tag":80,"props":3128,"children":3129},{"style":188},[3130],{"type":26,"value":3100},{"type":21,"tag":80,"props":3132,"children":3133},{"style":178},[3134],{"type":26,"value":3135},"createBufferSource",{"type":21,"tag":80,"props":3137,"children":3138},{"style":188},[3139],{"type":26,"value":587},{"type":21,"tag":80,"props":3141,"children":3142},{"class":157,"line":868},[3143,3148,3152],{"type":21,"tag":80,"props":3144,"children":3145},{"style":188},[3146],{"type":26,"value":3147},"            sourceNode.buffer ",{"type":21,"tag":80,"props":3149,"children":3150},{"style":172},[3151],{"type":26,"value":527},{"type":21,"tag":80,"props":3153,"children":3154},{"style":188},[3155],{"type":26,"value":3156}," audioBuffer;\n",{"type":21,"tag":80,"props":3158,"children":3159},{"class":157,"line":877},[3160,3165,3170],{"type":21,"tag":80,"props":3161,"children":3162},{"style":188},[3163],{"type":26,"value":3164},"            sourceNode.",{"type":21,"tag":80,"props":3166,"children":3167},{"style":178},[3168],{"type":26,"value":3169},"connect",{"type":21,"tag":80,"props":3171,"children":3172},{"style":188},[3173],{"type":26,"value":3174},"(_audioCtx.destination);\n",{"type":21,"tag":80,"props":3176,"children":3177},{"class":157,"line":886},[3178,3182,3187],{"type":21,"tag":80,"props":3179,"children":3180},{"style":188},[3181],{"type":26,"value":3164},{"type":21,"tag":80,"props":3183,"children":3184},{"style":178},[3185],{"type":26,"value":3186},"start",{"type":21,"tag":80,"props":3188,"children":3189},{"style":188},[3190],{"type":26,"value":587},{"type":21,"tag":80,"props":3192,"children":3193},{"class":157,"line":941},[3194],{"type":21,"tag":80,"props":3195,"children":3196},{"emptyLinePlaceholder":238},[3197],{"type":26,"value":241},{"type":21,"tag":80,"props":3199,"children":3200},{"class":157,"line":949},[3201,3206],{"type":21,"tag":80,"props":3202,"children":3203},{"style":172},[3204],{"type":26,"value":3205},"            return",{"type":21,"tag":80,"props":3207,"children":3208},{"style":188},[3209],{"type":26,"value":3210}," sourceNode;\n",{"type":21,"tag":80,"props":3212,"children":3213},{"class":157,"line":958},[3214],{"type":21,"tag":80,"props":3215,"children":3216},{"style":188},[3217],{"type":26,"value":3218},"        });\n",{"type":21,"tag":80,"props":3220,"children":3221},{"class":157,"line":966},[3222],{"type":21,"tag":80,"props":3223,"children":3224},{"style":188},[3225],{"type":26,"value":2948},{"type":21,"tag":22,"props":3227,"children":3228},{},[3229],{"type":26,"value":3230},"Okay, let's unpack it:",{"type":21,"tag":22,"props":3232,"children":3233},{},[3234,3236,3243,3245,3252,3254,3260,3262,3268],{"type":26,"value":3235},"First, we create our ",{"type":21,"tag":29,"props":3237,"children":3240},{"href":3238,"rel":3239},"https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/AudioContext",[33],[3241],{"type":26,"value":3242},"audio context",{"type":26,"value":3244},"—if you've dealt with the ",{"type":21,"tag":29,"props":3246,"children":3249},{"href":3247,"rel":3248},"https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API",[33],[3250],{"type":26,"value":3251},"Canvas API",{"type":26,"value":3253}," this is familiar—this is going to be our audio processing graph object. We look for the standard ",{"type":21,"tag":52,"props":3255,"children":3257},{"className":3256},[],[3258],{"type":26,"value":3259},"AudioContext",{"type":26,"value":3261}," object first, and if we can't find, we try to fall back to the browser-prefixed version, ",{"type":21,"tag":52,"props":3263,"children":3265},{"className":3264},[],[3266],{"type":26,"value":3267},"webkitAudioContext",{"type":26,"value":3269},", to broaden our browser support.",{"type":21,"tag":22,"props":3271,"children":3272},{},[3273,3275,3282],{"type":26,"value":3274},"Then, we declare an ",{"type":21,"tag":29,"props":3276,"children":3279},{"href":3277,"rel":3278},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function",[33],[3280],{"type":26,"value":3281},"async function",{"type":26,"value":3283},". This is part of the ECMAScript 2017 spec, but if you're able to use the Web Audio API, you're probably able to use this too. Async/Await is a wonderful bit of sugar over Promises, and you should take advantage of it if you can.",{"type":21,"tag":22,"props":3285,"children":3286},{},[3287,3289,3295,3297,3304],{"type":26,"value":3288},"Within this async function, ",{"type":21,"tag":52,"props":3290,"children":3292},{"className":3291},[],[3293],{"type":26,"value":3294},"load",{"type":26,"value":3296},", we rely on the ",{"type":21,"tag":29,"props":3298,"children":3301},{"href":3299,"rel":3300},"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API",[33],[3302],{"type":26,"value":3303},"Fetch API",{"type":26,"value":3305}," to go and get the file for us (my, aren't we linking to MDN an awful lot in this post!). There's no reason we couldn't use XMLHttpRequest here instead, but fetch is wonderfully compact and again, if you can use Web Audio, you can almost certainly use fetch.",{"type":21,"tag":22,"props":3307,"children":3308},{},[3309,3311,3318],{"type":26,"value":3310},"We ",{"type":21,"tag":29,"props":3312,"children":3315},{"href":3313,"rel":3314},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await",[33],[3316],{"type":26,"value":3317},"await",{"type":26,"value":3319}," the response from fetch, and then get the response body as an array buffer and return it.",{"type":21,"tag":22,"props":3321,"children":3322},{},[3323,3325,3331,3333,3338,3340,3345,3347,3354,3356,3362],{"type":26,"value":3324},"Then, in ",{"type":21,"tag":52,"props":3326,"children":3328},{"className":3327},[],[3329],{"type":26,"value":3330},"play",{"type":26,"value":3332},", we perform both the loading and playing, so our api becomes just a call to ",{"type":21,"tag":52,"props":3334,"children":3336},{"className":3335},[],[3337],{"type":26,"value":3330},{"type":26,"value":3339}," with the file location and wait on the promise returned from our async ",{"type":21,"tag":52,"props":3341,"children":3343},{"className":3342},[],[3344],{"type":26,"value":3294},{"type":26,"value":3346}," function. Then, we need to decode the audio data, turning it into PCM, and then create an ",{"type":21,"tag":29,"props":3348,"children":3351},{"href":3349,"rel":3350},"https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode",[33],[3352],{"type":26,"value":3353},"AudioBufferSourceNode",{"type":26,"value":3355}," from the return (that's the call to ",{"type":21,"tag":52,"props":3357,"children":3359},{"className":3358},[],[3360],{"type":26,"value":3361},"_audioCtx.createBufferSource()",{"type":26,"value":67},{"type":21,"tag":22,"props":3364,"children":3365},{},[3366,3368,3373,3375,3381,3383,3388],{"type":26,"value":3367},"We then set our buffer source to draw from the audio buffer we've created out of the array buffer we created out of the file (",{"type":21,"tag":360,"props":3369,"children":3370},{},[3371],{"type":26,"value":3372},"whew",{"type":26,"value":3374},"), and connect it to the destination of the audio context (e.g. the audio context output, like speakers), and ",{"type":21,"tag":3376,"props":3377,"children":3378},"strong",{},[3379],{"type":26,"value":3380},"finally",{"type":26,"value":3382}," we can call ",{"type":21,"tag":52,"props":3384,"children":3386},{"className":3385},[],[3387],{"type":26,"value":3186},{"type":26,"value":3389}," on the sourceNode to have it start pumping its audio into the audio context.",{"type":21,"tag":22,"props":3391,"children":3392},{},[3393],{"type":26,"value":3394},"Easy-peasy.",{"type":21,"tag":22,"props":3396,"children":3397},{},[3398,3400,3405],{"type":26,"value":3399},"Except... if you do this with your ",{"type":21,"tag":52,"props":3401,"children":3403},{"className":3402},[],[3404],{"type":26,"value":2704},{"type":26,"value":3406}," file, you won't hear anything. The file will successfully be fetched, loaded, decoded... but not played. What's going on?",{"type":21,"tag":2614,"props":3408,"children":3410},{"id":3409},"now-hear-this",[3411],{"type":26,"value":3412},"Now Hear This",{"type":21,"tag":22,"props":3414,"children":3415},{},[3416,3418,3425],{"type":26,"value":3417},"You've hit the browser's ",{"type":21,"tag":29,"props":3419,"children":3422},{"href":3420,"rel":3421},"https://developers.google.com/web/updates/2018/11/web-audio-autoplay",[33],[3423],{"type":26,"value":3424},"autoplay policy",{"type":26,"value":3426},". Nobody likes opening a new tab and having whatever website they've just navigated to start blaring their addiction to spongebob to the whole office. So, most browsers have various autoplay policies which restrict what the page can do with media before the user has interacted with it.",{"type":21,"tag":22,"props":3428,"children":3429},{},[3430],{"type":26,"value":3431},"For our purposes today, this boils down to not being able to play audio until the user has interacted with the page in some easily measurable way - that is, clicked/tapped on it.",{"type":21,"tag":22,"props":3433,"children":3434},{},[3435],{"type":26,"value":3436},"So, in order to unlock our audio, we'll need to listen for that interaction, and wait to play our sounds until after we've received it. We don't want to have to download and play real audio to make that happen, so we'll need to create an empty sound buffer that we can use as our stalking horse.",{"type":21,"tag":22,"props":3438,"children":3439},{},[3440],{"type":26,"value":3441},"Also, if you tried the above example on iOS, it would have failed regardless of autoplay policies, because iOS is a special snowflake and hasn't updated their audio APIs in a while. Let's handle that too.",{"type":21,"tag":22,"props":3443,"children":3444},{},[3445],{"type":26,"value":3446},"Oh, and it would be nice not to need to re-download a file every time we want to play it if we anticipate needing to play it multiple times. Let's throw that in there for good measure.",{"type":21,"tag":2614,"props":3448,"children":3450},{"id":3449},"kiss-redux",[3451],{"type":26,"value":3452},"KISS Redux",{"type":21,"tag":22,"props":3454,"children":3455},{},[3456],{"type":26,"value":3457},"Let's update our example above, and we'll walk through the new bits together:",{"type":21,"tag":146,"props":3459,"children":3461},{"className":2737,"code":3460,"language":2552,"meta":8,"style":8},"(function() {\n    const _af_buffers = new Map(),\n        _audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n    let _isUnlocked = false;\n\n    /**\n     * A shim to handle browsers which still expect the old callback-based decodeAudioData,\n     * notably iOS Safari - as usual.\n     * @param arraybuffer\n     * @returns {Promise\u003Cany>}\n     * @private\n     */\n    function _decodeShim(arraybuffer) {\n        return new Promise((resolve, reject) => {\n            _audioCtx.decodeAudioData(arraybuffer, (buffer) => {\n                return resolve(buffer);\n            }, (err) => {\n                return reject(err);\n            });\n        });\n    }\n\n    /**\n     * Some browsers/devices will only allow audio to be played after a user interaction.\n     * Attempt to automatically unlock audio on the first user interaction.\n     * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/\n     * Borrows in part from: https://github.com/goldfire/howler.js/blob/master/src/howler.core.js\n     */\n    function _unlockAudio() {\n        if (_isUnlocked) return;\n\n        // Scratch buffer to prevent memory leaks on iOS.\n        // See: https://stackoverflow.com/questions/24119684/web-audio-api-memory-leaks-on-mobile-platforms\n        const _scratchBuffer = _audioCtx.createBuffer(1, 1, 22050);\n\n        // We call this when user interaction will allow us to unlock\n        // the audio API.\n        var unlock = function (e) {\n            var source = _audioCtx.createBufferSource();\n            source.buffer = _scratchBuffer;\n            source.connect(_audioCtx.destination);\n\n            // Play the empty buffer.\n            source.start(0);\n\n            // Calling resume() on a stack initiated by user gesture is\n            // what actually unlocks the audio on Chrome >= 55.\n            if (typeof _audioCtx.resume === 'function') {\n                _audioCtx.resume();\n            }\n\n            // Once the source has fired the onended event, indicating it did indeed play,\n            // we can know that the audio API is now unlocked.\n            source.onended = function () {\n                source.disconnect(0);\n\n                // Don't bother trying to unlock the API more than once!\n                _isUnlocked = true;\n\n                // Remove the click/touch listeners.\n                document.removeEventListener('touchstart', unlock, true);\n                document.removeEventListener('touchend', unlock, true);\n                document.removeEventListener('click', unlock, true);\n            };\n        };\n\n        // Setup click/touch listeners to capture the first interaction\n        // within this context.\n        document.addEventListener('touchstart', unlock, true);\n        document.addEventListener('touchend', unlock, true);\n        document.addEventListener('click', unlock, true);\n    }\n\n    /**\n     * Allow the requester to load a new sfx, specifying a file to load.\n     * We store the decoded audio data for future (re-)use.\n     * @param {string} sfxFile\n     * @returns {Promise\u003CAudioBuffer>}\n     */\n    async function load (sfxFile) {\n        if (_af_buffers.has(sfxFile)) {\n            return _af_buffers.get(sfxFile);\n        }\n\n        const _sfxFile = await fetch(sfxFile);\n        const arraybuffer = await _sfxFile.arrayBuffer();\n        let audiobuffer;\n\n        try {\n            audiobuffer = await _audioCtx.decodeAudioData(arraybuffer);\n        } catch (e) {\n            // Browser wants older callback based usage of decodeAudioData\n            audiobuffer = await _decodeShim(arraybuffer);\n        }\n\n        _af_buffers.set(sfxFile, audiobuffer);\n\n        return audiobuffer;\n    };\n\n    /**\n     * Play the specified file, loading it first - either retrieving it from the saved buffers, or fetching\n     * it from the network.\n     * @param sfxFile\n     * @returns {Promise\u003CAudioBufferSourceNode>}\n     */\n    function play (sfxFile) {\n        return load(sfxFile).then((audioBuffer) => {\n            const sourceNode = _audioCtx.createBufferSource();\n            sourceNode.buffer = audioBuffer;\n            sourceNode.connect(_audioCtx.destination);\n            sourceNode.start();\n\n            return sourceNode;\n        });\n    };\n\n    _unlockAudio();\n}());\n\n",[3462],{"type":21,"tag":52,"props":3463,"children":3464},{"__ignoreMap":8},[3465,3482,3513,3541,3567,3574,3581,3589,3597,3613,3629,3641,3648,3673,3719,3753,3771,3796,3813,3821,3828,3836,3843,3850,3858,3866,3874,3882,3889,3905,3926,3933,3941,3949,4004,4011,4019,4028,4063,4093,4111,4128,4136,4145,4169,4177,4186,4195,4233,4251,4260,4268,4277,4286,4312,4338,4346,4355,4377,4385,4394,4431,4464,4497,4506,4515,4523,4532,4541,4575,4607,4639,4647,4655,4663,4671,4680,4700,4717,4725,4753,4776,4798,4807,4815,4843,4876,4890,4898,4911,4941,4960,4969,4993,5001,5009,5028,5036,5048,5056,5064,5072,5081,5090,5106,5122,5130,5154,5195,5223,5239,5255,5271,5279,5291,5299,5307,5315,5328],{"type":21,"tag":80,"props":3466,"children":3467},{"class":157,"line":158},[3468,3472,3477],{"type":21,"tag":80,"props":3469,"children":3470},{"style":188},[3471],{"type":26,"value":264},{"type":21,"tag":80,"props":3473,"children":3474},{"style":172},[3475],{"type":26,"value":3476},"function",{"type":21,"tag":80,"props":3478,"children":3479},{"style":188},[3480],{"type":26,"value":3481},"() {\n",{"type":21,"tag":80,"props":3483,"children":3484},{"class":157,"line":168},[3485,3490,3495,3499,3503,3508],{"type":21,"tag":80,"props":3486,"children":3487},{"style":172},[3488],{"type":26,"value":3489},"    const",{"type":21,"tag":80,"props":3491,"children":3492},{"style":544},[3493],{"type":26,"value":3494}," _af_buffers",{"type":21,"tag":80,"props":3496,"children":3497},{"style":172},[3498],{"type":26,"value":391},{"type":21,"tag":80,"props":3500,"children":3501},{"style":172},[3502],{"type":26,"value":2763},{"type":21,"tag":80,"props":3504,"children":3505},{"style":178},[3506],{"type":26,"value":3507}," Map",{"type":21,"tag":80,"props":3509,"children":3510},{"style":188},[3511],{"type":26,"value":3512},"(),\n",{"type":21,"tag":80,"props":3514,"children":3515},{"class":157,"line":184},[3516,3521,3525,3529,3533,3537],{"type":21,"tag":80,"props":3517,"children":3518},{"style":544},[3519],{"type":26,"value":3520},"        _audioCtx",{"type":21,"tag":80,"props":3522,"children":3523},{"style":172},[3524],{"type":26,"value":391},{"type":21,"tag":80,"props":3526,"children":3527},{"style":172},[3528],{"type":26,"value":2763},{"type":21,"tag":80,"props":3530,"children":3531},{"style":188},[3532],{"type":26,"value":2768},{"type":21,"tag":80,"props":3534,"children":3535},{"style":172},[3536],{"type":26,"value":2773},{"type":21,"tag":80,"props":3538,"children":3539},{"style":188},[3540],{"type":26,"value":2778},{"type":21,"tag":80,"props":3542,"children":3543},{"class":157,"line":194},[3544,3549,3554,3558,3563],{"type":21,"tag":80,"props":3545,"children":3546},{"style":172},[3547],{"type":26,"value":3548},"    let",{"type":21,"tag":80,"props":3550,"children":3551},{"style":188},[3552],{"type":26,"value":3553}," _isUnlocked ",{"type":21,"tag":80,"props":3555,"children":3556},{"style":172},[3557],{"type":26,"value":527},{"type":21,"tag":80,"props":3559,"children":3560},{"style":544},[3561],{"type":26,"value":3562}," false",{"type":21,"tag":80,"props":3564,"children":3565},{"style":188},[3566],{"type":26,"value":1893},{"type":21,"tag":80,"props":3568,"children":3569},{"class":157,"line":203},[3570],{"type":21,"tag":80,"props":3571,"children":3572},{"emptyLinePlaceholder":238},[3573],{"type":26,"value":241},{"type":21,"tag":80,"props":3575,"children":3576},{"class":157,"line":217},[3577],{"type":21,"tag":80,"props":3578,"children":3579},{"style":162},[3580],{"type":26,"value":2793},{"type":21,"tag":80,"props":3582,"children":3583},{"class":157,"line":225},[3584],{"type":21,"tag":80,"props":3585,"children":3586},{"style":162},[3587],{"type":26,"value":3588},"     * A shim to handle browsers which still expect the old callback-based decodeAudioData,\n",{"type":21,"tag":80,"props":3590,"children":3591},{"class":157,"line":234},[3592],{"type":21,"tag":80,"props":3593,"children":3594},{"style":162},[3595],{"type":26,"value":3596},"     * notably iOS Safari - as usual.\n",{"type":21,"tag":80,"props":3598,"children":3599},{"class":157,"line":244},[3600,3604,3608],{"type":21,"tag":80,"props":3601,"children":3602},{"style":162},[3603],{"type":26,"value":2809},{"type":21,"tag":80,"props":3605,"children":3606},{"style":172},[3607],{"type":26,"value":2814},{"type":21,"tag":80,"props":3609,"children":3610},{"style":188},[3611],{"type":26,"value":3612}," arraybuffer\n",{"type":21,"tag":80,"props":3614,"children":3615},{"class":157,"line":287},[3616,3620,3624],{"type":21,"tag":80,"props":3617,"children":3618},{"style":162},[3619],{"type":26,"value":2809},{"type":21,"tag":80,"props":3621,"children":3622},{"style":172},[3623],{"type":26,"value":2836},{"type":21,"tag":80,"props":3625,"children":3626},{"style":178},[3627],{"type":26,"value":3628}," {Promise\u003Cany>}\n",{"type":21,"tag":80,"props":3630,"children":3631},{"class":157,"line":324},[3632,3636],{"type":21,"tag":80,"props":3633,"children":3634},{"style":162},[3635],{"type":26,"value":2809},{"type":21,"tag":80,"props":3637,"children":3638},{"style":172},[3639],{"type":26,"value":3640},"@private\n",{"type":21,"tag":80,"props":3642,"children":3643},{"class":157,"line":333},[3644],{"type":21,"tag":80,"props":3645,"children":3646},{"style":162},[3647],{"type":26,"value":2849},{"type":21,"tag":80,"props":3649,"children":3650},{"class":157,"line":341},[3651,3655,3660,3664,3669],{"type":21,"tag":80,"props":3652,"children":3653},{"style":172},[3654],{"type":26,"value":3016},{"type":21,"tag":80,"props":3656,"children":3657},{"style":178},[3658],{"type":26,"value":3659}," _decodeShim",{"type":21,"tag":80,"props":3661,"children":3662},{"style":188},[3663],{"type":26,"value":264},{"type":21,"tag":80,"props":3665,"children":3666},{"style":475},[3667],{"type":26,"value":3668},"arraybuffer",{"type":21,"tag":80,"props":3670,"children":3671},{"style":188},[3672],{"type":26,"value":2882},{"type":21,"tag":80,"props":3674,"children":3675},{"class":157,"line":719},[3676,3680,3684,3689,3693,3698,3702,3707,3711,3715],{"type":21,"tag":80,"props":3677,"children":3678},{"style":172},[3679],{"type":26,"value":2922},{"type":21,"tag":80,"props":3681,"children":3682},{"style":172},[3683],{"type":26,"value":2763},{"type":21,"tag":80,"props":3685,"children":3686},{"style":544},[3687],{"type":26,"value":3688}," Promise",{"type":21,"tag":80,"props":3690,"children":3691},{"style":188},[3692],{"type":26,"value":3059},{"type":21,"tag":80,"props":3694,"children":3695},{"style":475},[3696],{"type":26,"value":3697},"resolve",{"type":21,"tag":80,"props":3699,"children":3700},{"style":188},[3701],{"type":26,"value":483},{"type":21,"tag":80,"props":3703,"children":3704},{"style":475},[3705],{"type":26,"value":3706},"reject",{"type":21,"tag":80,"props":3708,"children":3709},{"style":188},[3710],{"type":26,"value":3068},{"type":21,"tag":80,"props":3712,"children":3713},{"style":172},[3714],{"type":26,"value":3073},{"type":21,"tag":80,"props":3716,"children":3717},{"style":188},[3718],{"type":26,"value":3078},{"type":21,"tag":80,"props":3720,"children":3721},{"class":157,"line":727},[3722,3727,3731,3736,3741,3745,3749],{"type":21,"tag":80,"props":3723,"children":3724},{"style":188},[3725],{"type":26,"value":3726},"            _audioCtx.",{"type":21,"tag":80,"props":3728,"children":3729},{"style":178},[3730],{"type":26,"value":3105},{"type":21,"tag":80,"props":3732,"children":3733},{"style":188},[3734],{"type":26,"value":3735},"(arraybuffer, (",{"type":21,"tag":80,"props":3737,"children":3738},{"style":475},[3739],{"type":26,"value":3740},"buffer",{"type":21,"tag":80,"props":3742,"children":3743},{"style":188},[3744],{"type":26,"value":3068},{"type":21,"tag":80,"props":3746,"children":3747},{"style":172},[3748],{"type":26,"value":3073},{"type":21,"tag":80,"props":3750,"children":3751},{"style":188},[3752],{"type":26,"value":3078},{"type":21,"tag":80,"props":3754,"children":3755},{"class":157,"line":735},[3756,3761,3766],{"type":21,"tag":80,"props":3757,"children":3758},{"style":172},[3759],{"type":26,"value":3760},"                return",{"type":21,"tag":80,"props":3762,"children":3763},{"style":178},[3764],{"type":26,"value":3765}," resolve",{"type":21,"tag":80,"props":3767,"children":3768},{"style":188},[3769],{"type":26,"value":3770},"(buffer);\n",{"type":21,"tag":80,"props":3772,"children":3773},{"class":157,"line":744},[3774,3779,3784,3788,3792],{"type":21,"tag":80,"props":3775,"children":3776},{"style":188},[3777],{"type":26,"value":3778},"            }, (",{"type":21,"tag":80,"props":3780,"children":3781},{"style":475},[3782],{"type":26,"value":3783},"err",{"type":21,"tag":80,"props":3785,"children":3786},{"style":188},[3787],{"type":26,"value":3068},{"type":21,"tag":80,"props":3789,"children":3790},{"style":172},[3791],{"type":26,"value":3073},{"type":21,"tag":80,"props":3793,"children":3794},{"style":188},[3795],{"type":26,"value":3078},{"type":21,"tag":80,"props":3797,"children":3798},{"class":157,"line":797},[3799,3803,3808],{"type":21,"tag":80,"props":3800,"children":3801},{"style":172},[3802],{"type":26,"value":3760},{"type":21,"tag":80,"props":3804,"children":3805},{"style":178},[3806],{"type":26,"value":3807}," reject",{"type":21,"tag":80,"props":3809,"children":3810},{"style":188},[3811],{"type":26,"value":3812},"(err);\n",{"type":21,"tag":80,"props":3814,"children":3815},{"class":157,"line":805},[3816],{"type":21,"tag":80,"props":3817,"children":3818},{"style":188},[3819],{"type":26,"value":3820},"            });\n",{"type":21,"tag":80,"props":3822,"children":3823},{"class":157,"line":814},[3824],{"type":21,"tag":80,"props":3825,"children":3826},{"style":188},[3827],{"type":26,"value":3218},{"type":21,"tag":80,"props":3829,"children":3830},{"class":157,"line":860},[3831],{"type":21,"tag":80,"props":3832,"children":3833},{"style":188},[3834],{"type":26,"value":3835},"    }\n",{"type":21,"tag":80,"props":3837,"children":3838},{"class":157,"line":868},[3839],{"type":21,"tag":80,"props":3840,"children":3841},{"emptyLinePlaceholder":238},[3842],{"type":26,"value":241},{"type":21,"tag":80,"props":3844,"children":3845},{"class":157,"line":877},[3846],{"type":21,"tag":80,"props":3847,"children":3848},{"style":162},[3849],{"type":26,"value":2793},{"type":21,"tag":80,"props":3851,"children":3852},{"class":157,"line":886},[3853],{"type":21,"tag":80,"props":3854,"children":3855},{"style":162},[3856],{"type":26,"value":3857},"     * Some browsers/devices will only allow audio to be played after a user interaction.\n",{"type":21,"tag":80,"props":3859,"children":3860},{"class":157,"line":941},[3861],{"type":21,"tag":80,"props":3862,"children":3863},{"style":162},[3864],{"type":26,"value":3865},"     * Attempt to automatically unlock audio on the first user interaction.\n",{"type":21,"tag":80,"props":3867,"children":3868},{"class":157,"line":949},[3869],{"type":21,"tag":80,"props":3870,"children":3871},{"style":162},[3872],{"type":26,"value":3873},"     * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/\n",{"type":21,"tag":80,"props":3875,"children":3876},{"class":157,"line":958},[3877],{"type":21,"tag":80,"props":3878,"children":3879},{"style":162},[3880],{"type":26,"value":3881},"     * Borrows in part from: https://github.com/goldfire/howler.js/blob/master/src/howler.core.js\n",{"type":21,"tag":80,"props":3883,"children":3884},{"class":157,"line":966},[3885],{"type":21,"tag":80,"props":3886,"children":3887},{"style":162},[3888],{"type":26,"value":2849},{"type":21,"tag":80,"props":3890,"children":3891},{"class":157,"line":993},[3892,3896,3901],{"type":21,"tag":80,"props":3893,"children":3894},{"style":172},[3895],{"type":26,"value":3016},{"type":21,"tag":80,"props":3897,"children":3898},{"style":178},[3899],{"type":26,"value":3900}," _unlockAudio",{"type":21,"tag":80,"props":3902,"children":3903},{"style":188},[3904],{"type":26,"value":3481},{"type":21,"tag":80,"props":3906,"children":3907},{"class":157,"line":1001},[3908,3913,3918,3922],{"type":21,"tag":80,"props":3909,"children":3910},{"style":172},[3911],{"type":26,"value":3912},"        if",{"type":21,"tag":80,"props":3914,"children":3915},{"style":188},[3916],{"type":26,"value":3917}," (_isUnlocked) ",{"type":21,"tag":80,"props":3919,"children":3920},{"style":172},[3921],{"type":26,"value":2356},{"type":21,"tag":80,"props":3923,"children":3924},{"style":188},[3925],{"type":26,"value":1893},{"type":21,"tag":80,"props":3927,"children":3928},{"class":157,"line":1010},[3929],{"type":21,"tag":80,"props":3930,"children":3931},{"emptyLinePlaceholder":238},[3932],{"type":26,"value":241},{"type":21,"tag":80,"props":3934,"children":3935},{"class":157,"line":1018},[3936],{"type":21,"tag":80,"props":3937,"children":3938},{"style":162},[3939],{"type":26,"value":3940},"        // Scratch buffer to prevent memory leaks on iOS.\n",{"type":21,"tag":80,"props":3942,"children":3943},{"class":157,"line":1027},[3944],{"type":21,"tag":80,"props":3945,"children":3946},{"style":162},[3947],{"type":26,"value":3948},"        // See: https://stackoverflow.com/questions/24119684/web-audio-api-memory-leaks-on-mobile-platforms\n",{"type":21,"tag":80,"props":3950,"children":3951},{"class":157,"line":1046},[3952,3956,3961,3965,3969,3974,3978,3983,3987,3991,3995,4000],{"type":21,"tag":80,"props":3953,"children":3954},{"style":172},[3955],{"type":26,"value":2890},{"type":21,"tag":80,"props":3957,"children":3958},{"style":544},[3959],{"type":26,"value":3960}," _scratchBuffer",{"type":21,"tag":80,"props":3962,"children":3963},{"style":172},[3964],{"type":26,"value":391},{"type":21,"tag":80,"props":3966,"children":3967},{"style":188},[3968],{"type":26,"value":3100},{"type":21,"tag":80,"props":3970,"children":3971},{"style":178},[3972],{"type":26,"value":3973},"createBuffer",{"type":21,"tag":80,"props":3975,"children":3976},{"style":188},[3977],{"type":26,"value":264},{"type":21,"tag":80,"props":3979,"children":3980},{"style":544},[3981],{"type":26,"value":3982},"1",{"type":21,"tag":80,"props":3984,"children":3985},{"style":188},[3986],{"type":26,"value":483},{"type":21,"tag":80,"props":3988,"children":3989},{"style":544},[3990],{"type":26,"value":3982},{"type":21,"tag":80,"props":3992,"children":3993},{"style":188},[3994],{"type":26,"value":483},{"type":21,"tag":80,"props":3996,"children":3997},{"style":544},[3998],{"type":26,"value":3999},"22050",{"type":21,"tag":80,"props":4001,"children":4002},{"style":188},[4003],{"type":26,"value":552},{"type":21,"tag":80,"props":4005,"children":4006},{"class":157,"line":1055},[4007],{"type":21,"tag":80,"props":4008,"children":4009},{"emptyLinePlaceholder":238},[4010],{"type":26,"value":241},{"type":21,"tag":80,"props":4012,"children":4013},{"class":157,"line":1063},[4014],{"type":21,"tag":80,"props":4015,"children":4016},{"style":162},[4017],{"type":26,"value":4018},"        // We call this when user interaction will allow us to unlock\n",{"type":21,"tag":80,"props":4020,"children":4022},{"class":157,"line":4021},37,[4023],{"type":21,"tag":80,"props":4024,"children":4025},{"style":162},[4026],{"type":26,"value":4027},"        // the audio API.\n",{"type":21,"tag":80,"props":4029,"children":4031},{"class":157,"line":4030},38,[4032,4037,4042,4046,4050,4054,4059],{"type":21,"tag":80,"props":4033,"children":4034},{"style":172},[4035],{"type":26,"value":4036},"        var",{"type":21,"tag":80,"props":4038,"children":4039},{"style":178},[4040],{"type":26,"value":4041}," unlock",{"type":21,"tag":80,"props":4043,"children":4044},{"style":172},[4045],{"type":26,"value":391},{"type":21,"tag":80,"props":4047,"children":4048},{"style":172},[4049],{"type":26,"value":2862},{"type":21,"tag":80,"props":4051,"children":4052},{"style":188},[4053],{"type":26,"value":2872},{"type":21,"tag":80,"props":4055,"children":4056},{"style":475},[4057],{"type":26,"value":4058},"e",{"type":21,"tag":80,"props":4060,"children":4061},{"style":188},[4062],{"type":26,"value":2882},{"type":21,"tag":80,"props":4064,"children":4066},{"class":157,"line":4065},39,[4067,4072,4077,4081,4085,4089],{"type":21,"tag":80,"props":4068,"children":4069},{"style":172},[4070],{"type":26,"value":4071},"            var",{"type":21,"tag":80,"props":4073,"children":4074},{"style":188},[4075],{"type":26,"value":4076}," source ",{"type":21,"tag":80,"props":4078,"children":4079},{"style":172},[4080],{"type":26,"value":527},{"type":21,"tag":80,"props":4082,"children":4083},{"style":188},[4084],{"type":26,"value":3100},{"type":21,"tag":80,"props":4086,"children":4087},{"style":178},[4088],{"type":26,"value":3135},{"type":21,"tag":80,"props":4090,"children":4091},{"style":188},[4092],{"type":26,"value":587},{"type":21,"tag":80,"props":4094,"children":4096},{"class":157,"line":4095},40,[4097,4102,4106],{"type":21,"tag":80,"props":4098,"children":4099},{"style":188},[4100],{"type":26,"value":4101},"            source.buffer ",{"type":21,"tag":80,"props":4103,"children":4104},{"style":172},[4105],{"type":26,"value":527},{"type":21,"tag":80,"props":4107,"children":4108},{"style":188},[4109],{"type":26,"value":4110}," _scratchBuffer;\n",{"type":21,"tag":80,"props":4112,"children":4114},{"class":157,"line":4113},41,[4115,4120,4124],{"type":21,"tag":80,"props":4116,"children":4117},{"style":188},[4118],{"type":26,"value":4119},"            source.",{"type":21,"tag":80,"props":4121,"children":4122},{"style":178},[4123],{"type":26,"value":3169},{"type":21,"tag":80,"props":4125,"children":4126},{"style":188},[4127],{"type":26,"value":3174},{"type":21,"tag":80,"props":4129,"children":4131},{"class":157,"line":4130},42,[4132],{"type":21,"tag":80,"props":4133,"children":4134},{"emptyLinePlaceholder":238},[4135],{"type":26,"value":241},{"type":21,"tag":80,"props":4137,"children":4139},{"class":157,"line":4138},43,[4140],{"type":21,"tag":80,"props":4141,"children":4142},{"style":162},[4143],{"type":26,"value":4144},"            // Play the empty buffer.\n",{"type":21,"tag":80,"props":4146,"children":4148},{"class":157,"line":4147},44,[4149,4153,4157,4161,4165],{"type":21,"tag":80,"props":4150,"children":4151},{"style":188},[4152],{"type":26,"value":4119},{"type":21,"tag":80,"props":4154,"children":4155},{"style":178},[4156],{"type":26,"value":3186},{"type":21,"tag":80,"props":4158,"children":4159},{"style":188},[4160],{"type":26,"value":264},{"type":21,"tag":80,"props":4162,"children":4163},{"style":544},[4164],{"type":26,"value":547},{"type":21,"tag":80,"props":4166,"children":4167},{"style":188},[4168],{"type":26,"value":552},{"type":21,"tag":80,"props":4170,"children":4172},{"class":157,"line":4171},45,[4173],{"type":21,"tag":80,"props":4174,"children":4175},{"emptyLinePlaceholder":238},[4176],{"type":26,"value":241},{"type":21,"tag":80,"props":4178,"children":4180},{"class":157,"line":4179},46,[4181],{"type":21,"tag":80,"props":4182,"children":4183},{"style":162},[4184],{"type":26,"value":4185},"            // Calling resume() on a stack initiated by user gesture is\n",{"type":21,"tag":80,"props":4187,"children":4189},{"class":157,"line":4188},47,[4190],{"type":21,"tag":80,"props":4191,"children":4192},{"style":162},[4193],{"type":26,"value":4194},"            // what actually unlocks the audio on Chrome >= 55.\n",{"type":21,"tag":80,"props":4196,"children":4198},{"class":157,"line":4197},48,[4199,4204,4208,4213,4218,4223,4229],{"type":21,"tag":80,"props":4200,"children":4201},{"style":172},[4202],{"type":26,"value":4203},"            if",{"type":21,"tag":80,"props":4205,"children":4206},{"style":188},[4207],{"type":26,"value":2872},{"type":21,"tag":80,"props":4209,"children":4210},{"style":172},[4211],{"type":26,"value":4212},"typeof",{"type":21,"tag":80,"props":4214,"children":4215},{"style":188},[4216],{"type":26,"value":4217}," _audioCtx.resume ",{"type":21,"tag":80,"props":4219,"children":4220},{"style":172},[4221],{"type":26,"value":4222},"===",{"type":21,"tag":80,"props":4224,"children":4226},{"style":4225},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[4227],{"type":26,"value":4228}," 'function'",{"type":21,"tag":80,"props":4230,"children":4231},{"style":188},[4232],{"type":26,"value":2882},{"type":21,"tag":80,"props":4234,"children":4236},{"class":157,"line":4235},49,[4237,4242,4247],{"type":21,"tag":80,"props":4238,"children":4239},{"style":188},[4240],{"type":26,"value":4241},"                _audioCtx.",{"type":21,"tag":80,"props":4243,"children":4244},{"style":178},[4245],{"type":26,"value":4246},"resume",{"type":21,"tag":80,"props":4248,"children":4249},{"style":188},[4250],{"type":26,"value":587},{"type":21,"tag":80,"props":4252,"children":4254},{"class":157,"line":4253},50,[4255],{"type":21,"tag":80,"props":4256,"children":4257},{"style":188},[4258],{"type":26,"value":4259},"            }\n",{"type":21,"tag":80,"props":4261,"children":4263},{"class":157,"line":4262},51,[4264],{"type":21,"tag":80,"props":4265,"children":4266},{"emptyLinePlaceholder":238},[4267],{"type":26,"value":241},{"type":21,"tag":80,"props":4269,"children":4271},{"class":157,"line":4270},52,[4272],{"type":21,"tag":80,"props":4273,"children":4274},{"style":162},[4275],{"type":26,"value":4276},"            // Once the source has fired the onended event, indicating it did indeed play,\n",{"type":21,"tag":80,"props":4278,"children":4280},{"class":157,"line":4279},53,[4281],{"type":21,"tag":80,"props":4282,"children":4283},{"style":162},[4284],{"type":26,"value":4285},"            // we can know that the audio API is now unlocked.\n",{"type":21,"tag":80,"props":4287,"children":4289},{"class":157,"line":4288},54,[4290,4294,4299,4303,4307],{"type":21,"tag":80,"props":4291,"children":4292},{"style":188},[4293],{"type":26,"value":4119},{"type":21,"tag":80,"props":4295,"children":4296},{"style":178},[4297],{"type":26,"value":4298},"onended",{"type":21,"tag":80,"props":4300,"children":4301},{"style":172},[4302],{"type":26,"value":391},{"type":21,"tag":80,"props":4304,"children":4305},{"style":172},[4306],{"type":26,"value":2862},{"type":21,"tag":80,"props":4308,"children":4309},{"style":188},[4310],{"type":26,"value":4311}," () {\n",{"type":21,"tag":80,"props":4313,"children":4315},{"class":157,"line":4314},55,[4316,4321,4326,4330,4334],{"type":21,"tag":80,"props":4317,"children":4318},{"style":188},[4319],{"type":26,"value":4320},"                source.",{"type":21,"tag":80,"props":4322,"children":4323},{"style":178},[4324],{"type":26,"value":4325},"disconnect",{"type":21,"tag":80,"props":4327,"children":4328},{"style":188},[4329],{"type":26,"value":264},{"type":21,"tag":80,"props":4331,"children":4332},{"style":544},[4333],{"type":26,"value":547},{"type":21,"tag":80,"props":4335,"children":4336},{"style":188},[4337],{"type":26,"value":552},{"type":21,"tag":80,"props":4339,"children":4341},{"class":157,"line":4340},56,[4342],{"type":21,"tag":80,"props":4343,"children":4344},{"emptyLinePlaceholder":238},[4345],{"type":26,"value":241},{"type":21,"tag":80,"props":4347,"children":4349},{"class":157,"line":4348},57,[4350],{"type":21,"tag":80,"props":4351,"children":4352},{"style":162},[4353],{"type":26,"value":4354},"                // Don't bother trying to unlock the API more than once!\n",{"type":21,"tag":80,"props":4356,"children":4358},{"class":157,"line":4357},58,[4359,4364,4368,4373],{"type":21,"tag":80,"props":4360,"children":4361},{"style":188},[4362],{"type":26,"value":4363},"                _isUnlocked ",{"type":21,"tag":80,"props":4365,"children":4366},{"style":172},[4367],{"type":26,"value":527},{"type":21,"tag":80,"props":4369,"children":4370},{"style":544},[4371],{"type":26,"value":4372}," true",{"type":21,"tag":80,"props":4374,"children":4375},{"style":188},[4376],{"type":26,"value":1893},{"type":21,"tag":80,"props":4378,"children":4380},{"class":157,"line":4379},59,[4381],{"type":21,"tag":80,"props":4382,"children":4383},{"emptyLinePlaceholder":238},[4384],{"type":26,"value":241},{"type":21,"tag":80,"props":4386,"children":4388},{"class":157,"line":4387},60,[4389],{"type":21,"tag":80,"props":4390,"children":4391},{"style":162},[4392],{"type":26,"value":4393},"                // Remove the click/touch listeners.\n",{"type":21,"tag":80,"props":4395,"children":4397},{"class":157,"line":4396},61,[4398,4403,4408,4412,4417,4422,4427],{"type":21,"tag":80,"props":4399,"children":4400},{"style":188},[4401],{"type":26,"value":4402},"                document.",{"type":21,"tag":80,"props":4404,"children":4405},{"style":178},[4406],{"type":26,"value":4407},"removeEventListener",{"type":21,"tag":80,"props":4409,"children":4410},{"style":188},[4411],{"type":26,"value":264},{"type":21,"tag":80,"props":4413,"children":4414},{"style":4225},[4415],{"type":26,"value":4416},"'touchstart'",{"type":21,"tag":80,"props":4418,"children":4419},{"style":188},[4420],{"type":26,"value":4421},", unlock, ",{"type":21,"tag":80,"props":4423,"children":4424},{"style":544},[4425],{"type":26,"value":4426},"true",{"type":21,"tag":80,"props":4428,"children":4429},{"style":188},[4430],{"type":26,"value":552},{"type":21,"tag":80,"props":4432,"children":4434},{"class":157,"line":4433},62,[4435,4439,4443,4447,4452,4456,4460],{"type":21,"tag":80,"props":4436,"children":4437},{"style":188},[4438],{"type":26,"value":4402},{"type":21,"tag":80,"props":4440,"children":4441},{"style":178},[4442],{"type":26,"value":4407},{"type":21,"tag":80,"props":4444,"children":4445},{"style":188},[4446],{"type":26,"value":264},{"type":21,"tag":80,"props":4448,"children":4449},{"style":4225},[4450],{"type":26,"value":4451},"'touchend'",{"type":21,"tag":80,"props":4453,"children":4454},{"style":188},[4455],{"type":26,"value":4421},{"type":21,"tag":80,"props":4457,"children":4458},{"style":544},[4459],{"type":26,"value":4426},{"type":21,"tag":80,"props":4461,"children":4462},{"style":188},[4463],{"type":26,"value":552},{"type":21,"tag":80,"props":4465,"children":4467},{"class":157,"line":4466},63,[4468,4472,4476,4480,4485,4489,4493],{"type":21,"tag":80,"props":4469,"children":4470},{"style":188},[4471],{"type":26,"value":4402},{"type":21,"tag":80,"props":4473,"children":4474},{"style":178},[4475],{"type":26,"value":4407},{"type":21,"tag":80,"props":4477,"children":4478},{"style":188},[4479],{"type":26,"value":264},{"type":21,"tag":80,"props":4481,"children":4482},{"style":4225},[4483],{"type":26,"value":4484},"'click'",{"type":21,"tag":80,"props":4486,"children":4487},{"style":188},[4488],{"type":26,"value":4421},{"type":21,"tag":80,"props":4490,"children":4491},{"style":544},[4492],{"type":26,"value":4426},{"type":21,"tag":80,"props":4494,"children":4495},{"style":188},[4496],{"type":26,"value":552},{"type":21,"tag":80,"props":4498,"children":4500},{"class":157,"line":4499},64,[4501],{"type":21,"tag":80,"props":4502,"children":4503},{"style":188},[4504],{"type":26,"value":4505},"            };\n",{"type":21,"tag":80,"props":4507,"children":4509},{"class":157,"line":4508},65,[4510],{"type":21,"tag":80,"props":4511,"children":4512},{"style":188},[4513],{"type":26,"value":4514},"        };\n",{"type":21,"tag":80,"props":4516,"children":4518},{"class":157,"line":4517},66,[4519],{"type":21,"tag":80,"props":4520,"children":4521},{"emptyLinePlaceholder":238},[4522],{"type":26,"value":241},{"type":21,"tag":80,"props":4524,"children":4526},{"class":157,"line":4525},67,[4527],{"type":21,"tag":80,"props":4528,"children":4529},{"style":162},[4530],{"type":26,"value":4531},"        // Setup click/touch listeners to capture the first interaction\n",{"type":21,"tag":80,"props":4533,"children":4535},{"class":157,"line":4534},68,[4536],{"type":21,"tag":80,"props":4537,"children":4538},{"style":162},[4539],{"type":26,"value":4540},"        // within this context.\n",{"type":21,"tag":80,"props":4542,"children":4544},{"class":157,"line":4543},69,[4545,4550,4555,4559,4563,4567,4571],{"type":21,"tag":80,"props":4546,"children":4547},{"style":188},[4548],{"type":26,"value":4549},"        document.",{"type":21,"tag":80,"props":4551,"children":4552},{"style":178},[4553],{"type":26,"value":4554},"addEventListener",{"type":21,"tag":80,"props":4556,"children":4557},{"style":188},[4558],{"type":26,"value":264},{"type":21,"tag":80,"props":4560,"children":4561},{"style":4225},[4562],{"type":26,"value":4416},{"type":21,"tag":80,"props":4564,"children":4565},{"style":188},[4566],{"type":26,"value":4421},{"type":21,"tag":80,"props":4568,"children":4569},{"style":544},[4570],{"type":26,"value":4426},{"type":21,"tag":80,"props":4572,"children":4573},{"style":188},[4574],{"type":26,"value":552},{"type":21,"tag":80,"props":4576,"children":4578},{"class":157,"line":4577},70,[4579,4583,4587,4591,4595,4599,4603],{"type":21,"tag":80,"props":4580,"children":4581},{"style":188},[4582],{"type":26,"value":4549},{"type":21,"tag":80,"props":4584,"children":4585},{"style":178},[4586],{"type":26,"value":4554},{"type":21,"tag":80,"props":4588,"children":4589},{"style":188},[4590],{"type":26,"value":264},{"type":21,"tag":80,"props":4592,"children":4593},{"style":4225},[4594],{"type":26,"value":4451},{"type":21,"tag":80,"props":4596,"children":4597},{"style":188},[4598],{"type":26,"value":4421},{"type":21,"tag":80,"props":4600,"children":4601},{"style":544},[4602],{"type":26,"value":4426},{"type":21,"tag":80,"props":4604,"children":4605},{"style":188},[4606],{"type":26,"value":552},{"type":21,"tag":80,"props":4608,"children":4610},{"class":157,"line":4609},71,[4611,4615,4619,4623,4627,4631,4635],{"type":21,"tag":80,"props":4612,"children":4613},{"style":188},[4614],{"type":26,"value":4549},{"type":21,"tag":80,"props":4616,"children":4617},{"style":178},[4618],{"type":26,"value":4554},{"type":21,"tag":80,"props":4620,"children":4621},{"style":188},[4622],{"type":26,"value":264},{"type":21,"tag":80,"props":4624,"children":4625},{"style":4225},[4626],{"type":26,"value":4484},{"type":21,"tag":80,"props":4628,"children":4629},{"style":188},[4630],{"type":26,"value":4421},{"type":21,"tag":80,"props":4632,"children":4633},{"style":544},[4634],{"type":26,"value":4426},{"type":21,"tag":80,"props":4636,"children":4637},{"style":188},[4638],{"type":26,"value":552},{"type":21,"tag":80,"props":4640,"children":4642},{"class":157,"line":4641},72,[4643],{"type":21,"tag":80,"props":4644,"children":4645},{"style":188},[4646],{"type":26,"value":3835},{"type":21,"tag":80,"props":4648,"children":4650},{"class":157,"line":4649},73,[4651],{"type":21,"tag":80,"props":4652,"children":4653},{"emptyLinePlaceholder":238},[4654],{"type":26,"value":241},{"type":21,"tag":80,"props":4656,"children":4658},{"class":157,"line":4657},74,[4659],{"type":21,"tag":80,"props":4660,"children":4661},{"style":162},[4662],{"type":26,"value":2793},{"type":21,"tag":80,"props":4664,"children":4666},{"class":157,"line":4665},75,[4667],{"type":21,"tag":80,"props":4668,"children":4669},{"style":162},[4670],{"type":26,"value":2801},{"type":21,"tag":80,"props":4672,"children":4674},{"class":157,"line":4673},76,[4675],{"type":21,"tag":80,"props":4676,"children":4677},{"style":162},[4678],{"type":26,"value":4679},"     * We store the decoded audio data for future (re-)use.\n",{"type":21,"tag":80,"props":4681,"children":4683},{"class":157,"line":4682},77,[4684,4688,4692,4696],{"type":21,"tag":80,"props":4685,"children":4686},{"style":162},[4687],{"type":26,"value":2809},{"type":21,"tag":80,"props":4689,"children":4690},{"style":172},[4691],{"type":26,"value":2814},{"type":21,"tag":80,"props":4693,"children":4694},{"style":178},[4695],{"type":26,"value":2819},{"type":21,"tag":80,"props":4697,"children":4698},{"style":188},[4699],{"type":26,"value":2824},{"type":21,"tag":80,"props":4701,"children":4703},{"class":157,"line":4702},78,[4704,4708,4712],{"type":21,"tag":80,"props":4705,"children":4706},{"style":162},[4707],{"type":26,"value":2809},{"type":21,"tag":80,"props":4709,"children":4710},{"style":172},[4711],{"type":26,"value":2836},{"type":21,"tag":80,"props":4713,"children":4714},{"style":178},[4715],{"type":26,"value":4716}," {Promise\u003CAudioBuffer>}\n",{"type":21,"tag":80,"props":4718,"children":4720},{"class":157,"line":4719},79,[4721],{"type":21,"tag":80,"props":4722,"children":4723},{"style":162},[4724],{"type":26,"value":2849},{"type":21,"tag":80,"props":4726,"children":4728},{"class":157,"line":4727},80,[4729,4733,4737,4741,4745,4749],{"type":21,"tag":80,"props":4730,"children":4731},{"style":172},[4732],{"type":26,"value":2857},{"type":21,"tag":80,"props":4734,"children":4735},{"style":172},[4736],{"type":26,"value":2862},{"type":21,"tag":80,"props":4738,"children":4739},{"style":178},[4740],{"type":26,"value":2867},{"type":21,"tag":80,"props":4742,"children":4743},{"style":188},[4744],{"type":26,"value":2872},{"type":21,"tag":80,"props":4746,"children":4747},{"style":475},[4748],{"type":26,"value":2877},{"type":21,"tag":80,"props":4750,"children":4751},{"style":188},[4752],{"type":26,"value":2882},{"type":21,"tag":80,"props":4754,"children":4756},{"class":157,"line":4755},81,[4757,4761,4766,4771],{"type":21,"tag":80,"props":4758,"children":4759},{"style":172},[4760],{"type":26,"value":3912},{"type":21,"tag":80,"props":4762,"children":4763},{"style":188},[4764],{"type":26,"value":4765}," (_af_buffers.",{"type":21,"tag":80,"props":4767,"children":4768},{"style":178},[4769],{"type":26,"value":4770},"has",{"type":21,"tag":80,"props":4772,"children":4773},{"style":188},[4774],{"type":26,"value":4775},"(sfxFile)) {\n",{"type":21,"tag":80,"props":4777,"children":4779},{"class":157,"line":4778},82,[4780,4784,4789,4794],{"type":21,"tag":80,"props":4781,"children":4782},{"style":172},[4783],{"type":26,"value":3205},{"type":21,"tag":80,"props":4785,"children":4786},{"style":188},[4787],{"type":26,"value":4788}," _af_buffers.",{"type":21,"tag":80,"props":4790,"children":4791},{"style":178},[4792],{"type":26,"value":4793},"get",{"type":21,"tag":80,"props":4795,"children":4796},{"style":188},[4797],{"type":26,"value":2914},{"type":21,"tag":80,"props":4799,"children":4801},{"class":157,"line":4800},83,[4802],{"type":21,"tag":80,"props":4803,"children":4804},{"style":188},[4805],{"type":26,"value":4806},"        }\n",{"type":21,"tag":80,"props":4808,"children":4810},{"class":157,"line":4809},84,[4811],{"type":21,"tag":80,"props":4812,"children":4813},{"emptyLinePlaceholder":238},[4814],{"type":26,"value":241},{"type":21,"tag":80,"props":4816,"children":4818},{"class":157,"line":4817},85,[4819,4823,4827,4831,4835,4839],{"type":21,"tag":80,"props":4820,"children":4821},{"style":172},[4822],{"type":26,"value":2890},{"type":21,"tag":80,"props":4824,"children":4825},{"style":544},[4826],{"type":26,"value":2895},{"type":21,"tag":80,"props":4828,"children":4829},{"style":172},[4830],{"type":26,"value":391},{"type":21,"tag":80,"props":4832,"children":4833},{"style":172},[4834],{"type":26,"value":2904},{"type":21,"tag":80,"props":4836,"children":4837},{"style":178},[4838],{"type":26,"value":2909},{"type":21,"tag":80,"props":4840,"children":4841},{"style":188},[4842],{"type":26,"value":2914},{"type":21,"tag":80,"props":4844,"children":4846},{"class":157,"line":4845},86,[4847,4851,4856,4860,4864,4868,4872],{"type":21,"tag":80,"props":4848,"children":4849},{"style":172},[4850],{"type":26,"value":2890},{"type":21,"tag":80,"props":4852,"children":4853},{"style":544},[4854],{"type":26,"value":4855}," arraybuffer",{"type":21,"tag":80,"props":4857,"children":4858},{"style":172},[4859],{"type":26,"value":391},{"type":21,"tag":80,"props":4861,"children":4862},{"style":172},[4863],{"type":26,"value":2904},{"type":21,"tag":80,"props":4865,"children":4866},{"style":188},[4867],{"type":26,"value":2931},{"type":21,"tag":80,"props":4869,"children":4870},{"style":178},[4871],{"type":26,"value":2936},{"type":21,"tag":80,"props":4873,"children":4874},{"style":188},[4875],{"type":26,"value":587},{"type":21,"tag":80,"props":4877,"children":4879},{"class":157,"line":4878},87,[4880,4885],{"type":21,"tag":80,"props":4881,"children":4882},{"style":172},[4883],{"type":26,"value":4884},"        let",{"type":21,"tag":80,"props":4886,"children":4887},{"style":188},[4888],{"type":26,"value":4889}," audiobuffer;\n",{"type":21,"tag":80,"props":4891,"children":4893},{"class":157,"line":4892},88,[4894],{"type":21,"tag":80,"props":4895,"children":4896},{"emptyLinePlaceholder":238},[4897],{"type":26,"value":241},{"type":21,"tag":80,"props":4899,"children":4901},{"class":157,"line":4900},89,[4902,4907],{"type":21,"tag":80,"props":4903,"children":4904},{"style":172},[4905],{"type":26,"value":4906},"        try",{"type":21,"tag":80,"props":4908,"children":4909},{"style":188},[4910],{"type":26,"value":3078},{"type":21,"tag":80,"props":4912,"children":4914},{"class":157,"line":4913},90,[4915,4920,4924,4928,4932,4936],{"type":21,"tag":80,"props":4916,"children":4917},{"style":188},[4918],{"type":26,"value":4919},"            audiobuffer ",{"type":21,"tag":80,"props":4921,"children":4922},{"style":172},[4923],{"type":26,"value":527},{"type":21,"tag":80,"props":4925,"children":4926},{"style":172},[4927],{"type":26,"value":2904},{"type":21,"tag":80,"props":4929,"children":4930},{"style":188},[4931],{"type":26,"value":3100},{"type":21,"tag":80,"props":4933,"children":4934},{"style":178},[4935],{"type":26,"value":3105},{"type":21,"tag":80,"props":4937,"children":4938},{"style":188},[4939],{"type":26,"value":4940},"(arraybuffer);\n",{"type":21,"tag":80,"props":4942,"children":4944},{"class":157,"line":4943},91,[4945,4950,4955],{"type":21,"tag":80,"props":4946,"children":4947},{"style":188},[4948],{"type":26,"value":4949},"        } ",{"type":21,"tag":80,"props":4951,"children":4952},{"style":172},[4953],{"type":26,"value":4954},"catch",{"type":21,"tag":80,"props":4956,"children":4957},{"style":188},[4958],{"type":26,"value":4959}," (e) {\n",{"type":21,"tag":80,"props":4961,"children":4963},{"class":157,"line":4962},92,[4964],{"type":21,"tag":80,"props":4965,"children":4966},{"style":162},[4967],{"type":26,"value":4968},"            // Browser wants older callback based usage of decodeAudioData\n",{"type":21,"tag":80,"props":4970,"children":4972},{"class":157,"line":4971},93,[4973,4977,4981,4985,4989],{"type":21,"tag":80,"props":4974,"children":4975},{"style":188},[4976],{"type":26,"value":4919},{"type":21,"tag":80,"props":4978,"children":4979},{"style":172},[4980],{"type":26,"value":527},{"type":21,"tag":80,"props":4982,"children":4983},{"style":172},[4984],{"type":26,"value":2904},{"type":21,"tag":80,"props":4986,"children":4987},{"style":178},[4988],{"type":26,"value":3659},{"type":21,"tag":80,"props":4990,"children":4991},{"style":188},[4992],{"type":26,"value":4940},{"type":21,"tag":80,"props":4994,"children":4996},{"class":157,"line":4995},94,[4997],{"type":21,"tag":80,"props":4998,"children":4999},{"style":188},[5000],{"type":26,"value":4806},{"type":21,"tag":80,"props":5002,"children":5004},{"class":157,"line":5003},95,[5005],{"type":21,"tag":80,"props":5006,"children":5007},{"emptyLinePlaceholder":238},[5008],{"type":26,"value":241},{"type":21,"tag":80,"props":5010,"children":5012},{"class":157,"line":5011},96,[5013,5018,5023],{"type":21,"tag":80,"props":5014,"children":5015},{"style":188},[5016],{"type":26,"value":5017},"        _af_buffers.",{"type":21,"tag":80,"props":5019,"children":5020},{"style":178},[5021],{"type":26,"value":5022},"set",{"type":21,"tag":80,"props":5024,"children":5025},{"style":188},[5026],{"type":26,"value":5027},"(sfxFile, audiobuffer);\n",{"type":21,"tag":80,"props":5029,"children":5031},{"class":157,"line":5030},97,[5032],{"type":21,"tag":80,"props":5033,"children":5034},{"emptyLinePlaceholder":238},[5035],{"type":26,"value":241},{"type":21,"tag":80,"props":5037,"children":5039},{"class":157,"line":5038},98,[5040,5044],{"type":21,"tag":80,"props":5041,"children":5042},{"style":172},[5043],{"type":26,"value":2922},{"type":21,"tag":80,"props":5045,"children":5046},{"style":188},[5047],{"type":26,"value":4889},{"type":21,"tag":80,"props":5049,"children":5051},{"class":157,"line":5050},99,[5052],{"type":21,"tag":80,"props":5053,"children":5054},{"style":188},[5055],{"type":26,"value":2948},{"type":21,"tag":80,"props":5057,"children":5059},{"class":157,"line":5058},100,[5060],{"type":21,"tag":80,"props":5061,"children":5062},{"emptyLinePlaceholder":238},[5063],{"type":26,"value":241},{"type":21,"tag":80,"props":5065,"children":5067},{"class":157,"line":5066},101,[5068],{"type":21,"tag":80,"props":5069,"children":5070},{"style":162},[5071],{"type":26,"value":2793},{"type":21,"tag":80,"props":5073,"children":5075},{"class":157,"line":5074},102,[5076],{"type":21,"tag":80,"props":5077,"children":5078},{"style":162},[5079],{"type":26,"value":5080},"     * Play the specified file, loading it first - either retrieving it from the saved buffers, or fetching\n",{"type":21,"tag":80,"props":5082,"children":5084},{"class":157,"line":5083},103,[5085],{"type":21,"tag":80,"props":5086,"children":5087},{"style":162},[5088],{"type":26,"value":5089},"     * it from the network.\n",{"type":21,"tag":80,"props":5091,"children":5093},{"class":157,"line":5092},104,[5094,5098,5102],{"type":21,"tag":80,"props":5095,"children":5096},{"style":162},[5097],{"type":26,"value":2809},{"type":21,"tag":80,"props":5099,"children":5100},{"style":172},[5101],{"type":26,"value":2814},{"type":21,"tag":80,"props":5103,"children":5104},{"style":188},[5105],{"type":26,"value":2824},{"type":21,"tag":80,"props":5107,"children":5109},{"class":157,"line":5108},105,[5110,5114,5118],{"type":21,"tag":80,"props":5111,"children":5112},{"style":162},[5113],{"type":26,"value":2809},{"type":21,"tag":80,"props":5115,"children":5116},{"style":172},[5117],{"type":26,"value":2836},{"type":21,"tag":80,"props":5119,"children":5120},{"style":178},[5121],{"type":26,"value":3001},{"type":21,"tag":80,"props":5123,"children":5125},{"class":157,"line":5124},106,[5126],{"type":21,"tag":80,"props":5127,"children":5128},{"style":162},[5129],{"type":26,"value":2849},{"type":21,"tag":80,"props":5131,"children":5133},{"class":157,"line":5132},107,[5134,5138,5142,5146,5150],{"type":21,"tag":80,"props":5135,"children":5136},{"style":172},[5137],{"type":26,"value":3016},{"type":21,"tag":80,"props":5139,"children":5140},{"style":178},[5141],{"type":26,"value":3021},{"type":21,"tag":80,"props":5143,"children":5144},{"style":188},[5145],{"type":26,"value":2872},{"type":21,"tag":80,"props":5147,"children":5148},{"style":475},[5149],{"type":26,"value":2877},{"type":21,"tag":80,"props":5151,"children":5152},{"style":188},[5153],{"type":26,"value":2882},{"type":21,"tag":80,"props":5155,"children":5157},{"class":157,"line":5156},108,[5158,5162,5166,5170,5174,5178,5183,5187,5191],{"type":21,"tag":80,"props":5159,"children":5160},{"style":172},[5161],{"type":26,"value":2922},{"type":21,"tag":80,"props":5163,"children":5164},{"style":178},[5165],{"type":26,"value":2867},{"type":21,"tag":80,"props":5167,"children":5168},{"style":188},[5169],{"type":26,"value":3049},{"type":21,"tag":80,"props":5171,"children":5172},{"style":178},[5173],{"type":26,"value":3054},{"type":21,"tag":80,"props":5175,"children":5176},{"style":188},[5177],{"type":26,"value":3059},{"type":21,"tag":80,"props":5179,"children":5180},{"style":475},[5181],{"type":26,"value":5182},"audioBuffer",{"type":21,"tag":80,"props":5184,"children":5185},{"style":188},[5186],{"type":26,"value":3068},{"type":21,"tag":80,"props":5188,"children":5189},{"style":172},[5190],{"type":26,"value":3073},{"type":21,"tag":80,"props":5192,"children":5193},{"style":188},[5194],{"type":26,"value":3078},{"type":21,"tag":80,"props":5196,"children":5198},{"class":157,"line":5197},109,[5199,5203,5207,5211,5215,5219],{"type":21,"tag":80,"props":5200,"children":5201},{"style":172},[5202],{"type":26,"value":3086},{"type":21,"tag":80,"props":5204,"children":5205},{"style":544},[5206],{"type":26,"value":3122},{"type":21,"tag":80,"props":5208,"children":5209},{"style":172},[5210],{"type":26,"value":391},{"type":21,"tag":80,"props":5212,"children":5213},{"style":188},[5214],{"type":26,"value":3100},{"type":21,"tag":80,"props":5216,"children":5217},{"style":178},[5218],{"type":26,"value":3135},{"type":21,"tag":80,"props":5220,"children":5221},{"style":188},[5222],{"type":26,"value":587},{"type":21,"tag":80,"props":5224,"children":5226},{"class":157,"line":5225},110,[5227,5231,5235],{"type":21,"tag":80,"props":5228,"children":5229},{"style":188},[5230],{"type":26,"value":3147},{"type":21,"tag":80,"props":5232,"children":5233},{"style":172},[5234],{"type":26,"value":527},{"type":21,"tag":80,"props":5236,"children":5237},{"style":188},[5238],{"type":26,"value":3156},{"type":21,"tag":80,"props":5240,"children":5242},{"class":157,"line":5241},111,[5243,5247,5251],{"type":21,"tag":80,"props":5244,"children":5245},{"style":188},[5246],{"type":26,"value":3164},{"type":21,"tag":80,"props":5248,"children":5249},{"style":178},[5250],{"type":26,"value":3169},{"type":21,"tag":80,"props":5252,"children":5253},{"style":188},[5254],{"type":26,"value":3174},{"type":21,"tag":80,"props":5256,"children":5258},{"class":157,"line":5257},112,[5259,5263,5267],{"type":21,"tag":80,"props":5260,"children":5261},{"style":188},[5262],{"type":26,"value":3164},{"type":21,"tag":80,"props":5264,"children":5265},{"style":178},[5266],{"type":26,"value":3186},{"type":21,"tag":80,"props":5268,"children":5269},{"style":188},[5270],{"type":26,"value":587},{"type":21,"tag":80,"props":5272,"children":5274},{"class":157,"line":5273},113,[5275],{"type":21,"tag":80,"props":5276,"children":5277},{"emptyLinePlaceholder":238},[5278],{"type":26,"value":241},{"type":21,"tag":80,"props":5280,"children":5282},{"class":157,"line":5281},114,[5283,5287],{"type":21,"tag":80,"props":5284,"children":5285},{"style":172},[5286],{"type":26,"value":3205},{"type":21,"tag":80,"props":5288,"children":5289},{"style":188},[5290],{"type":26,"value":3210},{"type":21,"tag":80,"props":5292,"children":5294},{"class":157,"line":5293},115,[5295],{"type":21,"tag":80,"props":5296,"children":5297},{"style":188},[5298],{"type":26,"value":3218},{"type":21,"tag":80,"props":5300,"children":5302},{"class":157,"line":5301},116,[5303],{"type":21,"tag":80,"props":5304,"children":5305},{"style":188},[5306],{"type":26,"value":2948},{"type":21,"tag":80,"props":5308,"children":5310},{"class":157,"line":5309},117,[5311],{"type":21,"tag":80,"props":5312,"children":5313},{"emptyLinePlaceholder":238},[5314],{"type":26,"value":241},{"type":21,"tag":80,"props":5316,"children":5318},{"class":157,"line":5317},118,[5319,5324],{"type":21,"tag":80,"props":5320,"children":5321},{"style":178},[5322],{"type":26,"value":5323},"    _unlockAudio",{"type":21,"tag":80,"props":5325,"children":5326},{"style":188},[5327],{"type":26,"value":587},{"type":21,"tag":80,"props":5329,"children":5331},{"class":157,"line":5330},119,[5332],{"type":21,"tag":80,"props":5333,"children":5334},{"style":188},[5335],{"type":26,"value":5336},"}());\n",{"type":21,"tag":22,"props":5338,"children":5339},{},[5340,5342,5347,5349,5354],{"type":26,"value":5341},"That's the ticket! Now, when we load our ",{"type":21,"tag":52,"props":5343,"children":5345},{"className":5344},[],[5346],{"type":26,"value":2704},{"type":26,"value":5348}," by calling ",{"type":21,"tag":52,"props":5350,"children":5352},{"className":5351},[],[5353],{"type":26,"value":3330},{"type":26,"value":5355},", we attempt to fetch the audiobuffer from our impromptu cache if possible, or fallback to fetching it. (Note that it's important we cache the decoded audiobuffer, rather than the fetched array buffer—decoding is expensive! However, if we're planning to fetch large compressed files, these will be decoded into PCM and potentially eat up a huge chunk of memory - keep an eye on the tradeoff!)",{"type":21,"tag":22,"props":5357,"children":5358},{},[5359,5360,5365],{"type":26,"value":3324},{"type":21,"tag":52,"props":5361,"children":5363},{"className":5362},[],[5364],{"type":26,"value":3294},{"type":26,"value":5366},", we also perform the decoding, falling back to a decode shim if the browser throws an error (as it does in iOS), due to out-of-date API implementation.",{"type":21,"tag":22,"props":5368,"children":5369},{},[5370,5372,5377],{"type":26,"value":5371},"Otherwise, ",{"type":21,"tag":52,"props":5373,"children":5375},{"className":5374},[],[5376],{"type":26,"value":3330},{"type":26,"value":5378}," looks pretty much the same.",{"type":21,"tag":22,"props":5380,"children":5381},{},[5382,5384,5390],{"type":26,"value":5383},"And finally, we've wrapped all of this in an IIFE, with ",{"type":21,"tag":52,"props":5385,"children":5387},{"className":5386},[],[5388],{"type":26,"value":5389},"_unlockAudio",{"type":26,"value":5391}," getting called as soon as the IIFE is executed, adding touch/click listeners to the document so we can unlock the audio API as soon as the user has indicated they're willing to interact with our site.",{"type":21,"tag":22,"props":5393,"children":5394},{},[5395,5397,5404,5405,5412],{"type":26,"value":5396},"Phew! That's a fair amount of work to play a single file! Depending on the needs of your project, you may want to explore some of the libraries that can handle some of the grunt work for you, like ",{"type":21,"tag":29,"props":5398,"children":5401},{"href":5399,"rel":5400},"https://howlerjs.com/",[33],[5402],{"type":26,"value":5403},"Howler.js",{"type":26,"value":2500},{"type":21,"tag":29,"props":5406,"children":5409},{"href":5407,"rel":5408},"https://createjs.com/soundjs",[33],[5410],{"type":26,"value":5411},"SoundJS",{"type":26,"value":5413},". There's no magic, though, and you may need to dive into the true depths for your use case.",{"type":21,"tag":22,"props":5415,"children":5416},{},[5417],{"type":26,"value":5418},"The Meowsic machine project is on its way - next time, we prepare to begin working on the UI with Vue.js.",{"type":21,"tag":22,"props":5420,"children":5421},{},[5422],{"type":21,"tag":360,"props":5423,"children":5424},{},[5425,5427,5434,5435],{"type":26,"value":5426},"Images from Wikipedia Commons: ",{"type":21,"tag":29,"props":5428,"children":5431},{"href":5429,"rel":5430},"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Compactcassette.jpg/800px-Compactcassette.jpg",[33],[5432],{"type":26,"value":5433},"cassette",{"type":26,"value":483},{"type":21,"tag":29,"props":5436,"children":5439},{"href":5437,"rel":5438},"https://commons.wikimedia.org/wiki/Category:Padlocks",[33],[5440],{"type":26,"value":5441},"padlock",{"type":21,"tag":2520,"props":5443,"children":5444},{},[5445],{"type":26,"value":2524},{"title":8,"searchDepth":184,"depth":184,"links":5447},[5448,5450,5451,5452],{"id":2616,"depth":184,"text":5449},"PeriodicWave... Isn't that something the crowd does at a sports game?",{"id":2653,"depth":184,"text":2656},{"id":3409,"depth":184,"text":3412},{"id":3449,"depth":184,"text":3452},"content:ckeefer:2019-1:UnlockingWebAudio.md","ckeefer/2019-1/UnlockingWebAudio.md","ckeefer/2019-1/UnlockingWebAudio",{"user":5457,"name":5458},"ckeefer","Christopher Keefer",1780330268751]