earl 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2298 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://0xfe.blogspot.com/feeds/posts/default
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - "*/*"
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Cross-Origin-Opener-Policy-Report-Only:
22
+ - same-origin; report-to=coop_reporting
23
+ Report-To:
24
+ - '{"group":"blogger-renderd","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/httpsserver2/blogger-renderd"}]}'
25
+ Content-Security-Policy-Report-Only:
26
+ - script-src 'none';form-action 'none';frame-src 'none'; report-uri https://csp.withgoogle.com/csp/httpsserver2/blogger-renderd
27
+ Cross-Origin-Resource-Policy:
28
+ - cross-origin
29
+ Server:
30
+ - blogger-renderd
31
+ X-Content-Type-Options:
32
+ - nosniff
33
+ X-Xss-Protection:
34
+ - '0'
35
+ Content-Length:
36
+ - '242401'
37
+ X-Frame-Options:
38
+ - SAMEORIGIN
39
+ Date:
40
+ - Tue, 29 Jul 2025 09:55:38 GMT
41
+ Expires:
42
+ - Tue, 29 Jul 2025 09:55:39 GMT
43
+ Cache-Control:
44
+ - public, must-revalidate, proxy-revalidate, max-age=1
45
+ Last-Modified:
46
+ - Mon, 09 Jun 2025 12:31:35 GMT
47
+ Etag:
48
+ - W/"25062ffaf404d65f81e2ad9745e540e9e672dadd034afd042f1aeba8f35ccb2b"
49
+ Content-Type:
50
+ - application/atom+xml; charset=UTF-8
51
+ Age:
52
+ - '0'
53
+ Alt-Svc:
54
+ - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
55
+ body:
56
+ encoding: ASCII-8BIT
57
+ string: "<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href=\"http://www.blogger.com/styles/atom.css\"
58
+ type=\"text/css\"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
59
+ xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss'
60
+ xmlns:gd=\"http://schemas.google.com/g/2005\" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-19544619</id><updated>2025-06-09T08:31:35.340-04:00</updated><category
61
+ term=\"vexflow\"/><category term=\"haskell\"/><category term=\"vim\"/><category
62
+ term=\"webaudio\"/><title type='text'>0xFE - 11111110b - 0376</title><subtitle
63
+ type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml'
64
+ href='https://0xfe.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml'
65
+ href='https://www.blogger.com/feeds/19544619/posts/default'/><link rel='alternate'
66
+ type='text/html' href='https://0xfe.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link
67
+ rel='next' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default?start-index=26&amp;max-results=25'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
68
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator
69
+ version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>63</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-19544619.post-1927090407427133388</id><published>2023-12-22T13:33:00.000-05:00</published><updated>2023-12-22T13:33:16.974-05:00</updated><title
70
+ type='text'>The Firewall Guy</title><content type='html'>&lt;p&gt;About 20
71
+ years ago, I worked as an independent consultant at a major telecom. I wrote
72
+ a program that did some stuff, and couldn&#39;t get it to communicate on the
73
+ network.&lt;/p&gt;&lt;p&gt;Developers there didn&#39;t get root on a machine,
74
+ and there was limited access to tooling, so I reached out to the sysadmin.
75
+ A day later he came back to me and said, &quot;everything here&#39;s good,
76
+ check with the firewall guy.&quot;&lt;/p&gt;&lt;p&gt;The firewall guy. It
77
+ took me a day to track down the firewall guy.&lt;/p&gt;&lt;p&gt;&quot;Umm...
78
+ Mr. Firewall guy, can you help me out with this problem?&quot;&lt;/p&gt;&lt;p&gt;Firewall
79
+ guy was kinda grumpy. He begrudgingly collected some information, begrudgingly
80
+ looked at stuff. &quot;Firewall&#39;s fine. Talk to the network guy.&quot;&lt;/p&gt;&lt;p&gt;Two
81
+ days later, I found the network guy, who was nicer: &quot;Network&#39;s good.
82
+ Talk to the firewall guy.&quot;&lt;/p&gt;&lt;p&gt;&quot;I did a couple of
83
+ days ago. Firewall guy says everything&#39;s fine.&quot;&lt;/p&gt;&lt;p&gt;&quot;Okay,
84
+ then you&#39;re probably resolving to the wrong addresses. Talk to the DNS
85
+ guy.&quot;&lt;/p&gt;&lt;p&gt;It took me many days to find the DNS guy. DNS
86
+ guy left six months ago. Firewall guy was now also DNS guy.&lt;/p&gt;&lt;p&gt;I
87
+ did not like Firewall guy, but I didn&#39;t have a choice: &quot;So Mr. Firewall
88
+ guy, Network guy says it might be a DNS issue.&quot;&lt;/p&gt;&lt;p&gt;&quot;You
89
+ again. It definitely not a Firewall or DNS problem.&quot;&lt;/p&gt;&lt;p&gt;At
90
+ this point I was kinda stuck. I was accountable for delivering something that
91
+ works, but had no agency to actually deliver the thing.&lt;/p&gt;&lt;p&gt;As
92
+ I was thinking about what to do next, Firewall guy called me on my desk phone:
93
+ &quot;try your thing again.&quot; I tried my thing again. &quot;Thanks very
94
+ much, that worked! What was wrong?&quot;&lt;/p&gt;&lt;p&gt;&quot;It&#39;s
95
+ complicated. &amp;lt;hangs up&amp;gt;&quot; I took a peek and saw the DNS
96
+ entries didn&#39;t change. It was probably the firewall.&lt;/p&gt;&lt;p&gt;It
97
+ took me about two days to build the thing, and eight days to navigate their
98
+ bureaucracy. I billed them for ten days of my time, and apologized for taking
99
+ so long.&lt;/p&gt;&lt;p&gt;The manager replied back right away: &quot;Oh that
100
+ was quick! No need to apologize. Our regular consulting firm estimated 2 people
101
+ 3 months. Can you come by next week for your next project?&quot;&lt;/p&gt;</content><link
102
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/1927090407427133388/comments/default'
103
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2023/12/the-firewall-guy.html#comment-form'
104
+ title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1927090407427133388'/><link
105
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1927090407427133388'/><link
106
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2023/12/the-firewall-guy.html'
107
+ title='The Firewall Guy'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
108
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1309421684262886131</id><published>2020-03-02T14:14:00.000-05:00</published><updated>2020-03-02T14:27:52.942-05:00</updated><title
109
+ type='text'>Generating Spectrograms with Neural Networks</title><content type='html'>&lt;div
110
+ dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;\nIn
111
+ a previous experiments, I used &lt;i&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Spectrogram&quot;&gt;spectrograms&lt;/a&gt;&lt;/i&gt;&amp;nbsp;instead
112
+ of raw audio as inputs to neural networks, while training them to recognize
113
+ pitches, intervals, and chords.&lt;br /&gt;\n&lt;br /&gt;\nI found that feeding
114
+ the networks raw audio data got nowhere. Training was extremely slow, and
115
+ losses seemed to be bounded at unacceptably high values. After switching to
116
+ spectrograms, the networks started learning almost immediately -- it was quite
117
+ remarkable!&lt;br /&gt;\n&lt;br /&gt;\nThis post is about &lt;i&gt;&lt;b&gt;generating&lt;/b&gt;&lt;/i&gt;
118
+ spectrograms with neural networks.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
119
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
120
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
121
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDM2rOgj22MvXc9tLVrlD3jey8LAzjuXVUJljzhyPgaqEOluN1Fmtta9Iubv_X3rmQHWTv6y236myxVY_Hv-XBEeb3zkSPVbTYSxoW3Gk18XsOf-o9e9nEUzKDmwLaj4K6McaXEg/s1600/Screen+Shot+2020-03-02+at+11.17.26+AM.png&quot;
122
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
123
+ border=&quot;0&quot; data-original-height=&quot;684&quot; data-original-width=&quot;1316&quot;
124
+ height=&quot;332&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDM2rOgj22MvXc9tLVrlD3jey8LAzjuXVUJljzhyPgaqEOluN1Fmtta9Iubv_X3rmQHWTv6y236myxVY_Hv-XBEeb3zkSPVbTYSxoW3Gk18XsOf-o9e9nEUzKDmwLaj4K6McaXEg/s640/Screen+Shot+2020-03-02+at+11.17.26+AM.png&quot;
125
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
126
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;These
127
+ spectrograms were generated by a Neural Network&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
128
+ /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nOn Spectrograms&lt;/h3&gt;\n&lt;div&gt;\n&lt;br
129
+ /&gt;&lt;/div&gt;\nSpectrograms are 2-dimensional visual representations of
130
+ slices of audio (or really, any signal.) On the &lt;i&gt;x-axis&lt;/i&gt;
131
+ of a spectrogram is time, and on the &lt;i&gt;y-axis&lt;/i&gt; is frequency.&lt;br
132
+ /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot;
133
+ cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left:
134
+ auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
135
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggIrcRsL735uyROVhGVIftc7Yg8a_RMVsl50-0JS39fjJM50Oe8bemSs_009R_DDH-MYpc_8J3-T0V51mTAS5-ae785UKd4li91y_VWNg3BzNL8sZAv_3y_fXYB4uEp6esiYdJNg/s1600/Screen+Shot+2020-02-22+at+9.15.03+PM.png&quot;
136
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
137
+ border=&quot;0&quot; data-original-height=&quot;526&quot; data-original-width=&quot;820&quot;
138
+ height=&quot;256&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggIrcRsL735uyROVhGVIftc7Yg8a_RMVsl50-0JS39fjJM50Oe8bemSs_009R_DDH-MYpc_8J3-T0V51mTAS5-ae785UKd4li91y_VWNg3BzNL8sZAv_3y_fXYB4uEp6esiYdJNg/s400/Screen+Shot+2020-02-22+at+9.15.03+PM.png&quot;
139
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
140
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;A Violin
141
+ playing A4 (440hz)&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
142
+ /&gt;\nBecause the data is correlated well along both dimensions, spectrograms
143
+ lend themselves well to both human analysis and convolutional neural networks.&lt;br
144
+ /&gt;\n&lt;br /&gt;\nSo, I wondered, why can&#39;t the networks learn the
145
+ spectrograms themselves? Under the covers, spectrograms are built with &lt;a
146
+ href=&quot;https://en.wikipedia.org/wiki/Short-time_Fourier_transform&quot;&gt;STFTs&lt;/a&gt;,
147
+ which are entirely linear operations on data -- you slide a window over the
148
+ data at some stride length, then perform a discrete Fourier transform to get
149
+ the frequency components of the window.&lt;br /&gt;\n&lt;br /&gt;\nSince the
150
+ transformation is entirely linear, all you need is one network layer, no activations,
151
+ no biases. This should theoretically collapse down to a simple regression
152
+ problem. Right? Let&#39;s find out.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
153
+ left;&quot;&gt;\nGenerating Training Data&lt;/h3&gt;\n&lt;div&gt;\n&lt;br
154
+ /&gt;&lt;/div&gt;\nWe start by synthesizing some training data. To keep things
155
+ simple, let&#39;s assume that we want to generate spectrograms of 25ms of
156
+ audio sampled at 8khz, which is 2000 samples. Round up (in binary) to 2048
157
+ to make things GPU friendly.&lt;br /&gt;\n&lt;br /&gt;\nThe underlying &lt;a
158
+ href=&quot;https://en.wikipedia.org/wiki/Short-time_Fourier_transform&quot;&gt;STFT&lt;/a&gt;
159
+ will use a &lt;a href=&quot;https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows&quot;&gt;hanning
160
+ window&lt;/a&gt; of size 256, &lt;a href=&quot;https://en.wikipedia.org/wiki/Fast_Fourier_transform&quot;&gt;FFT&lt;/a&gt;
161
+ size of 256, and a stride length of 250, producing &lt;b&gt;33x129&lt;/b&gt;
162
+ images. That&#39;s 33 frequency-domain slices (along the time axis) capped
163
+ at &lt;a href=&quot;https://en.wikipedia.org/wiki/Nyquist_frequency&quot;&gt;128hz&lt;/a&gt;.&lt;br
164
+ /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/08204e72217ce29f06ac4c1ba7dd4d39.js&quot;&gt;&lt;/script&gt;\n\nNote
165
+ that the spectrograms return complex values. We want to make sure the networks
166
+ can learn to completely reconstruct both the magnitude and phase portions
167
+ of the signal. Also note that we&#39;re going to teach our network how to
168
+ compute hanning windows.&lt;br /&gt;\n&lt;br /&gt;\nHere&#39;s the code to
169
+ generate the training data -- we calculate &lt;i&gt;batch_size&lt;/i&gt; (15,000)
170
+ examples, each with 2048 samples and assign them to &lt;i&gt;xs&lt;/i&gt;.
171
+ We then calculate their spectrograms and assign them to &lt;i&gt;ys&lt;/i&gt;
172
+ (the targets.)&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/0da873c4ee32ff1bbb5d22b9f0b49817.js&quot;&gt;&lt;/script&gt;\n\nNote
173
+ that we separate the real and imaginary components of the spectrogram and
174
+ simply stack one atop the other. We also don&#39;t scale or normalize the
175
+ data in any way. &lt;b&gt;Let the network figure all that out! :-)&lt;/b&gt;&lt;br
176
+ /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nBuilding
177
+ the Model&lt;/h3&gt;\n&lt;br /&gt;\nNow the fun part. We build a single-layer
178
+ network with &lt;i&gt;2048&lt;/i&gt; inputs for the audio slice, and &lt;i&gt;&lt;b&gt;row
179
+ * col&lt;/b&gt;&lt;/i&gt; outputs for the image (&lt;b&gt;&lt;i&gt;times two&lt;/i&gt;&lt;/b&gt;
180
+ to hold the real and imaginary components of the outputs.) Since the outputs
181
+ are strictly a linear function of the inputs, we don&#39;t need a bias term
182
+ or activation functions.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/bd8db7b878cbef048543e58d850bab67.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
183
+ /&gt;\nAgain, this is really just linear regression. &lt;i&gt;With, oh, about
184
+ &lt;b&gt;17 million variables!&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;\n&lt;br /&gt;\n&lt;div
185
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
186
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3_UJQowtAxQB2JF104bcDRY-rslxaO7XVXZWMD-vwkZ1aOrwi2NgneYOrACxPa_V8VtmtlZ-tTbsZH1amO8D_gubfAZJ-5K0KAMOuiNCv-Ze-O8WgPjOhDTCBpPbkMznMSat8zw/s1600/Screen+Shot+2020-03-01+at+8.36.20+AM.png&quot;
187
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
188
+ border=&quot;0&quot; data-original-height=&quot;320&quot; data-original-width=&quot;1140&quot;
189
+ height=&quot;176&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3_UJQowtAxQB2JF104bcDRY-rslxaO7XVXZWMD-vwkZ1aOrwi2NgneYOrACxPa_V8VtmtlZ-tTbsZH1amO8D_gubfAZJ-5K0KAMOuiNCv-Ze-O8WgPjOhDTCBpPbkMznMSat8zw/s640/Screen+Shot+2020-03-01+at+8.36.20+AM.png&quot;
190
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\nThis model
191
+ trains very fast. In 4 epochs (about 80 seconds), the loss drops to 3.0e-08,
192
+ which is sufficient for our experiments, and in 10 epochs (about 7 minutes),
193
+ we can drop it all the way to 2.0e-15.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div
194
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
195
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlCy70eiXOmrryOkFXTIj7wXf3u2krxBTDuG-L_1fquE6l-J9sRGFomBL0dG7-H691XH22nPuQrwt9Fklage-dbLNfxMnllhj2S-w9b2is7ipDtNE5MNuuTLSdSSkfOzTi8mxfTQ/s1600/Screen+Shot+2020-03-02+at+11.34.06+AM.png&quot;
196
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
197
+ border=&quot;0&quot; data-original-height=&quot;686&quot; data-original-width=&quot;1390&quot;
198
+ height=&quot;313&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlCy70eiXOmrryOkFXTIj7wXf3u2krxBTDuG-L_1fquE6l-J9sRGFomBL0dG7-H691XH22nPuQrwt9Fklage-dbLNfxMnllhj2S-w9b2is7ipDtNE5MNuuTLSdSSkfOzTi8mxfTQ/s640/Screen+Shot+2020-03-02+at+11.34.06+AM.png&quot;
199
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
200
+ left;&quot;&gt;\nThe Real Test&lt;/h3&gt;\n&lt;br /&gt;\nOur model is ready.
201
+ Let&#39;s see how well this does on unseen data. We generate a slice of audio
202
+ playing four tones, and compare scipy&#39;s spectrogram function with our
203
+ neural network.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/231d285b53f15b0ace08f93934100e6c.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
204
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
205
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
206
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
207
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-ASUhhLR9dlj1s5OCAKk0f341ND0VOaDihlVFDCxqF6nYBaQ0kUZA8DdplPiOCe0g7wlQU28KEk75bXY_7cbiK1zz6KHszHnhrit5AWwqaloynohIeJWKBR8Cg6RybvNIvYXF-w/s1600/Screen+Shot+2020-03-01+at+12.28.19+PM.png&quot;
208
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
209
+ border=&quot;0&quot; data-original-height=&quot;384&quot; data-original-width=&quot;1444&quot;
210
+ height=&quot;170&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-ASUhhLR9dlj1s5OCAKk0f341ND0VOaDihlVFDCxqF6nYBaQ0kUZA8DdplPiOCe0g7wlQU28KEk75bXY_7cbiK1zz6KHszHnhrit5AWwqaloynohIeJWKBR8Cg6RybvNIvYXF-w/s640/Screen+Shot+2020-03-01+at+12.28.19+PM.png&quot;
211
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
212
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Left:
213
+ SciPy, Right: Neural Network&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
214
+ /&gt;\nWow, that&#39;s actually pretty good, however when we look at a log-scaled
215
+ version, you can see noise in the network-generated one.&lt;br /&gt;\n&lt;br
216
+ /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/77110315b05d55bad70d9a20dcc2594e.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
217
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
218
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
219
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
220
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVkxUewAMYUH4TCma7eMbiYgKrY9dVsgrRaE_4yYizNhSg8sAStsvmBkuPtpakQBwpj_YP2jf6Z7PUsULu37T4nlDA8K1B-yArvWpHbjLoJkA5VXS0uvHvU0hWoGGRjPmenOoegA/s1600/Screen+Shot+2020-03-01+at+12.28.29+PM.png&quot;
221
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
222
+ border=&quot;0&quot; data-original-height=&quot;398&quot; data-original-width=&quot;1434&quot;
223
+ height=&quot;176&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVkxUewAMYUH4TCma7eMbiYgKrY9dVsgrRaE_4yYizNhSg8sAStsvmBkuPtpakQBwpj_YP2jf6Z7PUsULu37T4nlDA8K1B-yArvWpHbjLoJkA5VXS0uvHvU0hWoGGRjPmenOoegA/s640/Screen+Shot+2020-03-01+at+12.28.29+PM.png&quot;
224
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
225
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;span
226
+ style=&quot;font-size: 12.8px;&quot;&gt;Log-scaled spectrogram: Left: SciPy,
227
+ Right: Neural Network&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
228
+ /&gt;\nMaybe we can train it for a bit longer and try again.&lt;br /&gt;\n&lt;br
229
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
230
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
231
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
232
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSVrD6v42Jo2FkXPdZBpxpVcmyh3NdobD8pE5hJka6FtshFQBWnI0NoBYZup-be4ME93Jc6r4POBCU4mSuv-s5WHEABdK-5f3pU6SfR0Eo16GawGAG4_G__TumqlC0ZLMJURIJcA/s1600/Screen+Shot+2020-03-01+at+12.34.27+PM.png&quot;
233
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
234
+ border=&quot;0&quot; data-original-height=&quot;392&quot; data-original-width=&quot;1434&quot;
235
+ height=&quot;174&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSVrD6v42Jo2FkXPdZBpxpVcmyh3NdobD8pE5hJka6FtshFQBWnI0NoBYZup-be4ME93Jc6r4POBCU4mSuv-s5WHEABdK-5f3pU6SfR0Eo16GawGAG4_G__TumqlC0ZLMJURIJcA/s640/Screen+Shot+2020-03-01+at+12.34.27+PM.png&quot;
236
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
237
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Left:
238
+ SciPy, Right: Neural Network&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
239
+ /&gt;\nOh yeah, that&#39;s way better!&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
240
+ left;&quot;&gt;\nPeeking into the Model&lt;/h3&gt;\n&lt;br /&gt;\nOkay, so
241
+ we know that this works pretty well. It&#39;s worth taking a little time to
242
+ dig in and see what exactly it learned. The best way to do this is by slicing
243
+ through the layers and examining the weight matrices.&lt;br /&gt;\n&lt;br
244
+ /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/af81df7faaae90a217e2a48b5251b63a.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
245
+ /&gt;\nLucky for us there&#39;s just one layer with (2048, 8514) weights.
246
+ The second dimension (8154) is just the flattened spectrogram for each sample
247
+ in the first. In the code above, we reshaped and transformed the data to make
248
+ it easy to visualize.&lt;br /&gt;\n&lt;br /&gt;\nHere it is below -- the weight
249
+ maps for the first, 11th, 21st, and 31st slices (out of 33) of the output.&lt;br
250
+ /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot; style=&quot;clear:
251
+ both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRV8G03H9-Xzc1giGrnxzWeq7O2NzAsJelVTkuFl6E0rHdYSBimnTcVrYDKZ8xKDb8nZA-eqqa6XYL7s9ImXVqElW_NNZnXP2721C9aaBDGws7FE_uv02rPmAgn9Az8FElCDtjyw/s1600/Screen+Shot+2020-03-02+at+11.43.05+AM.png&quot;
252
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
253
+ border=&quot;0&quot; data-original-height=&quot;836&quot; data-original-width=&quot;1444&quot;
254
+ height=&quot;370&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRV8G03H9-Xzc1giGrnxzWeq7O2NzAsJelVTkuFl6E0rHdYSBimnTcVrYDKZ8xKDb8nZA-eqqa6XYL7s9ImXVqElW_NNZnXP2721C9aaBDGws7FE_uv02rPmAgn9Az8FElCDtjyw/s640/Screen+Shot+2020-03-02+at+11.43.05+AM.png&quot;
255
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\nThe vertical
256
+ bands represent activated neurons. You can see how the bands move from left
257
+ to right as they work on a 256-sample slice of the audio. But more interesting
258
+ is the spiral pattern of the windows. What&#39;s going on there? Let&#39;s
259
+ slice through one of the bands and plot just the inner dimension.&lt;br /&gt;\n&lt;br
260
+ /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/a119bf4bfbe05eb4dc8e383f9355b37b.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
261
+ /&gt;\nThis is actually pretty cool -- each of the graphs below is a Hanning-Windowed
262
+ sine wave of an integer frequency along each of the vertical bands. These
263
+ sinusoids are correlated with the audio, one-by-one, to tease out the active
264
+ frequencies in the slice of audio.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
265
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
266
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
267
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuBIsIVk6YKFkLN2Mn_j4yLWxqYNd8UV8W1l-lABrjd_tWEynRNCzQsX5JektI4FL6N0RgTyaEUTZNaILS0FTL1j5SlrxoSR_MydSeln0smNXQdk-7G9aBf3GnQYbMT_4priqZ9g/s1600/Screen+Shot+2020-03-02+at+11.47.28+AM.png&quot;
268
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
269
+ border=&quot;0&quot; data-original-height=&quot;999&quot; data-original-width=&quot;1600&quot;
270
+ height=&quot;398&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuBIsIVk6YKFkLN2Mn_j4yLWxqYNd8UV8W1l-lABrjd_tWEynRNCzQsX5JektI4FL6N0RgTyaEUTZNaILS0FTL1j5SlrxoSR_MydSeln0smNXQdk-7G9aBf3GnQYbMT_4priqZ9g/s640/Screen+Shot+2020-03-02+at+11.47.28+AM.png&quot;
271
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
272
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;1, 5,
273
+ and 10hz Sine Waves (Windowed)&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
274
+ /&gt;\nTo put it simply, those pretty spirally vertical bands are... &lt;b&gt;&lt;i&gt;Fourier
275
+ Transforms&lt;/i&gt;&lt;/b&gt;!&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
276
+ left;&quot;&gt;\nLearning the Discrete Fourier Transform&lt;/h3&gt;\n&lt;br
277
+ /&gt;\nExploring that network was fun, however we must go deeper. Let&#39;s
278
+ build a quick network to perform a 128-point DFT, without any windowing, and
279
+ see if there&#39;s more we can learn.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script
280
+ src=&quot;https://gist.github.com/0xfe/5a029d815b9a5f22985933cee1a71562.js&quot;&gt;&lt;/script&gt;\n\nThis
281
+ is a much simpler network, with only about 65k weights. It trains very fast,
282
+ and works like a charm!&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
283
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOHIjjzVGyiyn58PQvtk4JzHPvx2opmuS_jPt118WaRlYTeIdrR6d1SnT-WiOdSg-XXlEBI4-C-iuSQmhDJd_mB6UDvnmVDQf09Hja9c51BfQ7VD-9o8aM59Rdmk2AlIREqRQmag/s1600/Screen+Shot+2020-03-02+at+1.36.11+PM.png&quot;
284
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
285
+ border=&quot;0&quot; data-original-height=&quot;346&quot; data-original-width=&quot;1126&quot;
286
+ height=&quot;196&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOHIjjzVGyiyn58PQvtk4JzHPvx2opmuS_jPt118WaRlYTeIdrR6d1SnT-WiOdSg-XXlEBI4-C-iuSQmhDJd_mB6UDvnmVDQf09Hja9c51BfQ7VD-9o8aM59Rdmk2AlIREqRQmag/s640/Screen+Shot+2020-03-02+at+1.36.11+PM.png&quot;
287
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\nDigging into
288
+ the weights, you can clearly see the complex sinusoids used to calculate the
289
+ Fourier transform.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/89a0c10da18fef15602af5b2d59a66d6.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
290
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
291
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
292
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
293
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7-rQnX9r7yc1_mRLb0ffAINnImyrTJAbtAimvFgFb2u5GkTGK-jzfIVcq5NfQVRAlMd5UJaIq8ie42XhyphenhyphenG8dGHEzO2eo3AOnT3SiMUKChTA94ZI5LCfykpHQQbW1w7wrYlq-raA/s1600/Screen+Shot+2020-03-02+at+1.41.30+PM.png&quot;
294
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
295
+ border=&quot;0&quot; data-original-height=&quot;1172&quot; data-original-width=&quot;1108&quot;
296
+ height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7-rQnX9r7yc1_mRLb0ffAINnImyrTJAbtAimvFgFb2u5GkTGK-jzfIVcq5NfQVRAlMd5UJaIq8ie42XhyphenhyphenG8dGHEzO2eo3AOnT3SiMUKChTA94ZI5LCfykpHQQbW1w7wrYlq-raA/s640/Screen+Shot+2020-03-02+at+1.41.30+PM.png&quot;
297
+ width=&quot;603&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
298
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Real
299
+ (blue) and Imaginary (green) Components&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
300
+ /&gt;\nIf you look at the weight matrix as a whole, you see the same pattern
301
+ we saw in the vertical bands of the spectrogram NN weights.&lt;br /&gt;\n&lt;br
302
+ /&gt;\n&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align:
303
+ center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMZpKg4uDe5zCWWXae8oWABbYGaVCSm-PeWz6UPu65ZpZ1lYzksc_LKPisXS91Ck8GbR7gKYRwiqyVcDO7fiow1UB28BuTe1FzpVrLBZlgqYCSXECqNaEBLFNuSiI8G6cmKbppLw/s1600/Screen+Shot+2020-03-02+at+1.59.01+PM.png&quot;
304
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
305
+ border=&quot;0&quot; data-original-height=&quot;1376&quot; data-original-width=&quot;1440&quot;
306
+ height=&quot;610&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMZpKg4uDe5zCWWXae8oWABbYGaVCSm-PeWz6UPu65ZpZ1lYzksc_LKPisXS91Ck8GbR7gKYRwiqyVcDO7fiow1UB28BuTe1FzpVrLBZlgqYCSXECqNaEBLFNuSiI8G6cmKbppLw/s640/Screen+Shot+2020-03-02+at+1.59.01+PM.png&quot;
307
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;div class=&quot;separator&quot;
308
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;br /&gt;&lt;/div&gt;\nThere&#39;s
309
+ a lot more we can explore in these networks, but I should probably end here...
310
+ this post is getting way too long.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
311
+ left;&quot;&gt;\nFinal Thoughts&lt;/h3&gt;\n&lt;br /&gt;\nIt&#39;s impressive
312
+ how well this works, and how quickly this works. The neural networks we trained
313
+ above are relatively crude, and there are techniques we can explore to optimize
314
+ them.&lt;br /&gt;\n&lt;br /&gt;\nFor example, with the spectrogram networks
315
+ -- instead of having it learn each FFT band independently for each window,
316
+ we could use a different network architecture (like recurrent networks), or
317
+ implement some kind of weight sharing strategy across multiple layers.&lt;br
318
+ /&gt;\n&lt;br /&gt;\nEither way, let me be clear: using Neural Networks to
319
+ perform FFTs or generate spectrograms is &lt;b style=&quot;font-style: italic;&quot;&gt;completely
320
+ impractical, &lt;/b&gt;and you shouldn&#39;t do it. Really, don&#39;t do it!
321
+ It is, however, a great way to explore the guts of machine learning models
322
+ as they learn to perform complicated tasks.&lt;br /&gt;\n&lt;br /&gt;\n&lt;br
323
+ /&gt;\n&lt;br /&gt;&lt;/div&gt;\n</content><link rel='replies' type='application/atom+xml'
324
+ href='https://0xfe.blogspot.com/feeds/1309421684262886131/comments/default'
325
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2020/03/generating-spectrograms-with-neural.html#comment-form'
326
+ title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1309421684262886131'/><link
327
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1309421684262886131'/><link
328
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2020/03/generating-spectrograms-with-neural.html'
329
+ title='Generating Spectrograms with Neural Networks'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
330
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
331
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDM2rOgj22MvXc9tLVrlD3jey8LAzjuXVUJljzhyPgaqEOluN1Fmtta9Iubv_X3rmQHWTv6y236myxVY_Hv-XBEeb3zkSPVbTYSxoW3Gk18XsOf-o9e9nEUzKDmwLaj4K6McaXEg/s72-c/Screen+Shot+2020-03-02+at+11.17.26+AM.png\"
332
+ height=\"72\" width=\"72\"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6954953458036352732</id><published>2020-02-28T07:44:00.001-05:00</published><updated>2020-02-28T07:44:23.757-05:00</updated><title
333
+ type='text'>Time Frequency Duality</title><content type='html'>&lt;div dir=&quot;ltr&quot;
334
+ style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;\nAn particularly
335
+ interesting characteristic of Fourier transforms is &lt;b&gt;&lt;i&gt;time-frequency
336
+ duality.&lt;/i&gt;&lt;/b&gt; This duality exposes a beautiful deep symmetry
337
+ between the time and frequency domains of a signal.&lt;br /&gt;\n&lt;br /&gt;\nFor
338
+ example, a sinusoid in the time domain is an impulse in the frequency domain,
339
+ &lt;i&gt;&lt;b&gt;and vice versa&lt;/b&gt;&lt;/i&gt;.&lt;br /&gt;\n&lt;br
340
+ /&gt;\nHere&#39;s what a 1-second 20hz sine wave looks like. If you play this
341
+ on your audio device, you&#39;ll hear a 20hz tone.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script
342
+ src=&quot;https://gist.github.com/0xfe/736216255e9bbcad5929350a29d1b6b6.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
343
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
344
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
345
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
346
+ center;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;
347
+ text-align: center;&quot;&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfuInGS7azSrsPIEHFf8IRLzX092heOhMA-TvyOl4EWhzTGibkGhEUt317yNEu0jSSE0OlKEQFtxeWHcHPF2GeSyRlN2R41kLaE4xGnwG6qJYqFEt1CHzCBKwfWvJvhvhjIlv6Sg/s1600/Screen+Shot+2020-02-27+at+5.24.39+PM.png&quot;
348
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
349
+ border=&quot;0&quot; data-original-height=&quot;388&quot; data-original-width=&quot;1212&quot;
350
+ height=&quot;204&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfuInGS7azSrsPIEHFf8IRLzX092heOhMA-TvyOl4EWhzTGibkGhEUt317yNEu0jSSE0OlKEQFtxeWHcHPF2GeSyRlN2R41kLaE4xGnwG6qJYqFEt1CHzCBKwfWvJvhvhjIlv6Sg/s640/Screen+Shot+2020-02-27+at+5.24.39+PM.png&quot;
351
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
352
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;20hz
353
+ Sine Wave&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br /&gt;\nWhen
354
+ you take the Fourier transform of the wave, and plot the frequency domain
355
+ representation of the signal, you get an impulse in the bin representing the
356
+ 20hz. (Ignore the&amp;nbsp;&lt;a href=&quot;https://en.wikipedia.org/wiki/Spectral_leakage&quot;&gt;tiny
357
+ neighbours&lt;/a&gt;&amp;nbsp;for now.)&lt;br /&gt;\n&lt;br /&gt;\n&lt;script
358
+ src=&quot;https://gist.github.com/0xfe/d5b29e9f9b987eeefce1148ec3698b60.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
359
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
360
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
361
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
362
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1Bs_O3s1cSwNgTZiRB0dTlIg5GsCMzVK3kCtvwJtNWtpjrmP1FLYNF5mGTLL5EgRHg8j5qvyBSvM0Fi9XTihdyXzNtBPcAsZqkDgOqJt9JnqvKg4UqK_NpVLF32Q-lI-2wNHS8g/s1600/Screen+Shot+2020-02-27+at+5.30.35+PM.png&quot;
363
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
364
+ border=&quot;0&quot; data-original-height=&quot;396&quot; data-original-width=&quot;1224&quot;
365
+ height=&quot;206&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1Bs_O3s1cSwNgTZiRB0dTlIg5GsCMzVK3kCtvwJtNWtpjrmP1FLYNF5mGTLL5EgRHg8j5qvyBSvM0Fi9XTihdyXzNtBPcAsZqkDgOqJt9JnqvKg4UqK_NpVLF32Q-lI-2wNHS8g/s640/Screen+Shot+2020-02-27+at+5.30.35+PM.png&quot;
366
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
367
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Frequency
368
+ Domain of 20hz Sine Wave&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
369
+ /&gt;\nIf you play this transformed representation out to your audio device,
370
+ you&#39;ll hear a click, generated from the single impulse pushing the speaker&#39;s
371
+ diaphragm. This is effectively an impulse signal.&lt;br /&gt;\n&lt;br /&gt;\nOkay,
372
+ let&#39;s create an impulse signal by hand -- a string of zeros, with a 1
373
+ somewhere in the middle. Play this on your speaker, and, again, you&#39;ll
374
+ hear a click. This signal is no different from the the previous transformed
375
+ signal, except for maybe the position of the impulse.&lt;br /&gt;\n&lt;br
376
+ /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/db8bbd3c1c8635e3b6fb8c2e28c93e20.js&quot;&gt;&lt;/script&gt;\n\nSo,
377
+ check this out. If you take the the FFT of the impulse and plot the frequency
378
+ domain representation, you get... &lt;i&gt;&lt;b&gt;a sinusoid&lt;/b&gt;&lt;/i&gt;!&lt;br
379
+ /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/154b2a1b0be3fd92cdae206b829b38ba.js&quot;&gt;&lt;/script&gt;\n&lt;a
380
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFWi8YhKu_nhS72RW8x_rcI2kuNQ4DqzZxYmd39x0KAtnA9PLrNaGEKSAOcTEuTvDOGnpM28adAauZqi26cMFi8MRJfCZJhVicEagWWkIlJXqP0ssEYHgdpDut5_hTAKckcJazMQ/s1600/Screen+Shot+2020-02-28+at+6.39.56+AM.png&quot;
381
+ imageanchor=&quot;1&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;392&quot;
382
+ data-original-width=&quot;1208&quot; height=&quot;208&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFWi8YhKu_nhS72RW8x_rcI2kuNQ4DqzZxYmd39x0KAtnA9PLrNaGEKSAOcTEuTvDOGnpM28adAauZqi26cMFi8MRJfCZJhVicEagWWkIlJXqP0ssEYHgdpDut5_hTAKckcJazMQ/s640/Screen+Shot+2020-02-28+at+6.39.56+AM.png&quot;
383
+ width=&quot;640&quot; /&gt;&lt;/a&gt;\n&lt;br /&gt;\n&lt;br /&gt;\nThis works
384
+ both ways. You can the the &lt;b&gt;&lt;i&gt;inverse FFT&lt;/i&gt;&lt;/b&gt;&amp;nbsp;of
385
+ a sine wave in the frequency domain, to produce an impulse in the time domain.&lt;br
386
+ /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/fe317c6a7357dea6f23d9968e98dea2e.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
387
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
388
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
389
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
390
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS4gwS_iQX8deUHZMtLPB4Vu4B5OzEvbDx2v8hHRzMbPEFeoEC2Qq6zDb863GrX3a09DYurSEjiAIELYexDHEjVndVw2fgQWVlp3kxft8SM1Cc8A7J0gkvL2rwfCquHr480IqMQQ/s1600/Screen+Shot+2020-02-28+at+6.42.45+AM.png&quot;
391
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
392
+ border=&quot;0&quot; data-original-height=&quot;796&quot; data-original-width=&quot;1214&quot;
393
+ height=&quot;261&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS4gwS_iQX8deUHZMtLPB4Vu4B5OzEvbDx2v8hHRzMbPEFeoEC2Qq6zDb863GrX3a09DYurSEjiAIELYexDHEjVndVw2fgQWVlp3kxft8SM1Cc8A7J0gkvL2rwfCquHr480IqMQQ/s400/Screen+Shot+2020-02-28+at+6.42.45+AM.png&quot;
394
+ width=&quot;400&quot; /&gt;&lt;/a&gt;\n&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
395
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Inverse
396
+ Fourier Transform of a Sine Wave&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
397
+ /&gt;\nThis is a wonderfully striking phenomenon, which I think reveals a
398
+ lot about our perception of nature.&lt;br /&gt;\n&lt;br /&gt;\nFor example,
399
+ here&#39;s another property of time-frequency duality --&amp;nbsp;&lt;a href=&quot;https://en.wikipedia.org/wiki/Convolution&quot;&gt;convolutions&lt;/a&gt;&amp;nbsp;in
400
+ the time domain are multiplications in the frequency domain, and vice versa&lt;i&gt;.
401
+ &lt;/i&gt;Because &lt;i&gt;multiplications require far fewer operations than
402
+ convolutions&lt;/i&gt;, it&#39;s much simpler to operate on frequency domain
403
+ representations of signals.&lt;br /&gt;\n&lt;br /&gt;\nYour inner ear consists
404
+ of lots of tiny hairs that vary in thickness and resonate at different frequencies
405
+ sending frequency domain representations of sound to your brain -- i.e.,&amp;nbsp;&lt;i&gt;your
406
+ ear evolved a little DSP chip in&lt;/i&gt; it to make it easier on your brain.&lt;/div&gt;\n</content><link
407
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/6954953458036352732/comments/default'
408
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2020/02/time-frequency-duality.html#comment-form'
409
+ title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6954953458036352732'/><link
410
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6954953458036352732'/><link
411
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2020/02/time-frequency-duality.html'
412
+ title='Time Frequency Duality'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
413
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
414
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfuInGS7azSrsPIEHFf8IRLzX092heOhMA-TvyOl4EWhzTGibkGhEUt317yNEu0jSSE0OlKEQFtxeWHcHPF2GeSyRlN2R41kLaE4xGnwG6qJYqFEt1CHzCBKwfWvJvhvhjIlv6Sg/s72-c/Screen+Shot+2020-02-27+at+5.24.39+PM.png\"
415
+ height=\"72\" width=\"72\"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1894211342385136711</id><published>2020-02-22T23:11:00.001-05:00</published><updated>2020-02-23T07:50:40.637-05:00</updated><title
416
+ type='text'>Pitch Detection with Convolutional Networks</title><content type='html'>&lt;div
417
+ dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;\nWhile
418
+ working on &lt;a href=&quot;https://pitchy.ninja/&quot;&gt;Pitchy Ninja&lt;/a&gt;&amp;nbsp;and
419
+ &lt;a href=&quot;https://vexflow.com/&quot;&gt;Vexflow&lt;/a&gt;, I explored
420
+ a variety of different techniques for pitch detection that would also work
421
+ well in a browser. Although, I settled on a relatively well-known algorithm,
422
+ the exploration took me down an interesting path -- I wondered if you could
423
+ build neural networks to classify pitches, intervals, and chords in recorded
424
+ audio.&lt;br /&gt;\n&lt;br /&gt;\nTurns out the answer is &lt;b&gt;&lt;i&gt;yes.&amp;nbsp;&lt;/i&gt;&lt;/b&gt;To
425
+ all of them.&lt;br /&gt;\n&lt;br /&gt;\nThis post details some of the techniques
426
+ I used to build a pitch-detection neural network. Although I focus on single-note
427
+ pitch estimation, these methods seem to work well for multi-note chords too.&lt;br
428
+ /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nOn Pitch
429
+ Estimation&lt;/h3&gt;\n&lt;br /&gt;\n&lt;a href=&quot;https://en.wikipedia.org/wiki/Pitch_detection_algorithm&quot;&gt;Pitch
430
+ detection&lt;/a&gt; (also called &lt;i&gt;fundamental frequency estimation&lt;/i&gt;)
431
+ is not an exact science. What your brain perceives as pitch is a function
432
+ of lots of different variables, from the physical materials that generate
433
+ the sounds to your body&#39;s physiological structure.&lt;br /&gt;\n&lt;br
434
+ /&gt;\nOne would presume that you can simply transform a signal to its frequency
435
+ domain representation, and look at the peak frequencies. This would work for
436
+ a sine wave, but as soon as you introduce any kind of timbre (e.g., when you
437
+ sing, or play a note on a guitar), the spectrum is flooded with &lt;a href=&quot;https://en.wikipedia.org/wiki/Overtone&quot;&gt;overtones&lt;/a&gt;
438
+ and &lt;a href=&quot;https://en.wikipedia.org/wiki/Harmonic&quot;&gt;harmonic
439
+ partials&lt;/a&gt;.&lt;br /&gt;\n&lt;br /&gt;\nHere&#39;s a 33ms spectrogram
440
+ of the note A4 (440hz) played on a piano. You can see a peak at 440hz, and
441
+ another around 1760hz.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
442
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbd4u91YSFmMe3uUXT-tsjfSVygsHoRChlZKlFKRMkN11BNIFJsqrVhRRRxnC7hCeKEmkgiv3OYFjRo395zqpz_0zUBfl9R64ryL8Qdllg22ONbb4raIC5T1zW01BA3nOtzJG7Tw/s1600/Screen+Shot+2020-02-22+at+9.14.50+PM.png&quot;
443
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
444
+ border=&quot;0&quot; data-original-height=&quot;534&quot; data-original-width=&quot;828&quot;
445
+ height=&quot;257&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbd4u91YSFmMe3uUXT-tsjfSVygsHoRChlZKlFKRMkN11BNIFJsqrVhRRRxnC7hCeKEmkgiv3OYFjRo395zqpz_0zUBfl9R64ryL8Qdllg22ONbb4raIC5T1zW01BA3nOtzJG7Tw/s400/Screen+Shot+2020-02-22+at+9.14.50+PM.png&quot;
446
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;br /&gt;\nHere&#39;s
447
+ the same A4 (440hz), but on a violin.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
448
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj61E0xsI4lmYpxhsVxaUh2k7woDBN220o6d_TjgnkEfDdo-yQkpUHq5hr1TYR_GCiGvKzmh9M205x2qbmeziTOydR6pRwyHp9j4X_gv4p2nfuJLPDvuVck8DR49mII0jmEAAABdA/s1600/Screen+Shot+2020-02-22+at+9.15.03+PM.png&quot;
449
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
450
+ border=&quot;0&quot; data-original-height=&quot;526&quot; data-original-width=&quot;820&quot;
451
+ height=&quot;256&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj61E0xsI4lmYpxhsVxaUh2k7woDBN220o6d_TjgnkEfDdo-yQkpUHq5hr1TYR_GCiGvKzmh9M205x2qbmeziTOydR6pRwyHp9j4X_gv4p2nfuJLPDvuVck8DR49mII0jmEAAABdA/s400/Screen+Shot+2020-02-22+at+9.15.03+PM.png&quot;
452
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\nAnd here&#39;s
453
+ a trumpet.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
454
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOL3_A1envcr3tirSEFs5-oOWWYFXg6bgIz5EFpI6w2cQjtP-QBr8KUx1QE9tzGNyi4LSylXsY-BZ-5Aczr1-Icd6VeNjxC_TO5fe09k0jGR8JV7LwTo8rSkLRnh1E1Jd4T4CrIg/s1600/Screen+Shot+2020-02-22+at+9.15.14+PM.png&quot;
455
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
456
+ border=&quot;0&quot; data-original-height=&quot;528&quot; data-original-width=&quot;826&quot;
457
+ height=&quot;255&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOL3_A1envcr3tirSEFs5-oOWWYFXg6bgIz5EFpI6w2cQjtP-QBr8KUx1QE9tzGNyi4LSylXsY-BZ-5Aczr1-Icd6VeNjxC_TO5fe09k0jGR8JV7LwTo8rSkLRnh1E1Jd4T4CrIg/s400/Screen+Shot+2020-02-22+at+9.15.14+PM.png&quot;
458
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\nNotice how
459
+ the thicker instruments have rich harmonic spectrums? These harmonics are
460
+ what make them beautiful, and also what make pitch detection hard.&lt;br /&gt;\n&lt;br
461
+ /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nEstimation Techniques&lt;/h3&gt;\n&lt;br
462
+ /&gt;\nA lot of the well understood pitch estimation algorithms resort to
463
+ transformations and heuristics which amplify the fundamental and cancel out
464
+ the overtones. Some, more advanced techniques work on (kind of) fingerprinting
465
+ timbres, and then attempting to correlate them with a signal.&lt;br /&gt;\n&lt;br
466
+ /&gt;\nFor single tones, these techniques work well, but they do break down
467
+ in their own unique ways. After all, they&#39;re heuristics that try to &lt;i&gt;estimate
468
+ human perception&lt;/i&gt;.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
469
+ left;&quot;&gt;\nConvolutional Networks&lt;/h3&gt;\n&lt;br /&gt;\nDeep &lt;a
470
+ href=&quot;https://en.wikipedia.org/wiki/Convolutional_neural_network&quot;&gt;convolutional
471
+ networks&lt;/a&gt; have been winning image labeling challenges for nearly
472
+ a decade, starting with &lt;a href=&quot;https://en.wikipedia.org/wiki/AlexNet&quot;&gt;AlexNet
473
+ in 2012&lt;/a&gt;. The key insight in these architectures is that detecting
474
+ objects require some level of locality in pattern recognition, i.e., learned
475
+ features should be agnostic to translations, rotations, intensities, etc.
476
+ Convolutional networks learn multiple layers of filters, each capturing some
477
+ perceptual element.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
478
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2LwwE33Uu5V1Kjy9nZxOQZEbh8LIe4nyUp728tznKUIym01KXtfscXhi8ASVftIu-7wJ8FKfBs4YDvDCTLSl7x2kmn7QPoubI9aBJ8zajZCg9IzGcbXvRDF8DM-pO1r_7IseNqw/s1600/Screen+Shot+2020-02-22+at+9.36.01+PM.png&quot;
479
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
480
+ border=&quot;0&quot; data-original-height=&quot;488&quot; data-original-width=&quot;1458&quot;
481
+ height=&quot;214&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2LwwE33Uu5V1Kjy9nZxOQZEbh8LIe4nyUp728tznKUIym01KXtfscXhi8ASVftIu-7wJ8FKfBs4YDvDCTLSl7x2kmn7QPoubI9aBJ8zajZCg9IzGcbXvRDF8DM-pO1r_7IseNqw/s640/Screen+Shot+2020-02-22+at+9.36.01+PM.png&quot;
482
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;br /&gt;\nFor
483
+ example, the bottom layer of an image recognition network might detect edges
484
+ and curves, the next might detect simple shapes, and the next would detect
485
+ objects, etc. Here&#39;s an example of extracted features from various layers
486
+ (via &lt;a href=&quot;https://www.groundai.com/project/deepfeat-a-bottom-up-and-top-down-saliency-model-based-on-deep-features-of-convolutional-neural-nets/&quot;&gt;DeepFeat&lt;/a&gt;.)&lt;br
487
+ /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot; style=&quot;clear:
488
+ both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijkByp7Wt8FbaM2YIKun1mLOehy2NOU5Oj8xgSKXrky_FeIHYqFkuxjEbtamdj_X_sC5pnC41QRUtTpUJjRXneSK7N5y4X8PiNQAKfB1Uoblcqo94HXEavYWlYQ2zYofVZHIWrdg/s1600/Screen+Shot+2020-02-22+at+9.40.06+PM.png&quot;
489
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
490
+ border=&quot;0&quot; data-original-height=&quot;1092&quot; data-original-width=&quot;1096&quot;
491
+ height=&quot;397&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijkByp7Wt8FbaM2YIKun1mLOehy2NOU5Oj8xgSKXrky_FeIHYqFkuxjEbtamdj_X_sC5pnC41QRUtTpUJjRXneSK7N5y4X8PiNQAKfB1Uoblcqo94HXEavYWlYQ2zYofVZHIWrdg/s400/Screen+Shot+2020-02-22+at+9.40.06+PM.png&quot;
492
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
493
+ left;&quot;&gt;\nConvolutional Networks for Audio&lt;/h3&gt;\n&lt;br /&gt;\nFor
494
+ audio feature extraction, &lt;a href=&quot;https://en.wikipedia.org/wiki/Time_domain&quot;&gt;time
495
+ domain&lt;/a&gt; representations don&#39;t seem to be very useful to convnets.
496
+ However, in the &lt;a href=&quot;https://en.wikipedia.org/wiki/Frequency_domain&quot;&gt;frequency
497
+ domain&lt;/a&gt;, convnets learn features &lt;i&gt;&lt;b&gt;extremely well&lt;/b&gt;&lt;/i&gt;.
498
+ Once networks start looking at spectrograms, all kinds of patterns start to
499
+ emerge.&lt;br /&gt;\n&lt;br /&gt;\nIn the next few sections, we&#39;ll build
500
+ a and train a simple convolutional network to detect fundamental frequencies
501
+ across six octaves.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
502
+ left;&quot;&gt;\nGetting Training Data&lt;/h3&gt;\n&lt;br /&gt;\nTo do this
503
+ well, we need data. Labeled. &lt;b&gt;Lots of it!&lt;/b&gt; There are a few
504
+ paths we can take:&lt;br /&gt;\n&lt;br /&gt;\n&lt;b&gt;Option 1&lt;/b&gt;:
505
+ Go find a whole bunch of single-tone music online, slice it up into little
506
+ bits, transcribe and label.&lt;br /&gt;\n&lt;br /&gt;\n&lt;b&gt;Option 2&lt;/b&gt;:
507
+ Take out my trusty guitar, record, slice, and label. Then my keyboard, and
508
+ my trumpet, and my clarinet. And maybe sing too. Ugh!&lt;br /&gt;\n&lt;br
509
+ /&gt;\n&lt;b&gt;Option 3&lt;/b&gt;: Build synthetic samples with... code!&lt;br
510
+ /&gt;\n&lt;br /&gt;\nSince, you know, the ultimate programmer virtue is laziness,
511
+ let&#39;s go with Option 3.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
512
+ left;&quot;&gt;\nTools of the Trade&lt;/h3&gt;\n&lt;br /&gt;\nThe goal is
513
+ to build a model that performs well, and &lt;i&gt;generalizes well&lt;/i&gt;,
514
+ so we&#39;ll need to make sure that we account for enough of the variability
515
+ in real audio as we can -- which means using a variety of instruments, velocities,
516
+ effects, envelopes, and noise profiles.&lt;br /&gt;\n&lt;br /&gt;\nWith a
517
+ good MIDI library, a patch bank, and some savvy, we can get this done. Here&#39;s
518
+ what we need:&lt;br /&gt;\n&lt;ul style=&quot;text-align: left;&quot;&gt;\n&lt;li&gt;&lt;a
519
+ href=&quot;https://pypi.org/project/MIDIUtil/&quot;&gt;MIDIUtil&lt;/a&gt;
520
+ - Python library to generate MIDI files.&lt;/li&gt;\n&lt;li&gt;&lt;a href=&quot;http://www.fluidsynth.org/&quot;&gt;FluidSynth&lt;/a&gt;
521
+ - Renders MIDI files to raw audio.&lt;/li&gt;\n&lt;li&gt;&lt;a href=&quot;http://www.schristiancollins.com/generaluser.php&quot;&gt;GeneralUser
522
+ GS&lt;/a&gt;&amp;nbsp;- A bank of GM instrument patches for FluidSynth.&lt;/li&gt;\n&lt;li&gt;&lt;a
523
+ href=&quot;http://sox.sourceforge.net/sox.html&quot;&gt;sox&lt;/a&gt; - To
524
+ post-process the audio (resample, normalize, etc.)&lt;/li&gt;\n&lt;li&gt;&lt;a
525
+ href=&quot;https://www.scipy.org/&quot;&gt;scipy.io&lt;/a&gt; - For generating
526
+ spectrograms&lt;/li&gt;\n&lt;li&gt;&lt;a href=&quot;https://tensorflow.org/&quot;&gt;Tensorflow&lt;/a&gt;
527
+ - For building and training the models.&lt;/li&gt;\n&lt;/ul&gt;\n&lt;div&gt;\n&lt;br
528
+ /&gt;\nAll of these are open-source and freely available. Download and install
529
+ them before proceeding.&lt;/div&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\n&lt;/h3&gt;\n&lt;h3
530
+ style=&quot;text-align: left;&quot;&gt;\n&lt;/h3&gt;\n&lt;h3 style=&quot;text-align:
531
+ left;&quot;&gt;\n&lt;br /&gt;&lt;/h3&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nSynthesizing
532
+ the Data&lt;/h3&gt;\n&lt;br /&gt;\nWe start with picking a bunch of instruments
533
+ encompassing a variety of different timbres and tonalities.&lt;br /&gt;\n&lt;br
534
+ /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/39f374a4ec66a1dc6af2a622851b2e74.js&quot;&gt;&lt;/script&gt;&lt;br
535
+ /&gt;\n&lt;div&gt;\n&lt;br /&gt;\nPick the notes and octaves you want to be
536
+ able to classify. I used all 12 tones between octaves 2 and 8 (and added some
537
+ random detunings.) Here&#39;s a handy class to deal with note to MIDI value
538
+ conversions.&lt;br /&gt;\n&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/0b8b66bf4e34caedcacb5894164e7c7c.js&quot;&gt;&lt;/script&gt;\n\nThe
539
+ next section is where the meat of the synthesis happens. It does the following:&lt;br
540
+ /&gt;\n&lt;ul&gt;\n&lt;li&gt;Renders the MIDI files to raw audio (wav) using
541
+ FluidSynth and a free GM sound font.&lt;/li&gt;\n&lt;li&gt;Resamples to single-channel,
542
+ unsigned 16-bit, at 44.1khz, normalized.&lt;/li&gt;\n&lt;li&gt;Slices the
543
+ sample up into its envelope components (attack, sustain, decay.)&lt;/li&gt;\n&lt;li&gt;Detunes
544
+ some of the samples to cover more of the harmonic surface.&lt;/li&gt;\n&lt;/ul&gt;\n&lt;div&gt;\n&lt;script
545
+ src=&quot;https://gist.github.com/0xfe/f01e3186b14543b42303e6af96286262.js&quot;&gt;&lt;/script&gt;\n&lt;/div&gt;\n&lt;div&gt;\n&lt;br
546
+ /&gt;&lt;/div&gt;\nFinally, we use the &lt;b&gt;Sample&lt;/b&gt;&amp;nbsp;class
547
+ to generate thousands of different 33ms long MIDI files, each playing a single
548
+ note.&amp;nbsp; The labels are part of the filename, and include the note,
549
+ octave, frequency, and envelope component.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script
550
+ src=&quot;https://gist.github.com/0xfe/29aedb7cc2733e7bde767eebcdd1d204.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
551
+ /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nBuilding
552
+ the Network&lt;/h3&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;div&gt;\nNow
553
+ that we have the training data, let&#39;s design the network.&lt;br /&gt;\n&lt;br
554
+ /&gt;&lt;/div&gt;\n&lt;div&gt;\nI experimented with a variety of different
555
+ architectures before I got here, starting with simple dense (non-convolution)
556
+ networks with time-domain inputs, then moving on to one-dimensional LSTMs,
557
+ then two-dimensional convolutional networks (convnets) with frequency-domain
558
+ inputs.&lt;/div&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;div&gt;\nAs
559
+ you can guess, the 2D networks with frequency-domain inputs worked &lt;b&gt;significantly
560
+ better.&lt;/b&gt; As soon as I got decent baseline performance with them,
561
+ I focused on incrementally improving accuracy by reducing validation loss.&lt;/div&gt;\n&lt;div&gt;\n&lt;br
562
+ /&gt;&lt;/div&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nModel Inputs&lt;/h3&gt;\n&lt;div&gt;\n&lt;br
563
+ /&gt;&lt;/div&gt;\n&lt;div&gt;\nThe inputs to the network will be &lt;a href=&quot;https://en.wikipedia.org/wiki/Spectrogram&quot;&gt;spectrograms&lt;/a&gt;,
564
+ which are 2D images representing a slice of audio. The X-axis is usually time,
565
+ and the Y-axis is frequency. They&#39;re great for visualizing audio spectrums,
566
+ but also for more advanced audio analysis.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table
567
+ align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
568
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
569
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
570
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfBj4NcA-QJtuTsDM3Rv6rNxZJumr7qXjNgspGKe9lOnv9XRUjsjfqZZ3GSa50F76ayZn7BMIPcjV6MWOsihu8z4Pb0vBijwEQrV5x8VEs8zhe5llGV766O1lmXrJS0VidEAisJw/s1600/Screen+Shot+2020-02-22+at+9.17.27+PM.png&quot;
571
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
572
+ border=&quot;0&quot; data-original-height=&quot;526&quot; data-original-width=&quot;840&quot;
573
+ height=&quot;250&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfBj4NcA-QJtuTsDM3Rv6rNxZJumr7qXjNgspGKe9lOnv9XRUjsjfqZZ3GSa50F76ayZn7BMIPcjV6MWOsihu8z4Pb0vBijwEQrV5x8VEs8zhe5llGV766O1lmXrJS0VidEAisJw/s400/Screen+Shot+2020-02-22+at+9.17.27+PM.png&quot;
574
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
575
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;A Church
576
+ Organ playing A4 (440hz)&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
577
+ /&gt;&lt;/div&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;div&gt;\nSpectrograms
578
+ are typically generated with &lt;a href=&quot;https://en.wikipedia.org/wiki/Short-time_Fourier_transform&quot;&gt;Short
579
+ Time Fourier Transforms (STFTs)&lt;/a&gt;. In short, the algorithm slides
580
+ a window over the audio, running &lt;a href=&quot;https://en.wikipedia.org/wiki/Fast_Fourier_transform&quot;&gt;FFTs&lt;/a&gt;
581
+ over the windowed data. Depending on the parameters of the STFT (and the associated
582
+ FFTs), the precision of the detected frequencies can be tweaked to match the
583
+ use case.&lt;/div&gt;\n&lt;br /&gt;\nFor this experiment, we&#39;re working
584
+ with 44.1khz 16-bit samples, 33ms long -- which is about 14,500 data points
585
+ per sample. We first &lt;b&gt;downsample&lt;/b&gt; the audio to 16khz, yielding
586
+ 5280 data points per sample.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/86a9c94f75bd06bde76cb6a612744d49.js&quot;&gt;&lt;/script&gt;\n\nThe
587
+ spectrogram will be generated via STFT, using a window size of 256, an overlap
588
+ of 200, and a 1024 point FFT zero-padded on both sides. This yields one &lt;b&gt;513x90&lt;/b&gt;
589
+ pixel image per sample.&lt;br /&gt;\n&lt;br /&gt;\nThe 1024-point FFT also
590
+ &lt;b&gt;&lt;i&gt;caps the resolution&lt;/i&gt;&lt;/b&gt; to about 19hz, which
591
+ isn&#39;t perfect, but fine for distinguishing pitches.&lt;br /&gt;\n&lt;br
592
+ /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\n\nThe Network Model&lt;/h3&gt;\n&lt;br
593
+ /&gt;\nOur network consists of 4 convolutional layers, with 64, 128, 128,
594
+ and 256 filters respectively, which are then immediately downsampled with
595
+ &lt;a href=&quot;https://computersciencewiki.org/index.php/Max-pooling_/_Pooling&quot;&gt;max-pooling&lt;/a&gt;
596
+ layers. The input layer reshapes the input tensors by adding a &lt;i&gt;channels&lt;/i&gt;
597
+ dimension for &lt;a href=&quot;https://www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D&quot;&gt;Conv2D&lt;/a&gt;.
598
+ We close out the model with two densely connected layers, and a final output
599
+ node for the floating-point frequency.&lt;br /&gt;\n&lt;br /&gt;\nTo prevent
600
+ overfitting, we &lt;i&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Regularization_(mathematics)&quot;&gt;regularize&lt;/a&gt;&lt;/i&gt;
601
+ by aggressively adding &lt;a href=&quot;https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dropout&quot;&gt;dropout&lt;/a&gt;
602
+ layers, including one right at the input which also doubles as an ad-hoc noise
603
+ generator.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/56f14b33ac8e09e94d65e77f50a61280.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
604
+ /&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\nAlthough we use &lt;a href=&quot;https://en.wikipedia.org/wiki/Mean_squared_error&quot;&gt;mean-squared-error&lt;/a&gt;
605
+ as our loss function, it&#39;s the &lt;a href=&quot;https://en.wikipedia.org/wiki/Mean_absolute_error&quot;&gt;mean-absolute-error&lt;/a&gt;
606
+ that we need to watch, since it&#39;s easier to reason about. Let&#39;s take
607
+ a look at the model summary.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
608
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLUdV6YW8fwFUwMsC0TGRx55VxkPmtXTC8mmDgu8iqKbGveU6Yz3pyyNTSRpX5rvgA2dtL6948KcbyiO1-h52ryC_R7KXom92l89UwACMadN_J9EmD7fB5_i72_ai6QtLRDGufTg/s1600/Screen+Shot+2020-02-22+at+5.08.33+PM.png&quot;
609
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
610
+ border=&quot;0&quot; data-original-height=&quot;1364&quot; data-original-width=&quot;1124&quot;
611
+ height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLUdV6YW8fwFUwMsC0TGRx55VxkPmtXTC8mmDgu8iqKbGveU6Yz3pyyNTSRpX5rvgA2dtL6948KcbyiO1-h52ryC_R7KXom92l89UwACMadN_J9EmD7fB5_i72_ai6QtLRDGufTg/s640/Screen+Shot+2020-02-22+at+5.08.33+PM.png&quot;
612
+ width=&quot;524&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;br /&gt;\nWow,
613
+ &lt;b&gt;12 million&lt;/b&gt; parameters! Feels like a lot for an experiment,
614
+ but it turns out we can build a model in less than 10 minutes on a modern
615
+ GPU. Let&#39;s start training.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/6a7234fe6e368bbd346f5fdddd88fa12.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
616
+ /&gt;\nAfter 100 epochs, we can achieve a validation MSE of 0.002, and a validation
617
+ MAE of 0.03.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
618
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjW4345UjhQ7l9tyvSRWymJ0Ic-usaJeM-C6VcBKpNU_Al2ur3xoDRFSjz-xtEOsfqiCUbTxn1yd91VDmWVvZAOWbS-0dGfV4gUtnH5sxDSHAdeZ_ToAK4c8L_kYPEDBZ9yS-MIYA/s1600/Screen+Shot+2020-02-22+at+11.03.15+PM.png&quot;
619
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
620
+ border=&quot;0&quot; data-original-height=&quot;1066&quot; data-original-width=&quot;814&quot;
621
+ height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjW4345UjhQ7l9tyvSRWymJ0Ic-usaJeM-C6VcBKpNU_Al2ur3xoDRFSjz-xtEOsfqiCUbTxn1yd91VDmWVvZAOWbS-0dGfV4gUtnH5sxDSHAdeZ_ToAK4c8L_kYPEDBZ9yS-MIYA/s400/Screen+Shot+2020-02-22+at+11.03.15+PM.png&quot;
622
+ width=&quot;305&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\nYou may be
623
+ wondering why the validation MAE is so much better than the training MAE.
624
+ This is because of the aggressive dropout regularization. Dropout layers are
625
+ only activated during training, not prediction.&lt;br /&gt;\n&lt;br /&gt;\nThese
626
+ results are quite promising for an experiment! For classification problems,
627
+ we could use &lt;a href=&quot;https://en.wikipedia.org/wiki/Confusion_matrix&quot;&gt;confusion
628
+ matrices&lt;/a&gt; to see where the models mispredict. For regression problems
629
+ (like this one), we can explore the losses a bit more by plotting a graph
630
+ of errors by pitch.&lt;br /&gt;\n&lt;br /&gt;\n&lt;script src=&quot;https://gist.github.com/0xfe/3e4d90436dce46ec121b9f94948001a1.js&quot;&gt;&lt;/script&gt;\n\n&lt;br
631
+ /&gt;\n&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align:
632
+ center;&quot;&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;div class=&quot;separator&quot;
633
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;table
634
+ align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
635
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
636
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
637
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFvd_SBwOkcp64POT5Y_z7-tn1eNFhP4vMU8nPBGWtXkAv-Cn8XvQjzPc4VuFLgHNKSPSpPDJXB5MbwS0RmijcQoRiUtGZ4HZPAr_QM1mdBYaerG7V1OYCNXviTT0iJvDFF_6jGg/s1600/Screen+Shot+2020-02-22+at+10.25.46+PM.png&quot;
638
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
639
+ border=&quot;0&quot; data-original-height=&quot;512&quot; data-original-width=&quot;786&quot;
640
+ height=&quot;260&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFvd_SBwOkcp64POT5Y_z7-tn1eNFhP4vMU8nPBGWtXkAv-Cn8XvQjzPc4VuFLgHNKSPSpPDJXB5MbwS0RmijcQoRiUtGZ4HZPAr_QM1mdBYaerG7V1OYCNXviTT0iJvDFF_6jGg/s400/Screen+Shot+2020-02-22+at+10.25.46+PM.png&quot;
641
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
642
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Prediction
643
+ Errors by Pitch&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;br
644
+ /&gt;\nAlready, we can see that the prediction errors are on the highest octaves.
645
+ This is very likely due to our downsampling to 16khz, causing &lt;a href=&quot;https://en.wikipedia.org/wiki/Aliasing&quot;&gt;aliasing&lt;/a&gt;
646
+ in the harmonics and confusing the model.&lt;br /&gt;\n&lt;br /&gt;\nAfter
647
+ discarding the last octave, we can take the mean of the prediction error,
648
+ and what do we see?&lt;br /&gt;\n&lt;div style=&quot;background-color: #fffffe;
649
+ font-family: monospace, Menlo, Monaco, &amp;quot;Courier New&amp;quot;, monospace;
650
+ font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;\nnp.mean(np.nan_to_num(errors_by_key[&lt;span
651
+ style=&quot;color: #09885a;&quot;&gt;0&lt;/span&gt;:&lt;span style=&quot;color:
652
+ #09885a;&quot;&gt;80&lt;/span&gt;]))&lt;/div&gt;\n&lt;span style=&quot;background-color:
653
+ white; color: #212121; font-family: monospace; font-size: 14px; white-space:
654
+ pre;&quot;&gt;19.244542657486097&lt;/span&gt;&lt;br /&gt;\n&lt;br /&gt;\nPretty
655
+ much exactly the resolution of the FFT we used. It&#39;s very hard to do better
656
+ given the inputs.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
657
+ left;&quot;&gt;\nThe Real Test&lt;/h3&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\nSo,
658
+ how does this perform in the wild? To answer this question, I recorded a few
659
+ samples of myself playing single notes on the guitar, and pulled some youtube
660
+ videos of various instruments and sliced them up for analysis. I also crossed
661
+ my fingers and sacrificed a dozen goats.&lt;br /&gt;\n&lt;br /&gt;\nAs hoped,
662
+ the predictions were &lt;b&gt;&lt;i&gt;right within the tolerances&lt;/i&gt;&lt;/b&gt;
663
+ of the model. Try it yourself and let me know how it works out.&lt;br /&gt;\n&lt;br
664
+ /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nImprovements and Variations&lt;/h3&gt;\n&lt;br
665
+ /&gt;\nThere&#39;s a few things we can do to improve what we have -- larger
666
+ FFT and window sizes, higher sample rates, better data, etc. We can also turn
667
+ this into a classification problem by using &lt;a href=&quot;https://en.wikipedia.org/wiki/Softmax_function&quot;&gt;softmax&lt;/a&gt;
668
+ at the bottom layer and training directly on musical pitches instead of frequencies.&lt;br
669
+ /&gt;\n&lt;br /&gt;\nThis experiment was part of a whole suite of models I
670
+ built for music recognition. In a future post I&#39;ll describe a more complex
671
+ set of models I built to recognize roots, intervals, and 2-4 note chords.&lt;br
672
+ /&gt;\n&lt;br /&gt;\nUntil then, hope you enjoyed this post. If you did, drop
673
+ me a note at &lt;a href=&quot;https://twitter.com/11111110b&quot;&gt;@11111110b&lt;/a&gt;.&lt;br
674
+ /&gt;\n&lt;br /&gt;\nAll the source code for these experiments will be available
675
+ on &lt;a href=&quot;https://github.com/0xfe&quot;&gt;my Github page&lt;/a&gt;
676
+ as soon as it&#39;s in slightly better shape.&lt;br /&gt;\n&lt;br /&gt;\n&lt;br
677
+ /&gt;&lt;/div&gt;\n&lt;/div&gt;\n</content><link rel='replies' type='application/atom+xml'
678
+ href='https://0xfe.blogspot.com/feeds/1894211342385136711/comments/default'
679
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2020/02/pitch-detection-with-convolutional.html#comment-form'
680
+ title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1894211342385136711'/><link
681
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1894211342385136711'/><link
682
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2020/02/pitch-detection-with-convolutional.html'
683
+ title='Pitch Detection with Convolutional Networks'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
684
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
685
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbd4u91YSFmMe3uUXT-tsjfSVygsHoRChlZKlFKRMkN11BNIFJsqrVhRRRxnC7hCeKEmkgiv3OYFjRo395zqpz_0zUBfl9R64ryL8Qdllg22ONbb4raIC5T1zW01BA3nOtzJG7Tw/s72-c/Screen+Shot+2020-02-22+at+9.14.50+PM.png\"
686
+ height=\"72\" width=\"72\"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-541596065311835171</id><published>2020-02-19T13:26:00.005-05:00</published><updated>2020-02-20T08:38:01.568-05:00</updated><title
687
+ type='text'>No Servers, Just Buckets: Hosting Static Websites on the Cloud</title><content
688
+ type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot;
689
+ trbidi=&quot;on&quot;&gt;\n&lt;br /&gt;\nFor over two decades, I&#39;ve hosted
690
+ websites on managed servers. Starting with web hosting providers, going to
691
+ dedicated machines, then dedicated VMs, then cloud VMs. Maintaining these
692
+ servers tend to come at a high cognitive cost -- machine and network setup,
693
+ OS patches, web server configuration, replication and high-availability, TLS
694
+ and cert management, security... the list goes on.&lt;br /&gt;\n&lt;br /&gt;\nLast
695
+ year, I moved [&lt;a href=&quot;https://pitchy.ninja/&quot;&gt;almost&lt;/a&gt;]
696
+ [&lt;a href=&quot;https://muthanna.com/&quot;&gt;all&lt;/a&gt;]&amp;nbsp;[&lt;a
697
+ href=&quot;https://float64.dev/&quot;&gt;my&lt;/a&gt;]&amp;nbsp;[&lt;a href=&quot;https://vexflow.com/&quot;&gt;websites&lt;/a&gt;]
698
+ to cloud buckets, and it has been amazing! Life just got simpler. With just
699
+ a few commands I got:&lt;br /&gt;\n&lt;br /&gt;\n&lt;ul style=&quot;text-align:
700
+ left;&quot;&gt;\n&lt;li&gt;A HTTP(s) web-server hosting my content.&lt;/li&gt;\n&lt;li&gt;Managed
701
+ TLS certificates.&lt;/li&gt;\n&lt;li&gt;Compression, Caching, and Content
702
+ Delivery.&lt;/li&gt;\n&lt;li&gt;Replication and High availability.&lt;/li&gt;\n&lt;li&gt;IPv6!&lt;/li&gt;\n&lt;li&gt;Fewer
703
+ headaches, and more spending money. :-)&lt;/li&gt;\n&lt;/ul&gt;\n&lt;br /&gt;\nIf
704
+ you don&#39;t need tight control over how your data is served, I would strongly
705
+ recommend that you host your sites on Cloud Buckets. (Yes, of course, servers
706
+ are still involved, you just don&#39;t need to worry about them.)&lt;br /&gt;\n&lt;br
707
+ /&gt;\nIn this post, I&#39;ll show you how I got the&amp;nbsp;&lt;a href=&quot;https://float64.dev/&quot;&gt;float64
708
+ website&lt;/a&gt;&amp;nbsp;up and serving in almost no time.&lt;br /&gt;\n&lt;br
709
+ /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nWhat are Cloud Buckets?&lt;/h3&gt;\n&lt;br
710
+ /&gt;\nBuckets are a storage abstraction for blobs of data offered by cloud
711
+ providers. E.g., &lt;a href=&quot;https://cloud.google.com/storage&quot;&gt;Google
712
+ Cloud Storage&lt;/a&gt; or &lt;a href=&quot;https://aws.amazon.com/s3/&quot;&gt;Amazon
713
+ S3&lt;/a&gt;. Put simply, they&#39;re a place in the cloud where you can store
714
+ directories of files (typically called objects.)&lt;br /&gt;\n&lt;br /&gt;\nData
715
+ in buckets are managed by cloud providers -- they take care of all the heavy
716
+ lifting around storing the data, replicating, backing up, and serving. You
717
+ can access this data with command line tools, via language APIs, or from the
718
+ browser. You can also manage permissions, ownership, replication, retention,
719
+ encryption, and audit controls.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
720
+ left;&quot;&gt;\n&lt;/h3&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nHosting
721
+ Websites on Cloud Buckets&lt;/h3&gt;\n&lt;br /&gt;\nMany cloud providers now
722
+ allow you to serve files (sometimes called bucket objects) over the web, and
723
+ let you distribute content over their respective CDNs. For this post, we&#39;ll
724
+ upload a website to a &lt;a href=&quot;https://cloud.google.com/storage&quot;&gt;Google
725
+ Cloud Storage&lt;/a&gt; bucket and serve it over the web.&lt;br /&gt;\n&lt;br
726
+ /&gt;\nMake sure you have your &lt;a href=&quot;https://cloud.google.com/&quot;&gt;Google
727
+ Cloud&lt;/a&gt;&amp;nbsp;account setup, &lt;a href=&quot;https://cloud.google.com/sdk&quot;&gt;command-line
728
+ tools installed&lt;/a&gt;, and are logged in on your terminal.&lt;br /&gt;\n&lt;br
729
+ /&gt;\n&lt;code&gt;\ngcloud auth login&lt;br /&gt;\ngcloud config set project
730
+ &amp;lt;your-project-id&amp;gt;&lt;/code&gt;&lt;br /&gt;\n&lt;code&gt;&lt;br
731
+ /&gt;&lt;/code&gt;\n\nCreate your storage bucket with &lt;a href=&quot;https://cloud.google.com/storage/docs/creating-buckets&quot;&gt;gsutil
732
+ mb&lt;/a&gt;. Bucket names must be globally unique, so you&#39;ll have to
733
+ pick something no one else has used. Here I&#39;m using &lt;i&gt;float64&lt;/i&gt;
734
+ as my bucket name.&lt;br /&gt;\n&lt;br /&gt;\n&lt;code&gt;gsutil mb gs://float64&lt;/code&gt;\n&lt;br
735
+ /&gt;\n&lt;code&gt;&lt;br /&gt;&lt;/code&gt;\nCopy your website content over
736
+ to the bucket. We specify &#39;-&lt;a href=&quot;https://cloud.google.com/storage/docs/gsutil/commands/cp&quot;&gt;a
737
+ public-read&lt;/a&gt;&#39; to make the objects world-readable.&lt;br /&gt;\n&lt;br
738
+ /&gt;\n&lt;code&gt;gsutil cp -a public-read index.html style.css index.AF4C.js
739
+ gs://float64&lt;/code&gt;\n&lt;br /&gt;\n&lt;code&gt;&lt;br /&gt;&lt;/code&gt;\nThat&#39;s
740
+ it. Your content is now available at &lt;i&gt;https://storage.googleapis.com/&amp;lt;BUCKET&amp;gt;/index.html&lt;/i&gt;.
741
+ Like mine is here:&amp;nbsp;&lt;a href=&quot;https://storage.googleapis.com/float64/index.html&quot;&gt;https://storage.googleapis.com/float64/index.html&lt;/a&gt;.&lt;br
742
+ /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\n&lt;/h3&gt;\n&lt;h3
743
+ style=&quot;text-align: left;&quot;&gt;\nUsing your own Domain&lt;/h3&gt;\n&lt;br
744
+ /&gt;\nTo serve data over your own domain using HTTPS, you need to create
745
+ a &lt;a href=&quot;https://cloud.google.com/load-balancing&quot;&gt;Cloud
746
+ Load Balancer&lt;/a&gt; (or use an existing one.) Go to the &lt;a href=&quot;https://console.cloud.google.com/net-services/loadbalancing&quot;&gt;Load
747
+ Balancer Console&lt;/a&gt;, click &quot;Create Load Balancer&quot;, and select
748
+ the HTTP/HTTPS option.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
749
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhipsKbza7Qsm2JCe2LJwWdOlpRexWoh8IwF0JVLK9BsUaaiV9jaUctCtiSiG6FNjoHpspbjVPED27FoXPYh8L671CzC-azbBzqp3LQ0LyJtMvQw78R3yLdjX963KIRUSd87b3Azg/s1600/Screen+Shot+2020-02-19+at+8.28.36+AM.png&quot;
750
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
751
+ border=&quot;0&quot; data-original-height=&quot;652&quot; data-original-width=&quot;878&quot;
752
+ height=&quot;295&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhipsKbza7Qsm2JCe2LJwWdOlpRexWoh8IwF0JVLK9BsUaaiV9jaUctCtiSiG6FNjoHpspbjVPED27FoXPYh8L671CzC-azbBzqp3LQ0LyJtMvQw78R3yLdjX963KIRUSd87b3Azg/s400/Screen+Shot+2020-02-19+at+8.28.36+AM.png&quot;
753
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\nThe balancer
754
+ configuration has three main parts: backend, routing rules, and frontend.&lt;br
755
+ /&gt;\n&lt;br /&gt;\nFor the backend, select &quot;backend buckets&quot;,
756
+ and pick the bucket that you just created. Check the &#39;Enable CDN&#39;
757
+ box if you want your content cached and delivered over Google&#39;s worldwide
758
+ Content Delivery Network.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
759
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9YZ38AmPpWnQ2YCL3PGI9xzJSbPD4GgyN776Ha5bZ82BubM7CjwmJ7qexivA9WywQpWBsEmKoFYRLrlvIMA3tfRkt95YDp7bZUnQbMC_8vw_OxGg5xq0J7MGNC8FyLZyT5jq3wQ/s1600/Screen+Shot+2020-02-19+at+8.31.48+AM.png&quot;
760
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
761
+ border=&quot;0&quot; data-original-height=&quot;100&quot; data-original-width=&quot;280&quot;
762
+ height=&quot;71&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9YZ38AmPpWnQ2YCL3PGI9xzJSbPD4GgyN776Ha5bZ82BubM7CjwmJ7qexivA9WywQpWBsEmKoFYRLrlvIMA3tfRkt95YDp7bZUnQbMC_8vw_OxGg5xq0J7MGNC8FyLZyT5jq3wQ/s200/Screen+Shot+2020-02-19+at+8.31.48+AM.png&quot;
763
+ width=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;br /&gt;\nFor
764
+ the routing rules, simply use your domain name (float64.dev) in the host field,
765
+ your bucket (float64) in the backends field, and &lt;code&gt;&lt;b&gt;/*&lt;/b&gt;&lt;/code&gt;
766
+ in Paths to say that all paths get routed to your bucket.&lt;br /&gt;\n&lt;br
767
+ /&gt;\nFinally, for the frontend, add a new IP address, and point your domain&#39;s
768
+ &lt;i&gt;A&lt;/i&gt; record at it. If you&#39;re with the times, you can also
769
+ add an IPv6 address, and point your domain&#39;s &lt;i&gt;AAAA&lt;/i&gt; record
770
+ at it.&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot; style=&quot;clear:
771
+ both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZYmJHBFv8QyXiXv30K_XVUf62MvxDLSmb4GijnBsHnc2vouiVM2bAqEtM0qpjfoj7_cCcg1p17ZVXntlwmSzctH0cWHKXAFRa38IfK5yS28Am_UCy1B140zhdfmzkB6AtPM0cKA/s1600/Screen+Shot+2020-02-19+at+8.29.55+AM.png&quot;
772
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
773
+ border=&quot;0&quot; data-original-height=&quot;272&quot; data-original-width=&quot;962&quot;
774
+ height=&quot;179&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZYmJHBFv8QyXiXv30K_XVUf62MvxDLSmb4GijnBsHnc2vouiVM2bAqEtM0qpjfoj7_cCcg1p17ZVXntlwmSzctH0cWHKXAFRa38IfK5yS28Am_UCy1B140zhdfmzkB6AtPM0cKA/s640/Screen+Shot+2020-02-19+at+8.29.55+AM.png&quot;
775
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;br /&gt;\nIf
776
+ you&#39;re serving over HTTPS, you can create a new &lt;a href=&quot;https://cloud.google.com/load-balancing/docs/ssl-certificates&quot;&gt;managed
777
+ certificate&lt;/a&gt;. These certs are issued by Let&#39;s Encrypt and managed
778
+ by Google (i.e., Goole takes care of attaching, verifying, and renewing them.)
779
+ The certificates take about 30 minutes to propagate.&lt;br /&gt;\n&lt;br /&gt;\nSave
780
+ and apply your changes, and your custom HTTPS website is up! A few more odds
781
+ and ends before we call it a day.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
782
+ left;&quot;&gt;\nSetup Index and Error Pages&lt;/h3&gt;\n&lt;div&gt;\n&lt;br
783
+ /&gt;&lt;/div&gt;\n&lt;div&gt;\nYou probably don&#39;t want your users typing
784
+ in the name of the index HTML file (&lt;a href=&quot;https://float64.dev/index.html&quot;&gt;https://float64.dev/index.html&lt;/a&gt;)
785
+ every time they visit your site. You also probably want invalid URLs showing
786
+ a pretty error page.&lt;/div&gt;\n&lt;br /&gt;\nYou can use &lt;a href=&quot;https://cloud.google.com/storage/docs/gsutil/commands/web&quot;&gt;&lt;b&gt;gsutil
787
+ web&lt;/b&gt;&lt;/a&gt;&amp;nbsp;to configure the index and 404 pages for
788
+ the bucket.&lt;br /&gt;\n&lt;br /&gt;\n&lt;code&gt;gsutil web set gs://my-super-bucket
789
+ -m index.html -e 404.html&lt;/code&gt;&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
790
+ left;&quot;&gt;\nCaching, Compression, and Content Delivery&lt;/h3&gt;\n&lt;br
791
+ /&gt;\nTo take advantage of Google&#39;s CDN (or even simply to improve bandwidth
792
+ usage and latency), you should set the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control&quot;&gt;Cache-Control
793
+ headers&lt;/a&gt; on your files. I like to keep the expiries for the index
794
+ page short, and everything else long (of course, also adding content hashes
795
+ to frequently modified files.)&lt;br /&gt;\n&lt;br /&gt;\nWe also want to
796
+ make sure that text files are served with &lt;i&gt;gzip&lt;/i&gt; compression
797
+ enabled. The &lt;code&gt;&lt;b&gt;-z&lt;/b&gt;&lt;/code&gt; flag compresses
798
+ the file, and sets the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding&quot;&gt;content-encoding&lt;/a&gt;
799
+ to &lt;i&gt;gzip&lt;/i&gt; while serving over HTTP(s).&lt;br /&gt;\n&lt;br
800
+ /&gt;\n&lt;code&gt;\ngsutil -h &quot;Cache-control:public,max-age=86400&quot;
801
+ -m \\&lt;/code&gt;&lt;br /&gt;\n&lt;code&gt;&amp;nbsp; cp -a public-read -z
802
+ js,map,css,svg \\&lt;br /&gt;\n&amp;nbsp; &amp;nbsp; $DIST/*.js $DIST/*.map
803
+ $DIST/*.css \\&lt;br /&gt;\n&amp;nbsp; &amp;nbsp; $DIST/*.jpg $DIST/*.svg
804
+ $DIST/*.png $DIST/*.ico \\&lt;br /&gt;\n&amp;nbsp; &amp;nbsp; gs://float64&lt;/code&gt;&lt;br
805
+ /&gt;\n&lt;code&gt;&lt;br /&gt;\ngsutil -h &quot;Cache-control:public,max-age=300&quot;
806
+ -m \\&lt;/code&gt;&lt;br /&gt;\n&lt;code&gt;&amp;nbsp; cp -a public-read -z
807
+ html \\&lt;/code&gt;&lt;br /&gt;\n&lt;code&gt;&amp;nbsp; $DIST/index.html
808
+ gs://float64&lt;/code&gt;&lt;br /&gt;\n&lt;code&gt;&lt;br /&gt;&lt;/code&gt;\nIf
809
+ you&#39;ve made it this far, you now have a (nearly) production-ready website
810
+ up and running. Congratulations!&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
811
+ left;&quot;&gt;\nSo, how much does it cost?&lt;/h3&gt;\n&lt;br /&gt;\nI have
812
+ about 8 different websites running on different domains, all using managed
813
+ certificates and the CDN, and I pay about $20 a month.&lt;br /&gt;\n&lt;br
814
+ /&gt;\nI use a single load balancer ($18/mo) and one IP address ($2/mo) for
815
+ all of them. I get about 10 - 20k requests a day across all my sites, and
816
+ bandwidth costs are in the pennies.&lt;br /&gt;\n&lt;br /&gt;\nNot cheap,
817
+ but not expensive either given the cognitive savings. And there are cheaper
818
+ options (as you&#39;ll see in the next section).&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3
819
+ style=&quot;text-align: left;&quot;&gt;\nAlternatives&lt;/h3&gt;\n&lt;div&gt;\n&lt;br
820
+ /&gt;&lt;/div&gt;\nThere are many ways to serve web content out of storage
821
+ buckets, and this is just one. Depending on your traffic, the number of sites
822
+ you&#39;re running, and what kinds of tradeoffs you&#39;re willing to make,
823
+ you can optimize costs further.&lt;br /&gt;\n&lt;br /&gt;\n&lt;a href=&quot;https://firebase.google.com/docs/hosting&quot;&gt;Firebase
824
+ Hosting&lt;/a&gt;&amp;nbsp;sticks all of this into one pretty package, with
825
+ a lower upfront cost (however, the bandwidth costs are higher as your traffic
826
+ increases.)&lt;br /&gt;\n&lt;br /&gt;\n&lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt;&amp;nbsp;has
827
+ a&amp;nbsp;free plan and lets you stick an SSL server and CDN in front of
828
+ your Cloud Storage bucket. However if you want dedicated certificates, they
829
+ charge you $5 each. Also, the minimum TTL on the free plan is 2 hours, which
830
+ is not great if you&#39;re building static Javascript applications.&lt;br
831
+ /&gt;\n&lt;br /&gt;\nAnd there&#39;s &lt;a href=&quot;https://aws.amazon.com/cloudfront/&quot;&gt;CloudFront&lt;/a&gt;,
832
+ &lt;a href=&quot;http://www.fastly.com/&quot;&gt;Fastly&lt;/a&gt;, &lt;a href=&quot;https://netlify.com/&quot;&gt;Netlify&lt;/a&gt;,
833
+ all which provide various levels of managed infrastructure, still all better
834
+ than running your own servers.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align:
835
+ left;&quot;&gt;\nCaveats&lt;/h3&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;div&gt;\nObviously,
836
+ there&#39;s no free lunch, and good engineering requires making tradeoffs,
837
+ and here are a few things to consider before you decide to migrate from servers
838
+ to buckets:&lt;/div&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;div&gt;\n&lt;ul
839
+ style=&quot;text-align: left;&quot;&gt;\n&lt;li&gt;&lt;b&gt;Vendor lock-in.&lt;/b&gt;
840
+ Are you okay with using proprietary technologies for your stack. If not, you&#39;re
841
+ better off running your own servers.&lt;/li&gt;\n&lt;li&gt;&lt;b&gt;Control
842
+ and Flexibility.&lt;/b&gt; Do you want advanced routing, URL rewriting, or
843
+ other custom behavior? If so you&#39;re better off running your own servers.&lt;/li&gt;\n&lt;li&gt;&lt;b&gt;Cost
844
+ transparency.&lt;/b&gt; Although both Google and Amazon do great jobs with
845
+ billing and detailed price breakdowns, they are super complicated and can
846
+ change on a whim.&lt;/li&gt;\n&lt;/ul&gt;\n&lt;div&gt;\nFor a lot of what
847
+ I do, these downsides are well worth it. The vendor lock-in troubles me the
848
+ most, however it&#39;s not hard to migrate this stuff to other providers if
849
+ I need to.&lt;/div&gt;\n&lt;/div&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\n&lt;div&gt;\nIf
850
+ you liked this, check out some of &lt;a href=&quot;https://0xfe.blogspot.com/&quot;&gt;my
851
+ other stuff&lt;/a&gt; on this blog.&lt;/div&gt;\n&lt;br /&gt;\n&lt;br /&gt;\n&lt;br
852
+ /&gt;&lt;/div&gt;\n</content><link rel='replies' type='application/atom+xml'
853
+ href='https://0xfe.blogspot.com/feeds/541596065311835171/comments/default'
854
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2020/02/no-servers-just-buckets-hosting-static.html#comment-form'
855
+ title='1 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/541596065311835171'/><link
856
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/541596065311835171'/><link
857
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2020/02/no-servers-just-buckets-hosting-static.html'
858
+ title='No Servers, Just Buckets: Hosting Static Websites on the Cloud'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
859
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
860
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhipsKbza7Qsm2JCe2LJwWdOlpRexWoh8IwF0JVLK9BsUaaiV9jaUctCtiSiG6FNjoHpspbjVPED27FoXPYh8L671CzC-azbBzqp3LQ0LyJtMvQw78R3yLdjX963KIRUSd87b3Azg/s72-c/Screen+Shot+2020-02-19+at+8.28.36+AM.png\"
861
+ height=\"72\" width=\"72\"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-421690605020819859</id><published>2016-07-12T09:44:00.000-04:00</published><updated>2016-07-12T10:23:04.730-04:00</updated><title
862
+ type='text'>New in VexFlow: ES6, Visual Regression Tests, and more!</title><content
863
+ type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot;
864
+ trbidi=&quot;on&quot;&gt;\nLots of developments since the last time I posted
865
+ about VexFlow.&lt;br /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\n&lt;/h3&gt;\n&lt;div&gt;\n&lt;br
866
+ /&gt;&lt;/div&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nVexFlow
867
+ is ES6&lt;/h3&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\nThanks to the heroics
868
+ of &lt;a href=&quot;http://github.com/SilverWolf90&quot;&gt;SilverWolf90&lt;/a&gt;
869
+ and &lt;a href=&quot;http://github.com/AaronMars&quot;&gt;AaronMars&lt;/a&gt;,
870
+ and the help from many others, VexFlow&#39;s entire &lt;code&gt;src/&lt;/code&gt;
871
+ tree has been migrated to ES6. This is a huge benefit to the project and to
872
+ the health of the codebase. Some of the wins are:&lt;br /&gt;\n&lt;br /&gt;\n&lt;ul
873
+ style=&quot;text-align: left;&quot;&gt;\n&lt;li&gt;Real modules, which allows
874
+ us to extract explicit dependency information and generate graphs like &lt;a
875
+ href=&quot;https://github.com/0xfe/vexflow/wiki/VexFlow-Dependency-Graph&quot;&gt;this&lt;/a&gt;.&lt;/li&gt;\n&lt;li&gt;Const-correctness
876
+ and predictable variable scoping with &lt;code&gt;const&lt;/code&gt; and &lt;code&gt;let&lt;/code&gt;.&lt;/li&gt;\n&lt;li&gt;Classes,
877
+ lambda functions, and lots of other structural enhancements that vastly improve
878
+ the clarity and conciseness of the codebase.&lt;/li&gt;\n&lt;/ul&gt;\n&lt;div&gt;\n&lt;div
879
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
880
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3aiqkRZhH7qzKgGfHCA1yXvmcc4CvQwetLQEHvrQej3ugBVywShEIL3tPMIFw93rJU4Qf7yyerY2Ha60AgIvadGwq98fZLMrTLbzRg0h0y9v1XG_sIyFh4lVEmi2tq-HbikKDMg/s1600/Screen+Shot+2016-07-12+at+10.06.20+AM.png&quot;
881
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
882
+ border=&quot;0&quot; height=&quot;390&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3aiqkRZhH7qzKgGfHCA1yXvmcc4CvQwetLQEHvrQej3ugBVywShEIL3tPMIFw93rJU4Qf7yyerY2Ha60AgIvadGwq98fZLMrTLbzRg0h0y9v1XG_sIyFh4lVEmi2tq-HbikKDMg/s640/Screen+Shot+2016-07-12+at+10.06.20+AM.png&quot;
883
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;br /&gt;\nPart
884
+ of the migration effort also involved making everything lint-clean, improving
885
+ the overall style and consistency of the codebase -- see &lt;a href=&quot;http://github.com/SilverWolf90&quot;&gt;SilverWolf90&lt;/a&gt;&#39;s
886
+ brief document on how &lt;a href=&quot;https://github.com/0xfe/vexflow/wiki/Migrating-to-ESLint&quot;&gt;here&lt;/a&gt;.&lt;/div&gt;\n&lt;h3
887
+ style=&quot;text-align: left;&quot;&gt;\n&lt;/h3&gt;\n&lt;div&gt;\n&lt;br
888
+ /&gt;&lt;/div&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nVisual
889
+ Regression Tests&lt;/h3&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\nVexFlow
890
+ now has a visual regression test system, and all image-generating QUnit tests
891
+ are automatically included.&lt;br /&gt;\n&lt;br /&gt;\nThe goal of this system
892
+ is to detect differences in the rendered output without having to rely on
893
+ human eyeballs, especially given the huge number of tests that exist today.
894
+ It does this by calculating a perceptual hash (PHASH) of each test image and
895
+ comparing it with the hash of a good known blessed image. The larger the arithmetic
896
+ distance between the hashes, the more different are the two images.&lt;br
897
+ /&gt;\n&lt;br /&gt;\nThe system also generates a diff image, which is an overlay
898
+ of the two images, with the differences highlighted, to ease debugging. Here&#39;s
899
+ an example of a failing test:&lt;br /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
900
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxeptv0TDTSQB4_RFZOumVUFEhBNkP8P3mIiTWAJSVescGm8Fx5x0-qqZYZiPSfK8Vfrc9GiNxsjhdTzalnb243CDZ_LmqI8Y-TvxblFvAxOKT_aK_j7I4q6SZ8DcdUqIGuZlSvA/s1600/Screen+Shot+2016-07-12+at+9.46.16+AM.png&quot;
901
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
902
+ border=&quot;0&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxeptv0TDTSQB4_RFZOumVUFEhBNkP8P3mIiTWAJSVescGm8Fx5x0-qqZYZiPSfK8Vfrc9GiNxsjhdTzalnb243CDZ_LmqI8Y-TvxblFvAxOKT_aK_j7I4q6SZ8DcdUqIGuZlSvA/s640/Screen+Shot+2016-07-12+at+9.46.16+AM.png&quot;
903
+ width=&quot;600&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;br /&gt;\n&lt;div
904
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;/div&gt;\nThese
905
+ tests are run automatically for all PRs, commits, and releases. Props to &lt;a
906
+ href=&quot;http://github.com/panarch&quot;&gt;Taehoon Moon&lt;/a&gt; for migrating
907
+ the regression tests from NodeJS to SlimerJS, giving us headless support and
908
+ Travis CI integration. To find out more, read the Wiki page on &lt;a href=&quot;https://github.com/0xfe/vexflow/wiki/Visual-Regression-Tests&quot;&gt;Visual
909
+ Regression Tests&lt;/a&gt;.&lt;br /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\n&lt;/h3&gt;\n&lt;div&gt;\n&lt;br
910
+ /&gt;&lt;/div&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nNative
911
+ SVG&lt;/h3&gt;\n&lt;div&gt;\n&lt;br /&gt;&lt;/div&gt;\nThanks to the awesome
912
+ contribution of &lt;a href=&quot;http://github.com/gristow&quot;&gt;Gregory
913
+ Ristow&lt;/a&gt;, VexFlow now has a native SVG rendering backend, and the
914
+ &lt;a href=&quot;http://raphaeljs.com/&quot;&gt;RaphaelJS&lt;/a&gt; backend
915
+ has been deprecated. This not only reduces the overall size and bloat, but
916
+ also hugely improves rendering performance.&lt;br /&gt;\n&lt;br /&gt;\nThe
917
+ new backend is called &lt;code&gt;Rendering.Backends.SVG&lt;/code&gt; with
918
+ the code at&amp;nbsp;&lt;a href=&quot;https://github.com/0xfe/vexflow/blob/master/src/svgcontext.js&quot;&gt;Vex.Flow.SVGContext&lt;/a&gt;.
919
+ Here is a quick example of how to use the new backend:&amp;nbsp;&lt;a href=&quot;https://jsfiddle.net/nL0cn3vL/2/&quot;&gt;https://jsfiddle.net/nL0cn3vL/2/&lt;/a&gt;.&lt;br
920
+ /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nImproved
921
+ Microtonal Support&lt;/h3&gt;\n&lt;br /&gt;\nVexFlow now has better support
922
+ for Arabic, Turkish, and other microtonal music via accidentals and key signatures.
923
+ Thanks to &lt;a href=&quot;http://github.com/infojunkie&quot;&gt;infojunkie&lt;/a&gt;
924
+ for a lot of the heavy lifting here, and to all the contributors in the &lt;a
925
+ href=&quot;https://github.com/0xfe/vexflow/issues/318&quot;&gt;GitHub issue&lt;/a&gt;.&lt;br
926
+ /&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot; style=&quot;clear:
927
+ both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKqsOnWIHkdB5VVqFfq6gj2wgiwJXzN1Sk8XR2qJX_6EZ-2xnrzBYL5eZy23MiDbm5GCvgNA_TBzjFQ8JOCoA2JlOVMD7rw3jQxk4YphClucw6TQGtnc1-ZJgNhWYtX18BKHKMcQ/s1600/Screen+Shot+2016-07-12+at+9.50.40+AM.png&quot;
928
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
929
+ border=&quot;0&quot; height=&quot;212&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKqsOnWIHkdB5VVqFfq6gj2wgiwJXzN1Sk8XR2qJX_6EZ-2xnrzBYL5eZy23MiDbm5GCvgNA_TBzjFQ8JOCoA2JlOVMD7rw3jQxk4YphClucw6TQGtnc1-ZJgNhWYtX18BKHKMcQ/s640/Screen+Shot+2016-07-12+at+9.50.40+AM.png&quot;
930
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\n&lt;div class=&quot;separator&quot;
931
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYh59DufskkVVEVorHw4Of9VzlaEjU2i2GG8p8q3-PpWfFZrbEcWs8rvfyiEvJ1gfJwvRoEjzkW88AzO4O2LUo-Vs3PU97AmA6ZINyvz5zm4vwfU_s89qNFNrcf56tys6hDXVN6w/s1600/Screen+Shot+2016-07-12+at+9.50.52+AM.png&quot;
932
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
933
+ border=&quot;0&quot; height=&quot;272&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYh59DufskkVVEVorHw4Of9VzlaEjU2i2GG8p8q3-PpWfFZrbEcWs8rvfyiEvJ1gfJwvRoEjzkW88AzO4O2LUo-Vs3PU97AmA6ZINyvz5zm4vwfU_s89qNFNrcf56tys6hDXVN6w/s640/Screen+Shot+2016-07-12+at+9.50.52+AM.png&quot;
934
+ width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;\n&lt;br /&gt;\nMicrotonal
935
+ support is by no means complete, but this is a noteworthy step forward in
936
+ the space.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3 style=&quot;text-align: left;&quot;&gt;\nOther
937
+ Stuff&lt;/h3&gt;\n&lt;br /&gt;\nLots of other stuff worth mentioning:&lt;br
938
+ /&gt;\n&lt;br /&gt;\n&lt;ul style=&quot;text-align: left;&quot;&gt;\n&lt;li&gt;Support
939
+ for user interactivity in SVG notation. You can attach event-handlers to elements
940
+ (or groups of elements) and dynamically modify various properties of the score.&lt;/li&gt;\n&lt;li&gt;Improved
941
+ bounding-box support.&lt;/li&gt;\n&lt;li&gt;Alignment of clef, timesignature,
942
+ and other stave modifiers during mid-measure changes.&lt;/li&gt;\n&lt;li&gt;Lots
943
+ of improvements to the build system and Travis CI integration.&lt;/li&gt;\n&lt;li&gt;Lots
944
+ of bug fixes related to beaming, tuplets, annotations, etc.&lt;/li&gt;\n&lt;/ul&gt;\n&lt;div&gt;\n&lt;br
945
+ /&gt;&lt;/div&gt;\n&lt;div&gt;\nMany thanks to all the contributors involved!&lt;/div&gt;\n&lt;/div&gt;\n</content><link
946
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/421690605020819859/comments/default'
947
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2016/07/new-in-vexflow-es6-visual-regression.html#comment-form'
948
+ title='2 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/421690605020819859'/><link
949
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/421690605020819859'/><link
950
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2016/07/new-in-vexflow-es6-visual-regression.html'
951
+ title='New in VexFlow: ES6, Visual Regression Tests, and more!'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
952
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
953
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3aiqkRZhH7qzKgGfHCA1yXvmcc4CvQwetLQEHvrQej3ugBVywShEIL3tPMIFw93rJU4Qf7yyerY2Ha60AgIvadGwq98fZLMrTLbzRg0h0y9v1XG_sIyFh4lVEmi2tq-HbikKDMg/s72-c/Screen+Shot+2016-07-12+at+10.06.20+AM.png\"
954
+ height=\"72\" width=\"72\"/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-2970930225553638408</id><published>2014-05-02T11:18:00.000-04:00</published><updated>2014-05-02T11:18:34.873-04:00</updated><title
955
+ type='text'>New in VexFlow (May 2014)</title><content type='html'>Lots of
956
+ commits into the repository lately. Thanks to Cyril Silverman for may of these.
957
+ Here are some of the highlights:\n\n&lt;p/&gt;\n\n&lt;h3&gt;Chord Symbols&lt;/h3&gt;\nThis
958
+ includes subscript/superscript support in &lt;code&gt;TextNote&lt;/code&gt;
959
+ and support for common symbols (dim, half-dim, maj7, etc.)\n&lt;p/&gt;\n&lt;div
960
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a
961
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhARWln9YaHdtVL5iBAiFKZHcGI85eDMyNLhN3USobfFRbFuq6FAZPsXK746YLJHrXEcb0Hpkbza1giiZ8sCb6I-1BVwuPTCNwkY8boODT0d1x7hYEnbys1OzHbdVRmwGwEYMSaSw/s1600/Screen+Shot+2014-05-02+at+10.51.12+AM.png&quot;
962
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
963
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhARWln9YaHdtVL5iBAiFKZHcGI85eDMyNLhN3USobfFRbFuq6FAZPsXK746YLJHrXEcb0Hpkbza1giiZ8sCb6I-1BVwuPTCNwkY8boODT0d1x7hYEnbys1OzHbdVRmwGwEYMSaSw/s400/Screen+Shot+2014-05-02+at+10.51.12+AM.png&quot;
964
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\n\n&lt;h3&gt;Stave Line Arrows&lt;/h3&gt;\nThis
965
+ is typically used in instructional material.\n&lt;p/&gt;\n&lt;div class=&quot;separator&quot;
966
+ style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNZTalVSVd5H_a4RBTaURK2A7MhCwpKTZDwqEC6j3ZALkCB3TFv4jfVQyX6GoiALkwsTkxnRK_Ntw7HuRm3S7n6ehmw8M1xa1qqysNo7HVxG06zc1y2TNE09PNp41kaGqiHo32tg/s1600/Screen+Shot+2014-05-02+at+10.50.57+AM.png&quot;
967
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
968
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNZTalVSVd5H_a4RBTaURK2A7MhCwpKTZDwqEC6j3ZALkCB3TFv4jfVQyX6GoiALkwsTkxnRK_Ntw7HuRm3S7n6ehmw8M1xa1qqysNo7HVxG06zc1y2TNE09PNp41kaGqiHo32tg/s400/Screen+Shot+2014-05-02+at+10.50.57+AM.png&quot;
969
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
970
+ both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdoHUP3nNvIHRvV0nF_w0sv1dI7Lrir4jLiyzMNMQgMUrsgH-HkATiIUUjoYYprV9rOSCkX8-UyUCRT-331MsHJ-BTkjuBh3k-Zqq019RbKHpYP3vOjZcd3mKY7p_xiUmPkJlK5Q/s1600/Screen+Shot+2014-05-02+at+10.51.06+AM.png&quot;
971
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
972
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdoHUP3nNvIHRvV0nF_w0sv1dI7Lrir4jLiyzMNMQgMUrsgH-HkATiIUUjoYYprV9rOSCkX8-UyUCRT-331MsHJ-BTkjuBh3k-Zqq019RbKHpYP3vOjZcd3mKY7p_xiUmPkJlK5Q/s400/Screen+Shot+2014-05-02+at+10.51.06+AM.png&quot;
973
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\n\n&lt;h3&gt;Slurs&lt;/h3&gt;\nFinally,
974
+ we have slurs. This uses a new VexFlow class called &lt;code&gt;Curve&lt;/code&gt;.
975
+ Slurs are highly configurable.\n&lt;p/&gt;\n&lt;div class=&quot;separator&quot;
976
+ style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwVjQOCOtb9K1roaI9Fkpdjer-LJGK_ItJNjilN5fxykR6nkpjO_ZG_Ows3HH20ix8sIOZrzb0jm5Z-VmqP2JjwAB_KuRQgvIXF618OUC7R4T6r5WfPzFq_E4ogC_Lak_wYGIkhg/s1600/Screen+Shot+2014-05-02+at+10.51.24+AM.png&quot;
977
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
978
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwVjQOCOtb9K1roaI9Fkpdjer-LJGK_ItJNjilN5fxykR6nkpjO_ZG_Ows3HH20ix8sIOZrzb0jm5Z-VmqP2JjwAB_KuRQgvIXF618OUC7R4T6r5WfPzFq_E4ogC_Lak_wYGIkhg/s400/Screen+Shot+2014-05-02+at+10.51.24+AM.png&quot;
979
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
980
+ both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhatECDRLVFWQyBskqVOcVUOZg66Jcywok5t9qyAfIVcqHoUfCZa1J2Ga1Gxrp3dpiFA7Zbm6I1XGGRrWBMX6fcZ5oZDxlWZu0hR1UVipMdAgmEf-96gHbWgppln4Dp2DOsBhIAxg/s1600/Screen+Shot+2014-05-02+at+10.51.29+AM.png&quot;
981
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
982
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhatECDRLVFWQyBskqVOcVUOZg66Jcywok5t9qyAfIVcqHoUfCZa1J2Ga1Gxrp3dpiFA7Zbm6I1XGGRrWBMX6fcZ5oZDxlWZu0hR1UVipMdAgmEf-96gHbWgppln4Dp2DOsBhIAxg/s400/Screen+Shot+2014-05-02+at+10.51.29+AM.png&quot;
983
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
984
+ both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgc_eVlyXPeFIevIo9au9bCa75PKtHctY2oRecgdQl15Gspn0s_i7uZK_7t6UclD-UAqCXFCJrozYETXUqg45BobYrgRGs0EZkTpCMriR21P0CdLfaH5sDfSVljgqHokOQwHM7Kbw/s1600/Screen+Shot+2014-05-02+at+10.51.36+AM.png&quot;
985
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
986
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgc_eVlyXPeFIevIo9au9bCa75PKtHctY2oRecgdQl15Gspn0s_i7uZK_7t6UclD-UAqCXFCJrozYETXUqg45BobYrgRGs0EZkTpCMriR21P0CdLfaH5sDfSVljgqHokOQwHM7Kbw/s400/Screen+Shot+2014-05-02+at+10.51.36+AM.png&quot;
987
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
988
+ both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiedlG-ypZuwWHvQl6PNXpcjXV-qnrHI4QbHCX4b-xPrLfwm7i9JJjhDTORtXX2WYL-N5hzGEuGK0PFvdifZmnqU-QjRA9L1HeZ2LDC5XLe0SpiprIkafTO1_CS-66aWyGiSZpNsw/s1600/Screen+Shot+2014-05-02+at+10.51.45+AM.png&quot;
989
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
990
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiedlG-ypZuwWHvQl6PNXpcjXV-qnrHI4QbHCX4b-xPrLfwm7i9JJjhDTORtXX2WYL-N5hzGEuGK0PFvdifZmnqU-QjRA9L1HeZ2LDC5XLe0SpiprIkafTO1_CS-66aWyGiSZpNsw/s400/Screen+Shot+2014-05-02+at+10.51.45+AM.png&quot;
991
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\n\n&lt;h3&gt;Improved auto-positioning
992
+ of Annotations and Articulations&lt;/h3&gt;\nAnnotations and Articulations
993
+ now self-position based on note, stem, and beam configuration.\n&lt;p/&gt;\n&lt;div
994
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a
995
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU13FCOX7XCOL9mnXofqMUGuOmGJbJvbCtUIixCdxqASZmSCLZNmPQ773x2hY5lWiPHorbJak3YtUg1BuhcV1HajHha3ryLrlId4AczhzrqALlOhdvm2wyBiz7ha8SZwRliBj11A/s1600/Screen+Shot+2014-05-02+at+10.54.03+AM.png&quot;
996
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
997
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU13FCOX7XCOL9mnXofqMUGuOmGJbJvbCtUIixCdxqASZmSCLZNmPQ773x2hY5lWiPHorbJak3YtUg1BuhcV1HajHha3ryLrlId4AczhzrqALlOhdvm2wyBiz7ha8SZwRliBj11A/s400/Screen+Shot+2014-05-02+at+10.54.03+AM.png&quot;
998
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\n\n&lt;h3&gt;Grace Notes&lt;/h3&gt;\nVexFlow
999
+ now has full support for Grace Notes. Grace Note groups can contain complex
1000
+ rhythmic elements, and are formatted using the same code as regular notes.\n\n&lt;p/&gt;\n&lt;div
1001
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a
1002
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSotGzQBCA_ljoJpIQM6N011kQTZ5AhkdoLgqO8ORq42h05hh82Rs_q9HBUN5lEkkba8_TbMNhx04aJ48K0GyPzsH1BHXG-1cNljOjvZgVfz7qGhcHD-N8KhS1juvDqSu7ECcd6g/s1600/Screen+Shot+2014-05-02+at+10.54.30+AM.png&quot;
1003
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
1004
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSotGzQBCA_ljoJpIQM6N011kQTZ5AhkdoLgqO8ORq42h05hh82Rs_q9HBUN5lEkkba8_TbMNhx04aJ48K0GyPzsH1BHXG-1cNljOjvZgVfz7qGhcHD-N8KhS1juvDqSu7ECcd6g/s400/Screen+Shot+2014-05-02+at+10.54.30+AM.png&quot;
1005
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
1006
+ both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZOxD9_LTKefUh0Vs9iqFpzlLI55B7tYSYKx79Zv43GL0BhyMqmfi63i7ulWmY1FrXmN_5HnFmORvMqCNA0yUX-jQEi6MJOv4cO3HBBvde5m1VOeRPzEXxvEO_FwR2qceVl1Zixg/s1600/Screen+Shot+2014-05-02+at+10.54.37+AM.png&quot;
1007
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
1008
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZOxD9_LTKefUh0Vs9iqFpzlLI55B7tYSYKx79Zv43GL0BhyMqmfi63i7ulWmY1FrXmN_5HnFmORvMqCNA0yUX-jQEi6MJOv4cO3HBBvde5m1VOeRPzEXxvEO_FwR2qceVl1Zixg/s400/Screen+Shot+2014-05-02+at+10.54.37+AM.png&quot;
1009
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\n\n&lt;h3&gt;Auto-Beam Imnprovements&lt;/h3&gt;\nLots
1010
+ more beaming options, including beaming over rests, stemlet rendering, and
1011
+ time-signature aware beaming.\n\n&lt;p/&gt;\n&lt;div class=&quot;separator&quot;
1012
+ style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjULM-Ju99byoC-ygScvma7aMv67b6ruuqb27v6rZiaQf-kvPep0n_nn8SSmm1XFFdML16g8vhXjFNaJu3gfNDCqiQ5UD69eWnvx0IFj9lfC6RRhHLcmv_v8C0MeHdwizCb01XJTg/s1600/Screen+Shot+2014-05-02+at+10.54.58+AM.png&quot;
1013
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
1014
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjULM-Ju99byoC-ygScvma7aMv67b6ruuqb27v6rZiaQf-kvPep0n_nn8SSmm1XFFdML16g8vhXjFNaJu3gfNDCqiQ5UD69eWnvx0IFj9lfC6RRhHLcmv_v8C0MeHdwizCb01XJTg/s400/Screen+Shot+2014-05-02+at+10.54.58+AM.png&quot;
1015
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
1016
+ both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgovZ5gaOWJyLcc9DxyQX0yI54NK-hy3hSgukICa5-gMrGTaFbVR1OXhoWjS7TkX5CDtoiBRQC4S88QBNv_IHsnRU5U8nKB0H8xHPEuU2o3goALytt_ayQgkMyCNicq7dCJ8qmgQg/s1600/Screen+Shot+2014-05-02+at+10.55.15+AM.png&quot;
1017
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
1018
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgovZ5gaOWJyLcc9DxyQX0yI54NK-hy3hSgukICa5-gMrGTaFbVR1OXhoWjS7TkX5CDtoiBRQC4S88QBNv_IHsnRU5U8nKB0H8xHPEuU2o3goALytt_ayQgkMyCNicq7dCJ8qmgQg/s400/Screen+Shot+2014-05-02+at+10.55.15+AM.png&quot;
1019
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
1020
+ both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihdUTDYtKZhcd-HQjXBxzNPfL0VycfORZBI7xoQWZ3Re4VpaLb9Ka59EL1WivBVfmt1cERK5RhN7zs0mbvxFwzbucMkfL50UYk7epEViA7L3UDk5ihFNJIGVdHmNTgZQ0gjPGrHg/s1600/Screen+Shot+2014-05-02+at+10.55.25+AM.png&quot;
1021
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
1022
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihdUTDYtKZhcd-HQjXBxzNPfL0VycfORZBI7xoQWZ3Re4VpaLb9Ka59EL1WivBVfmt1cERK5RhN7zs0mbvxFwzbucMkfL50UYk7epEViA7L3UDk5ihFNJIGVdHmNTgZQ0gjPGrHg/s400/Screen+Shot+2014-05-02+at+10.55.25+AM.png&quot;
1023
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
1024
+ both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7hzcAqJ2cabV3VXA9JCKovDSwx5yKTJB8lKEOmOmHZ_VKoM9uTktsiIR6f2ulKVShwrOm7v7dUuPvNPM3E5EZ-YupxVdfz9EXHRr_FMaOlhsoP5At99tKHoVBmU9CX9-YpIMjhA/s1600/Screen+Shot+2014-05-02+at+10.55.35+AM.png&quot;
1025
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
1026
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7hzcAqJ2cabV3VXA9JCKovDSwx5yKTJB8lKEOmOmHZ_VKoM9uTktsiIR6f2ulKVShwrOm7v7dUuPvNPM3E5EZ-YupxVdfz9EXHRr_FMaOlhsoP5At99tKHoVBmU9CX9-YpIMjhA/s400/Screen+Shot+2014-05-02+at+10.55.35+AM.png&quot;
1027
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\n&lt;h3&gt;Tab-Stem Features&lt;/h3&gt;\n\nYou
1028
+ can (optionally) render Tab Stems through stave lines.\n&lt;p/&gt;\n\n&lt;div
1029
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a
1030
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfsQjfpN66Lpyq_IC4dTlXCypbUy3s8wEOE1Tf-j0hRUS3JkAYk2C5topEC4P0cA2ZgQ2A7gxP1djMAp8ecedWzJgaO_lHOELzrFZq8EebqIO3Y7QZxa67UAKluCDHNhv-QgNkWQ/s1600/Screen+Shot+2014-05-02+at+10.57.42+AM.png&quot;
1031
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
1032
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfsQjfpN66Lpyq_IC4dTlXCypbUy3s8wEOE1Tf-j0hRUS3JkAYk2C5topEC4P0cA2ZgQ2A7gxP1djMAp8ecedWzJgaO_lHOELzrFZq8EebqIO3Y7QZxa67UAKluCDHNhv-QgNkWQ/s400/Screen+Shot+2014-05-02+at+10.57.42+AM.png&quot;
1033
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\n\nThat&#39;s all, Folks!</content><link
1034
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/2970930225553638408/comments/default'
1035
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2014/05/new-in-vexflow.html#comment-form'
1036
+ title='19 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/2970930225553638408'/><link
1037
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/2970930225553638408'/><link
1038
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2014/05/new-in-vexflow.html'
1039
+ title='New in VexFlow (May 2014)'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1040
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1041
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhARWln9YaHdtVL5iBAiFKZHcGI85eDMyNLhN3USobfFRbFuq6FAZPsXK746YLJHrXEcb0Hpkbza1giiZ8sCb6I-1BVwuPTCNwkY8boODT0d1x7hYEnbys1OzHbdVRmwGwEYMSaSw/s72-c/Screen+Shot+2014-05-02+at+10.51.12+AM.png\"
1042
+ height=\"72\" width=\"72\"/><thr:total>19</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6880188793332322250</id><published>2012-01-02T13:34:00.000-05:00</published><updated>2012-01-02T13:34:24.100-05:00</updated><title
1043
+ type='text'>More K-Means Clustering Experiments on Images</title><content
1044
+ type='html'>I spent a little more time experimenting with &lt;a href=&quot;http://0xfe.blogspot.com/2011/12/k-means-clustering-and-art.html&quot;&gt;k-means
1045
+ clustering&lt;/a&gt; on images and realized that I could use these clusters
1046
+ to recolor the image in interesting ways.\n&lt;p/&gt;\nI wrote the function
1047
+ &lt;code&gt;save_recolor&lt;/code&gt; to replace pixels from the given clusters
1048
+ (&lt;code&gt;replacements&lt;/code&gt;) with new ones of equal intensity,
1049
+ as specified by the &lt;code&gt;rgb_factors&lt;/code&gt; vector. For example,
1050
+ the following code will convert pixels of the first two clusters to greyscale.\n&lt;p/&gt;\n&lt;pre
1051
+ class=&quot;prettyprint&quot;&gt;\n&amp;gt; save_recolor(&quot;baby.jpeg&quot;,
1052
+ &quot;baby_new.jpg&quot;, replacements=c(1,2),\n rgb_factors=c(1/3,
1053
+ 1/3, 1/3))\n&lt;/pre&gt;\n&lt;p/&gt;\nIt&#39;s greyscale because the &lt;code&gt;rgb_factors&lt;/code&gt;
1054
+ distributes the pixel intensity evenly among the channels. A factor of &lt;code&gt;c(20/100,
1055
+ 60/100, 20/100)&lt;/code&gt; would make pixels from the cluster 60% more green.\n&lt;p/&gt;\nLet&#39;s
1056
+ get to some examples. Here&#39;s an unprocessed image, alongside its color
1057
+ clusters. I picked &lt;code&gt;k=10&lt;/code&gt;. You can set &lt;code&gt;k&lt;/code&gt;
1058
+ by specifying the &lt;code&gt;palette_size&lt;/code&gt; parameter to &lt;code&gt;save_recolor&lt;/code&gt;.\n\n&lt;div
1059
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
1060
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnRdj14UpXHdCnqTwLonrOWUuqLfH09kzSImmQpt8gaPgq8udoZZCtbCcvejK2mvW0u9RKzC355jFpINb2sxNvMccNBy5z8j477Vx0HLeTB87Mww1nldLP6aQ7qZn5mOalIi7-Tw/s1600/Screen+shot+2012-01-01+at+12.16.45+PM.png&quot;
1061
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1062
+ border=&quot;0&quot; height=&quot;274&quot; width=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnRdj14UpXHdCnqTwLonrOWUuqLfH09kzSImmQpt8gaPgq8udoZZCtbCcvejK2mvW0u9RKzC355jFpINb2sxNvMccNBy5z8j477Vx0HLeTB87Mww1nldLP6aQ7qZn5mOalIi7-Tw/s400/Screen+shot+2012-01-01+at+12.16.45+PM.png&quot;
1063
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\nHere&#39;s what happens when I
1064
+ remove the red (the first cluster).\n\n&lt;div class=&quot;separator&quot;
1065
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj21ggbKmvK0T2hmbK5tWykQeThSTgl-n-NtJnxUF0v7xQf0DAQV6IXutPz2DuPxFVqBNim_dAvmKQ3xWcGOXrN1tc9NSb-LM2rj-1sAMVV_W6YLkX3dVST_CnlzaE6D4IzfwMUFw/s1600/arkin_recolor_nored.jpg&quot;
1066
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1067
+ border=&quot;0&quot; height=&quot;400&quot; width=&quot;300&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj21ggbKmvK0T2hmbK5tWykQeThSTgl-n-NtJnxUF0v7xQf0DAQV6IXutPz2DuPxFVqBNim_dAvmKQ3xWcGOXrN1tc9NSb-LM2rj-1sAMVV_W6YLkX3dVST_CnlzaE6D4IzfwMUFw/s400/arkin_recolor_nored.jpg&quot;
1068
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;pre class=&quot;prettyprint&quot;&gt;\n&amp;gt;
1069
+ save_recolor(&quot;baby.jpeg&quot;, &quot;baby_new.jpg&quot;, replacements=1)\n&lt;/pre&gt;\n\n\n&lt;p/&gt;\nIn
1070
+ the next image, I keep the red, and remove everything else.\n\n&lt;div class=&quot;separator&quot;
1071
+ style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA27bcS661NHy3BNusm97m9j8-jR-DYdF-rRYXgjwfZcSE08rY8qwFakOGzHk35tbuanWAJBf4QRySbcDvzE0vmDvlM0S4l1AOGHb8J6NIjCeBWAaDIZNfp8WH3puF_uqPN7K4DA/s1600/arkin_recolor_red.jpeg&quot;
1072
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1073
+ border=&quot;0&quot; height=&quot;400&quot; width=&quot;300&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA27bcS661NHy3BNusm97m9j8-jR-DYdF-rRYXgjwfZcSE08rY8qwFakOGzHk35tbuanWAJBf4QRySbcDvzE0vmDvlM0S4l1AOGHb8J6NIjCeBWAaDIZNfp8WH3puF_uqPN7K4DA/s400/arkin_recolor_red.jpeg&quot;
1074
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;pre class=&quot;prettyprint&quot;&gt;\n&amp;gt;
1075
+ save_recolor(&quot;baby.jpeg&quot;, &quot;baby_new.jpg&quot;, replacements=2:10)\n&lt;/pre&gt;\n\n\n&lt;p/&gt;\nBelow,
1076
+ I replace the red cluster pixels, with green ones of corresponding intensity.\n\n&lt;div
1077
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
1078
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwxVeNhcer-um9mLCAKIdpVqaI5GsohQzeHECRjZ7bI6gDEHzsp45SuCy7GjANE9IYAXdI0qBmf4ZEO8yWwgPWWy8MOW_O8MGnr1BDR1A3P4U90SXaYy1fmBlQU8pRiSNHXZdKug/s1600/arkin_recolor_green.jpg&quot;
1079
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1080
+ border=&quot;0&quot; height=&quot;400&quot; width=&quot;300&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwxVeNhcer-um9mLCAKIdpVqaI5GsohQzeHECRjZ7bI6gDEHzsp45SuCy7GjANE9IYAXdI0qBmf4ZEO8yWwgPWWy8MOW_O8MGnr1BDR1A3P4U90SXaYy1fmBlQU8pRiSNHXZdKug/s400/arkin_recolor_green.jpg&quot;
1081
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;pre class=&quot;prettyprint&quot;&gt;\n&amp;gt;
1082
+ save_recolor(&quot;baby.jpeg&quot;, &quot;baby_new.jpg&quot;, replacements=1,\n
1083
+ \ rgb_factors=c(10/100, 80/100, 10/100))\n&lt;/pre&gt;\n\n&lt;p/&gt;\nAnd
1084
+ this is a fun one: Get rid of everything, keep just the grass.\n\n&lt;div
1085
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
1086
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSuSiJ8ulDFAstlCFQkYTAJIRIhYSVl_jrUYMaC-G4D9hmpIEBMnkl3DI4B54Kvi28b5q4qJPBS8M9w0OBPqLebGd5-nNN32MUJioCg-cFx3hhoEwARmexWb9kLyd20u8NIAu_Aw/s1600/arkin_recolor_grass.jpg&quot;
1087
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1088
+ border=&quot;0&quot; height=&quot;400&quot; width=&quot;300&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSuSiJ8ulDFAstlCFQkYTAJIRIhYSVl_jrUYMaC-G4D9hmpIEBMnkl3DI4B54Kvi28b5q4qJPBS8M9w0OBPqLebGd5-nNN32MUJioCg-cFx3hhoEwARmexWb9kLyd20u8NIAu_Aw/s400/arkin_recolor_grass.jpg&quot;
1089
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n\n&lt;pre class=&quot;prettyprint&quot;&gt;\n&amp;gt;
1090
+ save_recolor(&quot;baby.jpeg&quot;, &quot;baby_new.jpg&quot;, replacements=c(1,3:10))\n&lt;/pre&gt;\n\n&lt;p/&gt;\nI
1091
+ tried this on various images, using different cluster sizes, replacements,
1092
+ and RGB factors, with lots of interesting results. Anyhow, you should experiment
1093
+ with this yourselves and let me know what you find.\n&lt;p/&gt;\nI should
1094
+ point out that nothing here is novel or new -- it&#39;s all well known in
1095
+ image processing circles. It&#39;s still pretty impressive what you can do
1096
+ when you apply simple machine learning algorithms to other areas.\n\n&lt;p&gt;\nOkay,
1097
+ as in all my posts, the code is available in my &lt;a href=&quot;http://github.com/0xfe&quot;&gt;GitHub
1098
+ repository&lt;/a&gt;:\n&lt;p/&gt;\n&lt;a href=&quot;https://github.com/0xfe/experiments/blob/master/r/recolor.rscript&quot;&gt;https://github.com/0xfe/experiments/blob/master/r/recolor.rscript&lt;/a&gt;\n\n&lt;p/&gt;\nHappy
1099
+ new year!</content><link rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/6880188793332322250/comments/default'
1100
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2012/01/more-k-means-clustering-experiments-on.html#comment-form'
1101
+ title='3 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6880188793332322250'/><link
1102
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6880188793332322250'/><link
1103
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2012/01/more-k-means-clustering-experiments-on.html'
1104
+ title='More K-Means Clustering Experiments on Images'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1105
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1106
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnRdj14UpXHdCnqTwLonrOWUuqLfH09kzSImmQpt8gaPgq8udoZZCtbCcvejK2mvW0u9RKzC355jFpINb2sxNvMccNBy5z8j477Vx0HLeTB87Mww1nldLP6aQ7qZn5mOalIi7-Tw/s72-c/Screen+shot+2012-01-01+at+12.16.45+PM.png\"
1107
+ height=\"72\" width=\"72\"/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-8650024762996646704</id><published>2011-12-31T10:27:00.000-05:00</published><updated>2011-12-31T14:14:34.568-05:00</updated><title
1108
+ type='text'>K-Means Clustering and Art</title><content type='html'>&lt;i&gt;Cross
1109
+ posted from &lt;a href=&quot;https://plus.google.com/u/0/111867441083313519234/posts/dxp5w3R7ts3&quot;&gt;Google+&lt;/a&gt;.&lt;/i&gt;\n&lt;p/&gt;\nMy
1110
+ coworker at Google, Tony Rippy, has for a while been working on a fascinating
1111
+ problem. Take all the pixels of a photograph, and rearrange them so that the
1112
+ final image looks like an artist&#39;s palette -- something to which you can
1113
+ take a paintbrush and recreate the original image.\n&lt;p/&gt;\nHe&#39;s got
1114
+ some really good looking solutions which he might post if you ask him nicely.
1115
+ :-)\n&lt;p/&gt;\nThis turns out to be a tricky problem, and its hard to come
1116
+ up with an objective measure of the quality of any given solution. In fact,
1117
+ the quality is very subjective.\n&lt;p/&gt;\nAnyhow, while studying the &lt;a
1118
+ href=&quot;http://en.wikipedia.org/wiki/K-means_clustering&quot;&gt;K-means
1119
+ clustering algorithm&lt;/a&gt; from &lt;a href=&quot;http://www.ml-class.org&quot;&gt;ml-class&lt;/a&gt;,
1120
+ it struck me that &lt;i&gt;k-means&lt;/i&gt; could be used to help with extracting
1121
+ a small palette of colors from an image. For example, by using each of the
1122
+ RGB channels as features, and euclidian distance as the similarity metric,
1123
+ one could run stock &lt;i&gt;k-means&lt;/i&gt; to generate clusters of similar
1124
+ colors.\n&lt;p/&gt;\nI coded up a quick R script to test this and got some
1125
+ interesting results. Here is an example of an image with its potential palette.
1126
+ Recall that the second image is simply the first image with the pixels rearranged.\n&lt;p/&gt;\n\n&lt;div
1127
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
1128
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxNbcvY4I9RIAU91lzjK2m8HR9V5o9fhVa8AYncnYU1uhzq6uHAB3r0NgMlndNIw_TZG5S0gqcZmf-cV6A0A5ll8v66DqrHqoC9nPSWGVpfdAW1qGMape3yvvrC5_q019LmHJ74g/s1600/Screen+shot+2011-12-30+at+1.58.13+PM.png&quot;
1129
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1130
+ border=&quot;0&quot; height=&quot;180&quot; width=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxNbcvY4I9RIAU91lzjK2m8HR9V5o9fhVa8AYncnYU1uhzq6uHAB3r0NgMlndNIw_TZG5S0gqcZmf-cV6A0A5ll8v66DqrHqoC9nPSWGVpfdAW1qGMape3yvvrC5_q019LmHJ74g/s400/Screen+shot+2011-12-30+at+1.58.13+PM.png&quot;
1131
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n\n&lt;p/&gt;\nI experimented with various values
1132
+ of &lt;i&gt;k&lt;/i&gt; (number of clusters) for the different images. It
1133
+ turns out that it&#39;s pretty hard to algorithmically pre-determine this
1134
+ number (although there are various techniques that do exist.) The water villa
1135
+ pic above has 15 clusters, the nursery pic below has 20, and the cartoon has
1136
+ 6.\n&lt;p/&gt;\n&lt;div class=&quot;separator&quot; style=&quot;clear: both;
1137
+ text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-MFHMYHmP27nm5qteBSwvHnYI6jfyLCVledC8UBk9t8tjffNwLd4jldB691bTfQmNuBkQaQXQ-bEuEFYo-Z09908pU09B1VrhQmccYFw9hyphenhyphen0WL69_XQXRn2GnnWppDi64dVW_lg/s1600/Screen+shot+2011-12-30+at+1.57.12+PM.png&quot;
1138
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1139
+ border=&quot;0&quot; height=&quot;235&quot; width=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-MFHMYHmP27nm5qteBSwvHnYI6jfyLCVledC8UBk9t8tjffNwLd4jldB691bTfQmNuBkQaQXQ-bEuEFYo-Z09908pU09B1VrhQmccYFw9hyphenhyphen0WL69_XQXRn2GnnWppDi64dVW_lg/s400/Screen+shot+2011-12-30+at+1.57.12+PM.png&quot;
1140
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\nNote that this is only one subproblem
1141
+ of the original one; there is also the subproblem of placement, which I skirted
1142
+ around by simply arranging the colors in vertical bands across the final image.
1143
+ I&#39;m pretty sure no artist&#39;s palette looks like this.\n&lt;p/&gt;\n&lt;div
1144
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
1145
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlwZAhtmIefeK5li5WDXi9R8ieeu1d9aKww6_JScrZ9IbPnQqT1um3HKhpVcsH7X7yG-T0j873HOt-t0kOz3W4UryfCA_qzFFYNzbVv66X0zBGUzx_rP7dZiKnSXd8RkWNhGreQg/s1600/Screen+shot+2011-12-30+at+1.57.49+PM.png&quot;
1146
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1147
+ border=&quot;0&quot; height=&quot;182&quot; width=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlwZAhtmIefeK5li5WDXi9R8ieeu1d9aKww6_JScrZ9IbPnQqT1um3HKhpVcsH7X7yG-T0j873HOt-t0kOz3W4UryfCA_qzFFYNzbVv66X0zBGUzx_rP7dZiKnSXd8RkWNhGreQg/s400/Screen+shot+2011-12-30+at+1.57.49+PM.png&quot;
1148
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n\n&lt;p/&gt;\nAlso, these palettes aren&#39;t
1149
+ very &quot;clean&quot;. Since the original pictures themselves are noisy,
1150
+ some of this noise arbitrarily creep into the various clusters. Working with
1151
+ a filtered version of the picture would be cheating, so we won&#39;t do that.
1152
+ But we might be able to extract the noisy pixels, put them in a special cluster,
1153
+ and run &lt;i&gt;k-means&lt;/i&gt; on the remaining pixels.\n&lt;p/&gt;\nOkay,
1154
+ enough talk. Here&#39;s the code: &lt;a href=&quot;https://github.com/0xfe/experiments/blob/master/r/palette.rscript&quot;&gt;https://github.com/0xfe/experiments/blob/master/r/palette.rscript&lt;/a&gt;\n&lt;p/&gt;\nFirst
1155
+ install &lt;code&gt;cclust&lt;/code&gt; and &lt;code&gt;ReadImages&lt;/code&gt;
1156
+ packages from &lt;a href=&quot;http://cran.r-project.org&quot;&gt;CRAN&lt;/a&gt;,
1157
+ and try out the algorithm in an R console:\n&lt;p/&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;\n&amp;gt;
1158
+ source(&#39;/path/to/palette.rscript&#39;)\n&amp;gt; plot_palette(&#39;/path/to/some/image.jpg&#39;)\n&lt;/pre&gt;\n&lt;p/&gt;\nThis
1159
+ will produce a plot with the original image and the transformed one next to
1160
+ each other, like the attached pics below. It uses 10 clusters by default,
1161
+ for a palette of 10 colors. You can change this by passing the cluster count
1162
+ as the second parameter to &lt;code&gt;plot_palette&lt;/code&gt;.\n&lt;p/&gt;\n&lt;pre
1163
+ class=&quot;prettyprint&quot;&gt;\n&amp;gt; plot_palette(&#39;/path/to/some/image.jpg&#39;,
1164
+ 20)\n&lt;/pre&gt;\n&lt;p/&gt;\nThat&#39;s all folks!</content><link rel='replies'
1165
+ type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/8650024762996646704/comments/default'
1166
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2011/12/k-means-clustering-and-art.html#comment-form'
1167
+ title='18 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/8650024762996646704'/><link
1168
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/8650024762996646704'/><link
1169
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2011/12/k-means-clustering-and-art.html'
1170
+ title='K-Means Clustering and Art'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1171
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1172
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxNbcvY4I9RIAU91lzjK2m8HR9V5o9fhVa8AYncnYU1uhzq6uHAB3r0NgMlndNIw_TZG5S0gqcZmf-cV6A0A5ll8v66DqrHqoC9nPSWGVpfdAW1qGMape3yvvrC5_q019LmHJ74g/s72-c/Screen+shot+2011-12-30+at+1.58.13+PM.png\"
1173
+ height=\"72\" width=\"72\"/><thr:total>18</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7852661428465635210</id><published>2011-08-21T11:15:00.000-04:00</published><updated>2011-08-21T21:37:53.266-04:00</updated><category
1174
+ scheme=\"http://www.blogger.com/atom/ns#\" term=\"webaudio\"/><title type='text'>A
1175
+ Web Audio Spectrum Analyzer</title><content type='html'>In my last post, I
1176
+ went over some of the &lt;a href=&quot;http://0xfe.blogspot.com/2011/08/generating-tones-with-web-audio-api.html&quot;&gt;basics
1177
+ of the Web Audio API&lt;/a&gt; and showed you how to generate &lt;a href=&quot;http://0xfe.muthanna.com/tone&quot;&gt;sine
1178
+ waves&lt;/a&gt; of various frequencies and amplitudes. We were introduced
1179
+ to some key &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html&quot;&gt;Web
1180
+ Audio classes&lt;/a&gt;, such as &lt;code&gt;AudioContext&lt;/code&gt;, &lt;code&gt;AudioNode&lt;/code&gt;,
1181
+ and &lt;code&gt;JavaScriptAudioNode&lt;/code&gt;.\n&lt;p/&gt;\nThis time,
1182
+ I&#39;m going to go take things a little further and build a realtime spectrum
1183
+ analyzer with Web Audio and HTML5 Canvas. The final product plays a remote
1184
+ music file, and displays the frequency spectrum overlaid with a time domain
1185
+ graph.\n&lt;p/&gt;\n\n&lt;div class=&quot;separator&quot; style=&quot;clear:
1186
+ both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpX14OzEMC3DnQ74e2TZUz2RSTwgECiV182MM7scAFYjkJTdyT5xMNYngRGYwOy-FaaGIcnO-geVMsVzpfgcG0smJChWAWouNtkZMxUCWIT7LJdIq7JojMIpsAmfMOD5MMIx0lAg/s1600/Screen+shot+2011-08-20+at+11.47.10+AM.png&quot;
1187
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1188
+ border=&quot;0&quot; height=&quot;234&quot; width=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpX14OzEMC3DnQ74e2TZUz2RSTwgECiV182MM7scAFYjkJTdyT5xMNYngRGYwOy-FaaGIcnO-geVMsVzpfgcG0smJChWAWouNtkZMxUCWIT7LJdIq7JojMIpsAmfMOD5MMIx0lAg/s400/Screen+shot+2011-08-20+at+11.47.10+AM.png&quot;
1189
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\nThe demo is here: &lt;a href=&quot;http://0xfe.muthanna.com/wavebox&quot;&gt;JavaScript
1190
+ Spectrum Analyzer&lt;/a&gt;. The code for the demo is in my &lt;a href=&quot;https://github.com/0xfe/experiments/tree/master/www/wavebox&quot;&gt;GitHub&lt;/a&gt;
1191
+ repository.\n&lt;p/&gt;\n&lt;h3&gt;The New Classes&lt;/h3&gt;\n&lt;p/&gt;\nIn
1192
+ this post we introduce three new Web Audio classes: &lt;code&gt;AudioBuffer&lt;/code&gt;,
1193
+ &lt;code&gt;AudioBufferSourceNode&lt;/code&gt;, and &lt;code&gt;RealtimeAnalyzerNode&lt;/code&gt;.\n&lt;p/&gt;\nAn
1194
+ &lt;code&gt;AudioBuffer&lt;/code&gt; represents an in-memory audio asset.
1195
+ It is usually used to store short audio clips and can contain multiple channels.\n&lt;p/&gt;\nAn
1196
+ &lt;code&gt;AudioBufferSourceNode&lt;/code&gt; is a specialization of &lt;code&gt;AudioNode&lt;/code&gt;
1197
+ that serves audio from &lt;code&gt;AudioBuffer&lt;/code&gt;s.\n&lt;p/&gt;\nA
1198
+ &lt;code&gt;RealtimeAnalyzerNode&lt;/code&gt; is an &lt;code&gt;AudioNode&lt;/code&gt;
1199
+ that returns time- and frequency-domain analysis information in real time.\n\n&lt;p/&gt;\n&lt;h3&gt;The
1200
+ Plumbing&lt;/h3&gt;\n&lt;p/&gt;\n\nTo begin, we need to acquire some audio.
1201
+ The API supports a number of different formats, including MP3 and raw PCM-encoded
1202
+ audio. In our demo, we retrieve a remote audio asset (an MP3 file) using AJAX,
1203
+ and use it to populate a new &lt;code&gt;AudioBuffer&lt;/code&gt;. This is
1204
+ implemented in the &lt;code&gt;RemoteAudioPlayer&lt;/code&gt; class (&lt;a
1205
+ href=&quot;https://github.com/0xfe/experiments/blob/master/www/wavebox/js/remoteaudioplayer.js&quot;&gt;js/remoteaudioplayer.js&lt;/a&gt;)
1206
+ like so:\n\n&lt;pre class=&quot;prettyprint&quot;&gt;\nRemoteAudioPlayer.prototype.load
1207
+ = function(callback) {\n var request = new XMLHttpRequest();\n var that
1208
+ = this;\n request.open(&quot;GET&quot;, this.url, true);\n request.responseType
1209
+ = &quot;arraybuffer&quot;;\n request.onload = function() {\n that.buffer
1210
+ = that.context.createBuffer(request.response, true);\n that.reload();\n
1211
+ \ callback(request.response);\n }\n\n request.send();\n}\n&lt;/pre&gt;\n\nNotice
1212
+ that the &lt;i&gt;jQuery&lt;/i&gt;&#39;s AJAX calls aren&#39;t used here.
1213
+ This is because jQuery does not support the &lt;i&gt;arraybuffer&lt;/i&gt;
1214
+ response type, which is required for loading binary data from the server.
1215
+ The &lt;code&gt;AudioBuffer&lt;/code&gt; is created with the &lt;code&gt;AudioContext&lt;/code&gt;&#39;s
1216
+ &lt;code&gt;createBuffer&lt;/code&gt; function. The second parameter, &lt;code&gt;true&lt;/code&gt;,
1217
+ tells it to mix down all the channels to a single mono channel.\n\n&lt;p/&gt;\nThe
1218
+ &lt;code&gt;AudioBuffer&lt;/code&gt; is then provided to an &lt;code&gt;AudioBufferSourceNode&lt;/code&gt;,
1219
+ which will be the context&#39;s audio source. This source node is then connected
1220
+ to a &lt;code&gt;RealTimeAnalyzerNode&lt;/code&gt;, which in turn is connected
1221
+ to the context&#39;s destination, i.e, the computer&#39;s output device.\n\n&lt;pre
1222
+ class=&quot;prettyprint&quot;&gt;\nvar source_node = context.createBufferSource();\nsource_node.buffer
1223
+ = audio_buffer;\n\nvar analyzer = context.createAnalyser();\nanalyzer.fftSize
1224
+ = 2048; // 2048-point FFT\nsource_node.connect(analyzer);\nanalyzer.connect(context.destination);\n&lt;/pre&gt;\n\nTo
1225
+ start playing the music, call the &lt;code&gt;noteOn&lt;/code&gt; method of
1226
+ the source node. &lt;code&gt;noteOn&lt;/code&gt; takes one parameter: a timestamp
1227
+ indicating when to start playing. If set to &lt;code&gt;0&lt;/code&gt;, it
1228
+ plays immediately. To start playing the music 0.5 seconds from now, you can
1229
+ use &lt;code&gt;context.currentTime&lt;/code&gt; to get the reference point.\n\n&lt;pre
1230
+ class=&quot;prettyprint&quot;&gt;\n// Play music 0.5 seconds from now\nsource_node.noteOn(context.currentTime
1231
+ + 0.5);\n&lt;/pre&gt;\n\nIt&#39;s also worth noting that we specified the
1232
+ granularity of the FFT to 2048 by setting the &lt;code&gt;analyzer.fftSize&lt;/code&gt;
1233
+ variable. For those unfamiliar with DSP theory, this breaks the frequency
1234
+ spectrum of the audio into 2048 points, each point representing the magnitude
1235
+ of the &lt;i&gt;n/2048th&lt;/i&gt; frequency bin.\n\n&lt;p/&gt;\n\n&lt;div
1236
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;\n&lt;a
1237
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_sZlkl6rt9eyhJSC6_Veh4Q5eYipuxoAac_7G56fMnENz4dVXrfYXwQsjKX6ewUoj5GEvnE5JIpAh9iMuhFyR2OJw3AoQAe7vkd7XclMC7Mo0NO_QwSIDaQVmYa2QEnhrjLv5_A/s1600/Screen+shot+2011-08-20+at+11.38.45+AM.png&quot;
1238
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1239
+ border=&quot;0&quot; height=&quot;150&quot; width=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_sZlkl6rt9eyhJSC6_Veh4Q5eYipuxoAac_7G56fMnENz4dVXrfYXwQsjKX6ewUoj5GEvnE5JIpAh9iMuhFyR2OJw3AoQAe7vkd7XclMC7Mo0NO_QwSIDaQVmYa2QEnhrjLv5_A/s400/Screen+shot+2011-08-20+at+11.38.45+AM.png&quot;
1240
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n\n&lt;p/&gt;\n&lt;h3&gt;The Pretty Graphs&lt;/h3&gt;\n&lt;p/&gt;\n\nOkay,
1241
+ it&#39;s now all wired up -- how do I get the pretty graphs? The general strategy
1242
+ is to poll the analyzer every few milliseconds (e.g., with &lt;code&gt;window.setInterval&lt;/code&gt;),
1243
+ request the time- or frequency-domain data, and then render it onto a HTML5
1244
+ Canvas element. The analyzer exports a few different methods to access the
1245
+ analysis data: &lt;code&gt;getFloatFrequencyData&lt;/code&gt;, &lt;code&gt;getByteFrequencyData&lt;/code&gt;,
1246
+ &lt;code&gt;getByteTimeDomainData&lt;/code&gt;. Each of these methods populate
1247
+ a given &lt;code&gt;ArrayBuffer&lt;/code&gt; with the appropriate analysis
1248
+ data.\n\n&lt;p/&gt;\nIn the below snippet, we schedule an &lt;code&gt;update()&lt;/code&gt;
1249
+ function every 50ms, which breaks the frequency-domain data points into 30
1250
+ bins, and renders a bar representing the average magnitude of the points in
1251
+ each bin.\n\n&lt;pre class=&quot;prettyprint&quot;&gt;\ncanvas = document.getElementById(canvas_id);\ncanvas_context
1252
+ = canvas.getContext(&quot;2d&quot;);\n\nfunction update() {\n // This graph
1253
+ has 30 bars.\n var num_bars = 30;\n\n // Get the frequency-domain data\n
1254
+ \ var data = new Uint8Array(2048);\n analyzer.getByteFrequencyData(data);\n\n
1255
+ \ // Clear the canvas\n canvas_context.clearRect(0, 0, this.width, this.height);\n\n
1256
+ \ // Break the samples up into bins\n var bin_size = Math.floor(length /
1257
+ num_bars);\n for (var i=0; i &lt; num_bars; ++i) {\n var sum = 0;\n for
1258
+ (var j=0; j &lt; bin_size; ++j) {\n sum += data[(i * bin_size) + j];\n
1259
+ \ }\n\n // Calculate the average frequency of the samples in the bin\n
1260
+ \ var average = sum / bin_size;\n\n // Draw the bars on the canvas\n
1261
+ \ var bar_width = canvas.width / num_bars;\n var scaled_average = (average
1262
+ / 256) * canvas.height;\n\n canvas_context.fillRect(i * bar_width, canvas.height,
1263
+ bar_width - 2,\n -scaled_average);\n}\n\n// Render
1264
+ every 50ms\nwindow.setInterval(update, 50);\n\n// Start the music\nsource_node.noteOn(0);\n&lt;/pre&gt;\n\nA
1265
+ similar strategy can be employed for time-domain data, except for a few minor
1266
+ differences: Time-domain data is usually rendered as waves, so you might want
1267
+ to use lot more bins and plot pixels instead of drawing bars. The code that
1268
+ renders the time and frequency domain graphs in the demo is encapsulated in
1269
+ the &lt;code&gt;SpectrumBox&lt;/code&gt; class in &lt;a href=&quot;https://github.com/0xfe/experiments/blob/master/www/wavebox/js/spectrum.js&quot;&gt;js/spectrum.js&lt;/a&gt;.\n\n&lt;p/&gt;\n&lt;h3&gt;The
1270
+ Minutiae&lt;/h3&gt;\n&lt;p/&gt;\nI glossed over a number of things in this
1271
+ post, mostly with respect to the details of the demo. You can learn it all
1272
+ from the source code, but here&#39;s a summary for the impatient: \n&lt;p/&gt;\nThe
1273
+ graphs are actually two HTML5 Canvas elements overlaid using CSS absolute
1274
+ positioning. Each element is used by its own &lt;code&gt;SpectrumBox&lt;/code&gt;
1275
+ class, one which displays the frequency spectrum, the other which displays
1276
+ the time-domain wave. \n&lt;p/&gt;\nThe routing of the nodes is done in the
1277
+ &lt;code&gt;onclick&lt;/code&gt; handler to the &lt;code&gt;#play&lt;/code&gt;
1278
+ button -- it takes the &lt;code&gt;AudioSourceNode&lt;/code&gt; from the &lt;code&gt;RemoteAudioPlayer&lt;/code&gt;,
1279
+ routes it to node of the frequency analyzer, routes &lt;i&gt;that&lt;/i&gt;
1280
+ to the node of the time-domain analyzer, and then finally to the destination.\n&lt;p/&gt;\n&lt;h3&gt;Bonus:
1281
+ Another Demo&lt;/h3&gt;\n&lt;p/&gt;\n\nThat&#39;s all folks! You now have
1282
+ the knowhow to build yourself a fancy new graphical spectrum analyzer. If
1283
+ all you want to do is play with the waves and stare at the graphs, check out
1284
+ my other demo: The &lt;a href=&quot;http://0xfe.muthanna.com/analyzer&quot;&gt;Web
1285
+ Audio Tone Analyzer&lt;/code&gt; (&lt;a href=&quot;https://github.com/0xfe/experiments/tree/master/www/analyzer&quot;&gt;source&lt;/a&gt;).
1286
+ This is really just the same spectrum analyzer from the first demo, connected
1287
+ to the tone generator from the &lt;a href=&quot;https://github.com/0xfe/experiments/tree/master/www/wavebox&quot;&gt;last
1288
+ post&lt;/a&gt;.\n\n&lt;p/&gt;\n&lt;div class=&quot;separator&quot; style=&quot;clear:
1289
+ both; text-align: center;&quot;&gt;\n&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBgJGcbHueNFzV57A0hNmHy5pgk8CujXroKhFGrDgfpHQhVqkpcJbA3n11VSR-BJw6voEuyJubfEoUQXhyeisdk4h9Mwgap66UU4ivhRiJY_mKPESde1aquV7NsQt1gcbsY5ALdg/s1600/Screen+shot+2011-08-20+at+10.13.15+AM.png&quot;
1290
+ imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img
1291
+ border=&quot;0&quot; height=&quot;127&quot; width=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBgJGcbHueNFzV57A0hNmHy5pgk8CujXroKhFGrDgfpHQhVqkpcJbA3n11VSR-BJw6voEuyJubfEoUQXhyeisdk4h9Mwgap66UU4ivhRiJY_mKPESde1aquV7NsQt1gcbsY5ALdg/s320/Screen+shot+2011-08-20+at+10.13.15+AM.png&quot;
1292
+ /&gt;&lt;/a&gt;&lt;/div&gt;\n\n&lt;p/&gt;\n\n&lt;h3&gt;References&lt;/h3&gt;\n&lt;/p&gt;\n\nAs
1293
+ a reminder, all the code for my posts is available at my GitHub repository:
1294
+ &lt;a href=&quot;http://github.com/0xfe&quot;&gt;github.com/0xfe&lt;/a&gt;.\n\n&lt;p/&gt;\nThe
1295
+ audio track used in the demo is a discarded take of &lt;a href=&quot;http://captainstarr.bandcamp.com/track/who-da-man&quot;&gt;Who-Da-Man&lt;/a&gt;,
1296
+ which I recorded with my previous band &lt;a href=&quot;http://captainstarr.bandcamp.com/album/ep&quot;&gt;Captain
1297
+ Starr&lt;/a&gt; many many years ago.\n&lt;p/&gt;\nFinally, don&#39;t forget
1298
+ to read the &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html&quot;&gt;Web
1299
+ Audio API&lt;/a&gt; draft specification for more information.\n&lt;p/&gt;\nEnjoy!</content><link
1300
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/7852661428465635210/comments/default'
1301
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2011/08/web-audio-spectrum-analyzer.html#comment-form'
1302
+ title='9 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/7852661428465635210'/><link
1303
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/7852661428465635210'/><link
1304
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2011/08/web-audio-spectrum-analyzer.html'
1305
+ title='A Web Audio Spectrum Analyzer'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1306
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1307
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpX14OzEMC3DnQ74e2TZUz2RSTwgECiV182MM7scAFYjkJTdyT5xMNYngRGYwOy-FaaGIcnO-geVMsVzpfgcG0smJChWAWouNtkZMxUCWIT7LJdIq7JojMIpsAmfMOD5MMIx0lAg/s72-c/Screen+shot+2011-08-20+at+11.47.10+AM.png\"
1308
+ height=\"72\" width=\"72\"/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6394151762251429282</id><published>2011-08-13T20:58:00.005-04:00</published><updated>2011-08-22T07:12:12.828-04:00</updated><title
1309
+ type='text'>Generating Tones with the Web Audio API</title><content type='html'>The
1310
+ Web Audio API is a W3C draft standard interface for building in-browser audio
1311
+ applications. Although the draft is well specified, it is almost impossible
1312
+ to find useful documentation on building applications with it. &lt;p/&gt;In
1313
+ my quest to deeper understand HTML5 audio, I spent some time figuring out
1314
+ how the API works, and decided to write up this quick tutorial on doing useful
1315
+ things with it. &lt;p/&gt;We will build a sine wave tone-generator entirely
1316
+ in JavaScript. The final product looks like this: &lt;a href=&quot;http://0xfe.muthanna.com/tone&quot;&gt;Web
1317
+ Audio Tone Generator&lt;/a&gt;. &lt;p/&gt;The full code is available in my
1318
+ GitHub repository: &lt;a href=&quot;https://github.com/0xfe/experiments/tree/master/www/tone&quot;&gt;https://github.com/0xfe/experiments/tree/master/www/tone&lt;/a&gt;
1319
+ &lt;p/&gt;&lt;h3&gt;\nCaveats&lt;/h3&gt;\n&lt;p/&gt;The Web Audio API is draft,
1320
+ is likely to change, and does not work on all browsers. Right now, only the
1321
+ latest versions of Chrome and Safari support it. &lt;p/&gt;&lt;h3&gt;\nOnwards
1322
+ We Go&lt;/h3&gt;\n&lt;p/&gt;Getting started making sounds with the Web Audio
1323
+ API is straightforward so long as you take the time to study the plumbing,
1324
+ most of which exists to allow for real-time audio processing and synthesis.
1325
+ The complete specification is available on the &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html&quot;&gt;W3C
1326
+ Web Audio API&lt;/a&gt; page, and I&#39;d strongly recommend that you read
1327
+ it thoroughly if you&#39;re interested in building advanced applications with
1328
+ the API. &lt;p/&gt;To produce any form of sound, you need an &lt;code&gt;AudioContext&lt;/code&gt;
1329
+ and a few &lt;code&gt;AudioNode&lt;/code&gt;s. The &lt;code&gt;AudioContext&lt;/code&gt;
1330
+ is sort of like an environment for audio processing -- it&#39;s where various
1331
+ attributes such as the sample rate, the clock status, and other environment-global
1332
+ state reside. Most applications will need no more than a single instance of
1333
+ &lt;code&gt;AudioContext&lt;/code&gt;. &lt;p/&gt;The &lt;code&gt;AudioNode&lt;/code&gt;
1334
+ is probably the most important component in the API, and is responsible for
1335
+ synthesizing or processing audio. An &lt;code&gt;AudioNode&lt;/code&gt; instance
1336
+ can be an input source, an output destination, or a mid-stream processor.
1337
+ These nodes can be linked together to form processing pipelines to render
1338
+ a complete audio stream. &lt;p/&gt;One kind of &lt;code&gt;AudioNode&lt;/code&gt;
1339
+ is &lt;code&gt;JavaScriptAudioNode&lt;/code&gt;, which is used to generate
1340
+ sounds in JavaScript. This is what what we will use in this tutorial to build
1341
+ a tone generator. &lt;p/&gt;Let us begin by instantiating an &lt;code&gt;AudioContext&lt;/code&gt;
1342
+ and creating a &lt;code&gt;JavaScriptAudioNode&lt;/code&gt;. &lt;pre class=&quot;prettyprint&quot;&gt;var
1343
+ context = new webkitAudioContext();\nvar node = context.createJavaScriptNode(1024,
1344
+ 1, 1);\n&lt;/pre&gt;\nThe parameters to &lt;code&gt;createJavaScriptNode&lt;/code&gt;
1345
+ refer to the buffer size, the number of input channels, and the number of
1346
+ output channels. The buffer size must be in units of sample frames, i.e.,
1347
+ one of: 256, 512, 1024, 2048, 4096, 8192, or 16384. It controls the frequency
1348
+ of callbacks asking for a buffer refill. Smaller sizes allow for lower latency
1349
+ and higher for better overall quality. &lt;p/&gt;We&#39;re going to use the
1350
+ &lt;code&gt;JavaScriptNode&lt;/code&gt; as the source node along with a bit
1351
+ of code to create sine waves. To actually &lt;i&gt;hear&lt;/i&gt;&amp;nbsp;anything,
1352
+ it must be connected to an output node. It turns out that &lt;code&gt;context.destination&lt;/code&gt;
1353
+ gives us just that -- a node that maps to the speaker on your machine. &lt;p/&gt;&lt;h3&gt;\nThe
1354
+ SineWave Class&lt;/h3&gt;\n&lt;p/&gt;To start off our tone generator, we create
1355
+ a &lt;code&gt;SineWave&lt;/code&gt; class, which wraps the &lt;code&gt;AudioNode&lt;/code&gt;
1356
+ and wave generation logic into one cohesive package. This class will be responsible
1357
+ for creating the &lt;code&gt;JavaScriptNode&lt;/code&gt; instances, generating
1358
+ the sine waves, and managing the connection to the destination node. &lt;pre
1359
+ class=&quot;prettyprint&quot;&gt;SineWave = function(context) {\n var that
1360
+ = this;\n this.x = 0; // Initial sample number\n this.context = context;\n
1361
+ \ this.node = context.createJavaScriptNode(1024, 1, 1);\n this.node.onaudioprocess
1362
+ = function(e) { that.process(e) };\n}\n\nSineWave.prototype.process = function(e)
1363
+ {\n var data = e.outputBuffer.getChannelData(0);\n for (var i = 0; i &amp;lt;
1364
+ data.length; ++i) {\n data[i] = Math.sin(this.x++);\n }\n}\n\nSineWave.prototype.play
1365
+ = function() {\n this.node.connect(this.context.destination);\n}\n\nSineWave.prototype.pause
1366
+ = function() {\n this.node.disconnect();\n}\n&lt;/pre&gt;\nUpon instantiation,
1367
+ this class creates a &lt;code&gt;JavaScriptAudioNode&lt;/code&gt; and attaches
1368
+ an event handler to &lt;code&gt;onaudioprocess&lt;/code&gt; for buffer refills.
1369
+ The event handler requests a reference to the output buffer for the first
1370
+ channel, and fills it with a sine wave. Notice that the handler does not know
1371
+ the buffer size in advance, and gets it from &lt;code&gt;data.length&lt;/code&gt;.
1372
+ &lt;p/&gt;The buffer is of type &lt;code&gt;ArrayBuffer&lt;/code&gt; which
1373
+ is a JavaScript Typed Array. These arrays allow for high throughput processing
1374
+ of raw binary data. To learn more about Typed Arrays, check out the &lt;a
1375
+ href=&quot;https://developer.mozilla.org/en/javascript_typed_arrays&quot;&gt;Mozilla
1376
+ Developer Documentation on Typed Arrays&lt;/a&gt;. &lt;p/&gt;To try out a
1377
+ quick demo of the &lt;code&gt;SineWave&lt;/code&gt; class, add the following
1378
+ code to the &lt;code&gt;onload&lt;/code&gt; handler for your page: &lt;pre
1379
+ class=&quot;prettyprint&quot;&gt;var context = new webkitAudioContext();\nvar
1380
+ sinewave = new SineWave(context);\nsinewave.play();\n&lt;/pre&gt;\n&lt;div&gt;\nNotice
1381
+ that &lt;code&gt;sinewave.play()&lt;/code&gt; works by wiring up the node
1382
+ to the &lt;code&gt;AudioContext&lt;/code&gt;&#39;s destination (the speakers).
1383
+ To stop the tone, call &lt;code&gt;sinewave.pause()&lt;/code&gt;, which unplugs
1384
+ this connection.&lt;/div&gt;\n&lt;p/&gt;&lt;div&gt;\n&lt;h3&gt;\nGenerating
1385
+ Specific Tones&lt;/h3&gt;\n&lt;/div&gt;\n&lt;p/&gt; &lt;div&gt;\nSo, now you
1386
+ have yourself a tone. Are we done yet?&lt;/div&gt;\n\n&lt;p/&gt;\n&lt;div&gt;\nNot
1387
+ quite. How does one know what the frequency of the generated wave is? How
1388
+ does one generate tones of arbitrary frequencies?&lt;/div&gt;\n&lt;p/&gt;\n&lt;div&gt;\nTo
1389
+ answer these questions, we must find out the sample rate of the audio. Each
1390
+ data value we stuff into the buffer in out handler is a sample, and the sample
1391
+ rate is the number of samples processed per second. We can calculate the frequency
1392
+ of the tone by dividing the sample rate by the length of a full wave cycle.&lt;/div&gt;\n&lt;p/&gt;\n&lt;div&gt;\nHow
1393
+ do we get the sample rate? Via the &lt;code&gt;getSampleRate()&lt;/code&gt;
1394
+ method of &lt;code&gt;AudioContext&lt;/code&gt;. On my machine, the default
1395
+ sample rate is 44KHz, i.e., 44100 samples per second. This means that the
1396
+ frequency of the generated tone in our above code is:&lt;/div&gt;\n&lt;pre
1397
+ class=&quot;prettyprint&quot;&gt;freq = context.getSampleRate() / 2 * Math.PI\n&lt;/pre&gt;\n&lt;div&gt;\nThat&#39;s
1398
+ about 7KHz. Ouch! Lets use our newfound knowledge to generate less spine-curdling
1399
+ tones. To generate a tone of a specific frequency, you can change &lt;code&gt;SineWave.process&lt;/code&gt;
1400
+ to:&lt;/div&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;SineWave.prototype.process
1401
+ = function(e) {\n var data = e.outputBuffer.getChannelData(0);\n for (var
1402
+ i = 0; i &amp;lt; data.length; ++i) {\n data[i] = Math.sin(this.x++ / (this.sample_rate
1403
+ / 2 * Math.PI * this.frequency));\n }\n}\n&lt;/pre&gt;\nAlso make sure you
1404
+ add the following two lines to &lt;code&gt;SineWave&lt;/code&gt;&#39;s constructor:
1405
+ \ &lt;pre class=&quot;prettyprint&quot;&gt;this.sample_rate = this.context.getSampleRate();\nthis.frequency
1406
+ = 440;\n&lt;/pre&gt;\nThis initializes the frequency to &lt;i&gt;pitch standard
1407
+ A440&lt;/i&gt;, i.e., the A above &lt;i&gt;middle C.&lt;/i&gt; &lt;p/&gt;&lt;h3&gt;\nThe
1408
+ Theremin Effect&lt;/h3&gt;\n&lt;p/&gt;Now that we can generate tones of arbitrary
1409
+ frequencies, it&#39;s only natural that we connect our class to some sort
1410
+ of slider widget so we can experience the entire spectrum right in our browsers.
1411
+ Turns out that &lt;a href=&quot;http://jqueryui.com/&quot;&gt;JQueryUI&lt;/a&gt;
1412
+ already has such a &lt;a href=&quot;http://jqueryui.com/demos/slider/&quot;&gt;slider&lt;/a&gt;,
1413
+ leaving us only a little plumbing to do. &lt;p/&gt;We add a setter function
1414
+ to our &lt;code&gt;SineWave&lt;/code&gt; class, and call it from our slider
1415
+ widget&#39;s change handler. &lt;pre class=&quot;prettyprint&quot;&gt;SineWave.prototype.setFrequency
1416
+ = function(freq) {\n this.next_frequency = freq;\n}\n&lt;/pre&gt;\nA JQueryUI
1417
+ snippet would look like this: &lt;pre class=&quot;prettyprint&quot;&gt;$(&quot;#slider&quot;).slider({\n
1418
+ \ value: 440,\n min: 1,\n max: 2048,\n slide: function(event, ui)
1419
+ { sinewave.setFrequency(ui.value); }\n});\n&lt;/pre&gt;\n&lt;h3&gt;\nGoing
1420
+ up to Eleven&lt;/h3&gt;\n&lt;p/&gt;Adding support for volume is straightforward.
1421
+ Add an amplitude member to the &lt;code&gt;SineWave&lt;/code&gt; constructor
1422
+ along with a setter method, just like we did for frequency, and change &lt;code&gt;SineWave.process&lt;/code&gt;
1423
+ to: &lt;pre class=&quot;prettyprint&quot;&gt;SineWave.prototype.process =
1424
+ function(e) {\n var data = e.outputBuffer.getChannelData(0);\n for (var
1425
+ i = 0; i &amp;lt; data.length; ++i) {\n data[i] = this.amplitude * Math.sin(this.x++
1426
+ / (this.sample_rate / 2 * Math.PI * this.frequency));\n }\n}\n&lt;/pre&gt;\nFolks,
1427
+ we now have a full fledged sine wave generator! &lt;p/&gt;&lt;h3&gt;\nBoo
1428
+ Hiss Crackle&lt;/h3&gt;\n&lt;p/&gt;But, we&#39;re not done yet. You&#39;ve
1429
+ probably noticed that changing the frequency causes mildly annoying crackling
1430
+ sounds. This happens because when the frequency changes, discontinuity occurs
1431
+ in the wave, causing a high-frequency &lt;i&gt;pop&lt;/i&gt;&amp;nbsp;in the
1432
+ audio stream. &lt;p/&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot;
1433
+ cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left:
1434
+ auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1435
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhalpMImRBOQHBmOscmOwmDAlyeLBL3bJ-unGATwmWuxUWy1OggewBKAyp4ckF67vY4RODDhgK1J0inVUaE0gcE4SG9s-bbQE7kOm5sdtIcTjvHi7jl99iV4XPXhnv-QqqehGBKPQ/s1600/Screen+shot+2011-08-13+at+8.45.06+PM.png&quot;
1436
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1437
+ border=&quot;0&quot; height=&quot;137&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhalpMImRBOQHBmOscmOwmDAlyeLBL3bJ-unGATwmWuxUWy1OggewBKAyp4ckF67vY4RODDhgK1J0inVUaE0gcE4SG9s-bbQE7kOm5sdtIcTjvHi7jl99iV4XPXhnv-QqqehGBKPQ/s320/Screen+shot+2011-08-13+at+8.45.06+PM.png&quot;
1438
+ width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
1439
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Discontinuity
1440
+ when Changing Frequencies&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;\n&lt;p/&gt;We
1441
+ try to eliminate the discontinuity by only shifting frequencies when the cycle
1442
+ of the previous frequency completes, i.e., the sample value is (approximately)
1443
+ zero. (There are better ways to do this, e.g., windowing, LPFs, etc., but
1444
+ these techniques are out of the scope of this tutorial.) &lt;p/&gt;Although
1445
+ this complicates the code a little bit, waiting for the cycle to end significantly
1446
+ reduces the noise upon frequency shifts. &lt;pre class=&quot;prettyprint&quot;&gt;SineWave.prototype.setFrequency
1447
+ = function(freq) {\n this.next_frequency = freq;\n}\n\nSineWave.prototype.process
1448
+ = function(e) {\n // Get a reference to the output buffer and fill it up.\n
1449
+ \ var data = e.outputBuffer.getChannelData(0);\n\n // We need to be careful
1450
+ about filling up the entire buffer and not\n // overflowing.\n for (var
1451
+ i = 0; i &amp;lt; data.length; ++i) {\n data[i] = this.amplitude * Math.sin(\n
1452
+ \ this.x++ / (this.sampleRate / (this.frequency * 2 * Math.PI)));\n\n
1453
+ \ // This reduces high-frequency blips while switching frequencies. It works\n
1454
+ \ // by waiting for the sine wave to hit 0 (on it&#39;s way to positive
1455
+ territory)\n // before switching frequencies.\n if (this.next_frequency
1456
+ != this.frequency) {\n // Figure out what the next point is.\n next_data
1457
+ = this.amplitude * Math.sin(\n this.x / (this.sampleRate / (this.frequency
1458
+ * 2 * Math.PI)));\n\n // If the current point approximates 0, and the
1459
+ direction is positive,\n // switch frequencies.\n if (data[i] &amp;lt;
1460
+ 0.001 &amp;amp;&amp;amp; data[i] &amp;gt; -0.001 &amp;amp;&amp;amp; data[i]
1461
+ &amp;lt; next_data) {\n this.frequency = this.next_frequency;\n this.x
1462
+ = 0;\n }\n }\n }\n}\n&lt;/pre&gt;\n&lt;p/&gt;&lt;div&gt;\n&lt;h3&gt;\nThe
1463
+ End&lt;/h3&gt;\n&lt;/div&gt;\n&lt;div&gt;\n&lt;p/&gt;&lt;/div&gt;\n&lt;div&gt;\nAs
1464
+ mentioned in the beginning of this tutorial, a demo of the full code is available
1465
+ at&amp;nbsp;&lt;a href=&quot;http://0xfe.muthanna.com/tone/&quot;&gt;http://0xfe.muthanna.com/tone/&lt;/a&gt;&amp;nbsp;and
1466
+ the entire source code is available at&amp;nbsp;&lt;a href=&quot;https://github.com/0xfe/experiments/tree/master/www/tone&quot;&gt;https://github.com/0xfe/experiments/tree/master/www/tone&lt;/a&gt;.&lt;/div&gt;\n&lt;p/&gt;
1467
+ &lt;div&gt;\nDo check out the &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html&quot;&gt;W3C
1468
+ Web Audio API&lt;/a&gt; specification. Do check out the &lt;a href=&quot;https://developer.mozilla.org/en/javascript_typed_arrays&quot;&gt;Mozilla
1469
+ document on JavaScript Typed Arrays&lt;/a&gt;.&lt;/div&gt;\n&lt;p/&gt; &lt;div&gt;\nComments,
1470
+ criticism, and error reports welcome. Enjoy!&lt;/div&gt;\n</content><link
1471
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/6394151762251429282/comments/default'
1472
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2011/08/generating-tones-with-web-audio-api.html#comment-form'
1473
+ title='11 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6394151762251429282'/><link
1474
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6394151762251429282'/><link
1475
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2011/08/generating-tones-with-web-audio-api.html'
1476
+ title='Generating Tones with the Web Audio API'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1477
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1478
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhalpMImRBOQHBmOscmOwmDAlyeLBL3bJ-unGATwmWuxUWy1OggewBKAyp4ckF67vY4RODDhgK1J0inVUaE0gcE4SG9s-bbQE7kOm5sdtIcTjvHi7jl99iV4XPXhnv-QqqehGBKPQ/s72-c/Screen+shot+2011-08-13+at+8.45.06+PM.png\"
1479
+ height=\"72\" width=\"72\"/><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1841038669655438377</id><published>2011-03-31T13:50:00.000-04:00</published><updated>2011-03-31T13:50:44.471-04:00</updated><title
1480
+ type='text'>A Music Theory API</title><content type='html'>Most of my work
1481
+ last week consisted of writing music theory code. &lt;a href=&quot;http://vexflow.com/&quot;&gt;VexFlow&lt;/a&gt;
1482
+ now has a neat little music theory API, that gives you answers to questions
1483
+ like the following:&lt;br /&gt;\n&lt;br /&gt;\n&lt;ul&gt;&lt;li&gt;What note
1484
+ is a minor 3rd above a B?&lt;/li&gt;\n&lt;li&gt;What are the scale tones of
1485
+ a Gb Harmonic Minor?&lt;/li&gt;\n&lt;li&gt;What relation is the C# note to
1486
+ an A Major scale? (Major 3rd)&lt;/li&gt;\n&lt;li&gt;What accidentals should
1487
+ be displayed for the perfect 4th note of a G Major scale?&lt;/li&gt;\n&lt;li&gt;etc.&lt;/li&gt;\n&lt;/ul&gt;&lt;br
1488
+ /&gt;\nThe API is part of VexFlow, and can be used independently of the rendering
1489
+ API. Take a look at &lt;a href=&quot;https://github.com/0xfe/vexflow/blob/master/src/music.js&quot;&gt;music.js&lt;/a&gt;
1490
+ in the &lt;a href=&quot;http://github.com/0xfe/vexflow&quot;&gt;VexFlow GitHub
1491
+ repository&lt;/a&gt; for the complete reference. There&#39;s also a handy
1492
+ key management library for building scores in &lt;a href=&quot;https://github.com/0xfe/vexflow/blob/master/src/keymanager.js&quot;&gt;keymanager.js&lt;/a&gt;.&lt;br
1493
+ /&gt;\n&lt;br /&gt;\nI&#39;m currently working on updating the&amp;nbsp;&lt;a
1494
+ href=&quot;http://vexflow.com/docs/tutorial.html&quot;&gt;VexFlow Tutorial&lt;/a&gt;&amp;nbsp;with
1495
+ a quickstart on the music theory API, but meanwhile, here are some teasers
1496
+ (pulled straight out of the&amp;nbsp;&lt;a href=&quot;https://github.com/0xfe/vexflow/blob/master/tests/music_tests.js&quot;&gt;tests&lt;/a&gt;).&lt;br
1497
+ /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;// What does
1498
+ C note consist of?\nvar parts = music.getNoteParts(&quot;c&quot;);\nequals(parts.root,
1499
+ &quot;c&quot;);\nequals(parts.accidental, null);\n\n// What does C# note consist
1500
+ of?\nvar parts = music.getNoteParts(&quot;c#&quot;);\nequals(parts.root, &quot;c&quot;);\nequals(parts.accidental,
1501
+ &quot;#&quot;);\n\n// What is a flat-5th above C?\nvar value = music.getRelativeNoteValue(music.getNoteValue(&quot;c&quot;),\n
1502
+ \ music.getIntervalValue(&quot;b5&quot;));\nequals(value,
1503
+ music.getNoteValue(&quot;gb&quot;);\nequals(value, music.getNoteValue(&quot;f#&quot;);\n\n//
1504
+ What is the C quality of a Db?\nequals(music.getRelativeNoteName(&quot;c&quot;,
1505
+ music.getNoteValue(&quot;db&quot;)), &quot;c#&quot;);\n\n// What are the tones
1506
+ of a C major scale?\nvar c_major = music.getScaleTones(\n music.getNoteValue(&quot;c&quot;),
1507
+ Vex.Flow.Music.scales.major);\n// result: [&quot;c&quot;, &quot;d&quot;, &quot;e&quot;,
1508
+ &quot;f&quot;, &quot;g&quot;, &quot;a&quot;, &quot;b&quot;]\n\n// What is
1509
+ the interval between a C and a D?\nequals(music.getCanonicalIntervalName(music.getIntervalBetween(\n
1510
+ \ music.getNoteValue(&quot;c&quot;), music.getNoteValue(&quot;d&quot;))),
1511
+ &quot;M2&quot;);\n&lt;/pre&gt;&lt;br /&gt;\n&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot;
1512
+ style=&quot;line-height: 16px;&quot;&gt;&lt;br /&gt;\n&lt;/span&gt;&lt;/div&gt;Thanks
1513
+ to the theory support, we now have smarter Accidentals in the standard notation
1514
+ stave that VexTab generates.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
1515
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
1516
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1517
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN1ilwcTQy81IgS1-R_nl7tp4oG0agi_Q3jvBZvwvFH3qq3Ktm7ikP87TY-S_YqrZx8cfd6-I21BeYfkqe2cidfztdCK1YLOC4Kb_z0OX2oKsVgdOlDsobtYqiSvBEc-9_lGAp4A/s1600/Screen+shot+2011-03-31+at+1.03.07+PM.png&quot;
1518
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1519
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN1ilwcTQy81IgS1-R_nl7tp4oG0agi_Q3jvBZvwvFH3qq3Ktm7ikP87TY-S_YqrZx8cfd6-I21BeYfkqe2cidfztdCK1YLOC4Kb_z0OX2oKsVgdOlDsobtYqiSvBEc-9_lGAp4A/s1600/Screen+shot+2011-03-31+at+1.03.07+PM.png&quot;
1520
+ /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td class=&quot;tr-caption&quot;
1521
+ style=&quot;text-align: center;&quot;&gt;Smarter Accidentals&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
1522
+ /&gt;\nNotice how the accidentals are correctly picked according to the rules
1523
+ of standard notation? Yep, so do I.&lt;br /&gt;\n&lt;br /&gt;\nWe also have
1524
+ lots more tests -- over 750 of them! &lt;a href=&quot;http://vexflow.com/tests&quot;&gt;Try
1525
+ running them on your browser&lt;/a&gt; and tell me how long it takes.&lt;br
1526
+ /&gt;\n&lt;br /&gt;\nThat&#39;s all folks!</content><link rel='replies' type='application/atom+xml'
1527
+ href='https://0xfe.blogspot.com/feeds/1841038669655438377/comments/default'
1528
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2011/03/music-theory-api.html#comment-form'
1529
+ title='19 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1841038669655438377'/><link
1530
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1841038669655438377'/><link
1531
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2011/03/music-theory-api.html'
1532
+ title='A Music Theory API'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1533
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1534
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN1ilwcTQy81IgS1-R_nl7tp4oG0agi_Q3jvBZvwvFH3qq3Ktm7ikP87TY-S_YqrZx8cfd6-I21BeYfkqe2cidfztdCK1YLOC4Kb_z0OX2oKsVgdOlDsobtYqiSvBEc-9_lGAp4A/s72-c/Screen+shot+2011-03-31+at+1.03.07+PM.png\"
1535
+ height=\"72\" width=\"72\"/><thr:total>19</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6538091093974991528</id><published>2011-03-27T14:48:00.000-04:00</published><updated>2011-03-27T14:48:07.944-04:00</updated><title
1536
+ type='text'>Prettier Tablature</title><content type='html'>Spot the difference:&lt;br
1537
+ /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot;
1538
+ cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left:
1539
+ auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1540
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvDxctz41t8lkakd87vv6buAIPudM5HtxgjG12vGWh6yZ3Qe3keTOSaVtvJHYwxOHmqpm911ybd8t4jW-A4SFn6MzBOdCOaK1ZsOA29iPPif-UudHxuIpN5WJFLUfb_b9WIILBtA/s1600/Screen+shot+2011-03-27+at+2.34.14+PM.png&quot;
1541
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1542
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvDxctz41t8lkakd87vv6buAIPudM5HtxgjG12vGWh6yZ3Qe3keTOSaVtvJHYwxOHmqpm911ybd8t4jW-A4SFn6MzBOdCOaK1ZsOA29iPPif-UudHxuIpN5WJFLUfb_b9WIILBtA/s1600/Screen+shot+2011-03-27+at+2.34.14+PM.png&quot;
1543
+ /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td class=&quot;tr-caption&quot;
1544
+ style=&quot;text-align: center;&quot;&gt;Before&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
1545
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
1546
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
1547
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
1548
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3EiDHCEJfygzSLmLpnBqYC8yjLl4LMjkrlNFDhqQi5HO6TOpSUrOsOi2I3u1BRrNfVeGg8cz-NMIsdednVAHu-lxEA5yRwunWuoE06EbEyeNXYy_VBRqrw3gvJ2GOT7PHvn8rkA/s1600/Screen+shot+2011-03-27+at+2.32.23+PM.png&quot;
1549
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1550
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3EiDHCEJfygzSLmLpnBqYC8yjLl4LMjkrlNFDhqQi5HO6TOpSUrOsOi2I3u1BRrNfVeGg8cz-NMIsdednVAHu-lxEA5yRwunWuoE06EbEyeNXYy_VBRqrw3gvJ2GOT7PHvn8rkA/s1600/Screen+shot+2011-03-27+at+2.32.23+PM.png&quot;
1551
+ /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td class=&quot;tr-caption&quot;
1552
+ style=&quot;text-align: center;&quot;&gt;After&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;Still
1553
+ can&#39;t tell? Let me help you out. We have:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Slightly
1554
+ greater spacing between the tablature stave lines. This makes it more consistent
1555
+ in appearance with printed tablature.&lt;/li&gt;\n&lt;li&gt;Stave lines are
1556
+ cleared before fret numbers are rendered, vastly improving readability.&lt;/li&gt;\n&lt;li&gt;Font
1557
+ sizes for fret numbers and annotations are bigger.&lt;/li&gt;\n&lt;li&gt;Associated
1558
+ notation and tablature staves are connected with a vertical bar on the left.&lt;/li&gt;\n&lt;li&gt;Micro-changes
1559
+ in spacing between fret numbers, effects, annotations, etc.&lt;/li&gt;\n&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;All
1560
+ these changes have been incorporated into &lt;a href=&quot;http://vexflow.com/tabdiv&quot;&gt;TabDiv&lt;/a&gt;,
1561
+ and pushed to &lt;a href=&quot;http://github.com/0xfe/vexflow&quot;&gt;GitHub&lt;/a&gt;.
1562
+ See more on the &lt;a href=&quot;http://vexflow.com/vextab/tutorial.html&quot;&gt;VexTab
1563
+ Tutorial&lt;/a&gt; page. Enjoy!&lt;/div&gt;</content><link rel='replies' type='application/atom+xml'
1564
+ href='https://0xfe.blogspot.com/feeds/6538091093974991528/comments/default'
1565
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2011/03/prettier-tablature.html#comment-form'
1566
+ title='1 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6538091093974991528'/><link
1567
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6538091093974991528'/><link
1568
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2011/03/prettier-tablature.html'
1569
+ title='Prettier Tablature'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1570
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1571
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvDxctz41t8lkakd87vv6buAIPudM5HtxgjG12vGWh6yZ3Qe3keTOSaVtvJHYwxOHmqpm911ybd8t4jW-A4SFn6MzBOdCOaK1ZsOA29iPPif-UudHxuIpN5WJFLUfb_b9WIILBtA/s72-c/Screen+shot+2011-03-27+at+2.34.14+PM.png\"
1572
+ height=\"72\" width=\"72\"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1929109118059060998</id><published>2011-03-24T16:44:00.000-04:00</published><updated>2011-03-24T16:44:47.737-04:00</updated><category
1573
+ scheme=\"http://www.blogger.com/atom/ns#\" term=\"vexflow\"/><title type='text'>The
1574
+ VexFlow Tutorial (...and other goodies)</title><content type='html'>Finally...
1575
+ finally... finally... we have the humble beginnings of what could be considered
1576
+ &quot;documentation&quot;.&lt;br /&gt;\n&lt;br /&gt;\nI present to you &lt;a
1577
+ href=&quot;http://vexflow.com/docs/tutorial.html&quot;&gt;The VexFlow Tutorial&lt;/a&gt;.&lt;br
1578
+ /&gt;\n&lt;br /&gt;\nAlthough still in its infancy, the tutorial covers everything
1579
+ you need to &lt;i&gt;start&lt;/i&gt; using VexFlow in your own code. My plan
1580
+ for the next few weeks is to make this document as comprehensive as possible,
1581
+ and write up a separate API reference.&lt;br /&gt;\n&lt;br /&gt;\nI hope that
1582
+ this tutorial will help developers understand VexFlow better, and enable them
1583
+ to build new and interesting libraries, parsers, and applications.&lt;br /&gt;\n&lt;br
1584
+ /&gt;\nThe entire tutorial is stored in the &lt;a href=&quot;https://github.com/0xfe/vexflow/blob/master/docs/tutorial.html&quot;&gt;Git
1585
+ repo&lt;/a&gt;; feel free to send me your corrections or other updates.&lt;br
1586
+ /&gt;\n&lt;br /&gt;\nAbout time, I know.&lt;br /&gt;\n&lt;br /&gt;\nIn other
1587
+ news, we have had a few contributions to both VexFlow and VexTab. A big thanks
1588
+ to &lt;a href=&quot;https://github.com/airfrog&quot;&gt;airfrog&lt;/a&gt;,
1589
+ &lt;a href=&quot;https://github.com/wiseleyb&quot;&gt;wiseleyb&lt;/a&gt;,
1590
+ and &lt;a href=&quot;https://github.com/adamf&quot;&gt;adamf&lt;/a&gt; for
1591
+ getting these done.&lt;br /&gt;\n&lt;br /&gt;\nFirst, we have the ability
1592
+ to render dotted notes.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
1593
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
1594
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1595
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTJvi0zGz6LnMaUlGrYTQQCtHK5qDwop451cOBsWGXLwSaSUGrHs6UpuBK95zj-fRdph9w6ez4pzLuQIMze2owizc3Ly9Ja8Br3Va3KV-8J9Do0w6e0gb5W3La87_5sBro9mmEmA/s1600/Screen+shot+2011-03-24+at+4.29.01+PM.png&quot;
1596
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1597
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTJvi0zGz6LnMaUlGrYTQQCtHK5qDwop451cOBsWGXLwSaSUGrHs6UpuBK95zj-fRdph9w6ez4pzLuQIMze2owizc3Ly9Ja8Br3Va3KV-8J9Do0w6e0gb5W3La87_5sBro9mmEmA/s1600/Screen+shot+2011-03-24+at+4.29.01+PM.png&quot;
1598
+ /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td class=&quot;tr-caption&quot;
1599
+ style=&quot;text-align: center;&quot;&gt;Dotted Notes&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
1600
+ /&gt;\nThen we have key signatures.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
1601
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
1602
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1603
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheUE2y0VoCdcCgZCE32Gwj4JCEFDx8hh7b2TjjwV42XamUDvNNhQX5Xq6RypmUawFxFo0zf9zBoQATGJXO_g3JPbYpzG5dDEzhm7zibHJ9eZnlWc0hKzmAFFi164GazKoULiI8Eg/s1600/Screen+shot+2011-03-24+at+4.30.42+PM.png&quot;
1604
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1605
+ border=&quot;0&quot; height=&quot;131&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheUE2y0VoCdcCgZCE32Gwj4JCEFDx8hh7b2TjjwV42XamUDvNNhQX5Xq6RypmUawFxFo0zf9zBoQATGJXO_g3JPbYpzG5dDEzhm7zibHJ9eZnlWc0hKzmAFFi164GazKoULiI8Eg/s320/Screen+shot+2011-03-24+at+4.30.42+PM.png&quot;
1606
+ width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
1607
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Key
1608
+ Signatures&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;\nWe
1609
+ also have time signatures.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
1610
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
1611
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1612
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg27y5Z87WaSWtfaCNXHl9HVU0W9gH9Aso7krjOZS4QF0qaNfqJSiftWZcPNvnGmTe19-LS4rbPPyBaOnJCKE7s5vQEnA85ZnGC8RDbP8oqxOIraHn835bZ9lUe-0CmyElU8tAAXQ/s1600/Screen+shot+2011-03-24+at+4.30.53+PM.png&quot;
1613
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1614
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg27y5Z87WaSWtfaCNXHl9HVU0W9gH9Aso7krjOZS4QF0qaNfqJSiftWZcPNvnGmTe19-LS4rbPPyBaOnJCKE7s5vQEnA85ZnGC8RDbP8oqxOIraHn835bZ9lUe-0CmyElU8tAAXQ/s1600/Screen+shot+2011-03-24+at+4.30.53+PM.png&quot;
1615
+ /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td class=&quot;tr-caption&quot;
1616
+ style=&quot;text-align: center;&quot;&gt;Time Signatures&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
1617
+ /&gt;\nThis includes the really crazy time signatures too.&lt;br /&gt;\n&lt;br
1618
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
1619
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
1620
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
1621
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6onDHjQYycBooCK4HNjeECNfDU6s5UcPQai7Ab8ybP-vmVd2iZijJkoOb0H3-RZdS8-IdHCNQbPlyhsa6_s61YWGa0M6UkvTuy1zGGdaNkf_xYRJEiFKK92fb1v1-SQKXjBK0_w/s1600/Screen+shot+2011-03-24+at+4.31.03+PM.png&quot;
1622
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1623
+ border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6onDHjQYycBooCK4HNjeECNfDU6s5UcPQai7Ab8ybP-vmVd2iZijJkoOb0H3-RZdS8-IdHCNQbPlyhsa6_s61YWGa0M6UkvTuy1zGGdaNkf_xYRJEiFKK92fb1v1-SQKXjBK0_w/s1600/Screen+shot+2011-03-24+at+4.31.03+PM.png&quot;
1624
+ /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td class=&quot;tr-caption&quot;
1625
+ style=&quot;text-align: center;&quot;&gt;Whacky Time Signatures&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
1626
+ /&gt;\nFinally, we have support for different types of clefs.&lt;br /&gt;\n&lt;br
1627
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
1628
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
1629
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
1630
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUXGZKV-uwG5IG2PIILUssE5giARboG92nDQDdlC3CJt-zu3QicRDirgKZxx_1geAMV_YHG-8QdEFP1hE6ek9_jTXdPUVNlQn0QOUsTh_81yimLMQ0mazZCGL0_p-YYRkfG3vWPg/s1600/Screen+shot+2011-03-24+at+4.31.40+PM.png&quot;
1631
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1632
+ border=&quot;0&quot; height=&quot;92&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUXGZKV-uwG5IG2PIILUssE5giARboG92nDQDdlC3CJt-zu3QicRDirgKZxx_1geAMV_YHG-8QdEFP1hE6ek9_jTXdPUVNlQn0QOUsTh_81yimLMQ0mazZCGL0_p-YYRkfG3vWPg/s320/Screen+shot+2011-03-24+at+4.31.40+PM.png&quot;
1633
+ width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
1634
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Alternate
1635
+ Clefs&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;\nBut
1636
+ wait... there&#39;s more.&amp;nbsp;All of this is supported in &lt;a href=&quot;http://vexflow.com/vextab/tutorial.html&quot;&gt;VexTab&lt;/a&gt;
1637
+ by way of new &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
1638
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;tabstave&lt;/span&gt;
1639
+ parameters. Take a look at the updated &lt;a href=&quot;http://vexflow.com/vextab/tutorial.html&quot;&gt;VexTab
1640
+ Tutorial&lt;/a&gt; for the details.</content><link rel='replies' type='application/atom+xml'
1641
+ href='https://0xfe.blogspot.com/feeds/1929109118059060998/comments/default'
1642
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2011/03/vexflow-tutorial-and-other-goodies.html#comment-form'
1643
+ title='5 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1929109118059060998'/><link
1644
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/1929109118059060998'/><link
1645
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2011/03/vexflow-tutorial-and-other-goodies.html'
1646
+ title='The VexFlow Tutorial (...and other goodies)'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1647
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1648
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTJvi0zGz6LnMaUlGrYTQQCtHK5qDwop451cOBsWGXLwSaSUGrHs6UpuBK95zj-fRdph9w6ez4pzLuQIMze2owizc3Ly9Ja8Br3Va3KV-8J9Do0w6e0gb5W3La87_5sBro9mmEmA/s72-c/Screen+shot+2011-03-24+at+4.29.01+PM.png\"
1649
+ height=\"72\" width=\"72\"/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6639885962680967449</id><published>2011-03-23T13:26:00.000-04:00</published><updated>2011-09-10T11:05:46.322-04:00</updated><category
1650
+ scheme=\"http://www.blogger.com/atom/ns#\" term=\"vim\"/><title type='text'>Editing
1651
+ XML and HTML in Vim</title><content type='html'>I just discovered the Vim
1652
+ &lt;a href=&quot;https://github.com/sukima/xmledit/&quot;&gt;xmledit&lt;/a&gt;
1653
+ plugin.&lt;br /&gt;\n&lt;br /&gt;\nWith features like tag-completion, auto-wrapping
1654
+ and unwrapping, quick navigation, etc., it has, in a matter of minutes, measurably
1655
+ decreased my level of frustration while editing markup in Vim.&lt;br /&gt;\n&lt;br
1656
+ /&gt;\n&lt;h3&gt;Installation&lt;/h3&gt;\n&lt;p/&gt;\n&lt;div&gt;The quickest
1657
+ way to install the plugin is by downloading the latest .&lt;code&gt;vba&lt;/code&gt;
1658
+ from the &lt;a href=&quot;http://vim.sourceforge.net/scripts/script.php?script_id=301&quot;&gt;plugin
1659
+ site&lt;/a&gt;, and run the following commands:&lt;/div&gt;&lt;br /&gt;\n&lt;pre
1660
+ class=&quot;prettyprint&quot;&gt;$ vim xmledit.vba\n:so %\n&lt;/pre&gt;&lt;br
1661
+ /&gt;\nYou also need to edit your&amp;nbsp;&lt;code&gt;.vimrc&lt;/code&gt;
1662
+ and add the following:&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;filetype
1663
+ plugin on\n&lt;/pre&gt;&lt;br /&gt;\n&lt;div&gt;This installs the plugin into
1664
+ your &lt;code&gt;.vim/ftplugin&lt;/code&gt; directory, and enables it for
1665
+ &lt;code&gt;.xml&lt;/code&gt; files. To enable it for other file types, create
1666
+ a link to the file with the new extension name in the same directory. (Copying
1667
+ the file also works.)&lt;/div&gt;&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$
1668
+ cd ~/.vim/ftplugin\n$ ln -s xml.vim html.vim\n$ ln -s xml.vim xhtml.vim\n&lt;/pre&gt;&lt;br
1669
+ /&gt;\n&lt;h3&gt;Usage&lt;/h3&gt;&lt;br /&gt;\nThe plugin supports the various
1670
+ Vim modes in interesting ways.&lt;br /&gt;\n&lt;br /&gt;\nIn insert mode,
1671
+ when you finish a tag (with the &lt;code&gt;&lt;span class=&quot;Apple-style-span&quot;
1672
+ style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&amp;nbsp;character),
1673
+ it will be autocompleted and the cursor placed in-between the tags.&lt;br
1674
+ /&gt;\n&lt;br /&gt;\nIf you immediately type another &lt;code&gt;&lt;span
1675
+ class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;,
1676
+ Courier, monospace;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;, it will close
1677
+ the tag on its own line, and place the cursor on a new line right in between.&lt;br
1678
+ /&gt;\n&lt;br /&gt;\nThe &lt;code&gt;%&lt;/code&gt; key jumps between the
1679
+ start and end of a tag.&lt;br /&gt;\n&lt;br /&gt;\nThe &lt;code&gt;\\%&lt;/code&gt;
1680
+ combination jumps between opening and closing tags. (Note that backslash is
1681
+ the default key-prefix for scripts and plugins to use. You can change this
1682
+ prefix with the &lt;code&gt;mapleader&lt;/code&gt; setting.)&lt;br /&gt;\n&lt;br
1683
+ /&gt;\nIf you select text (for example with &lt;code&gt;v&lt;/code&gt;), and
1684
+ type &lt;code&gt;\\x&lt;/code&gt;, it will prompt you to wrap the text with
1685
+ a custom tag.&lt;br /&gt;\n&lt;br /&gt;\nTyping in &lt;code&gt;\\d&lt;/code&gt;
1686
+ unwraps surrounding tags from the cursor.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3&gt;Learning
1687
+ More&lt;/h3&gt;&lt;br /&gt;\nType &lt;code&gt;:help xml-plugin&lt;/code&gt;
1688
+ for help and more information.&lt;br /&gt;\n&lt;br /&gt;\nYay for another
1689
+ awesome Vim plugin. You can see my entire Vim profile in my &lt;a href=&quot;https://github.com/0xfe/evil/tree/master/dotfiles/vim_local&quot;&gt;Evil
1690
+ Tomato GitHub Repository&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml'
1691
+ href='https://0xfe.blogspot.com/feeds/6639885962680967449/comments/default'
1692
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2011/03/editing-xml-and-html-in-vim.html#comment-form'
1693
+ title='3 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6639885962680967449'/><link
1694
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6639885962680967449'/><link
1695
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2011/03/editing-xml-and-html-in-vim.html'
1696
+ title='Editing XML and HTML in Vim'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1697
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-4947829838430309691</id><published>2011-03-20T08:43:00.000-04:00</published><updated>2011-03-20T08:43:13.268-04:00</updated><title
1698
+ type='text'>On Twitter</title><content type='html'>It turns out I&#39;m on
1699
+ twitter. I have absolutely no idea what I&#39;m going to do with it.&lt;br
1700
+ /&gt;\n&lt;br /&gt;\nMaybe I&#39;ll tweet every time time the compiler yells
1701
+ at me... or when my kernel panics... or when my browser &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiAOC6OcErm77z3wo9yVSCI1strQm99Ap5KQOXC7JYi1P1YdC0CMaaUWfpUh-0pZzGyodL1WA7AcdRp0WLDJ-kCTFmHDtGtAkKc5HmY6ohmqYVV0ZCFZ1-NYxKBB5FG1iuj1mZ/s400/aw,+snap.png&quot;&gt;frowns&lt;/a&gt;.&lt;br
1702
+ /&gt;\n&lt;br /&gt;\nWhatever it is, I&#39;m on twitter: &lt;a href=&quot;http://twitter.com/11111110b&quot;&gt;twitter.com/11111110b&lt;/a&gt;&lt;br
1703
+ /&gt;\n&lt;br /&gt;\nThat&#39;s seven ones and a zero, followed by a bee.</content><link
1704
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/4947829838430309691/comments/default'
1705
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2011/03/on-twitter.html#comment-form'
1706
+ title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/4947829838430309691'/><link
1707
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/4947829838430309691'/><link
1708
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2011/03/on-twitter.html'
1709
+ title='On Twitter'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1710
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-5987592118110172726</id><published>2010-09-13T11:03:00.000-04:00</published><updated>2010-09-13T11:03:44.098-04:00</updated><category
1711
+ scheme=\"http://www.blogger.com/atom/ns#\" term=\"haskell\"/><title type='text'>Regex
1712
+ Substitution in Haskell</title><content type='html'>I&#39;m shocked and appalled
1713
+ at the fact that there is no generic regex substitution function in the GHC
1714
+ libraries. All I&#39;m looking for is a simple function equivalent to perl&#39;s
1715
+ &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier
1716
+ New&#39;, Courier, monospace;&quot;&gt;s/.../.../&lt;/span&gt; expression.&lt;br
1717
+ /&gt;\n&lt;br /&gt;\nAfter digging around a bit, I found &lt;span class=&quot;Apple-style-span&quot;
1718
+ style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;subRegex&lt;/span&gt;
1719
+ in &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier
1720
+ New&#39;, Courier, monospace;&quot;&gt;regex-compat&lt;/span&gt;. While this
1721
+ works well, it does not use PCRE, and as far as I can tell, there&#39;s no
1722
+ support for &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
1723
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;ByteString&lt;/span&gt;s.&lt;br
1724
+ /&gt;\n&lt;br /&gt;\nGrrr.&lt;br /&gt;\n&lt;br /&gt;\nAnyhow, I took the &lt;span
1725
+ class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;,
1726
+ Courier, monospace;&quot;&gt;subRegex&lt;/span&gt; implementation from &lt;span
1727
+ class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;,
1728
+ Courier, monospace;&quot;&gt;regex-compat&lt;/span&gt; and mangled it slightly
1729
+ to work with &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
1730
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;Text.Regex.PCRE&lt;/span&gt;.
1731
+ I also added the &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
1732
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;(=~$)&lt;/span&gt; function
1733
+ which feels a bit more familiar to perl users. For example:&lt;br /&gt;\n&lt;pre
1734
+ class=&quot;prettyprint&quot;&gt;Prelude PCRESub&amp;gt; &quot;me boo&quot;
1735
+ =~$ (&quot;(me) boo&quot;, &quot;he \\\\1&quot;)\n&quot;he me&quot;&lt;/pre&gt;The
1736
+ above is equivalent to perl&#39;s:&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$text
1737
+ = &quot;me boo&quot;;\n$text =~ s/(me) boo/he $1/;&lt;/pre&gt;&lt;span class=&quot;Apple-style-span&quot;
1738
+ style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;(~=$)&lt;/span&gt;
1739
+ is implemented with &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
1740
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;reSub&lt;/span&gt; (which
1741
+ is also exported by &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
1742
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;PCRESub&lt;/span&gt;).
1743
+ &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier
1744
+ New&#39;, Courier, monospace;&quot;&gt;reSub&lt;/span&gt; allows you to provide
1745
+ your own &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
1746
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;CompOption&lt;/span&gt;
1747
+ and &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier
1748
+ New&#39;, Courier, monospace;&quot;&gt;ExecOption&lt;/span&gt; options.&lt;br
1749
+ /&gt;\n&lt;br /&gt;\nHere&#39;s the &lt;span class=&quot;Apple-style-span&quot;
1750
+ style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;PCRESub&lt;/span&gt;
1751
+ module:&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;-- PCRE-based
1752
+ Regex Substitution\n-- Mohit Muthanna Cheppudira\n--\n-- Based off code by
1753
+ Chris Kuklewicz from regex-compat library.\n--\n-- Requires Text.Regex.PCRE
1754
+ from regex-pcre.\n\nmodule PCRESub(\n (=~$),\n reSub\n) where\n\nimport
1755
+ Data.Array((!))\nimport Text.Regex.PCRE\n\nsubRegex :: Regex --
1756
+ ^ Search pattern\n -&gt; String -- ^ Input
1757
+ string\n -&gt; String -- ^ Replacement text\n
1758
+ \ -&gt; String -- ^ Output string\nsubRegex
1759
+ _ &quot;&quot; _ = &quot;&quot;\nsubRegex regexp inp repl =\n let compile
1760
+ _i str [] = \\ _m -&gt; (str++)\n compile i str ((&quot;\\\\&quot;,(off,len)):rest)
1761
+ =\n let i&#39; = off+len\n pre = take (off-i) str\n str&#39;
1762
+ = drop (i&#39;-i) str\n in if null str&#39; then \\ _m -&gt; (pre ++)
1763
+ . (&#39;\\\\&#39;:)\n else \\ m -&gt; (pre ++) . (&#39;\\\\&#39;
1764
+ :) . compile i&#39; str&#39; rest m\n compile i str ((xstr,(off,len)):rest)
1765
+ =\n let i&#39; = off+len\n pre = take (off-i) str\n str&#39;
1766
+ = drop (i&#39;-i) str\n x = read xstr\n in if null str&#39;
1767
+ then \\ m -&gt; (pre++) . ((fst (m!x))++)\n else \\ m -&gt; (pre++)
1768
+ . ((fst (m!x))++) . compile i&#39; str&#39; rest m\n compiled :: MatchText
1769
+ String -&gt; String -&gt; String\n compiled = compile 0 repl findrefs
1770
+ where\n bre = makeRegexOpts defaultCompOpt execBlank &quot;\\\\\\\\(\\\\\\\\|[0-9]+)&quot;\n
1771
+ \ findrefs = map (\\m -&gt; (fst (m!1),snd (m!0))) (matchAllText bre
1772
+ repl)\n go _i str [] = str\n go i str (m:ms) =\n let (_,(off,len))
1773
+ = m!0\n i&#39; = off+len\n pre = take (off-i) str\n
1774
+ \ str&#39; = drop (i&#39;-i) str\n in if null str&#39; then
1775
+ pre ++ (compiled m &quot;&quot;)\n else pre ++ (compiled m (go
1776
+ i&#39; str&#39; ms))\n in go 0 inp (matchAllText regexp inp)\n\n-- Substitue
1777
+ re with sub in str using options copts and eopts.\nreSub :: String -&gt; String
1778
+ -&gt; String -&gt; CompOption -&gt; ExecOption -&gt; String\nreSub str re
1779
+ sub copts eopts = subRegex (makeRegexOpts copts eopts re) str sub\n\n-- Substitute
1780
+ re with sub in str, e.g.,\n--\n-- The perl expression:\n--\n-- $text = &quot;me
1781
+ boo&quot;;\n-- $text =~ s/(me) boo/he $1/;\n--\n-- can be written as:\n--\n--
1782
+ \ text = &quot;me boo&quot; =~$ (&quot;(me) boo&quot;, &quot;he \\\\1&quot;)\n--\n(=~$)
1783
+ :: String -&gt; (String, String) -&gt; String\n(=~$) str (re, sub) = reSub
1784
+ str re sub defaultCompOpt defaultExecOpt&lt;/pre&gt;Example usage:&lt;br /&gt;\n&lt;br
1785
+ /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;import PCRESub\n\nmain = do\n
1786
+ \ let text = &quot;me boo&quot; =~$ (&quot;(me) boo&quot;, &quot;he \\\\1&quot;)\n
1787
+ \ print text&lt;/pre&gt;Paste this code in, or browse the source at my GitHub
1788
+ repo: &lt;a href=&quot;http://github.com/0xfe/experiments/blob/master/haskell/PCRESub.hs&quot;&gt;PCRESub.hs&lt;/a&gt;&lt;br
1789
+ /&gt;\n&lt;br /&gt;\nSomeone please make this work across all the regex backends
1790
+ (and add support for &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
1791
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;ByteString&lt;/span&gt;s)!</content><link
1792
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/5987592118110172726/comments/default'
1793
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/09/regex-substitution-in-haskell.html#comment-form'
1794
+ title='3 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/5987592118110172726'/><link
1795
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/5987592118110172726'/><link
1796
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/09/regex-substitution-in-haskell.html'
1797
+ title='Regex Substitution in Haskell'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1798
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-5809791720055478982</id><published>2010-09-12T12:24:00.000-04:00</published><updated>2010-09-12T12:24:22.308-04:00</updated><category
1799
+ scheme=\"http://www.blogger.com/atom/ns#\" term=\"vexflow\"/><title type='text'>VexFlow
1800
+ Google Group</title><content type='html'>I&#39;ve been out of touch for a
1801
+ while, and it took me way too long to set this up; but hey - better late than
1802
+ never. :-)&lt;br /&gt;\n&lt;br /&gt;\nAfter looking into various options for
1803
+ the VexFlow mailing list, I eventually decided to use Google Groups. It&#39;s
1804
+ super easy to setup and manage, and has all the features I&#39;ll ever need.&lt;br
1805
+ /&gt;\n&lt;br /&gt;\nIf you&#39;re interested in hacking, discussing, or simply
1806
+ keeping up with VexFlow, sign up here:&lt;br /&gt;\n&lt;br /&gt;\n&lt;div
1807
+ style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;http://groups.google.com/group/vexflow&quot;&gt;http://groups.google.com/group/vexflow&lt;/a&gt;&lt;/div&gt;&lt;br
1808
+ /&gt;\nUnfortunately, I haven&#39;t had the time lately to hack on VexFlow,
1809
+ but I assure you that it&#39;s only temporary. I have a bunch of partial changes
1810
+ in the works, along with some interesting ideas floating around.&amp;nbsp;More
1811
+ later.</content><link rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/5809791720055478982/comments/default'
1812
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/09/vexflow-google-group.html#comment-form'
1813
+ title='1 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/5809791720055478982'/><link
1814
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/5809791720055478982'/><link
1815
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/09/vexflow-google-group.html'
1816
+ title='VexFlow Google Group'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1817
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3059590296733853422</id><published>2010-08-03T18:04:00.000-04:00</published><updated>2010-08-03T18:04:26.454-04:00</updated><title
1818
+ type='text'>VexFlow is Open Source</title><content type='html'>&lt;div style=&quot;margin-bottom:
1819
+ 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;That&#39;s
1820
+ right folks! All the &lt;a href=&quot;http://www.vexflow.com/&quot;&gt;VexFlow&lt;/a&gt;
1821
+ code is now available in the &lt;a href=&quot;http://github.com/0xfe/vexflow&quot;&gt;VexFlow
1822
+ GitHub Repository&lt;/a&gt;.&lt;/div&gt;&lt;div style=&quot;margin-bottom:
1823
+ 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;&lt;br
1824
+ /&gt;\n&lt;/div&gt;&lt;div style=&quot;margin-bottom: 0px; margin-left: 0px;
1825
+ margin-right: 0px; margin-top: 0px;&quot;&gt;It&#39;s distributed under the
1826
+ OSI approved MIT License, so feel free to tinker, tweak, hack, fix, fork,
1827
+ and redistribute it.&lt;/div&gt;&lt;div style=&quot;margin-bottom: 0px; margin-left:
1828
+ 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;&lt;br /&gt;\n&lt;/div&gt;&lt;div
1829
+ class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a
1830
+ href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCCX_uGvdpHI6QDxV6euNvnnKUFw7lBIkbPcBqlyD75_J7RsVW5qbWjSK9qCbeQNe9i_UwEPvWSCOSNnpEunNPcA4TyAO2-FPX77mdDwUhFYe6JSYmFEVHAedxT0oOF5_iVGqooQ/s1600/Screen+shot+2010-08-03+at+5.36.09+PM.png&quot;
1831
+ imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img
1832
+ border=&quot;0&quot; height=&quot;187&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCCX_uGvdpHI6QDxV6euNvnnKUFw7lBIkbPcBqlyD75_J7RsVW5qbWjSK9qCbeQNe9i_UwEPvWSCOSNnpEunNPcA4TyAO2-FPX77mdDwUhFYe6JSYmFEVHAedxT0oOF5_iVGqooQ/s320/Screen+shot+2010-08-03+at+5.36.09+PM.png&quot;
1833
+ width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;margin-bottom:
1834
+ 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;&lt;br
1835
+ /&gt;\n&lt;/div&gt;&lt;div style=&quot;margin-bottom: 0px; margin-left: 0px;
1836
+ margin-right: 0px; margin-top: 0px;&quot;&gt;&lt;br /&gt;\n&lt;/div&gt;&lt;div
1837
+ style=&quot;margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top:
1838
+ 0px;&quot;&gt;A lot of the core infrastructure (e.g., contexts, formatting,
1839
+ etc.) is ready and stable, and most of the work that needs to be done is adding
1840
+ support for various types of modifiers, effects, and annotations. I&#39;ve
1841
+ worked on some of the trickier ones, like accidentals and beams, and have
1842
+ left the easier ones out so interested coders can learn by contributing.&lt;/div&gt;&lt;div
1843
+ style=&quot;margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top:
1844
+ 0px;&quot;&gt;&lt;br /&gt;\n&lt;/div&gt;&lt;div style=&quot;margin-bottom:
1845
+ 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;This said,
1846
+ algorithms-enthusiasts need not feel left out - there are some hard problems
1847
+ to solve as well :-)&lt;/div&gt;&lt;div style=&quot;margin-bottom: 0px; margin-left:
1848
+ 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;&lt;br /&gt;\n&lt;/div&gt;&lt;div
1849
+ style=&quot;margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top:
1850
+ 0px;&quot;&gt;Here&#39;s where I would like help from the community:&lt;/div&gt;&lt;div
1851
+ style=&quot;margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top:
1852
+ 0px;&quot;&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Dots (Easy)&lt;/li&gt;\n&lt;li&gt;Trills
1853
+ (Easy)&lt;/li&gt;\n&lt;li&gt;Grace Notes (Moderate)&lt;/li&gt;\n&lt;li&gt;Slurs
1854
+ (Easy)&lt;/li&gt;\n&lt;li&gt;Glyphs for time signatures (Easy)&lt;/li&gt;\n&lt;li&gt;Key
1855
+ signature (Easy if you reuse the accidental placement code from accidentals.js)&lt;/li&gt;\n&lt;li&gt;Guitar
1856
+ effects: Palm Muting, Scratches, Whammy, Harmonics, etc. (Easy)&lt;/li&gt;\n&lt;li&gt;Chord
1857
+ Stave with Rhythm Slashes (Moderate to Hard)&lt;/li&gt;\n&lt;li&gt;Lyrics
1858
+ (Easy)&lt;/li&gt;\n&lt;/ul&gt;&lt;br /&gt;\n&lt;div style=&quot;margin-bottom:
1859
+ 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;Here&#39;s
1860
+ what I&#39;m working on right now (and also wouldn&#39;t mind some help with):&lt;/div&gt;&lt;div
1861
+ style=&quot;margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top:
1862
+ 0px;&quot;&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Tuplets / Triplets&lt;/li&gt;\n&lt;li&gt;VexTab
1863
+ parser support for rests, alternate keys, and multiple voices.&lt;/li&gt;\n&lt;li&gt;Alternate
1864
+ tunings and support for arbitrary-string instruments.&lt;/li&gt;\n&lt;/ul&gt;&lt;div&gt;There
1865
+ isn&#39;t much developer documentation right now, but a good place to start
1866
+ is by going through the &lt;a href=&quot;http://github.com/0xfe/vexflow/tree/master/tests/&quot;&gt;code&lt;/a&gt;
1867
+ for &lt;a href=&quot;http://vexflow.com/tests/&quot;&gt;the tests&lt;/a&gt;.
1868
+ You may notice that some files are commented better than others - a great
1869
+ way to help is by adding better comments along with more thorough tests.&lt;/div&gt;&lt;div&gt;&lt;br
1870
+ /&gt;\n&lt;/div&gt;&lt;div&gt;If you&#39;re not a coder and would like to
1871
+ help, you can do so by testing and &lt;a href=&quot;http://github.com/0xfe/vexflow/issues&quot;&gt;reporting
1872
+ bugs&lt;/a&gt;, helping with documentation, spending $7 on a &lt;a href=&quot;http://vexflow.com/tabdiv/&quot;&gt;TabDiv
1873
+ license&lt;/a&gt;, or simply spreading the word.&lt;/div&gt;&lt;div&gt;&lt;br
1874
+ /&gt;\n&lt;/div&gt;&lt;div&gt;Thanks for all the support and help over the
1875
+ past few months. &lt;a href=&quot;http://github.com/0xfe/vexflow&quot;&gt;Dive
1876
+ in&lt;/a&gt; and enjoy!&lt;/div&gt;</content><link rel='replies' type='application/atom+xml'
1877
+ href='https://0xfe.blogspot.com/feeds/3059590296733853422/comments/default'
1878
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/08/vexflow-is-open-source.html#comment-form'
1879
+ title='15 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/3059590296733853422'/><link
1880
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/3059590296733853422'/><link
1881
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/08/vexflow-is-open-source.html'
1882
+ title='VexFlow is Open Source'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1883
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1884
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCCX_uGvdpHI6QDxV6euNvnnKUFw7lBIkbPcBqlyD75_J7RsVW5qbWjSK9qCbeQNe9i_UwEPvWSCOSNnpEunNPcA4TyAO2-FPX77mdDwUhFYe6JSYmFEVHAedxT0oOF5_iVGqooQ/s72-c/Screen+shot+2010-08-03+at+5.36.09+PM.png\"
1885
+ height=\"72\" width=\"72\"/><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7961142667231282175</id><published>2010-07-20T21:54:00.000-04:00</published><updated>2010-07-20T21:54:23.905-04:00</updated><title
1886
+ type='text'>More Durations and Better Beaming</title><content type='html'>I
1887
+ finally got most of the duration and beaming support worked out last weekend,
1888
+ and I gotta say that the generated scores by &lt;a href=&quot;http://vexflow.com/&quot;&gt;VexFlow&lt;/a&gt;
1889
+ (and &lt;a href=&quot;http://vexflow.com/vextab&quot;&gt;VexTab&lt;/a&gt;)
1890
+ are starting to look pretty good.&lt;br /&gt;\n&lt;br /&gt;\nIn VexTab notation,
1891
+ you can now set the duration of the subsequent notes using the colon (&lt;span
1892
+ class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;,
1893
+ Courier, monospace;&quot;&gt;:&lt;/span&gt;) character. By default, note durations
1894
+ are set to eighth notes.&lt;br /&gt;\n&lt;br /&gt;\nHere&#39;s an example
1895
+ of a line that generates a half-note followed by two quarter-notes.&lt;br
1896
+ /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot;
1897
+ cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left:
1898
+ auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1899
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4SokXKqQO6IN0lEH3D2PlyQERmbOVipuHNHDG4r-kmDdSPdJDz8hH38O8SKUyg1Xafi9Sp0jljjnPayVNJ1ta8Ez8H_c1PJwrLdqq5WtE7UXf7RgWWQXDmXmMlpSbf0llA1537Q/s1600/Screen+shot+2010-07-20+at+9.17.44+PM.png&quot;
1900
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1901
+ border=&quot;0&quot; height=&quot;328&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4SokXKqQO6IN0lEH3D2PlyQERmbOVipuHNHDG4r-kmDdSPdJDz8hH38O8SKUyg1Xafi9Sp0jljjnPayVNJ1ta8Ez8H_c1PJwrLdqq5WtE7UXf7RgWWQXDmXmMlpSbf0llA1537Q/s400/Screen+shot+2010-07-20+at+9.17.44+PM.png&quot;
1902
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
1903
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Basic
1904
+ Duration Support&lt;br /&gt;\n&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
1905
+ /&gt;\nValid duration values (currently) are: &lt;span class=&quot;Apple-style-span&quot;
1906
+ style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;w&lt;/span&gt;,
1907
+ &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier
1908
+ New&#39;, Courier, monospace;&quot;&gt;h&lt;/span&gt;, &lt;span class=&quot;Apple-style-span&quot;
1909
+ style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;q&lt;/span&gt;,
1910
+ &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier
1911
+ New&#39;, Courier, monospace;&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;Apple-style-span&quot;
1912
+ style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;16&lt;/span&gt;,
1913
+ and &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier
1914
+ New&#39;, Courier, monospace;&quot;&gt;32&lt;/span&gt;. Support for dots and
1915
+ tuplets/triplets is not yet implemented.&lt;br /&gt;\n&lt;br /&gt;\nDurations
1916
+ can be specified inside slides, bends, and other types of ties by prefixing
1917
+ the fret with the duration value enclosed within colon characters.&lt;br /&gt;\n&lt;br
1918
+ /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
1919
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
1920
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
1921
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKIJDWT5dvihx_d6pAC88xRKSznZvATNRSTXth6Kc3-UZgerT7Mg5t3F8z__X8uZGQ8dfHAAbkGSf1vXEvnEdG9J3CKzDuZvo5FLcCoiJyWDEasHpooW296n-xj1X8YNoCwTS1Vg/s1600/Screen+shot+2010-07-20+at+9.18.50+PM.png&quot;
1922
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1923
+ border=&quot;0&quot; height=&quot;330&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKIJDWT5dvihx_d6pAC88xRKSznZvATNRSTXth6Kc3-UZgerT7Mg5t3F8z__X8uZGQ8dfHAAbkGSf1vXEvnEdG9J3CKzDuZvo5FLcCoiJyWDEasHpooW296n-xj1X8YNoCwTS1Vg/s400/Screen+shot+2010-07-20+at+9.18.50+PM.png&quot;
1924
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
1925
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Durations
1926
+ within Ties&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;\nAlso,
1927
+ I spent time working on some of the tricker beam configurations, where notes
1928
+ with varying durations are beamed.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
1929
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
1930
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1931
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCpz_M1ppQZsOebHxPliRfXzxugqHBkSBmNjXQlEZg4HUFaAL3JI6G2WsM7aUnqn-lp0mi7r0Z14frYzVcyn5SB2YuJ08zOVyuSVA-d5JbFqx3qV_7v493PTGFmgeflHrKHLgAkA/s1600/Screen+shot+2010-07-20+at+9.20.49+PM.png&quot;
1932
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1933
+ border=&quot;0&quot; height=&quot;127&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCpz_M1ppQZsOebHxPliRfXzxugqHBkSBmNjXQlEZg4HUFaAL3JI6G2WsM7aUnqn-lp0mi7r0Z14frYzVcyn5SB2YuJ08zOVyuSVA-d5JbFqx3qV_7v493PTGFmgeflHrKHLgAkA/s200/Screen+shot+2010-07-20+at+9.20.49+PM.png&quot;
1934
+ width=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
1935
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Crazy
1936
+ Beaming&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;\nIn
1937
+ VexTab, you can create beams by enclosing your notes within brackets (separated
1938
+ by spaces).&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
1939
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
1940
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1941
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY52epYKi4fdr_f1eQQRr2uzjG0H6qW1oKc-ND8x9U9129pIzxGd0bmW5RonRIfGnZ279nUOcOJ37X_iZiO7HaZSL3ESQKwnqHPpM1GroFHSzO7ByHIzfdkEdTBun3rBEBGxnbHw/s1600/Screen+shot+2010-07-20+at+9.25.47+PM.png&quot;
1942
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1943
+ border=&quot;0&quot; height=&quot;332&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY52epYKi4fdr_f1eQQRr2uzjG0H6qW1oKc-ND8x9U9129pIzxGd0bmW5RonRIfGnZ279nUOcOJ37X_iZiO7HaZSL3ESQKwnqHPpM1GroFHSzO7ByHIzfdkEdTBun3rBEBGxnbHw/s400/Screen+shot+2010-07-20+at+9.25.47+PM.png&quot;
1944
+ width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
1945
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Beaming
1946
+ in VexTab&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;\nAnyhow,
1947
+ I&#39;ve pushed out the latest revision, with support for standard notation,
1948
+ durations, and beaming to the&amp;nbsp;&lt;a href=&quot;http://vexflow.com/tabdiv&quot;&gt;TabDiv
1949
+ website&lt;/a&gt;. Feel free to &lt;a href=&quot;http://vexflow.com/vextab/tutorial.html&quot;&gt;toy
1950
+ with it&lt;/a&gt; and report any issues you come across.&lt;br /&gt;\n&lt;br
1951
+ /&gt;\nHere&#39;s a screenshot of a bluesy guitar lick written in VexTab.&lt;br
1952
+ /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot;
1953
+ cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left:
1954
+ auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
1955
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiR7GuMPHx-SaGfRCOiB-3FL9pkFaDKuocLuIiJigPH6MP91HqMsTHMpFXhg5jx5XhNa2yo7QuBvLQ3nbI3z2-mYHwtqSEfJQp5TvfqwtxqmRV0Hmv-OUtIM0-rkqYkYlf4omfg0A/s1600/Screen+shot+2010-07-20+at+9.50.07+PM.png&quot;
1956
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
1957
+ border=&quot;0&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiR7GuMPHx-SaGfRCOiB-3FL9pkFaDKuocLuIiJigPH6MP91HqMsTHMpFXhg5jx5XhNa2yo7QuBvLQ3nbI3z2-mYHwtqSEfJQp5TvfqwtxqmRV0Hmv-OUtIM0-rkqYkYlf4omfg0A/s640/Screen+shot+2010-07-20+at+9.50.07+PM.png&quot;
1958
+ width=&quot;408&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
1959
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;A Blues
1960
+ Lick in VexTab&lt;br /&gt;\n&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
1961
+ /&gt;\nThat&#39;s all for this week, folks! There are a lot more interesting
1962
+ things coming up. Check out the &lt;a href=&quot;http://vexflow.com/vextab/tutorial.html&quot;&gt;VexTab
1963
+ tutorial&lt;/a&gt; to play around in the sandboxes, and stay in touch.</content><link
1964
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/7961142667231282175/comments/default'
1965
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/07/more-durations-and-better-beaming.html#comment-form'
1966
+ title='10 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/7961142667231282175'/><link
1967
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/7961142667231282175'/><link
1968
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/07/more-durations-and-better-beaming.html'
1969
+ title='More Durations and Better Beaming'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
1970
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
1971
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4SokXKqQO6IN0lEH3D2PlyQERmbOVipuHNHDG4r-kmDdSPdJDz8hH38O8SKUyg1Xafi9Sp0jljjnPayVNJ1ta8Ez8H_c1PJwrLdqq5WtE7UXf7RgWWQXDmXmMlpSbf0llA1537Q/s72-c/Screen+shot+2010-07-20+at+9.17.44+PM.png\"
1972
+ height=\"72\" width=\"72\"/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7789963467582961010</id><published>2010-07-14T11:51:00.000-04:00</published><updated>2010-07-14T11:51:00.426-04:00</updated><title
1973
+ type='text'>Migrating VexFlow to SCons</title><content type='html'>I love
1974
+ tools. Tools keep my projects predictable and smooth. Tools help me code fast
1975
+ and release fast.&lt;br /&gt;\n&lt;br /&gt;\n&lt;a href=&quot;http://www.vexflow.com/&quot;&gt;VexFlow&lt;/a&gt;
1976
+ has gone through many iterations of design, implementation, and deployment,
1977
+ and I couldn&#39;t have brought it this far so quickly without my tools. (Well,
1978
+ automated testing had a lot to do with it too, but that&#39;s for another
1979
+ post.)&lt;br /&gt;\n&lt;br /&gt;\nYesterday, I added a new tool to my toolbox
1980
+ - &lt;a href=&quot;http://www.scons.org/&quot;&gt;SCons&lt;/a&gt;. I migrated
1981
+ all my building, packaging, test driving, and deployment code to SCons. Compared
1982
+ to the ugly shell scripts I previously used, SCons is a lot cleaner, a lot
1983
+ faster, and significantly easier to manage.&lt;br /&gt;\n&lt;br /&gt;\nI chose
1984
+ SCons for two reasons:&lt;br /&gt;\n&lt;ol&gt;&lt;li&gt;Simplicity. It&#39;s
1985
+ Python-based and super-easy to work with.&lt;/li&gt;\n&lt;li&gt;Familiarity.
1986
+ I&#39;ve used it before, so already know my way around it.&lt;/li&gt;\n&lt;/ol&gt;&lt;div&gt;Since
1987
+ I use the &lt;a href=&quot;http://code.google.com/closure/compiler/&quot;&gt;Google
1988
+ Closure Compiler&lt;/a&gt; to build and minimize my JavaScript code, I had
1989
+ to write a new builder for SCons. That turned out to be pretty straightforward
1990
+ to implement.&lt;/div&gt;&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;def
1991
+ js_builder(target, source, env):\n &quot;&quot;&quot; A JavaScript builder
1992
+ using Google Closure Compiler. &quot;&quot;&quot;\n\n cmd = env.subst(\n
1993
+ \ &quot;$JAVA -jar $JS_COMPILER --compilation_level $JS_COMPILATION_LEVEL&quot;);\n\n
1994
+ \ # Add defines to the command\n for define in env[&#39;JS_DEFINES&#39;].keys():\n
1995
+ \ cmd += &quot; --define=\\&quot;%s=%s\\&quot;&quot; % (define, env[&#39;JS_DEFINES&#39;][define])\n\n
1996
+ \ # Add the source files\n for file in source:\n cmd += &quot; --js &quot;
1997
+ + str(file)\n\n # Add the output file\n cmd += &quot; --js_output_file &quot;
1998
+ + str(target[0])\n\n # Log the command and run\n print env.subst(cmd)\n
1999
+ \ os.system(env.subst(cmd))\n&lt;/pre&gt;&lt;br /&gt;\nI also needed a new
2000
+ builder to stamp my output with the relevant build information. So, I created
2001
+ a &lt;i&gt;Stamper, &lt;/i&gt;which is just a builder that runs some string
2002
+ substitution on files with &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
2003
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;sed&lt;/span&gt;. The
2004
+ stamper looks like this:&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;def
2005
+ vexflow_stamper(target, source, env):\n &quot;&quot;&quot; A Build Stamper
2006
+ for VexFlow &quot;&quot;&quot;\n\n cmd = &quot;sed &quot;\n cmd += &quot;
2007
+ -e s/__VEX_BUILD_PREFIX__/$VEX_BUILD_PREFIX/&quot;\n cmd += &quot; -e s/__VEX_VERSION__/$VEX_VERSION/&quot;\n
2008
+ \ cmd += &#39; -e &quot;s/__VEX_BUILD_DATE__/${VEX_BUILD_DATE}/&quot;&#39;\n
2009
+ \ cmd += &quot; -e s/__VEX_GIT_SHA1__/`git rev-list --max-count=1 HEAD`/ &quot;\n
2010
+ \ cmd += (&quot;%s &amp;gt; %s&quot; % (source[0], target[0]))\n\n print
2011
+ env.subst(cmd)\n os.system(env.subst(cmd))\n&lt;/pre&gt;&lt;br /&gt;\nBefore
2012
+ you can use these builders, you need to add them to your environment:&lt;br
2013
+ /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;env.Append(BUILDERS
2014
+ = {&#39;JavaScript&#39;: Builder(action = js_builder),\n &#39;VexFlowStamp&#39;:
2015
+ Builder(action = vexflow_stamper)})\n&lt;/pre&gt;&lt;br /&gt;\nOnce this is
2016
+ done, you can add build JavaScript targets with the &lt;i&gt;JavaScript&lt;/i&gt;
2017
+ command.&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;env[&#39;JAVA&#39;]
2018
+ = &quot;/usr/bin/java&quot;\nenv[&#39;JS_COMPILER&#39;] = &quot;support/compiler.jar&quot;\nenv[&#39;JS_DEFINES&#39;
2019
+ ] = {\n &quot;Vex.Debug&quot;: &quot;true&quot;,\n &quot;Vex.LogLevel&quot;:
2020
+ &quot;4&quot;\n}\n\nsources = [&quot;src1.js&quot;, &quot;src2.js&quot;, &quot;src3.js&quot;]\n\nenv.JavaScript(&quot;src.min.js&quot;,
2021
+ sources)\n&lt;/pre&gt;&lt;br /&gt;\nThis really is just scratching the surface.
2022
+ There&#39;s a lot more you can do with SCons to automate and streamline your
2023
+ builds. To learn more, take a look at the &lt;a href=&quot;http://www.scons.org/doc/2.0.0.final.0/HTML/scons-user/index.html&quot;&gt;user
2024
+ guide&lt;/a&gt;.&lt;br /&gt;\n&lt;br /&gt;\nI added support for testing, packaging,
2025
+ and deployment (of the web pages and demos) to my SCons scripts in a matter
2026
+ of hours, and finally purged all my nasty shell scripts from the VexFlow codebase.&lt;br
2027
+ /&gt;\n&lt;br /&gt;\nGive it a try. I guarantee you&#39;ll be happier.</content><link
2028
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/7789963467582961010/comments/default'
2029
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/07/migrating-vexflow-to-scons.html#comment-form'
2030
+ title='3 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/7789963467582961010'/><link
2031
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/7789963467582961010'/><link
2032
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/07/migrating-vexflow-to-scons.html'
2033
+ title='Migrating VexFlow to SCons'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
2034
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6514910752490146447</id><published>2010-07-12T14:50:00.000-04:00</published><updated>2010-07-12T14:50:31.885-04:00</updated><title
2035
+ type='text'>Durations, Code, and Posters</title><content type='html'>The last
2036
+ few weeks have been relatively quiet on the VexFlow side. I&#39;ve been vacationing
2037
+ in Cape Cod with my wife and 3-month-old.&lt;br /&gt;\n&lt;br /&gt;\nObviously,
2038
+ vacation is never fun without a few good coding sprints. I started work on
2039
+ incorporating standard notation into &lt;a href=&quot;http://www.vexflow.com/vextab&quot;&gt;VexTab&lt;/a&gt;.&lt;br
2040
+ /&gt;\n&lt;br /&gt;\nThe first thing I needed to do was create a class to
2041
+ convert fret-string pairs to notes. In order to support alternate tunings,
2042
+ I created a &lt;i&gt;Tuning&lt;/i&gt;&amp;nbsp;class, whose sole responsibility
2043
+ is to return the correct note for a given fret-string pair, based on the instrument
2044
+ type and tuning.&lt;br /&gt;\n&lt;br /&gt;\nSo, to convert the fret-string
2045
+ pair &quot;5/2&quot; on a 5-string bass to standard notation, all I need to
2046
+ do is:&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;var
2047
+ tuning = new Vex.Flow.Tuning(&quot;G/4,D/4,A/3,E/3,B/2&quot;);\nvar note =
2048
+ tuning.getNoteForFret(5, 2);\n&lt;/pre&gt;&lt;br /&gt;\nThe next part was
2049
+ augmenting the language to render standard notation when requested. I modified
2050
+ &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier
2051
+ New&#39;, Courier, monospace;&quot;&gt;tabstave&lt;/span&gt; to accept &lt;span
2052
+ class=&quot;Apple-style-span&quot; style=&quot;font-family: inherit;&quot;&gt;&lt;i&gt;key=value&lt;/i&gt;&lt;/span&gt;
2053
+ parameters, and added a parameter called &lt;span class=&quot;Apple-style-span&quot;
2054
+ style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;notation&lt;/span&gt;.
2055
+ When set to &lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family:
2056
+ &#39;Courier New&#39;, Courier, monospace;&quot;&gt;true&lt;/span&gt;, it
2057
+ renders standard notation above the guitar tab.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table
2058
+ align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;
2059
+ class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right:
2060
+ auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td style=&quot;text-align:
2061
+ center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOfzb0VTihrFnDXvA642NRGLuWU45tUoukDnqlFYI5bF-J_CBAWHriN3qroiWKRqWYDoOPFNKfgDoZp5jmRwRqYjla4YEQ2Chd0yL_O6bJAFow9mG6-kUlCNHTbrFvHpupX02O3g/s1600/Screen+shot+2010-07-12+at+2.20.47+PM.png&quot;
2062
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
2063
+ border=&quot;0&quot; height=&quot;276&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOfzb0VTihrFnDXvA642NRGLuWU45tUoukDnqlFYI5bF-J_CBAWHriN3qroiWKRqWYDoOPFNKfgDoZp5jmRwRqYjla4YEQ2Chd0yL_O6bJAFow9mG6-kUlCNHTbrFvHpupX02O3g/s320/Screen+shot+2010-07-12+at+2.20.47+PM.png&quot;
2064
+ width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
2065
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;VexTab
2066
+ with Standard Notation&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
2067
+ /&gt;\nI also started work on basic duration support and auto-beaming. I don&#39;t
2068
+ have much to show for this yet, because they&#39;re currently a bit intertwined,
2069
+ and automatic beaming is harder than I anticipated (yet again!)&lt;br /&gt;\n&lt;br
2070
+ /&gt;\nIn other news, I &lt;a href=&quot;http://github.com/0xfe/vex/blob/master/vextab/vextab.js&quot;&gt;open
2071
+ sourced&lt;/a&gt; the VexTab parser, so you can learn more about the language
2072
+ or use it in your own rendering engines. It is currently slightly coupled
2073
+ to VexFlow, but pretty trivial to decouple. (I&#39;m going to fully decouple
2074
+ it as this project progresses.)&lt;br /&gt;\n&lt;br /&gt;\nThe parser is licensed
2075
+ under the &lt;a href=&quot;http://www.opensource.org/licenses/mit-license.php&quot;&gt;MIT
2076
+ license&lt;/a&gt;, and is available on GitHub at: &lt;a href=&quot;http://github.com/0xfe/vex/tree/master/vextab/&quot;&gt;http://github.com/0xfe/vex/tree/master/vextab/&lt;/a&gt;.&lt;br
2077
+ /&gt;\n&lt;br /&gt;\nFinally, some readers who liked my previous &lt;a href=&quot;http://0xfe.blogspot.com/2009/12/google-chrome-poster-from-source-code.html&quot;&gt;Chrome
2078
+ Poster from Source Code&lt;/a&gt; post requested posters for other open-source
2079
+ projects. I generated posters for Firefox, Linux, and FreeBSD, and made them
2080
+ available on my other side project: &lt;a href=&quot;http://wickedmeanposters.com/&quot;&gt;Wicked
2081
+ Mean Posters&lt;/a&gt;.&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot;
2082
+ cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot;
2083
+ style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
2084
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAD0NHLeE4x2HoXGDzW6pydG0jqBIb_bI1AzrlDohHGM14iu928BTzOftpGh0cHv4uKD47sphwVDgO1sbkX61XXvgeIU1F0He8VGp0Na_QoGFUtXfOXd-ItH6nITciSpGfCm-fHA/s1600/Screen+shot+2010-07-12+at+2.43.14+PM.png&quot;
2085
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
2086
+ border=&quot;0&quot; height=&quot;198&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAD0NHLeE4x2HoXGDzW6pydG0jqBIb_bI1AzrlDohHGM14iu928BTzOftpGh0cHv4uKD47sphwVDgO1sbkX61XXvgeIU1F0He8VGp0Na_QoGFUtXfOXd-ItH6nITciSpGfCm-fHA/s320/Screen+shot+2010-07-12+at+2.43.14+PM.png&quot;
2087
+ width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
2088
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Firefox
2089
+ Poster from Source Code&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br
2090
+ /&gt;\nMore next time!</content><link rel='replies' type='application/atom+xml'
2091
+ href='https://0xfe.blogspot.com/feeds/6514910752490146447/comments/default'
2092
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/07/durations-code-and-posters.html#comment-form'
2093
+ title='5 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6514910752490146447'/><link
2094
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/6514910752490146447'/><link
2095
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/07/durations-code-and-posters.html'
2096
+ title='Durations, Code, and Posters'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
2097
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
2098
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOfzb0VTihrFnDXvA642NRGLuWU45tUoukDnqlFYI5bF-J_CBAWHriN3qroiWKRqWYDoOPFNKfgDoZp5jmRwRqYjla4YEQ2Chd0yL_O6bJAFow9mG6-kUlCNHTbrFvHpupX02O3g/s72-c/Screen+shot+2010-07-12+at+2.20.47+PM.png\"
2099
+ height=\"72\" width=\"72\"/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-8369373862453509821</id><published>2010-06-25T17:25:00.000-04:00</published><updated>2011-09-10T11:20:07.098-04:00</updated><title
2100
+ type='text'>Encrypted Incremental Backups to S3</title><content type='html'>I
2101
+ spent some time this week trying to get secure online backups working for
2102
+ all my machines.&lt;br /&gt;\n&lt;br /&gt;\nSo far, I&#39;ve been managing
2103
+ most of my data and workspaces with replicated Git repositories. I have scripts
2104
+ that allow me to maintain roaming profiles across my machines (almost) seamlessly,
2105
+ and these scripts try to ensure that these profiles are consistently replicated.
2106
+ My profiles include things like dot files (&lt;code&gt;.vimrc&lt;/code&gt;,
2107
+ &lt;code&gt;.screenrc&lt;/code&gt;, etc.), startup scripts, tools, workspaces,
2108
+ repositories, and other odds and ends.&lt;br /&gt;\n&lt;br /&gt;\nBecause
2109
+ I tend to be ultra-paranoid about security and reliability, the replicas are
2110
+ encrypted and distributed across different machines in different locations.
2111
+ For encryption, I use &lt;a href=&quot;https://launchpad.net/ecryptfs&quot;&gt;ecryptfs&lt;/a&gt;
2112
+ on Linux machines, and FileVault on the Mac.&lt;br /&gt;\n&lt;br /&gt;\nAnyhow,
2113
+ this week I lost my Mac to a hardware failure, and my co-located Linux machine
2114
+ to a service-provider &lt;i&gt;dismantling&lt;/i&gt;. That left me with one
2115
+ replica... just waiting to fail.&lt;br /&gt;\n&lt;br /&gt;\nI decided that
2116
+ I needed another replica, but didn&#39;t want to pay for, or have to setup
2117
+ another co-located server. After spending some time researching various online-backup
2118
+ providers, I decided to go with &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;Amazon&#39;s
2119
+ S3 service&lt;/a&gt;.&lt;br /&gt;\n&lt;br /&gt;\nI chose S3 because - it&#39;s
2120
+ cheap, it&#39;s tried and tested, it&#39;s built on an internal distributed
2121
+ and replicated database, and there are some great tools that work with it.&lt;br
2122
+ /&gt;\n&lt;br /&gt;\n&lt;h3&gt;Brackup&lt;/h3&gt;&lt;br /&gt;\n&lt;a href=&quot;http://code.google.com/p/brackup/&quot;&gt;Brackup&lt;/a&gt;,
2123
+ by Brad Fitzpatrick, is one of those tools. It allows you to make encrypted
2124
+ incremental backups to S3, without a lot of hair-pulling or teeth-gnashing.&lt;br
2125
+ /&gt;\n&lt;br /&gt;\nTo get Brackup running on your machine, you need to have
2126
+ GPG, Perl 5, and the Net::Amazon::S3 Perl module installed. On the Mac, you
2127
+ also need to get MacPorts.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3&gt;Installation&lt;/h3&gt;&lt;br
2128
+ /&gt;\nMost modern distributions come with Perl 5 pre-installed, but not with
2129
+ GPG. The package name you want on both MacPorts and Ubuntu, is &lt;code&gt;gnupg&lt;/code&gt;.&lt;br
2130
+ /&gt;\n&lt;br /&gt;\nThe first thing you need to do, if you don&#39;t already
2131
+ have a GPG key, is to generate one.&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$
2132
+ gpg --gen-keys&lt;/pre&gt;&lt;br /&gt;\nIf you need to backup multiple machines,
2133
+ export your public key to a text file, and import it on the other machines.&lt;br
2134
+ /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;hostA$ gpg --export
2135
+ -a &quot;User Name&quot; &amp;gt; public.key\nhostA$ scp public.key hostB:/tmp\nhostB$
2136
+ gpg --import /tmp/public.key\n&lt;/pre&gt;&lt;br /&gt;\nRemember that all
2137
+ your backups will be encrypted with your public key, so if you lose your private
2138
+ key, the only thing you can do with your backups is generate white noise.
2139
+ Export your private key and save it in a safe place. (I suggest &lt;a href=&quot;http://vexcrypto.appspot.com/&quot;&gt;VexCrypto&lt;/a&gt;.)&lt;br
2140
+ /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$ gpg --export-secret-key
2141
+ -a &quot;User Name&quot; &amp;gt; private.key\n&lt;/pre&gt;&lt;br /&gt;\nNow
2142
+ that you have your keys setup, download and install Brackup. The easiest way
2143
+ to do this is by using the &lt;code&gt;cpan&lt;/code&gt; tool.&lt;br /&gt;\n&lt;br
2144
+ /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$ sudo cpan Net::Amazon::S3\n$
2145
+ sudo cpan Brackup\n&lt;/pre&gt;&lt;br /&gt;\nNote that it&#39;s better (and
2146
+ way faster) to use your distribution&#39;s package for Net::Amazon::S3. On
2147
+ Ubuntu the package is &lt;code&gt;libnet-amazon-s3-perl&lt;/code&gt;, and
2148
+ on MacPorts, the package is &lt;code&gt;p5-amazon-s3&lt;/code&gt;.&lt;br /&gt;\n&lt;br
2149
+ /&gt;\n&lt;h3&gt;Configuration&lt;/h3&gt;\n&lt;br /&gt;\nOnce this is done,
2150
+ you can generate a template configuration file by typing in &lt;code&gt;brackup&lt;/code&gt;
2151
+ on the command line. This file is stored in &lt;code&gt;$HOME/.brackup&lt;/code&gt;.&lt;br
2152
+ /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$ &lt;b&gt;brackup&lt;/b&gt;\nError:
2153
+ Your config file needs tweaking. I put a commented-out template at: /home/muthanna/.brackup.conf\n\nbrackup
2154
+ --from=[source_name] --to=[target_name] [--output=&lt;backup_metafile.brackup&gt;]\nbrackup
2155
+ --help\n&lt;/backup_metafile.brackup&gt;&lt;/pre&gt;&lt;br /&gt;\nEdit the
2156
+ configuration file and create your sources and targets. You will likely have
2157
+ multiple sources, and one target. Here&#39;s a snip of my configuration:&lt;br
2158
+ /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;[TARGET:amazon]\ntype
2159
+ = Amazon\naws_access_key_id = XXXXXXXXXXX\naws_secret_access_key = XXXXXXXXXXXXXX\nkeep_backups
2160
+ = 10\n\n[SOURCE:mac_repos]\npath = /Users/0xfe/Local\nchunk_size = 5m\ngpg_recipient
2161
+ = 79E44165\nignore = ^.*\\.(swp|swo|hi|o|a|pyc|svn|class|DS_Store|Trash|Trashes)$/\n\n[SOURCE:mac_desktop_books]\npath
2162
+ = /Users/0xfe/Desktop/Books\ngpg_recipient = 79E44165\nignore = ^.*\\.(swp|swo|hi|o|a|pyc|svn|class|DS_Store|Trash|Trashes)$/\n\n[SOURCE:mac_desktop_workspace]\npath
2163
+ = /Users/0xfe/Desktop/Workspace\ngpg_recipient = 79E44165\nignore = ^.*\\.(swp|swo|hi|o|a|pyc|svn|class|DS_Store|Trash|Trashes)$/\n&lt;/pre&gt;&lt;br
2164
+ /&gt;\nThe configuration keys are pretty self explanatory. I should point
2165
+ out that &lt;code&gt;gpg_recipient&lt;/code&gt; is your public key ID, as
2166
+ shown by &lt;code&gt;gpg --list-keys&lt;/code&gt;.&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre
2167
+ class=&quot;prettyprint&quot;&gt;$ gpg --list-keys\n/Users/0xfe/.gnupg/pubring.gpg\n-----------------------------------\npub
2168
+ \ 2048R/79E44165 2010-06-24\nuid My Username &amp;lt;snip@snip.com&amp;gt;\nsub
2169
+ \ 2048R/43AD4B72 2010-06-24\n&lt;/pre&gt;&lt;br /&gt;\nFor more details on
2170
+ the various parameters, see &lt;a href=&quot;http://search.cpan.org/~bradfitz/Brackup/lib/Brackup/Manual/Overview.pod&quot;&gt;The
2171
+ Brackup Manual&lt;/a&gt;.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3&gt;Start a Backup&lt;/h3&gt;\n&lt;br
2172
+ /&gt;\nTo backup one of your sources, use the &lt;code&gt;brackup&lt;/code&gt;
2173
+ command, as so:&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$
2174
+ brackup -v --from=mac_repos --to=amazon\n&lt;/pre&gt;&lt;br /&gt;\nIf you
2175
+ now take a look at your &lt;a href=&quot;http://aws.amazon.com/&quot;&gt;AWS
2176
+ Dashboard&lt;/a&gt;, you should see the buckets and chunks created for your
2177
+ backup data.&lt;br /&gt;\n&lt;br /&gt;\nNotice that &lt;code&gt;brackup&lt;/code&gt;
2178
+ creates an output file (with the extension &lt;code&gt;.brackup&lt;/code&gt;)
2179
+ in the current directory. This file serves as an index, and maintains pointers
2180
+ to the S3 chunks for each backed-up file. You will need this file to locate
2181
+ and restore your data, and a copy of it is maintained on S3.&lt;br /&gt;\n&lt;br
2182
+ /&gt;\n&lt;h3&gt;Restoring&lt;/h3&gt;\n&lt;br /&gt;\n&quot;&lt;i&gt;Test restores
2183
+ regularly.&lt;/i&gt;&quot; -- a wise man.&lt;br /&gt;\n&lt;br /&gt;\nTo restore
2184
+ your Brackup backups, you will need to have your private key handy on the
2185
+ machine that you&#39;re restoring to. Brackup accesses your private key via
2186
+ &lt;code&gt;gpg-agent&lt;/code&gt;.&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$
2187
+ sudo port install gpg-agent\n$ eval $(gpg-agent --daemon)\n&lt;/pre&gt;&lt;br
2188
+ /&gt;\nThe &lt;code&gt;brackup-restore&lt;/code&gt; command restores a source
2189
+ tree to a path specified on the command line. It makes use of the output file
2190
+ that brackup generated during the initial backup to locate and restore your
2191
+ data. If you don&#39;t have a local copy of the output file, you can use &lt;code&gt;brackup-target&lt;/code&gt;
2192
+ to retrieve a copy from S3.&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$
2193
+ brackup-restore -v --from=mac_repos-20100624.brackup \\\n --to=/Users/0xfe/temp/mac_repos
2194
+ --all\n&lt;/pre&gt;&lt;br /&gt;\nYou will be prompted for you AWS key, your
2195
+ AWS secret key, and your GPG private key passphrase. Make sure that that the
2196
+ restore completed successfully and correctly. Comparing the SHA1 hashes of
2197
+ the restored data with those of the original data is a good way to validate
2198
+ correctness.&lt;br /&gt;\n&lt;br /&gt;\n&lt;h3&gt;Garbage Collection&lt;/h3&gt;\n&lt;br
2199
+ /&gt;\nYou will need to prune and garbage collect your data regularly to keep
2200
+ backups from piling up and using up space in S3. The following commands delete
2201
+ old backed up chunks based on the &lt;i&gt;keep_files &lt;/i&gt;configuration
2202
+ value of the target.&lt;br /&gt;\n&lt;br /&gt;\n&lt;pre class=&quot;prettyprint&quot;&gt;$
2203
+ brackup-target amazon prune\n$ brackup-target amazon gc\n&lt;/pre&gt;&lt;br
2204
+ /&gt;\nThat&#39;s all folks! Secure, on-line, off-site, incremental, buzz-word-ridden
2205
+ backups. Code safely!</content><link rel='replies' type='application/atom+xml'
2206
+ href='https://0xfe.blogspot.com/feeds/8369373862453509821/comments/default'
2207
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/06/encrypted-incremental-backups-to-s3.html#comment-form'
2208
+ title='4 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/8369373862453509821'/><link
2209
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/8369373862453509821'/><link
2210
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/06/encrypted-incremental-backups-to-s3.html'
2211
+ title='Encrypted Incremental Backups to S3'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
2212
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3313904850292755497</id><published>2010-06-21T14:12:00.000-04:00</published><updated>2010-06-21T14:12:11.700-04:00</updated><title
2213
+ type='text'>On Parsing and Licenses</title><content type='html'>So I spent
2214
+ this weekend rewriting the &lt;a href=&quot;http://vexflow.com/tabdiv/tutorial.html&quot;&gt;VexTab&lt;/a&gt;
2215
+ parser. The original version, though it served its purpose as a quick prototype
2216
+ for the language, was severely limited due to it being built primarily out
2217
+ of regular expressions.&lt;br /&gt;\n&lt;br /&gt;\nThe new parser uses a recursive-descent
2218
+ algorithm, and fully supports the original grammar. Adding new syntactic elements
2219
+ to the language is now simple, as is adding support for more complex grammars.&lt;br
2220
+ /&gt;\n&lt;br /&gt;\nSome new features I added to the language are support
2221
+ for slides, hammer-ons, pull-offs, and tapping. Here&#39;s a blues lick written
2222
+ in VexTab:&lt;br /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot;
2223
+ cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left:
2224
+ auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
2225
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOTpU652I2h8heK-MA-5nu0w1I70rvYWZDuPrcr2ufE663YB3DK-yexfnHSJz3BnbRSGvk8N3OpPwmu_ZIgDX0zHQkialvKR014n87LzrgUHY0vDCVutWBuAABvtU8yR3BFYY60g/s1600/Picture+11.png&quot;
2226
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
2227
+ border=&quot;0&quot; height=&quot;262&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOTpU652I2h8heK-MA-5nu0w1I70rvYWZDuPrcr2ufE663YB3DK-yexfnHSJz3BnbRSGvk8N3OpPwmu_ZIgDX0zHQkialvKR014n87LzrgUHY0vDCVutWBuAABvtU8yR3BFYY60g/s320/Picture+11.png&quot;
2228
+ width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
2229
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Blues
2230
+ Lick in VexTab&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;\nSome
2231
+ readers have asked me about durations and how to specify rhythms in VexTab.
2232
+ Although the VexFlow core has full support for durations and timing, I still
2233
+ need a good way to represent them in the language. I&#39;m open to ideas here
2234
+ if you have any.&lt;br /&gt;\n&lt;br /&gt;\n&lt;b&gt;Enter TabDiv&lt;/b&gt;&lt;br
2235
+ /&gt;\n&lt;br /&gt;\nI also spent this weekend working on the release of the
2236
+ first VexFlow-based product:&amp;nbsp;&lt;a href=&quot;http://vexflow.com/tabdiv/index.html&quot;&gt;TabDiv&lt;/a&gt;.
2237
+ TabDiv lets you easily embed guitar tablature into your website or blog.&lt;br
2238
+ /&gt;\n&lt;br /&gt;\nAfter you&#39;ve included the TabDiv &lt;code&gt;.js&lt;/code&gt;
2239
+ and &lt;code&gt;.css&lt;/code&gt; files in your HTML document (or blog template),
2240
+ you can add tabs by simply creating DIV elements and setting the class to
2241
+ vex-tabdiv.&lt;br /&gt;\n&lt;br /&gt;\nYou can get TabDiv here:&amp;nbsp;&lt;a
2242
+ href=&quot;http://vexflow.com/tabdiv/index.html&quot;&gt;http://vexflow.com/tabdiv/index.html&lt;/a&gt;.&lt;br
2243
+ /&gt;\n&lt;br /&gt;\n&lt;b&gt;Why not Open-Source?&lt;/b&gt;&lt;br /&gt;\n&lt;br
2244
+ /&gt;\nI&#39;m no stranger to open-source. I&#39;ve been writing, maintaining,
2245
+ and contributing to open-source software for over a decade.&lt;br /&gt;\n&lt;br
2246
+ /&gt;\nAlthough I hope to eventually open-source all the VexFlow source code,
2247
+ I&#39;m going to hold off on it until I figure out where I want to take this
2248
+ product. I&#39;ve invested a lot of time and effort into making VexFlow a
2249
+ fast high-quality renderer, and I&#39;d like to find a way to cater to both
2250
+ a commercial-audience, and the open-source community.&lt;br /&gt;\n&lt;br
2251
+ /&gt;\nSo, how does one find and maintain this delicate balance? Do I completely
2252
+ open-source it? Should I keep it closed and charge for it? Dual-license maybe?</content><link
2253
+ rel='replies' type='application/atom+xml' href='https://0xfe.blogspot.com/feeds/3313904850292755497/comments/default'
2254
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/06/on-parsing-and-licenses.html#comment-form'
2255
+ title='16 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/3313904850292755497'/><link
2256
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/3313904850292755497'/><link
2257
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/06/on-parsing-and-licenses.html'
2258
+ title='On Parsing and Licenses'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
2259
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
2260
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOTpU652I2h8heK-MA-5nu0w1I70rvYWZDuPrcr2ufE663YB3DK-yexfnHSJz3BnbRSGvk8N3OpPwmu_ZIgDX0zHQkialvKR014n87LzrgUHY0vDCVutWBuAABvtU8yR3BFYY60g/s72-c/Picture+11.png\"
2261
+ height=\"72\" width=\"72\"/><thr:total>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7599469828136185502</id><published>2010-06-17T15:01:00.000-04:00</published><updated>2010-06-17T15:01:57.156-04:00</updated><title
2262
+ type='text'>Benchmarking VexFlow</title><content type='html'>I have about
2263
+ 340 tests now for VexFlow, and one of the things I find really impressive
2264
+ is the speed at which browsers currently load, execute, and render web-pages.&lt;br
2265
+ /&gt;\n&lt;br /&gt;\nSince the code exercises the browser on a few different
2266
+ dimensions (heavy JavaScript, lots of DOM manipulation, a few new HTML5 features),
2267
+ I decided to pit the major browsers against each other and run a few benchmarks.&lt;br
2268
+ /&gt;\n&lt;br /&gt;\n&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot;
2269
+ cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left:
2270
+ auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;\n&lt;tr&gt;&lt;td
2271
+ style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYbJtGgfxWbAZNnhARDNSNFhsLELRdCUQC9AJgQVTSxMrw0AcWkLlOZYuhzbxW5wUGHjptKYm1Ul758647rbb2p2hdn8ItTxlQN4qJe8CzrLH01dzwV248vhoP27vsIFWikAo0nA/s1600/Picture+9.png&quot;
2272
+ imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img
2273
+ border=&quot;0&quot; height=&quot;127&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYbJtGgfxWbAZNnhARDNSNFhsLELRdCUQC9AJgQVTSxMrw0AcWkLlOZYuhzbxW5wUGHjptKYm1Ul758647rbb2p2hdn8ItTxlQN4qJe8CzrLH01dzwV248vhoP27vsIFWikAo0nA/s320/Picture+9.png&quot;
2274
+ width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n&lt;tr&gt;&lt;td
2275
+ class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Test
2276
+ Suite on Chrome 5.0.375&lt;/td&gt;&lt;/tr&gt;\n&lt;/tbody&gt;&lt;/table&gt;I
2277
+ ran the 340 tests in a loop a 1000 times on each browser, and calculated the
2278
+ mean runtime in milliseconds. Here are the results:&lt;br /&gt;\n&lt;br /&gt;\n&lt;ul&gt;&lt;li&gt;Chrome
2279
+ 5.0.375: &lt;b&gt;754ms&lt;/b&gt;&lt;/li&gt;\n&lt;li&gt;Safari 4.0.4: &lt;b&gt;1118ms&lt;/b&gt;&lt;/li&gt;\n&lt;li&gt;Opera
2280
+ 10.53: &lt;b&gt;1511ms&lt;/b&gt;&lt;/li&gt;\n&lt;li&gt;Firefox 3.6.3: &lt;b&gt;3209ms&lt;/b&gt;&lt;/li&gt;\n&lt;/ul&gt;&lt;div&gt;&lt;br
2281
+ /&gt;\nThe difference between the Chrome and Firefox numbers is quite surprising.&lt;/div&gt;&lt;br
2282
+ /&gt;\nI also ran some SVG vs. Canvas benchmarks and found that SVG was about
2283
+ 3 times slower than Canvas. This factor increased significantly as the number
2284
+ of elements in the SVG image grew. That said, SVG rendered much more consistently
2285
+ across the different browsers.&lt;br /&gt;\n&lt;br /&gt;\nThe test machine
2286
+ used was a dual-core MacBook Pro with a 2.53 GHz Intel Core 2 Duo processor
2287
+ and 4GB of DDR3 RAM.</content><link rel='replies' type='application/atom+xml'
2288
+ href='https://0xfe.blogspot.com/feeds/7599469828136185502/comments/default'
2289
+ title='Post Comments'/><link rel='replies' type='text/html' href='https://0xfe.blogspot.com/2010/06/benchmarking-vexflow.html#comment-form'
2290
+ title='3 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/7599469828136185502'/><link
2291
+ rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/19544619/posts/default/7599469828136185502'/><link
2292
+ rel='alternate' type='text/html' href='https://0xfe.blogspot.com/2010/06/benchmarking-vexflow.html'
2293
+ title='Benchmarking VexFlow'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image
2294
+ rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail
2295
+ xmlns:media=\"http://search.yahoo.com/mrss/\" url=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYbJtGgfxWbAZNnhARDNSNFhsLELRdCUQC9AJgQVTSxMrw0AcWkLlOZYuhzbxW5wUGHjptKYm1Ul758647rbb2p2hdn8ItTxlQN4qJe8CzrLH01dzwV248vhoP27vsIFWikAo0nA/s72-c/Picture+9.png\"
2296
+ height=\"72\" width=\"72\"/><thr:total>3</thr:total></entry></feed>"
2297
+ recorded_at: Tue, 29 Jul 2025 09:55:38 GMT
2298
+ recorded_with: VCR 6.3.1