[{"data":1,"prerenderedAt":1080},["ShallowReactive",2],{"project-text-arpeggiator-en":3},{"id":4,"title":5,"body":6,"description":1073,"extension":1074,"meta":1075,"navigation":533,"path":1076,"seo":1077,"stem":1078,"__hash__":1079},"projects_en\u002Fen\u002Fprojects\u002Farpeggiator.md","Arpeggiator",{"type":7,"value":8,"toc":1052},"minimark",[9,14,18,47,52,55,93,97,121,170,215,219,226,233,237,240,263,266,278,367,370,379,390,406,549,558,562,565,595,598,602,615,651,666,670,700,771,774,778,790,876,879,883,886,890,922,926,952,956,982,986,1024,1028,1048],[10,11,13],"h2",{"id":12},"how-an-arpeggiator-works","How an arpeggiator works",[15,16,17],"p",{},"An arpeggiator doesn't invent harmony. It takes the notes you choose, arranges them into a pattern and schedules each sound at the right moment. The path has four stages:",[19,20,21,29,35,41],"ol",{},[22,23,24,28],"li",{},[25,26,27],"strong",{},"Note input:"," clicks on the on-screen keyboard or computer keys.",[22,30,31,34],{},[25,32,33],{},"Pattern builder:"," up, down, random, across several octaves.",[22,36,37,40],{},[25,38,39],{},"Audio scheduler:"," notes are booked ahead of time on the audio clock.",[22,42,43,46],{},[25,44,45],{},"Synth voice:"," an oscillator, an envelope and a filter produce each note.",[48,49,51],"h3",{"id":50},"_1-notes-become-numbers","1. Notes become numbers",[15,53,54],{},"Each piano key is represented by a MIDI note number. Middle C is 60, C sharp is 61, D is 62, and so on. That turns transposition and octave copies into simple arithmetic: one octave up means adding 12.",[56,57,62],"math",{"display":58,"className":59,"style":61},"block",[60],"tml-display","display:block math;",[63,64,65,69,73,77,81,85,88,90],"mrow",{},[66,67,68],"mtext",{},"C4",[70,71,72],"mo",{},"=",[74,75,76],"mn",{},"60",[70,78,80],{"separator":79},"true",",",[82,83],"mspace",{"width":84},"2em",[66,86,87],{},"C5",[70,89,72],{},[74,91,92],{},"72",[48,94,96],{"id":95},"_2-the-midi-number-becomes-a-frequency","2. The MIDI number becomes a frequency",[15,98,99,100,120],{},"The oscillator needs a frequency in hertz, not a note name. In equal temperament, each semitone multiplies the frequency by ",[56,101,102],{},[103,104,105,108],"msup",{},[74,106,107],{},"2",[63,109,110,113,117],{},[74,111,112],{},"1",[114,115,116],"mi",{},"\u002F",[74,118,119],{},"12",". Using A4 (MIDI 69) at 440 Hz as the reference:",[122,123,124],"key-formula",{},[56,125,127],{"display":58,"className":126,"style":61},[60],[63,128,129,132,134,137,140],{},[114,130,131],{},"f",[70,133,72],{},[74,135,136],{},"440",[70,138,139],{},"⋅",[103,141,142,144],{},[74,143,107],{},[63,145,146,166,168],{},[63,147,148,153,156,159,162],{},[70,149,152],{"fence":79,"form":150,"stretchy":151},"prefix","false","(",[114,154,155],{},"n",[70,157,158],{},"−",[74,160,161],{},"69",[70,163,165],{"fence":79,"form":164,"stretchy":151},"postfix",")",[114,167,116],{},[74,169,119],{},[15,171,172,173,183,184,214],{},"For example, middle C (",[56,174,175],{},[63,176,177,179,181],{},[114,178,155],{},[70,180,72],{},[74,182,76],{},") sits at ",[56,185,186],{},[63,187,188,190,192,208,211],{},[74,189,136],{},[70,191,139],{},[103,193,194,196],{},[74,195,107],{},[63,197,198,201,204,206],{},[70,199,158],{"form":150,"stretchy":151,"lspace":200,"rspace":200},"0em",[74,202,203],{},"9",[114,205,116],{},[74,207,119],{},[70,209,210],{},"≈",[74,212,213],{},"261.6"," Hz.",[48,216,218],{"id":217},"_3-the-chord-becomes-a-sequence","3. The chord becomes a sequence",[15,220,221,222,225],{},"In the ",[25,223,224],{},"up"," pattern, the notes are sorted from lowest to highest, and extra octaves are created by adding 12 semitones. The sequence plays exactly the notes you picked, with nothing added: a one-octave Cmaj7 plays C, E, G, B. The other patterns reverse, mirror or shuffle that list.",[15,227,228,229,232],{},"The ",[25,230,231],{},"random"," pattern is shuffled only when the notes, the pattern, the range or the transposition change. Moving the tempo or the sound controls never reshuffles it.",[48,234,236],{"id":235},"_4-the-tempo-sets-the-clock","4. The tempo sets the clock",[15,238,239],{},"Tempo in BPM says how many beats fit in a minute, and the rate says what fraction of a beat each step takes:",[56,241,243],{"display":58,"className":242,"style":61},[60],[63,244,245,248,250,258,260],{},[66,246,247],{},"step",[70,249,72],{},[251,252,253,255],"mfrac",{},[74,254,76],{},[66,256,257],{},"BPM",[70,259,139],{},[66,261,262],{},"rate",[15,264,265],{},"At 120 BPM, a beat lasts half a second. A sixteenth note (1\u002F16) is a quarter of that, so a new step starts every 125 ms.",[15,267,268,271,272,277],{},[25,269,270],{},"Swing"," lengthens the first step of each pair and shortens the second by the same amount, so every second note lands slightly late. With swing ",[56,273,274],{},[114,275,276],{},"s"," (from 0 to 0.6):",[56,279,281],{"display":58,"className":280,"style":61},[60],[282,283,286,330],"mtable",{"displaystyle":79,"className":284},[285],"tml-jot",[287,288,289],"mtr",{},[290,291,293],"mtd",{"style":292},"padding-left:0em;padding-right:0em;",[63,294,295,303,305,307,309],{},[296,297,298,300],"msub",{},[66,299,247],{},[66,301,302],{},"even",[70,304,72],{},[66,306,247],{},[70,308,139],{},[63,310,311,313,315,318,328],{},[70,312,152],{"fence":79,"form":150,"stretchy":79},[74,314,112],{},[70,316,317],{},"+",[319,320,322],"mstyle",{"displaystyle":151,"scriptlevel":321},"0",[251,323,324,326],{},[114,325,276],{},[74,327,107],{},[70,329,165],{"fence":79,"form":164,"stretchy":79},[287,331,332],{},[290,333,334],{"style":292},[63,335,336,343,345,347,349],{},[296,337,338,340],{},[66,339,247],{},[66,341,342],{},"odd",[70,344,72],{},[66,346,247],{},[70,348,139],{},[63,350,351,353,355,357,365],{},[70,352,152],{"fence":79,"form":150,"stretchy":79},[74,354,112],{},[70,356,158],{},[319,358,359],{"displaystyle":151,"scriptlevel":321},[251,360,361,363],{},[114,362,276],{},[74,364,107],{},[70,366,165],{"fence":79,"form":164,"stretchy":79},[15,368,369],{},"Pairs are counted from the total number of steps since playback started, not from the position in the sequence. That keeps the groove alternating even when the sequence has an odd number of notes.",[48,371,373,374,378],{"id":372},"_5-why-not-play-the-notes-with-setinterval","5. Why not play the notes with ",[375,376,377],"code",{},"setInterval","?",[15,380,381,382,385,386,389],{},"JavaScript timers can be delayed by rendering, scrolling or other work on the page. A 20 ms delay is already audible in a sixteenth-note rhythm. So the project separates ",[25,383,384],{},"deciding"," from ",[25,387,388],{},"playing",":",[391,392,393,396,403],"ul",{},[22,394,395],{},"a short loop wakes up every 25 ms and books the notes that fall within the next 100 ms;",[22,397,398,399,402],{},"each note is scheduled on the ",[25,400,401],{},"Web Audio clock",", which runs outside the page and plays at the exact time;",[22,404,405],{},"the screen follows the same clock: the highlighted note changes in an animation loop that reads the queue of scheduled notes.",[407,408,413],"pre",{"className":409,"code":410,"language":411,"meta":412,"style":412},"language-js shiki shiki-themes github-light-high-contrast github-dark-high-contrast","function scheduler() {\n  while (nextNoteTime \u003C audioContext.currentTime + 0.1) {\n    const midi = sequence[step % sequence.length]\n    playVoice(midi, nextNoteTime, gateTime)\n    nextNoteTime += stepDuration(step)\n    step++\n  }\n}\n\nsetInterval(scheduler, 25)\n","js","",[375,414,415,432,456,483,492,507,516,522,528,535],{"__ignoreMap":412},[416,417,420,424,428],"span",{"class":418,"line":419},"line",1,[416,421,423],{"class":422},"sHUrx","function",[416,425,427],{"class":426},"sKwhi"," scheduler",[416,429,431],{"class":430},"suds8","() {\n",[416,433,435,438,441,444,447,449,453],{"class":418,"line":434},2,[416,436,437],{"class":422},"  while",[416,439,440],{"class":430}," (nextNoteTime ",[416,442,443],{"class":422},"\u003C",[416,445,446],{"class":430}," audioContext.currentTime ",[416,448,317],{"class":422},[416,450,452],{"class":451},"s-5SL"," 0.1",[416,454,455],{"class":430},") {\n",[416,457,459,462,465,468,471,474,477,480],{"class":418,"line":458},3,[416,460,461],{"class":422},"    const",[416,463,464],{"class":451}," midi",[416,466,467],{"class":422}," =",[416,469,470],{"class":430}," sequence[step ",[416,472,473],{"class":422},"%",[416,475,476],{"class":430}," sequence.",[416,478,479],{"class":451},"length",[416,481,482],{"class":430},"]\n",[416,484,486,489],{"class":418,"line":485},4,[416,487,488],{"class":426},"    playVoice",[416,490,491],{"class":430},"(midi, nextNoteTime, gateTime)\n",[416,493,495,498,501,504],{"class":418,"line":494},5,[416,496,497],{"class":430},"    nextNoteTime ",[416,499,500],{"class":422},"+=",[416,502,503],{"class":426}," stepDuration",[416,505,506],{"class":430},"(step)\n",[416,508,510,513],{"class":418,"line":509},6,[416,511,512],{"class":430},"    step",[416,514,515],{"class":422},"++\n",[416,517,519],{"class":418,"line":518},7,[416,520,521],{"class":430},"  }\n",[416,523,525],{"class":418,"line":524},8,[416,526,527],{"class":430},"}\n",[416,529,531],{"class":418,"line":530},9,[416,532,534],{"emptyLinePlaceholder":533},true,"\n",[416,536,538,540,543,546],{"class":418,"line":537},10,[416,539,377],{"class":426},[416,541,542],{"class":430},"(scheduler, ",[416,544,545],{"class":451},"25",[416,547,548],{"class":430},")\n",[550,551,552],"callout",{},[15,553,554,557],{},[25,555,556],{},"Schedule first, play later."," JavaScript only needs to wake up before the 100 ms window runs out. Even if the loop is a few milliseconds late, the notes are already booked on the audio clock and come out on time.",[48,559,561],{"id":560},"_6-the-synthesizer-creates-the-sound","6. The synthesizer creates the sound",[15,563,564],{},"For every step, the browser creates a new voice and discards it when the sound ends:",[19,566,567,573,579,585],{},[22,568,569,572],{},[25,570,571],{},"Two oscillators"," generate the wave (sine, triangle, square or sawtooth). The second one is slightly detuned, which widens the sound.",[22,574,228,575,578],{},[25,576,577],{},"envelope"," shapes the volume over the note: it rises during the attack, dips a little, holds while the note lasts and fades during the release.",[22,580,228,581,584],{},[25,582,583],{},"low-pass filter"," removes frequencies above the cutoff, and resonance boosts the region around it.",[22,586,587,590,591,594],{},[25,588,589],{},"Saturation"," and ",[25,592,593],{},"echo"," add color and space before the compressor and the speakers.",[15,596,597],{},"With a very short gate, a note can end before its attack is over. In that case each envelope stage is clamped to the end of the note, so the volume automation never goes back in time.",[48,599,601],{"id":600},"_7-a-build-up-is-parameter-automation","7. A build-up is parameter automation",[15,603,604,605,609,610,389],{},"A build-up (also called a riser) gradually shortens the time between notes. At the same time, it opens the filter and adds saturation. With progress ",[56,606,607],{},[114,608,15],{}," going from 0 to 1 and a final speed ",[56,611,612],{},[114,613,614],{},"v",[122,616,617],{},[56,618,620],{"display":58,"className":619,"style":61},[60],[63,621,622,624,632,634],{},[66,623,247],{},[63,625,626,628,630],{},[70,627,152],{"fence":79,"form":150,"stretchy":151},[114,629,15],{},[70,631,165],{"fence":79,"form":164,"stretchy":151},[70,633,72],{},[251,635,636,638],{},[66,637,247],{},[103,639,640,642],{},[114,641,614],{},[63,643,646,649],{"className":644},[645],"tml-sml-pad",[82,647],{"width":648},"0.1667em",[114,650,15],{},[15,652,653,654,665],{},"Because it divides by a power, the acceleration is exponential: with ",[56,655,656],{},[63,657,658,660,662],{},[114,659,614],{},[70,661,72],{},[74,663,664],{},"4",", halfway through the steps are already twice as short. The filter cutoff rises the same way, up to about 12 kHz.",[48,667,669],{"id":668},"_8-saturation-bends-the-waveform","8. Saturation bends the waveform",[15,671,672,673,676,677,691,692,389],{},"A ",[375,674,675],{},"WaveShaperNode"," runs each sample through a nonlinear curve. Here the curve is the hyperbolic tangent, normalized so that ",[56,678,679],{},[63,680,681,684,686,689],{},[114,682,683],{},"x",[70,685,72],{},[70,687,688],{"form":150,"stretchy":151},"±",[74,690,112],{}," still maps to ",[56,693,694],{},[63,695,696,698],{},[70,697,688],{"form":150,"stretchy":151},[74,699,112],{},[56,701,703],{"display":58,"className":702,"style":61},[60],[63,704,705,708,710,751,753,755,757,759,761,763],{},[114,706,707],{},"y",[70,709,72],{},[251,711,712,735],{},[63,713,714,722],{},[63,715,716,719],{},[114,717,718],{},"tanh",[70,720,721],{},"⁡",[63,723,724,726,729,731,733],{},[70,725,152],{"fence":79,"form":150,"stretchy":151},[114,727,728],{},"a",[82,730],{"width":648},[114,732,683],{},[70,734,165],{"fence":79,"form":164,"stretchy":151},[63,736,737,743],{},[63,738,739,741],{},[114,740,718],{},[70,742,721],{},[63,744,745,747,749],{},[70,746,152],{"fence":79,"form":150,"stretchy":151},[114,748,728],{},[70,750,165],{"fence":79,"form":164,"stretchy":151},[70,752,80],{"separator":79},[82,754],{"width":84},[114,756,728],{},[70,758,72],{},[74,760,112],{},[70,762,317],{},[251,764,765,768],{},[66,766,767],{},"drive",[74,769,770],{},"8",[15,772,773],{},"The more drive, the more the curve flattens the peaks of the wave, and that flattening creates new harmonics. The mix control blends the clean and saturated paths (with cosine and sine gains, so the perceived loudness stays the same), so the sound gains density without having to turn harsh.",[48,775,777],{"id":776},"_9-the-spectrum-shows-the-filter-at-work","9. The spectrum shows the filter at work",[15,779,780,781,784,785,789],{},"An ",[375,782,783],{},"AnalyserNode"," applies the fast Fourier transform (FFT) to the final output. The horizontal axis is logarithmic, like the spacing of musical octaves: each frequency ",[56,786,787],{},[114,788,131],{}," is drawn at",[56,791,793],{"display":58,"className":792,"style":61},[60],[63,794,795,797,805,807],{},[114,796,683],{},[63,798,799,801,803],{},[70,800,152],{"fence":79,"form":150,"stretchy":151},[114,802,131],{},[70,804,165],{"fence":79,"form":164,"stretchy":151},[70,806,72],{},[251,808,809,839],{},[63,810,811,818],{},[63,812,813,816],{},[114,814,815],{},"log",[70,817,721],{},[63,819,820,822,824,826,837],{},[70,821,152],{"fence":79,"form":150,"stretchy":151},[114,823,131],{},[114,825,116],{},[296,827,828,830],{},[114,829,131],{},[63,831,832,835],{},[114,833,834],{},"min",[70,836,721],{},[70,838,165],{"fence":79,"form":164,"stretchy":151},[63,840,841,847],{},[63,842,843,845],{},[114,844,815],{},[70,846,721],{},[63,848,849,851,862,864,874],{},[70,850,152],{"fence":79,"form":150,"stretchy":151},[296,852,853,855],{},[114,854,131],{},[63,856,857,860],{},[114,858,859],{},"max",[70,861,721],{},[114,863,116],{},[296,865,866,868],{},[114,867,131],{},[63,869,870,872],{},[114,871,834],{},[70,873,721],{},[70,875,165],{"fence":79,"form":164,"stretchy":151},[15,877,878],{},"Each frame is normalized so the strongest component sits at 0 dB. That makes it easy to compare the spectral shape and see the filter cutoff: move the cutoff and the harmonics to the right of the marker disappear.",[10,880,882],{"id":881},"what-each-control-changes","What each control changes",[15,884,885],{},"Change one parameter at a time and listen to the difference.",[48,887,889],{"id":888},"timing-and-movement","Timing and movement",[391,891,892,898,904,910,916],{},[22,893,894,897],{},[25,895,896],{},"Pattern:"," the order of the notes (up, down, up and down, chosen order or random).",[22,899,900,903],{},[25,901,902],{},"Rate:"," the rhythmic length of each step.",[22,905,906,909],{},[25,907,908],{},"Tempo:"," the overall speed, in BPM.",[22,911,912,915],{},[25,913,914],{},"Gate:"," the fraction of the step during which the note keeps sounding.",[22,917,918,921],{},[25,919,920],{},"Swing:"," the delay applied to every second step.",[48,923,925],{"id":924},"notes-and-pitch","Notes and pitch",[391,927,928,934,940,946],{},[22,929,930,933],{},[25,931,932],{},"Keyboard octave:"," chooses the notes assigned to the keys A to K.",[22,935,936,939],{},[25,937,938],{},"Range:"," copies the selected notes one or two octaves above.",[22,941,942,945],{},[25,943,944],{},"Transpose:"," shifts the whole sequence while keeping the chosen intervals.",[22,947,948,951],{},[25,949,950],{},"Chord presets:"," select the notes of a chord at once; the arpeggiator still plays them one at a time.",[48,953,955],{"id":954},"envelope-and-filter","Envelope and filter",[391,957,958,964,970,976],{},[22,959,960,963],{},[25,961,962],{},"Attack:"," how quickly the sound reaches its peak after a note starts.",[22,965,966,969],{},[25,967,968],{},"Release:"," how long the sound takes to fade after the note ends.",[22,971,972,975],{},[25,973,974],{},"Filter cutoff:"," the highest frequency the low-pass filter lets through.",[22,977,978,981],{},[25,979,980],{},"Resonance:"," a boost around the cutoff frequency.",[48,983,985],{"id":984},"tone-space-and-automation","Tone, space and automation",[391,987,988,994,1000,1006,1012,1018],{},[22,989,990,993],{},[25,991,992],{},"Oscillator:"," the waveform and harmonic character of the sound.",[22,995,996,999],{},[25,997,998],{},"Echo:"," tempo-synced repeats mixed behind the note.",[22,1001,1002,1005],{},[25,1003,1004],{},"Saturation drive:"," how hard the signal is pushed into the nonlinear curve.",[22,1007,1008,1011],{},[25,1009,1010],{},"Saturation mix:"," the balance between the clean and saturated paths.",[22,1013,1014,1017],{},[25,1015,1016],{},"Analog detune:"," the small pitch difference between the oscillators that adds width and movement.",[22,1019,1020,1023],{},[25,1021,1022],{},"Build-up:"," the ramp that raises speed, cutoff and saturation until you reset it.",[10,1025,1027],{"id":1026},"everything-happens-in-the-browser","Everything happens in the browser",[391,1029,1030,1036,1042],{},[22,1031,1032,1035],{},[25,1033,1034],{},"No audio files:"," every sound is generated in real time by oscillators.",[22,1037,1038,1041],{},[25,1039,1040],{},"No audio libraries:"," the engine uses only the Web Audio API.",[22,1043,1044,1047],{},[25,1045,1046],{},"No data upload:"," your notes and audio stay on your device.",[1049,1050,1051],"style",{},"html pre.shiki code .sHUrx, html code.shiki .sHUrx{--shiki-default:#A0111F;--shiki-dark:#FF9492}html pre.shiki code .sKwhi, html code.shiki .sKwhi{--shiki-default:#622CBC;--shiki-dark:#DBB7FF}html pre.shiki code .suds8, html code.shiki .suds8{--shiki-default:#0E1116;--shiki-dark:#F0F3F6}html pre.shiki code .s-5SL, html code.shiki .s-5SL{--shiki-default:#023B95;--shiki-dark:#91CBFF}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":412,"searchDepth":434,"depth":434,"links":1053},[1054,1066,1072],{"id":12,"depth":434,"text":13,"children":1055},[1056,1057,1058,1059,1060,1062,1063,1064,1065],{"id":50,"depth":458,"text":51},{"id":95,"depth":458,"text":96},{"id":217,"depth":458,"text":218},{"id":235,"depth":458,"text":236},{"id":372,"depth":458,"text":1061},"5. Why not play the notes with setInterval?",{"id":560,"depth":458,"text":561},{"id":600,"depth":458,"text":601},{"id":668,"depth":458,"text":669},{"id":776,"depth":458,"text":777},{"id":881,"depth":434,"text":882,"children":1067},[1068,1069,1070,1071],{"id":888,"depth":458,"text":889},{"id":924,"depth":458,"text":925},{"id":954,"depth":458,"text":955},{"id":984,"depth":458,"text":985},{"id":1026,"depth":434,"text":1027},"A real-time arpeggiator and synthesizer in the browser, built only with the Web Audio API, with no libraries and no audio files.","md",{},"\u002Fen\u002Fprojects\u002Farpeggiator",{"title":5,"description":1073},"en\u002Fprojects\u002Farpeggiator","lx7pCwYTcvGOi6Hmzfgt72piAFWKtEtFO4isGa7W0Ho",1792101206725]