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.
- checksums.yaml +7 -0
- data/.github/workflows/ruby-tests.yml +32 -0
- data/.gitignore +20 -4
- data/.rubocop.yml +35 -0
- data/.rubocop_todo.yml +22 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +15 -3
- data/Guardfile +9 -4
- data/LICENSE +3 -1
- data/{README.rdoc → README.md} +44 -46
- data/Rakefile +7 -32
- data/earl.gemspec +19 -20
- data/lib/earl/earl.rb +63 -49
- data/lib/earl/scraper.rb +16 -17
- data/lib/earl/version.rb +3 -1
- data/lib/earl.rb +6 -0
- data/spec/fixtures/cassettes/feed/is_atom_feed.yml +2298 -0
- data/spec/fixtures/cassettes/feed/is_rss_feed.yml +48 -0
- data/spec/fixtures/cassettes/feed/no_feed.yml +69 -0
- data/spec/fixtures/cassettes/feed/with_atom_and_rss_feed.yml +1471 -0
- data/spec/fixtures/cassettes/feed/with_rss_feed.yml +47 -0
- data/spec/fixtures/cassettes/oembed/no_oembed.yml +101 -0
- data/spec/fixtures/cassettes/oembed/youtube_oembed.yml +129 -0
- data/spec/integration/feed_spec.rb +54 -54
- data/spec/integration/oembed_spec.rb +23 -27
- data/spec/spec_helper.rb +4 -2
- data/spec/support/fixtures.rb +8 -3
- data/spec/support/vcr.rb +9 -0
- data/spec/unit/earl/earl_spec.rb +5 -6
- data/spec/unit/earl/feed_spec.rb +29 -26
- data/spec/unit/earl/oembed_spec.rb +24 -23
- data/spec/unit/earl/scraper_spec.rb +19 -18
- data/spec/unit/earl_spec.rb +39 -30
- metadata +48 -97
- data/.document +0 -5
- data/.rspec +0 -1
- data/.travis.yml +0 -11
- data/Gemfile.lock +0 -60
- data/script/console +0 -10
@@ -0,0 +1,1471 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://0xfe.blogspot.com/
|
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
|
+
Content-Type:
|
22
|
+
- text/html; charset=UTF-8
|
23
|
+
Expires:
|
24
|
+
- Tue, 29 Jul 2025 09:54:37 GMT
|
25
|
+
Date:
|
26
|
+
- Tue, 29 Jul 2025 09:54:37 GMT
|
27
|
+
Cache-Control:
|
28
|
+
- private, max-age=0
|
29
|
+
Last-Modified:
|
30
|
+
- Mon, 09 Jun 2025 12:31:35 GMT
|
31
|
+
Etag:
|
32
|
+
- W/"d9ecb14bc27900564451bb6132fca8a617847311bf76c3f6e60ee6d70df2730c"
|
33
|
+
X-Content-Type-Options:
|
34
|
+
- nosniff
|
35
|
+
X-Xss-Protection:
|
36
|
+
- 1; mode=block
|
37
|
+
Server:
|
38
|
+
- GSE
|
39
|
+
Alt-Svc:
|
40
|
+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
|
41
|
+
Transfer-Encoding:
|
42
|
+
- chunked
|
43
|
+
body:
|
44
|
+
encoding: ASCII-8BIT
|
45
|
+
string: "<!DOCTYPE html>\n<html class='v2' dir='ltr' xmlns='http://www.w3.org/1999/xhtml'
|
46
|
+
xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data'
|
47
|
+
xmlns:expr='http://www.google.com/2005/gml/expr'>\n<head>\n<link href='https://www.blogger.com/static/v1/widgets/573632073-css_bundle_v2.css'
|
48
|
+
rel='stylesheet' type='text/css'/>\n<!--[if lte IE 8]> <meta content='IE=EmulateIE7'
|
49
|
+
http-equiv='X-UA-Compatible'/> <![endif]-->\n<meta content='width=1100' name='viewport'/>\n<meta
|
50
|
+
content='text/html; charset=UTF-8' http-equiv='Content-Type'/>\n<meta content='blogger'
|
51
|
+
name='generator'/>\n<link href='https://0xfe.blogspot.com/favicon.ico' rel='icon'
|
52
|
+
type='image/x-icon'/>\n<link href='http://0xfe.blogspot.com/' rel='canonical'/>\n<link
|
53
|
+
rel=\"alternate\" type=\"application/atom+xml\" title=\"0xFE - 11111110b -
|
54
|
+
0376 - Atom\" href=\"https://0xfe.blogspot.com/feeds/posts/default\" />\n<link
|
55
|
+
rel=\"alternate\" type=\"application/rss+xml\" title=\"0xFE - 11111110b -
|
56
|
+
0376 - RSS\" href=\"https://0xfe.blogspot.com/feeds/posts/default?alt=rss\"
|
57
|
+
/>\n<link rel=\"service.post\" type=\"application/atom+xml\" title=\"0xFE
|
58
|
+
- 11111110b - 0376 - Atom\" href=\"https://draft.blogger.com/feeds/19544619/posts/default\"
|
59
|
+
/>\n<link rel=\"me\" href=\"https://draft.blogger.com/profile/11179501091623983192\"
|
60
|
+
/>\n<!--Can't find substitution for tag [blog.ieCssRetrofitLinks]-->\n<meta
|
61
|
+
content='http://0xfe.blogspot.com/' property='og:url'/>\n<meta content='0xFE
|
62
|
+
- 11111110b - 0376' property='og:title'/>\n<meta content='' property='og:description'/>\n<title>0xFE
|
63
|
+
- 11111110b - 0376</title>\n<style type='text/css'>@font-face{font-family:'Cardo';font-style:normal;font-weight:400;font-display:swap;src:url(//fonts.gstatic.com/s/cardo/v20/wlp_gwjKBV1pqhvP3IE7225PUCk.woff2)format('woff2');unicode-range:U+0304-0305,U+0308,U+0331,U+10330-1034A;}@font-face{font-family:'Cardo';font-style:normal;font-weight:400;font-display:swap;src:url(//fonts.gstatic.com/s/cardo/v20/wlp_gwjKBV1pqhv03IE7225PUCk.woff2)format('woff2');unicode-range:U+1F00-1FFF;}@font-face{font-family:'Cardo';font-style:normal;font-weight:400;font-display:swap;src:url(//fonts.gstatic.com/s/cardo/v20/wlp_gwjKBV1pqhv73IE7225PUCk.woff2)format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF;}@font-face{font-family:'Cardo';font-style:normal;font-weight:400;font-display:swap;src:url(//fonts.gstatic.com/s/cardo/v20/wlp_gwjKBV1pqhv63IE7225PUCk.woff2)format('woff2');unicode-range:U+0307-0308,U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F;}@font-face{font-family:'Cardo';font-style:normal;font-weight:400;font-display:swap;src:url(//fonts.gstatic.com/s/cardo/v20/wlp_gwjKBV1pqhu63IE7225PUCk.woff2)format('woff2');unicode-range:U+10300-1032F;}@font-face{font-family:'Cardo';font-style:normal;font-weight:400;font-display:swap;src:url(//fonts.gstatic.com/s/cardo/v20/wlp_gwjKBV1pqhvM3IE7225PUCk.woff2)format('woff2');unicode-range:U+16A0-16F8;}@font-face{font-family:'Cardo';font-style:normal;font-weight:400;font-display:swap;src:url(//fonts.gstatic.com/s/cardo/v20/wlp_gwjKBV1pqhv23IE7225PUCk.woff2)format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}@font-face{font-family:'Cardo';font-style:normal;font-weight:400;font-display:swap;src:url(//fonts.gstatic.com/s/cardo/v20/wlp_gwjKBV1pqhv43IE7225P.woff2)format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}</style>\n<style
|
64
|
+
id='page-skin-1' type='text/css'><!--\n/*\n-----------------------------------------------\nBlogger
|
65
|
+
Template Style\nName: Simple\nDesigner: Josh Peterson\nURL: www.noaesthetic.com\n-----------------------------------------------
|
66
|
+
*/\n/* Variable definitions\n====================\n<Variable name=\"keycolor\"
|
67
|
+
description=\"Main Color\" type=\"color\" default=\"#66bbdd\"/>\n<Group description=\"Page
|
68
|
+
Text\" selector=\"body\">\n<Variable name=\"body.font\" description=\"Font\"
|
69
|
+
type=\"font\"\ndefault=\"normal normal 12px Arial, Tahoma, Helvetica, FreeSans,
|
70
|
+
sans-serif\"/>\n<Variable name=\"body.text.color\" description=\"Text Color\"
|
71
|
+
type=\"color\" default=\"#222222\"/>\n</Group>\n<Group description=\"Backgrounds\"
|
72
|
+
selector=\".body-fauxcolumns-outer\">\n<Variable name=\"body.background.color\"
|
73
|
+
description=\"Outer Background\" type=\"color\" default=\"#66bbdd\"/>\n<Variable
|
74
|
+
name=\"content.background.color\" description=\"Main Background\" type=\"color\"
|
75
|
+
default=\"#ffffff\"/>\n<Variable name=\"header.background.color\" description=\"Header
|
76
|
+
Background\" type=\"color\" default=\"transparent\"/>\n</Group>\n<Group description=\"Links\"
|
77
|
+
selector=\".main-outer\">\n<Variable name=\"link.color\" description=\"Link
|
78
|
+
Color\" type=\"color\" default=\"#2288bb\"/>\n<Variable name=\"link.visited.color\"
|
79
|
+
description=\"Visited Color\" type=\"color\" default=\"#888888\"/>\n<Variable
|
80
|
+
name=\"link.hover.color\" description=\"Hover Color\" type=\"color\" default=\"#33aaff\"/>\n</Group>\n<Group
|
81
|
+
description=\"Blog Title\" selector=\".header h1\">\n<Variable name=\"header.font\"
|
82
|
+
description=\"Font\" type=\"font\"\ndefault=\"normal normal 60px Arial, Tahoma,
|
83
|
+
Helvetica, FreeSans, sans-serif\"/>\n<Variable name=\"header.text.color\"
|
84
|
+
description=\"Title Color\" type=\"color\" default=\"#3399bb\" />\n</Group>\n<Group
|
85
|
+
description=\"Blog Description\" selector=\".header .description\">\n<Variable
|
86
|
+
name=\"description.text.color\" description=\"Description Color\" type=\"color\"\ndefault=\"#777777\"
|
87
|
+
/>\n</Group>\n<Group description=\"Tabs Text\" selector=\".tabs-inner .widget
|
88
|
+
li a\">\n<Variable name=\"tabs.font\" description=\"Font\" type=\"font\"\ndefault=\"normal
|
89
|
+
normal 14px Arial, Tahoma, Helvetica, FreeSans, sans-serif\"/>\n<Variable
|
90
|
+
name=\"tabs.text.color\" description=\"Text Color\" type=\"color\" default=\"#999999\"/>\n<Variable
|
91
|
+
name=\"tabs.selected.text.color\" description=\"Selected Color\" type=\"color\"
|
92
|
+
default=\"#000000\"/>\n</Group>\n<Group description=\"Tabs Background\" selector=\".tabs-outer
|
93
|
+
.PageList\">\n<Variable name=\"tabs.background.color\" description=\"Background
|
94
|
+
Color\" type=\"color\" default=\"#f5f5f5\"/>\n<Variable name=\"tabs.selected.background.color\"
|
95
|
+
description=\"Selected Color\" type=\"color\" default=\"#eeeeee\"/>\n</Group>\n<Group
|
96
|
+
description=\"Post Title\" selector=\"h3.post-title, .comments h4\">\n<Variable
|
97
|
+
name=\"post.title.font\" description=\"Font\" type=\"font\"\ndefault=\"normal
|
98
|
+
normal 22px Arial, Tahoma, Helvetica, FreeSans, sans-serif\"/>\n</Group>\n<Group
|
99
|
+
description=\"Date Header\" selector=\".date-header\">\n<Variable name=\"date.header.color\"
|
100
|
+
description=\"Text Color\" type=\"color\"\ndefault=\"#000000\"/>\n<Variable
|
101
|
+
name=\"date.header.background.color\" description=\"Background Color\" type=\"color\"\ndefault=\"transparent\"/>\n</Group>\n<Group
|
102
|
+
description=\"Post Footer\" selector=\".post-footer\">\n<Variable name=\"post.footer.text.color\"
|
103
|
+
description=\"Text Color\" type=\"color\" default=\"#666666\"/>\n<Variable
|
104
|
+
name=\"post.footer.background.color\" description=\"Background Color\" type=\"color\"\ndefault=\"#f9f9f9\"/>\n<Variable
|
105
|
+
name=\"post.footer.border.color\" description=\"Shadow Color\" type=\"color\"
|
106
|
+
default=\"#eeeeee\"/>\n</Group>\n<Group description=\"Gadgets\" selector=\"h2\">\n<Variable
|
107
|
+
name=\"widget.title.font\" description=\"Title Font\" type=\"font\"\ndefault=\"normal
|
108
|
+
bold 11px Arial, Tahoma, Helvetica, FreeSans, sans-serif\"/>\n<Variable name=\"widget.title.text.color\"
|
109
|
+
description=\"Title Color\" type=\"color\" default=\"#000000\"/>\n<Variable
|
110
|
+
name=\"widget.alternate.text.color\" description=\"Alternate Color\" type=\"color\"
|
111
|
+
default=\"#999999\"/>\n</Group>\n<Group description=\"Images\" selector=\".main-inner\">\n<Variable
|
112
|
+
name=\"image.background.color\" description=\"Background Color\" type=\"color\"
|
113
|
+
default=\"#ffffff\"/>\n<Variable name=\"image.border.color\" description=\"Border
|
114
|
+
Color\" type=\"color\" default=\"#eeeeee\"/>\n<Variable name=\"image.text.color\"
|
115
|
+
description=\"Caption Text Color\" type=\"color\" default=\"#000000\"/>\n</Group>\n<Group
|
116
|
+
description=\"Accents\" selector=\".content-inner\">\n<Variable name=\"body.rule.color\"
|
117
|
+
description=\"Separator Line Color\" type=\"color\" default=\"#eeeeee\"/>\n<Variable
|
118
|
+
name=\"tabs.border.color\" description=\"Tabs Border Color\" type=\"color\"
|
119
|
+
default=\"transparent\"/>\n</Group>\n<Variable name=\"body.background\" description=\"Body
|
120
|
+
Background\" type=\"background\"\ncolor=\"#ffffff\" default=\"$(color) none
|
121
|
+
repeat scroll top left\"/>\n<Variable name=\"body.background.override\" description=\"Body
|
122
|
+
Background Override\" type=\"string\" default=\"\"/>\n<Variable name=\"body.background.gradient.cap\"
|
123
|
+
description=\"Body Gradient Cap\" type=\"url\"\ndefault=\"url(//www.blogblog.com/1kt/simple/gradients_light.png)\"/>\n<Variable
|
124
|
+
name=\"body.background.gradient.tile\" description=\"Body Gradient Tile\"
|
125
|
+
type=\"url\"\ndefault=\"url(//www.blogblog.com/1kt/simple/body_gradient_tile_light.png)\"/>\n<Variable
|
126
|
+
name=\"content.background.color.selector\" description=\"Content Background
|
127
|
+
Color Selector\" type=\"string\" default=\".content-inner\"/>\n<Variable name=\"content.padding\"
|
128
|
+
description=\"Content Padding\" type=\"length\" default=\"10px\"/>\n<Variable
|
129
|
+
name=\"content.padding.horizontal\" description=\"Content Horizontal Padding\"
|
130
|
+
type=\"length\" default=\"10px\"/>\n<Variable name=\"content.shadow.spread\"
|
131
|
+
description=\"Content Shadow Spread\" type=\"length\" default=\"40px\"/>\n<Variable
|
132
|
+
name=\"content.shadow.spread.webkit\" description=\"Content Shadow Spread
|
133
|
+
(WebKit)\" type=\"length\" default=\"5px\"/>\n<Variable name=\"content.shadow.spread.ie\"
|
134
|
+
description=\"Content Shadow Spread (IE)\" type=\"length\" default=\"10px\"/>\n<Variable
|
135
|
+
name=\"main.border.width\" description=\"Main Border Width\" type=\"length\"
|
136
|
+
default=\"0\"/>\n<Variable name=\"header.background.gradient\" description=\"Header
|
137
|
+
Gradient\" type=\"url\" default=\"none\"/>\n<Variable name=\"header.shadow.offset.left\"
|
138
|
+
description=\"Header Shadow Offset Left\" type=\"length\" default=\"-1px\"/>\n<Variable
|
139
|
+
name=\"header.shadow.offset.top\" description=\"Header Shadow Offset Top\"
|
140
|
+
type=\"length\" default=\"-1px\"/>\n<Variable name=\"header.shadow.spread\"
|
141
|
+
description=\"Header Shadow Spread\" type=\"length\" default=\"1px\"/>\n<Variable
|
142
|
+
name=\"header.padding\" description=\"Header Padding\" type=\"length\" default=\"30px\"/>\n<Variable
|
143
|
+
name=\"header.border.size\" description=\"Header Border Size\" type=\"length\"
|
144
|
+
default=\"1px\"/>\n<Variable name=\"header.bottom.border.size\" description=\"Header
|
145
|
+
Bottom Border Size\" type=\"length\" default=\"1px\"/>\n<Variable name=\"header.border.horizontalsize\"
|
146
|
+
description=\"Header Horizontal Border Size\" type=\"length\" default=\"0\"/>\n<Variable
|
147
|
+
name=\"description.text.size\" description=\"Description Text Size\" type=\"string\"
|
148
|
+
default=\"140%\"/>\n<Variable name=\"tabs.margin.top\" description=\"Tabs
|
149
|
+
Margin Top\" type=\"length\" default=\"0\" />\n<Variable name=\"tabs.margin.side\"
|
150
|
+
description=\"Tabs Side Margin\" type=\"length\" default=\"30px\" />\n<Variable
|
151
|
+
name=\"tabs.background.gradient\" description=\"Tabs Background Gradient\"
|
152
|
+
type=\"url\"\ndefault=\"url(//www.blogblog.com/1kt/simple/gradients_light.png)\"/>\n<Variable
|
153
|
+
name=\"tabs.border.width\" description=\"Tabs Border Width\" type=\"length\"
|
154
|
+
default=\"1px\"/>\n<Variable name=\"tabs.bevel.border.width\" description=\"Tabs
|
155
|
+
Bevel Border Width\" type=\"length\" default=\"1px\"/>\n<Variable name=\"date.header.padding\"
|
156
|
+
description=\"Date Header Padding\" type=\"string\" default=\"inherit\"/>\n<Variable
|
157
|
+
name=\"date.header.letterspacing\" description=\"Date Header Letter Spacing\"
|
158
|
+
type=\"string\" default=\"inherit\"/>\n<Variable name=\"date.header.margin\"
|
159
|
+
description=\"Date Header Margin\" type=\"string\" default=\"inherit\"/>\n<Variable
|
160
|
+
name=\"post.margin.bottom\" description=\"Post Bottom Margin\" type=\"length\"
|
161
|
+
default=\"25px\"/>\n<Variable name=\"image.border.small.size\" description=\"Image
|
162
|
+
Border Small Size\" type=\"length\" default=\"2px\"/>\n<Variable name=\"image.border.large.size\"
|
163
|
+
description=\"Image Border Large Size\" type=\"length\" default=\"5px\"/>\n<Variable
|
164
|
+
name=\"page.width.selector\" description=\"Page Width Selector\" type=\"string\"
|
165
|
+
default=\".region-inner\"/>\n<Variable name=\"page.width\" description=\"Page
|
166
|
+
Width\" type=\"string\" default=\"auto\"/>\n<Variable name=\"main.section.margin\"
|
167
|
+
description=\"Main Section Margin\" type=\"length\" default=\"15px\"/>\n<Variable
|
168
|
+
name=\"main.padding\" description=\"Main Padding\" type=\"length\" default=\"15px\"/>\n<Variable
|
169
|
+
name=\"main.padding.top\" description=\"Main Padding Top\" type=\"length\"
|
170
|
+
default=\"30px\"/>\n<Variable name=\"main.padding.bottom\" description=\"Main
|
171
|
+
Padding Bottom\" type=\"length\" default=\"30px\"/>\n<Variable name=\"paging.background\"\ncolor=\"#ffffff\"\ndescription=\"Background
|
172
|
+
of blog paging area\" type=\"background\"\ndefault=\"transparent none no-repeat
|
173
|
+
scroll top center\"/>\n<Variable name=\"footer.bevel\" description=\"Bevel
|
174
|
+
border length of footer\" type=\"length\" default=\"0\"/>\n<Variable name=\"mobile.background.overlay\"
|
175
|
+
description=\"Mobile Background Overlay\" type=\"string\"\ndefault=\"transparent
|
176
|
+
none repeat scroll top left\"/>\n<Variable name=\"mobile.background.size\"
|
177
|
+
description=\"Mobile Background Size\" type=\"string\" default=\"auto\"/>\n<Variable
|
178
|
+
name=\"mobile.button.color\" description=\"Mobile Button Color\" type=\"color\"
|
179
|
+
default=\"#ffffff\" />\n<Variable name=\"startSide\" description=\"Side where
|
180
|
+
text starts in blog language\" type=\"automatic\" default=\"left\"/>\n<Variable
|
181
|
+
name=\"endSide\" description=\"Side where text ends in blog language\" type=\"automatic\"
|
182
|
+
default=\"right\"/>\n*/\n/* Content\n-----------------------------------------------
|
183
|
+
*/\nbody, .body-fauxcolumn-outer {\nfont: normal normal 15px Cardo;\ncolor:
|
184
|
+
#000000;\nbackground: #ffffff none repeat scroll top left;\npadding: 0 0 0
|
185
|
+
0;\n}\nhtml body .region-inner {\nmin-width: 0;\nmax-width: 100%;\nwidth:
|
186
|
+
auto;\n}\na:link {\ntext-decoration:none;\ncolor: #0b5394;\n}\na:visited {\ntext-decoration:none;\ncolor:
|
187
|
+
#0b5394;\n}\na:hover {\ntext-decoration:underline;\ncolor: #0b5394;\n}\n.body-fauxcolumn-outer
|
188
|
+
.fauxcolumn-inner {\nbackground: transparent none repeat scroll top left;\n_background-image:
|
189
|
+
none;\n}\n.body-fauxcolumn-outer .cap-top {\nposition: absolute;\nz-index:
|
190
|
+
1;\nheight: 400px;\nwidth: 100%;\nbackground: #ffffff none repeat scroll top
|
191
|
+
left;\n}\n.body-fauxcolumn-outer .cap-top .cap-left {\nwidth: 100%;\nbackground:
|
192
|
+
transparent none repeat-x scroll top left;\n_background-image: none;\n}\n.content-outer
|
193
|
+
{\n-moz-box-shadow: 0 0 0 rgba(0, 0, 0, .15);\n-webkit-box-shadow: 0 0 0 rgba(0,
|
194
|
+
0, 0, .15);\n-goog-ms-box-shadow: 0 0 0 #333333;\nbox-shadow: 0 0 0 rgba(0,
|
195
|
+
0, 0, .15);\nmargin-bottom: 1px;\n}\n.content-inner {\npadding: 10px 40px;\n}\n.content-inner
|
196
|
+
{\nbackground-color: #ffffff;\n}\n/* Header\n-----------------------------------------------
|
197
|
+
*/\n.header-outer {\nbackground: transparent none repeat-x scroll 0 -400px;\n_background-image:
|
198
|
+
none;\n}\n.Header h1 {\nfont: normal normal 42px Cardo;\ncolor: #000000;\ntext-shadow:
|
199
|
+
0 0 0 rgba(0, 0, 0, .2);\n}\n.Header h1 a {\ncolor: #000000;\n}\n.Header .description
|
200
|
+
{\nfont-size: 18px;\ncolor: #000000;\n}\n.header-inner .Header .titlewrapper
|
201
|
+
{\npadding: 22px 0;\n}\n.header-inner .Header .descriptionwrapper {\npadding:
|
202
|
+
0 0;\n}\n/* Tabs\n----------------------------------------------- */\n.tabs-inner
|
203
|
+
.section:first-child {\nborder-top: 0 solid transparent;\n}\n.tabs-inner .section:first-child
|
204
|
+
ul {\nmargin-top: -1px;\nborder-top: 1px solid transparent;\nborder-left:
|
205
|
+
1px solid transparent;\nborder-right: 1px solid transparent;\n}\n.tabs-inner
|
206
|
+
.widget ul {\nbackground: transparent none repeat-x scroll 0 -800px;\n_background-image:
|
207
|
+
none;\nborder-bottom: 1px solid transparent;\nmargin-top: 0;\nmargin-left:
|
208
|
+
-30px;\nmargin-right: -30px;\n}\n.tabs-inner .widget li a {\ndisplay: inline-block;\npadding:
|
209
|
+
.6em 1em;\nfont: normal normal 15px Cardo;\ncolor: #000000;\nborder-left:
|
210
|
+
1px solid #ffffff;\nborder-right: 1px solid transparent;\n}\n.tabs-inner .widget
|
211
|
+
li:first-child a {\nborder-left: none;\n}\n.tabs-inner .widget li.selected
|
212
|
+
a, .tabs-inner .widget li a:hover {\ncolor: #000000;\nbackground-color: #eeeeee;\ntext-decoration:
|
213
|
+
none;\n}\n/* Columns\n----------------------------------------------- */\n.main-outer
|
214
|
+
{\nborder-top: 0 solid transparent;\n}\n.fauxcolumn-left-outer .fauxcolumn-inner
|
215
|
+
{\nborder-right: 1px solid transparent;\n}\n.fauxcolumn-right-outer .fauxcolumn-inner
|
216
|
+
{\nborder-left: 1px solid transparent;\n}\n/* Headings\n-----------------------------------------------
|
217
|
+
*/\nh2 {\nmargin: 0 0 1em 0;\nfont: normal normal 11px Cardo;\ncolor: #000000;\ntext-transform:
|
218
|
+
uppercase;\n}\n/* Widgets\n-----------------------------------------------
|
219
|
+
*/\n.widget .zippy {\ncolor: #999999;\ntext-shadow: 2px 2px 1px rgba(0, 0,
|
220
|
+
0, .1);\n}\n.widget .popular-posts ul {\nlist-style: none;\n}\n/* Posts\n-----------------------------------------------
|
221
|
+
*/\n.date-header span {\nbackground-color: #ffffff;\ncolor: #000000;\npadding:
|
222
|
+
0.4em;\nletter-spacing: 3px;\nmargin: inherit;\n}\n.main-inner {\npadding-top:
|
223
|
+
35px;\npadding-bottom: 65px;\n}\n.main-inner .column-center-inner {\npadding:
|
224
|
+
0 0;\n}\n.main-inner .column-center-inner .section {\nmargin: 0 1em;\n}\n.post
|
225
|
+
{\nmargin: 0 0 45px 0;\n}\nh3.post-title, .comments h4 {\nfont: normal normal
|
226
|
+
22px Cardo;\nmargin: .75em 0 0;\n}\n.post-body {\nfont-size: 110%;\nline-height:
|
227
|
+
1.4;\nposition: relative;\n}\n.post-body img, .post-body .tr-caption-container,
|
228
|
+
.Profile img, .Image img,\n.BlogList .item-thumbnail img {\npadding: 2px;\nbackground:
|
229
|
+
#ffffff;\nborder: 1px solid #dddddd;\n-moz-box-shadow: 1px 1px 5px rgba(0,
|
230
|
+
0, 0, .1);\n-webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, .1);\nbox-shadow:
|
231
|
+
1px 1px 5px rgba(0, 0, 0, .1);\n}\n.post-body img, .post-body .tr-caption-container
|
232
|
+
{\npadding: 5px;\n}\n.post-body .tr-caption-container {\ncolor: #000000;\n}\n.post-body
|
233
|
+
.tr-caption-container img {\npadding: 0;\nbackground: transparent;\nborder:
|
234
|
+
none;\n-moz-box-shadow: 0 0 0 rgba(0, 0, 0, .1);\n-webkit-box-shadow: 0 0
|
235
|
+
0 rgba(0, 0, 0, .1);\nbox-shadow: 0 0 0 rgba(0, 0, 0, .1);\n}\n.post-header
|
236
|
+
{\nmargin: 0 0 1.5em;\nline-height: 1.6;\nfont-size: 90%;\n}\n.post-footer
|
237
|
+
{\nmargin: 20px -2px 0;\npadding: 5px 10px;\ncolor: #000000;\nbackground-color:
|
238
|
+
#ffffff;\nborder-bottom: 1px solid #eeeeee;\nline-height: 1.6;\nfont-size:
|
239
|
+
90%;\n}\n#comments .comment-author {\npadding-top: 1.5em;\nborder-top: 1px
|
240
|
+
solid transparent;\nbackground-position: 0 1.5em;\n}\n#comments .comment-author:first-child
|
241
|
+
{\npadding-top: 0;\nborder-top: none;\n}\n.avatar-image-container {\nmargin:
|
242
|
+
.2em 0 0;\n}\n#comments .avatar-image-container img {\nborder: 1px solid #dddddd;\n}\n/*
|
243
|
+
Comments\n----------------------------------------------- */\n.comments .comments-content
|
244
|
+
.icon.blog-author {\nbackground-repeat: no-repeat;\nbackground-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB9sLFwMeCjjhcOMAAAD+SURBVDjLtZSvTgNBEIe/WRRnm3U8RC1neQdsm1zSBIU9VVF1FkUguQQsD9ITmD7ECZIJSE4OZo9stoVjC/zc7ky+zH9hXwVwDpTAWWLrgS3QAe8AZgaAJI5zYAmc8r0G4AHYHQKVwII8PZrZFsBFkeRCABYiMh9BRUhnSkPTNCtVXYXURi1FpBDgArj8QU1eVXUzfnjv7yP7kwu1mYrkWlU33vs1QNu2qU8pwN0UpKoqokjWwCztrMuBhEhmh8bD5UDqur75asbcX0BGUB9/HAMB+r32hznJgXy2v0sGLBcyAJ1EK3LFcbo1s91JeLwAbwGYu7TP/3ZGfnXYPgAVNngtqatUNgAAAABJRU5ErkJggg==);\n}\n.comments
|
245
|
+
.comments-content .loadmore a {\nborder-top: 1px solid #999999;\nborder-bottom:
|
246
|
+
1px solid #999999;\n}\n.comments .comment-thread.inline-thread {\nbackground-color:
|
247
|
+
#ffffff;\n}\n.comments .continue {\nborder-top: 2px solid #999999;\n}\n/*
|
248
|
+
Accents\n---------------------------------------------- */\n.section-columns
|
249
|
+
td.columns-cell {\nborder-left: 1px solid transparent;\n}\n.blog-pager {\nbackground:
|
250
|
+
transparent url(//www.blogblog.com/1kt/simple/paging_dot.png) repeat-x scroll
|
251
|
+
top center;\n}\n.blog-pager-older-link, .home-link,\n.blog-pager-newer-link
|
252
|
+
{\nbackground-color: #ffffff;\npadding: 5px;\n}\n.footer-outer {\nborder-top:
|
253
|
+
1px dashed #bbbbbb;\n}\n/* Mobile\n-----------------------------------------------
|
254
|
+
*/\nbody.mobile {\nbackground-size: auto;\n}\n.mobile .body-fauxcolumn-outer
|
255
|
+
{\nbackground: transparent none repeat scroll top left;\n}\n.mobile .body-fauxcolumn-outer
|
256
|
+
.cap-top {\nbackground-size: 100% auto;\n}\n.mobile .content-outer {\n-webkit-box-shadow:
|
257
|
+
0 0 3px rgba(0, 0, 0, .15);\nbox-shadow: 0 0 3px rgba(0, 0, 0, .15);\npadding:
|
258
|
+
0 0;\n}\nbody.mobile .AdSense {\nmargin: 0 -0;\n}\n.mobile .tabs-inner .widget
|
259
|
+
ul {\nmargin-left: 0;\nmargin-right: 0;\n}\n.mobile .post {\nmargin: 0;\n}\n.mobile
|
260
|
+
.main-inner .column-center-inner .section {\nmargin: 0;\n}\n.mobile .date-header
|
261
|
+
span {\npadding: 0.1em 10px;\nmargin: 0 -10px;\n}\n.mobile h3.post-title {\nmargin:
|
262
|
+
0;\n}\n.mobile .blog-pager {\nbackground: transparent none no-repeat scroll
|
263
|
+
top center;\n}\n.mobile .footer-outer {\nborder-top: none;\n}\n.mobile .main-inner,
|
264
|
+
.mobile .footer-inner {\nbackground-color: #ffffff;\n}\n.mobile-index-contents
|
265
|
+
{\ncolor: #000000;\n}\n.mobile-link-button {\nbackground-color: #0b5394;\n}\n.mobile-link-button
|
266
|
+
a:link, .mobile-link-button a:visited {\ncolor: #ffffff;\n}\n.mobile .tabs-inner
|
267
|
+
.section:first-child {\nborder-top: none;\n}\n.mobile .tabs-inner .PageList
|
268
|
+
.widget-content {\nbackground-color: #eeeeee;\ncolor: #000000;\nborder-top:
|
269
|
+
1px solid transparent;\nborder-bottom: 1px solid transparent;\n}\n.mobile
|
270
|
+
.tabs-inner .PageList .widget-content .pagelist-arrow {\nborder-left: 1px
|
271
|
+
solid transparent;\n}\n@font-face {\nfont-family: 'Cousine';\nfont-style:
|
272
|
+
normal;\nfont-weight: normal;\nsrc: local('Cousine'), url('//themes.googleusercontent.com/static/fonts/cousine/v2/S-JOYqoHekxYm2RHFzWYtgLUuEpTyoUstqEm5AMlJo4.woff')
|
273
|
+
format('woff');\n}\ncode {\nfont-family: Cousine;\nfont-size: 14px;\ncolor:
|
274
|
+
#333;\n}\n/* Pretty printing styles. Used with prettify.js. */\n.str { color:
|
275
|
+
#080; }\n.kwd { color: #008; }\n.com { color: #800; }\n.typ { color: #606;
|
276
|
+
}\n.lit { color: #066; }\n.pun { color: #660; }\n.pln { color: #000; }\n.tag
|
277
|
+
{ color: #008; }\n.atn { color: #606; }\n.atv { color: #080; }\n.dec { color:
|
278
|
+
#606; }\npre.prettyprint {\npadding: 6px;\nfont-family: Cousine;\nfont-size:
|
279
|
+
12px;\nbackground: #eee;\n}\n@media print {\n.str { color: #060; }\n.kwd {
|
280
|
+
color: #006; font-weight: bold; }\n.com { color: #600; font-style: italic;
|
281
|
+
}\n.typ { color: #404; font-weight: bold; }\n.lit { color: #044; }\n.pun {
|
282
|
+
color: #440; }\n.pln { color: #000; }\n.tag { color: #006; font-weight: bold;
|
283
|
+
}\n.atn { color: #404; }\n.atv { color: #060; }\n}\n#navbar-iframe {\ndisplay:
|
284
|
+
none !important;\n}\n/* Links */\na:hover { text-decoration: none; border-bottom:
|
285
|
+
dotted #3d85c6 1px; }\n.post-title a:link { color: black; }\n.post-title a:active
|
286
|
+
{ color: black; }\n.post-title a:visited { color: black; }\n.post-title a:hover
|
287
|
+
{ color: black; }\n/* Remove line between posts */\ndiv.post-footer { border-bottom:
|
288
|
+
white; }\n/* Remove footer */\nfooter { display: none; }\n/* Clean up pager
|
289
|
+
*/\ndiv.blog-pager { background: white }\ndiv.blog-pager a.home-link { display:
|
290
|
+
none }\nspan#blog-pager-older-link { float: left; font-size: 20px; }\ndiv.feed-links
|
291
|
+
{ display: none; }\n/* Neater looking headers */\nh3 { font-weight: normal;
|
292
|
+
}@font-face {\nfont-family: 'Cousine';\nfont-style: normal;\nfont-weight:
|
293
|
+
normal;\nsrc: local('Cousine'), url('//themes.googleusercontent.com/static/fonts/cousine/v2/S-JOYqoHekxYm2RHFzWYtgLUuEpTyoUstqEm5AMlJo4.woff')
|
294
|
+
format('woff');\n}\ncode {\nfont-family: Cousine;\nfont-size: 14px;\ncolor:
|
295
|
+
#333;\n}\n/* Pretty printing styles. Used with prettify.js. */\n.str { color:
|
296
|
+
#080; }\n.kwd { color: #008; }\n.com { color: #800; }\n.typ { color: #606;
|
297
|
+
}\n.lit { color: #066; }\n.pun { color: #660; }\n.pln { color: #000; }\n.tag
|
298
|
+
{ color: #008; }\n.atn { color: #606; }\n.atv { color: #080; }\n.dec { color:
|
299
|
+
#606; }\npre.prettyprint {\npadding: 6px;\nfont-family: Cousine;\nfont-size:
|
300
|
+
12px;\nbackground: #eee;\n}\n@media print {\n.str { color: #060; }\n.kwd {
|
301
|
+
color: #006; font-weight: bold; }\n.com { color: #600; font-style: italic;
|
302
|
+
}\n.typ { color: #404; font-weight: bold; }\n.lit { color: #044; }\n.pun {
|
303
|
+
color: #440; }\n.pln { color: #000; }\n.tag { color: #006; font-weight: bold;
|
304
|
+
}\n.atn { color: #404; }\n.atv { color: #060; }\n}\n#navbar-iframe {\ndisplay:
|
305
|
+
none !important;\n}\n/* Links */\na:hover { text-decoration: none; border-bottom:
|
306
|
+
dotted #3d85c6 1px; }\n.post-title a:link { color: black; }\n.post-title a:active
|
307
|
+
{ color: black; }\n.post-title a:visited { color: black; }\n.post-title a:hover
|
308
|
+
{ color: black; }\n/* Remove line between posts */\ndiv.post-footer { border-bottom:
|
309
|
+
white; }\n/* Remove footer */\nfooter { display: none; }\n/* Clean up pager
|
310
|
+
*/\ndiv.blog-pager { background: white }\ndiv.blog-pager a.home-link { display:
|
311
|
+
none }\nspan#blog-pager-older-link { float: left; font-size: 20px; }\ndiv.feed-links
|
312
|
+
{ display: none; }\n/* Neater looking headers */\nh3 { font-weight: normal;
|
313
|
+
}@font-face {\nfont-family: 'Cousine';\nfont-style: normal;\nfont-weight:
|
314
|
+
normal;\nsrc: local('Cousine'), url('//themes.googleusercontent.com/static/fonts/cousine/v2/S-JOYqoHekxYm2RHFzWYtgLUuEpTyoUstqEm5AMlJo4.woff')
|
315
|
+
format('woff');\n}\ncode {\nfont-family: Cousine;\nfont-size: 14px;\ncolor:
|
316
|
+
#333;\n}\n/* Pretty printing styles. Used with prettify.js. */\n.str { color:
|
317
|
+
#080; }\n.kwd { color: #008; }\n.com { color: #800; }\n.typ { color: #606;
|
318
|
+
}\n.lit { color: #066; }\n.pun { color: #660; }\n.pln { color: #000; }\n.tag
|
319
|
+
{ color: #008; }\n.atn { color: #606; }\n.atv { color: #080; }\n.dec { color:
|
320
|
+
#606; }\npre.prettyprint {\npadding: 6px;\nfont-family: Cousine;\nfont-size:
|
321
|
+
12px;\nbackground: #eee;\n}\n@media print {\n.str { color: #060; }\n.kwd {
|
322
|
+
color: #006; font-weight: bold; }\n.com { color: #600; font-style: italic;
|
323
|
+
}\n.typ { color: #404; font-weight: bold; }\n.lit { color: #044; }\n.pun {
|
324
|
+
color: #440; }\n.pln { color: #000; }\n.tag { color: #006; font-weight: bold;
|
325
|
+
}\n.atn { color: #404; }\n.atv { color: #060; }\n}\n#navbar-iframe {\ndisplay:
|
326
|
+
none !important;\n}\n/* Links */\na:hover { text-decoration: none; border-bottom:
|
327
|
+
dotted #3d85c6 1px; }\n.post-title a:link { color: black; }\n.post-title a:active
|
328
|
+
{ color: black; }\n.post-title a:visited { color: black; }\n.post-title a:hover
|
329
|
+
{ color: black; }\n/* Remove line between posts */\ndiv.post-footer { border-bottom:
|
330
|
+
white; }\n/* Remove footer */\nfooter { display: none; }\n/* Clean up pager
|
331
|
+
*/\ndiv.blog-pager { background: white }\ndiv.blog-pager a.home-link { display:
|
332
|
+
none }\nspan#blog-pager-older-link { float: left; font-size: 20px; }\ndiv.feed-links
|
333
|
+
{ display: none; }\n/* Neater looking headers */\nh3 { font-weight: normal;
|
334
|
+
}@font-face {\nfont-family: 'Cousine';\nfont-style: normal;\nfont-weight:
|
335
|
+
normal;\nsrc: local('Cousine'), url('//themes.googleusercontent.com/static/fonts/cousine/v2/S-JOYqoHekxYm2RHFzWYtgLUuEpTyoUstqEm5AMlJo4.woff')
|
336
|
+
format('woff');\n}\ncode {\nfont-family: Cousine;\nfont-size: 14px;\ncolor:
|
337
|
+
#333;\n}\n/* Pretty printing styles. Used with prettify.js. */\n.str { color:
|
338
|
+
#080; }\n.kwd { color: #008; }\n.com { color: #800; }\n.typ { color: #606;
|
339
|
+
}\n.lit { color: #066; }\n.pun { color: #660; }\n.pln { color: #000; }\n.tag
|
340
|
+
{ color: #008; }\n.atn { color: #606; }\n.atv { color: #080; }\n.dec { color:
|
341
|
+
#606; }\npre.prettyprint {\npadding: 6px;\nfont-family: Cousine;\nfont-size:
|
342
|
+
12px;\nbackground: #eee;\n}\n@media print {\n.str { color: #060; }\n.kwd {
|
343
|
+
color: #006; font-weight: bold; }\n.com { color: #600; font-style: italic;
|
344
|
+
}\n.typ { color: #404; font-weight: bold; }\n.lit { color: #044; }\n.pun {
|
345
|
+
color: #440; }\n.pln { color: #000; }\n.tag { color: #006; font-weight: bold;
|
346
|
+
}\n.atn { color: #404; }\n.atv { color: #060; }\n}\n#navbar-iframe {\ndisplay:
|
347
|
+
none !important;\n}\n/* Links */\na:hover { text-decoration: none; border-bottom:
|
348
|
+
dotted #3d85c6 1px; }\n.post-title a:link { color: black; }\n.post-title a:active
|
349
|
+
{ color: black; }\n.post-title a:visited { color: black; }\n.post-title a:hover
|
350
|
+
{ color: black; }\n/* Remove line between posts */\ndiv.post-footer { border-bottom:
|
351
|
+
white; }\n/* Remove footer */\nfooter { display: none; }\n/* Clean up pager
|
352
|
+
*/\ndiv.blog-pager { background: white }\ndiv.blog-pager a.home-link { display:
|
353
|
+
none }\nspan#blog-pager-older-link { float: left; font-size: 20px; }\ndiv.feed-links
|
354
|
+
{ display: none; }\n/* Neater looking headers */\nh3 { font-weight: normal;
|
355
|
+
}\n@font-face {\nfont-family: 'Cousine';\nfont-style: normal;\nfont-weight:
|
356
|
+
normal;\nsrc: local('Cousine'), url('//themes.googleusercontent.com/static/fonts/cousine/v2/S-JOYqoHekxYm2RHFzWYtgLUuEpTyoUstqEm5AMlJo4.woff')
|
357
|
+
format('woff');\n}\ncode {\nfont-family: Cousine;\nfont-size: 0.8em;\ncolor:
|
358
|
+
black;\n}\n/* Pretty printing styles. Used with prettify.js. */\n.str { color:
|
359
|
+
#080; }\n.kwd { color: #008; }\n.com { color: #800; }\n.typ { color: #606;
|
360
|
+
}\n.lit { color: #066; }\n.pun { color: #660; }\n.pln { color: #000; }\n.tag
|
361
|
+
{ color: #008; }\n.atn { color: #606; }\n.atv { color: #080; }\n.dec { color:
|
362
|
+
#606; }\npre.prettyprint {\npadding: 6px;\nfont-family: Cousine;\nfont-size:
|
363
|
+
12px;\nbackground: #eee;\n}\n@media print {\n.str { color: #060; }\n.kwd {
|
364
|
+
color: #006; font-weight: bold; }\n.com { color: #600; font-style: italic;
|
365
|
+
}\n.typ { color: #404; font-weight: bold; }\n.lit { color: #044; }\n.pun {
|
366
|
+
color: #440; }\n.pln { color: #000; }\n.tag { color: #006; font-weight: bold;
|
367
|
+
}\n.atn { color: #404; }\n.atv { color: #060; }\n}\n#navbar-iframe {\ndisplay:
|
368
|
+
none !important;\n}\n/* Links */\na:hover { text-decoration: none; border-bottom:
|
369
|
+
dotted #3d85c6 1px; }\n.post-title a:link { color: black; }\n.post-title a:active
|
370
|
+
{ color: black; }\n.post-title a:visited { color: black; }\n.post-title a:hover
|
371
|
+
{ color: black; }\n/* Remove line between posts */\ndiv.post-footer { border-bottom:
|
372
|
+
white; }\n/* Remove footer */\nfooter { display: none; }\n/* Clean up pager
|
373
|
+
*/\ndiv.blog-pager { background: white }\ndiv.blog-pager a.home-link { display:
|
374
|
+
none }\nspan#blog-pager-older-link { float: left; font-size: 20px; }\ndiv.feed-links
|
375
|
+
{ display: none; }\n/* Neater looking headers */\nh3 { font-weight: bold;
|
376
|
+
}\nh1, .title, .titlewrapper, .description, .descriptionwrapper {\ndisplay:
|
377
|
+
inline;\nmargin: 0;\npadding: 0;\n}\np.description > span {\ndisplay: block;\npadding:
|
378
|
+
0;\nmargin-top: 0px;\nfont-size: 1rem;\n}\n--></style>\n<style id='template-skin-1'
|
379
|
+
type='text/css'><!--\nbody {\nmin-width: 940px;\n}\n.content-outer, .content-fauxcolumn-outer,
|
380
|
+
.region-inner {\nmin-width: 940px;\nmax-width: 940px;\n_width: 940px;\n}\n.main-inner
|
381
|
+
.columns {\npadding-left: 0px;\npadding-right: 200px;\n}\n.main-inner .fauxcolumn-center-outer
|
382
|
+
{\nleft: 0px;\nright: 200px;\n/* IE6 does not respect left and right together
|
383
|
+
*/\n_width: expression(this.parentNode.offsetWidth -\nparseInt(\"0px\") -\nparseInt(\"200px\")
|
384
|
+
+ 'px');\n}\n.main-inner .fauxcolumn-left-outer {\nwidth: 0px;\n}\n.main-inner
|
385
|
+
.fauxcolumn-right-outer {\nwidth: 200px;\n}\n.main-inner .column-left-outer
|
386
|
+
{\nwidth: 0px;\nright: 100%;\nmargin-left: -0px;\n}\n.main-inner .column-right-outer
|
387
|
+
{\nwidth: 200px;\nmargin-right: -200px;\n}\n#layout {\nmin-width: 0;\n}\n#layout
|
388
|
+
.content-outer {\nmin-width: 0;\nwidth: 800px;\n}\n#layout .region-inner {\nmin-width:
|
389
|
+
0;\nwidth: auto;\n}\n--></style>\n<link href='https://draft.blogger.com/dyn-css/authorization.css?targetBlogID=19544619&zx=e0a1d691-0381-419a-86f9-de283fff86a9'
|
390
|
+
media='none' onload='if(media!='all')media='all'' rel='stylesheet'/><noscript><link
|
391
|
+
href='https://draft.blogger.com/dyn-css/authorization.css?targetBlogID=19544619&zx=e0a1d691-0381-419a-86f9-de283fff86a9'
|
392
|
+
rel='stylesheet'/></noscript>\n<meta name='google-adsense-platform-account'
|
393
|
+
content='ca-host-pub-1556223355139109'/>\n<meta name='google-adsense-platform-domain'
|
394
|
+
content='blogspot.com'/>\n\n</head>\n<body class='loading'>\n<div class='navbar
|
395
|
+
no-items section' id='navbar'></div>\n<div class='body-fauxcolumns'>\n<div
|
396
|
+
class='fauxcolumn-outer body-fauxcolumn-outer'>\n<div class='cap-top'>\n<div
|
397
|
+
class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n<div class='fauxborder-left'>\n<div
|
398
|
+
class='fauxborder-right'></div>\n<div class='fauxcolumn-inner'>\n</div>\n</div>\n<div
|
399
|
+
class='cap-bottom'>\n<div class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n</div>\n<div
|
400
|
+
class='content'>\n<div class='content-fauxcolumns'>\n<div class='fauxcolumn-outer
|
401
|
+
content-fauxcolumn-outer'>\n<div class='cap-top'>\n<div class='cap-left'></div>\n<div
|
402
|
+
class='cap-right'></div>\n</div>\n<div class='fauxborder-left'>\n<div class='fauxborder-right'></div>\n<div
|
403
|
+
class='fauxcolumn-inner'>\n</div>\n</div>\n<div class='cap-bottom'>\n<div
|
404
|
+
class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n</div>\n<div
|
405
|
+
class='content-outer'>\n<div class='content-cap-top cap-top'>\n<div class='cap-left'></div>\n<div
|
406
|
+
class='cap-right'></div>\n</div>\n<div class='fauxborder-left content-fauxborder-left'>\n<div
|
407
|
+
class='fauxborder-right content-fauxborder-right'></div>\n<div class='content-inner'>\n<header>\n<div
|
408
|
+
class='header-outer'>\n<div class='header-cap-top cap-top'>\n<div class='cap-left'></div>\n<div
|
409
|
+
class='cap-right'></div>\n</div>\n<div class='fauxborder-left header-fauxborder-left'>\n<div
|
410
|
+
class='fauxborder-right header-fauxborder-right'></div>\n<div class='region-inner
|
411
|
+
header-inner'>\n<div class='header section' id='header'><div class='widget
|
412
|
+
Header' data-version='1' id='Header1'>\n<div id='header-inner'>\n<div class='titlewrapper'>\n<h1
|
413
|
+
class='title'>\n0xFE - 11111110b - 0376\n</h1>\n</div>\n<div class='descriptionwrapper'>\n<p
|
414
|
+
class='description'><span>\n</span></p>\n</div>\n</div>\n</div></div>\n</div>\n</div>\n<div
|
415
|
+
class='header-cap-bottom cap-bottom'>\n<div class='cap-left'></div>\n<div
|
416
|
+
class='cap-right'></div>\n</div>\n</div>\n</header>\n<div class='tabs-outer'>\n<div
|
417
|
+
class='tabs-cap-top cap-top'>\n<div class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n<div
|
418
|
+
class='fauxborder-left tabs-fauxborder-left'>\n<div class='fauxborder-right
|
419
|
+
tabs-fauxborder-right'></div>\n<div class='region-inner tabs-inner'>\n<div
|
420
|
+
class='tabs no-items section' id='crosscol'></div>\n<div class='tabs no-items
|
421
|
+
section' id='crosscol-overflow'></div>\n</div>\n</div>\n<div class='tabs-cap-bottom
|
422
|
+
cap-bottom'>\n<div class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n<div
|
423
|
+
class='main-outer'>\n<div class='main-cap-top cap-top'>\n<div class='cap-left'></div>\n<div
|
424
|
+
class='cap-right'></div>\n</div>\n<div class='fauxborder-left main-fauxborder-left'>\n<div
|
425
|
+
class='fauxborder-right main-fauxborder-right'></div>\n<div class='region-inner
|
426
|
+
main-inner'>\n<div class='columns fauxcolumns'>\n<div class='fauxcolumn-outer
|
427
|
+
fauxcolumn-center-outer'>\n<div class='cap-top'>\n<div class='cap-left'></div>\n<div
|
428
|
+
class='cap-right'></div>\n</div>\n<div class='fauxborder-left'>\n<div class='fauxborder-right'></div>\n<div
|
429
|
+
class='fauxcolumn-inner'>\n</div>\n</div>\n<div class='cap-bottom'>\n<div
|
430
|
+
class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n<div
|
431
|
+
class='fauxcolumn-outer fauxcolumn-left-outer'>\n<div class='cap-top'>\n<div
|
432
|
+
class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n<div class='fauxborder-left'>\n<div
|
433
|
+
class='fauxborder-right'></div>\n<div class='fauxcolumn-inner'>\n</div>\n</div>\n<div
|
434
|
+
class='cap-bottom'>\n<div class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n<div
|
435
|
+
class='fauxcolumn-outer fauxcolumn-right-outer'>\n<div class='cap-top'>\n<div
|
436
|
+
class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n<div class='fauxborder-left'>\n<div
|
437
|
+
class='fauxborder-right'></div>\n<div class='fauxcolumn-inner'>\n</div>\n</div>\n<div
|
438
|
+
class='cap-bottom'>\n<div class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n<!--
|
439
|
+
corrects IE6 width calculation -->\n<div class='columns-inner'>\n<div class='column-center-outer'>\n<div
|
440
|
+
class='column-center-inner'>\n<div class='main section' id='main'><div class='widget
|
441
|
+
Blog' data-version='1' id='Blog1'>\n<div class='blog-posts hfeed'>\n\n <div
|
442
|
+
class=\"date-outer\">\n \n<h2 class='date-header'><span>Friday, December
|
443
|
+
22, 2023</span></h2>\n\n <div class=\"date-posts\">\n \n<div
|
444
|
+
class='post-outer'>\n<div class='post hentry'>\n<a name='1927090407427133388'></a>\n<h3
|
445
|
+
class='post-title entry-title'>\n<a href='https://0xfe.blogspot.com/2023/12/the-firewall-guy.html'>The
|
446
|
+
Firewall Guy</a>\n</h3>\n<div class='post-header'>\n<div class='post-header-line-1'></div>\n</div>\n<div
|
447
|
+
class='post-body entry-content' id='post-body-1927090407427133388'>\n<p>About
|
448
|
+
20 years ago, I worked as an independent consultant at a major telecom. I
|
449
|
+
wrote a program that did some stuff, and couldn't get it to communicate on
|
450
|
+
the network.</p><p>Developers there didn't get root on a machine, and there
|
451
|
+
was limited access to tooling, so I reached out to the sysadmin. A day later
|
452
|
+
he came back to me and said, \"everything here's good, check with the firewall
|
453
|
+
guy.\"</p><p>The firewall guy. It took me a day to track down the firewall
|
454
|
+
guy.</p><p>\"Umm... Mr. Firewall guy, can you help me out with this problem?\"</p><p>Firewall
|
455
|
+
guy was kinda grumpy. He begrudgingly collected some information, begrudgingly
|
456
|
+
looked at stuff. \"Firewall's fine. Talk to the network guy.\"</p><p>Two days
|
457
|
+
later, I found the network guy, who was nicer: \"Network's good. Talk to the
|
458
|
+
firewall guy.\"</p><p>\"I did a couple of days ago. Firewall guy says everything's
|
459
|
+
fine.\"</p><p>\"Okay, then you're probably resolving to the wrong addresses.
|
460
|
+
Talk to the DNS guy.\"</p><p>It took me many days to find the DNS guy. DNS
|
461
|
+
guy left six months ago. Firewall guy was now also DNS guy.</p><p>I did not
|
462
|
+
like Firewall guy, but I didn't have a choice: \"So Mr. Firewall guy, Network
|
463
|
+
guy says it might be a DNS issue.\"</p><p>\"You again. It definitely not a
|
464
|
+
Firewall or DNS problem.\"</p><p>At this point I was kinda stuck. I was accountable
|
465
|
+
for delivering something that works, but had no agency to actually deliver
|
466
|
+
the thing.</p><p>As I was thinking about what to do next, Firewall guy called
|
467
|
+
me on my desk phone: \"try your thing again.\" I tried my thing again. \"Thanks
|
468
|
+
very much, that worked! What was wrong?\"</p><p>\"It's complicated. <hangs
|
469
|
+
up>\" I took a peek and saw the DNS entries didn't change. It was probably
|
470
|
+
the firewall.</p><p>It took me about two days to build the thing, and eight
|
471
|
+
days to navigate their bureaucracy. I billed them for ten days of my time,
|
472
|
+
and apologized for taking so long.</p><p>The manager replied back right away:
|
473
|
+
\"Oh that was quick! No need to apologize. Our regular consulting firm estimated
|
474
|
+
2 people 3 months. Can you come by next week for your next project?\"</p>\n<div
|
475
|
+
style='clear: both;'></div>\n</div>\n<div class='post-footer'>\n<div class='post-footer-line
|
476
|
+
post-footer-line-1'><span class='post-author vcard'>\n</span>\n<span class='post-timestamp'>\n</span>\n<span
|
477
|
+
class='post-comment-link'>\n<a class='comment-link' href='https://0xfe.blogspot.com/2023/12/the-firewall-guy.html#comment-form'
|
478
|
+
onclick=''>0\ncomments</a>\n</span>\n<span class='post-icons'>\n</span>\n<div
|
479
|
+
class='post-share-buttons goog-inline-block'>\n</div>\n</div>\n<div class='post-footer-line
|
480
|
+
post-footer-line-2'></div>\n<div class='post-footer-line post-footer-line-3'></div>\n</div>\n</div>\n</div>\n\n
|
481
|
+
\ </div></div>\n \n\n <div class=\"date-outer\">\n
|
482
|
+
\ \n<h2 class='date-header'><span>Monday, March 02, 2020</span></h2>\n\n
|
483
|
+
\ <div class=\"date-posts\">\n \n<div class='post-outer'>\n<div
|
484
|
+
class='post hentry'>\n<a name='1309421684262886131'></a>\n<h3 class='post-title
|
485
|
+
entry-title'>\n<a href='https://0xfe.blogspot.com/2020/03/generating-spectrograms-with-neural.html'>Generating
|
486
|
+
Spectrograms with Neural Networks</a>\n</h3>\n<div class='post-header'>\n<div
|
487
|
+
class='post-header-line-1'></div>\n</div>\n<div class='post-body entry-content'
|
488
|
+
id='post-body-1309421684262886131'>\n<div dir=\"ltr\" style=\"text-align:
|
489
|
+
left;\" trbidi=\"on\">\nIn a previous experiments, I used <i><a href=\"https://en.wikipedia.org/wiki/Spectrogram\">spectrograms</a></i> instead
|
490
|
+
of raw audio as inputs to neural networks, while training them to recognize
|
491
|
+
pitches, intervals, and chords.<br />\n<br />\nI found that feeding the networks
|
492
|
+
raw audio data got nowhere. Training was extremely slow, and losses seemed
|
493
|
+
to be bounded at unacceptably high values. After switching to spectrograms,
|
494
|
+
the networks started learning almost immediately -- it was quite remarkable!<br
|
495
|
+
/>\n<br />\nThis post is about <i><b>generating</b></i> spectrograms with
|
496
|
+
neural networks.<br />\n<br />\n<table align=\"center\" cellpadding=\"0\"
|
497
|
+
cellspacing=\"0\" class=\"tr-caption-container\" style=\"margin-left: auto;
|
498
|
+
margin-right: auto; text-align: center;\"><tbody>\n<tr><td style=\"text-align:
|
499
|
+
center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDM2rOgj22MvXc9tLVrlD3jey8LAzjuXVUJljzhyPgaqEOluN1Fmtta9Iubv_X3rmQHWTv6y236myxVY_Hv-XBEeb3zkSPVbTYSxoW3Gk18XsOf-o9e9nEUzKDmwLaj4K6McaXEg/s1600/Screen+Shot+2020-03-02+at+11.17.26+AM.png\"
|
500
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
501
|
+
data-original-height=\"684\" data-original-width=\"1316\" height=\"332\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDM2rOgj22MvXc9tLVrlD3jey8LAzjuXVUJljzhyPgaqEOluN1Fmtta9Iubv_X3rmQHWTv6y236myxVY_Hv-XBEeb3zkSPVbTYSxoW3Gk18XsOf-o9e9nEUzKDmwLaj4K6McaXEg/s640/Screen+Shot+2020-03-02+at+11.17.26+AM.png\"
|
502
|
+
width=\"640\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
503
|
+
center;\">These spectrograms were generated by a Neural Network</td></tr>\n</tbody></table>\n<br
|
504
|
+
/>\n<h3 style=\"text-align: left;\">\nOn Spectrograms</h3>\n<div>\n<br /></div>\nSpectrograms
|
505
|
+
are 2-dimensional visual representations of slices of audio (or really, any
|
506
|
+
signal.) On the <i>x-axis</i> of a spectrogram is time, and on the <i>y-axis</i>
|
507
|
+
is frequency.<br />\n<br />\n<table align=\"center\" cellpadding=\"0\" cellspacing=\"0\"
|
508
|
+
class=\"tr-caption-container\" style=\"margin-left: auto; margin-right: auto;
|
509
|
+
text-align: center;\"><tbody>\n<tr><td style=\"text-align: center;\"><a href=\"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\"
|
510
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
511
|
+
data-original-height=\"526\" data-original-width=\"820\" height=\"256\" src=\"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\"
|
512
|
+
width=\"400\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
513
|
+
center;\">A Violin playing A4 (440hz)</td></tr>\n</tbody></table>\n<br />\nBecause
|
514
|
+
the data is correlated well along both dimensions, spectrograms lend themselves
|
515
|
+
well to both human analysis and convolutional neural networks.<br />\n<br
|
516
|
+
/>\nSo, I wondered, why can't the networks learn the spectrograms themselves?
|
517
|
+
Under the covers, spectrograms are built with <a href=\"https://en.wikipedia.org/wiki/Short-time_Fourier_transform\">STFTs</a>,
|
518
|
+
which are entirely linear operations on data -- you slide a window over the
|
519
|
+
data at some stride length, then perform a discrete Fourier transform to get
|
520
|
+
the frequency components of the window.<br />\n<br />\nSince the transformation
|
521
|
+
is entirely linear, all you need is one network layer, no activations, no
|
522
|
+
biases. This should theoretically collapse down to a simple regression problem.
|
523
|
+
Right? Let's find out.<br />\n<br />\n<h3 style=\"text-align: left;\">\nGenerating
|
524
|
+
Training Data</h3>\n<div>\n<br /></div>\nWe start by synthesizing some training
|
525
|
+
data. To keep things simple, let's assume that we want to generate spectrograms
|
526
|
+
of 25ms of audio sampled at 8khz, which is 2000 samples. Round up (in binary)
|
527
|
+
to 2048 to make things GPU friendly.<br />\n<br />\nThe underlying <a href=\"https://en.wikipedia.org/wiki/Short-time_Fourier_transform\">STFT</a>
|
528
|
+
will use a <a href=\"https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows\">hanning
|
529
|
+
window</a> of size 256, <a href=\"https://en.wikipedia.org/wiki/Fast_Fourier_transform\">FFT</a>
|
530
|
+
size of 256, and a stride length of 250, producing <b>33x129</b> images. That's
|
531
|
+
33 frequency-domain slices (along the time axis) capped at <a href=\"https://en.wikipedia.org/wiki/Nyquist_frequency\">128hz</a>.<br
|
532
|
+
/>\n<br />\n<script src=\"https://gist.github.com/0xfe/08204e72217ce29f06ac4c1ba7dd4d39.js\"></script>\n\nNote
|
533
|
+
that the spectrograms return complex values. We want to make sure the networks
|
534
|
+
can learn to completely reconstruct both the magnitude and phase portions
|
535
|
+
of the signal. Also note that we're going to teach our network how to compute
|
536
|
+
hanning windows.<br />\n<br />\nHere's the code to generate the training data
|
537
|
+
-- we calculate <i>batch_size</i> (15,000) examples, each with 2048 samples
|
538
|
+
and assign them to <i>xs</i>. We then calculate their spectrograms and assign
|
539
|
+
them to <i>ys</i> (the targets.)<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/0da873c4ee32ff1bbb5d22b9f0b49817.js\"></script>\n\nNote
|
540
|
+
that we separate the real and imaginary components of the spectrogram and
|
541
|
+
simply stack one atop the other. We also don't scale or normalize the data
|
542
|
+
in any way. <b>Let the network figure all that out! :-)</b><br />\n<br />\n<h3
|
543
|
+
style=\"text-align: left;\">\nBuilding the Model</h3>\n<br />\nNow the fun
|
544
|
+
part. We build a single-layer network with <i>2048</i> inputs for the audio
|
545
|
+
slice, and <i><b>row * col</b></i> outputs for the image (<b><i>times two</i></b>
|
546
|
+
to hold the real and imaginary components of the outputs.) Since the outputs
|
547
|
+
are strictly a linear function of the inputs, we don't need a bias term or
|
548
|
+
activation functions.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/bd8db7b878cbef048543e58d850bab67.js\"></script>\n\n<br
|
549
|
+
/>\nAgain, this is really just linear regression. <i>With, oh, about <b>17
|
550
|
+
million variables!</b></i><br />\n<br />\n<div class=\"separator\" style=\"clear:
|
551
|
+
both; text-align: center;\">\n<a href=\"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\"
|
552
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
553
|
+
data-original-height=\"320\" data-original-width=\"1140\" height=\"176\" src=\"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\"
|
554
|
+
width=\"640\" /></a></div>\n<br />\nThis model trains very fast. In 4 epochs
|
555
|
+
(about 80 seconds), the loss drops to 3.0e-08, which is sufficient for our
|
556
|
+
experiments, and in 10 epochs (about 7 minutes), we can drop it all the way
|
557
|
+
to 2.0e-15.<br />\n<br />\n<div class=\"separator\" style=\"clear: both; text-align:
|
558
|
+
center;\">\n<a href=\"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\"
|
559
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
560
|
+
data-original-height=\"686\" data-original-width=\"1390\" height=\"313\" src=\"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\"
|
561
|
+
width=\"640\" /></a></div>\n<br />\n<h3 style=\"text-align: left;\">\nThe
|
562
|
+
Real Test</h3>\n<br />\nOur model is ready. Let's see how well this does on
|
563
|
+
unseen data. We generate a slice of audio playing four tones, and compare
|
564
|
+
scipy's spectrogram function with our neural network.<br />\n<br />\n<script
|
565
|
+
src=\"https://gist.github.com/0xfe/231d285b53f15b0ace08f93934100e6c.js\"></script>\n\n<br
|
566
|
+
/>\n<table align=\"center\" cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\"
|
567
|
+
style=\"margin-left: auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td
|
568
|
+
style=\"text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-ASUhhLR9dlj1s5OCAKk0f341ND0VOaDihlVFDCxqF6nYBaQ0kUZA8DdplPiOCe0g7wlQU28KEk75bXY_7cbiK1zz6KHszHnhrit5AWwqaloynohIeJWKBR8Cg6RybvNIvYXF-w/s1600/Screen+Shot+2020-03-01+at+12.28.19+PM.png\"
|
569
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
570
|
+
data-original-height=\"384\" data-original-width=\"1444\" height=\"170\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-ASUhhLR9dlj1s5OCAKk0f341ND0VOaDihlVFDCxqF6nYBaQ0kUZA8DdplPiOCe0g7wlQU28KEk75bXY_7cbiK1zz6KHszHnhrit5AWwqaloynohIeJWKBR8Cg6RybvNIvYXF-w/s640/Screen+Shot+2020-03-01+at+12.28.19+PM.png\"
|
571
|
+
width=\"640\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
572
|
+
center;\">Left: SciPy, Right: Neural Network</td></tr>\n</tbody></table>\n<br
|
573
|
+
/>\nWow, that's actually pretty good, however when we look at a log-scaled
|
574
|
+
version, you can see noise in the network-generated one.<br />\n<br />\n<script
|
575
|
+
src=\"https://gist.github.com/0xfe/77110315b05d55bad70d9a20dcc2594e.js\"></script>\n\n<br
|
576
|
+
/>\n<table align=\"center\" cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\"
|
577
|
+
style=\"margin-left: auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td
|
578
|
+
style=\"text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVkxUewAMYUH4TCma7eMbiYgKrY9dVsgrRaE_4yYizNhSg8sAStsvmBkuPtpakQBwpj_YP2jf6Z7PUsULu37T4nlDA8K1B-yArvWpHbjLoJkA5VXS0uvHvU0hWoGGRjPmenOoegA/s1600/Screen+Shot+2020-03-01+at+12.28.29+PM.png\"
|
579
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
580
|
+
data-original-height=\"398\" data-original-width=\"1434\" height=\"176\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVkxUewAMYUH4TCma7eMbiYgKrY9dVsgrRaE_4yYizNhSg8sAStsvmBkuPtpakQBwpj_YP2jf6Z7PUsULu37T4nlDA8K1B-yArvWpHbjLoJkA5VXS0uvHvU0hWoGGRjPmenOoegA/s640/Screen+Shot+2020-03-01+at+12.28.29+PM.png\"
|
581
|
+
width=\"640\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
582
|
+
center;\"><span style=\"font-size: 12.8px;\">Log-scaled spectrogram: Left:
|
583
|
+
SciPy, Right: Neural Network</span></td></tr>\n</tbody></table>\n<br />\nMaybe
|
584
|
+
we can train it for a bit longer and try again.<br />\n<br />\n<table align=\"center\"
|
585
|
+
cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\" style=\"margin-left:
|
586
|
+
auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td style=\"text-align:
|
587
|
+
center;\"><a href=\"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\"
|
588
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
589
|
+
data-original-height=\"392\" data-original-width=\"1434\" height=\"174\" src=\"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\"
|
590
|
+
width=\"640\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
591
|
+
center;\">Left: SciPy, Right: Neural Network</td></tr>\n</tbody></table>\n<br
|
592
|
+
/>\nOh yeah, that's way better!<br />\n<br />\n<h3 style=\"text-align: left;\">\nPeeking
|
593
|
+
into the Model</h3>\n<br />\nOkay, so we know that this works pretty well.
|
594
|
+
It's worth taking a little time to dig in and see what exactly it learned.
|
595
|
+
The best way to do this is by slicing through the layers and examining the
|
596
|
+
weight matrices.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/af81df7faaae90a217e2a48b5251b63a.js\"></script>\n\n<br
|
597
|
+
/>\nLucky for us there's just one layer with (2048, 8514) weights. The second
|
598
|
+
dimension (8154) is just the flattened spectrogram for each sample in the
|
599
|
+
first. In the code above, we reshaped and transformed the data to make it
|
600
|
+
easy to visualize.<br />\n<br />\nHere it is below -- the weight maps for
|
601
|
+
the first, 11th, 21st, and 31st slices (out of 33) of the output.<br />\n<br
|
602
|
+
/>\n<div class=\"separator\" style=\"clear: both; text-align: center;\">\n<a
|
603
|
+
href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRV8G03H9-Xzc1giGrnxzWeq7O2NzAsJelVTkuFl6E0rHdYSBimnTcVrYDKZ8xKDb8nZA-eqqa6XYL7s9ImXVqElW_NNZnXP2721C9aaBDGws7FE_uv02rPmAgn9Az8FElCDtjyw/s1600/Screen+Shot+2020-03-02+at+11.43.05+AM.png\"
|
604
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
605
|
+
data-original-height=\"836\" data-original-width=\"1444\" height=\"370\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRV8G03H9-Xzc1giGrnxzWeq7O2NzAsJelVTkuFl6E0rHdYSBimnTcVrYDKZ8xKDb8nZA-eqqa6XYL7s9ImXVqElW_NNZnXP2721C9aaBDGws7FE_uv02rPmAgn9Az8FElCDtjyw/s640/Screen+Shot+2020-03-02+at+11.43.05+AM.png\"
|
606
|
+
width=\"640\" /></a></div>\n<br />\nThe vertical bands represent activated
|
607
|
+
neurons. You can see how the bands move from left to right as they work on
|
608
|
+
a 256-sample slice of the audio. But more interesting is the spiral pattern
|
609
|
+
of the windows. What's going on there? Let's slice through one of the bands
|
610
|
+
and plot just the inner dimension.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/a119bf4bfbe05eb4dc8e383f9355b37b.js\"></script>\n\n<br
|
611
|
+
/>\nThis is actually pretty cool -- each of the graphs below is a Hanning-Windowed
|
612
|
+
sine wave of an integer frequency along each of the vertical bands. These
|
613
|
+
sinusoids are correlated with the audio, one-by-one, to tease out the active
|
614
|
+
frequencies in the slice of audio.<br />\n<br />\n<table align=\"center\"
|
615
|
+
cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\" style=\"margin-left:
|
616
|
+
auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td style=\"text-align:
|
617
|
+
center;\"><a href=\"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\"
|
618
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
619
|
+
data-original-height=\"999\" data-original-width=\"1600\" height=\"398\" src=\"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\"
|
620
|
+
width=\"640\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
621
|
+
center;\">1, 5, and 10hz Sine Waves (Windowed)</td></tr>\n</tbody></table>\n<br
|
622
|
+
/>\nTo put it simply, those pretty spirally vertical bands are... <b><i>Fourier
|
623
|
+
Transforms</i></b>!<br />\n<br />\n<h3 style=\"text-align: left;\">\nLearning
|
624
|
+
the Discrete Fourier Transform</h3>\n<br />\nExploring that network was fun,
|
625
|
+
however we must go deeper. Let's build a quick network to perform a 128-point
|
626
|
+
DFT, without any windowing, and see if there's more we can learn.<br />\n<br
|
627
|
+
/>\n<script src=\"https://gist.github.com/0xfe/5a029d815b9a5f22985933cee1a71562.js\"></script>\n\nThis
|
628
|
+
is a much simpler network, with only about 65k weights. It trains very fast,
|
629
|
+
and works like a charm!<br />\n<br />\n<div class=\"separator\" style=\"clear:
|
630
|
+
both; text-align: center;\">\n<a href=\"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\"
|
631
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
632
|
+
data-original-height=\"346\" data-original-width=\"1126\" height=\"196\" src=\"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\"
|
633
|
+
width=\"640\" /></a></div>\n<br />\nDigging into the weights, you can clearly
|
634
|
+
see the complex sinusoids used to calculate the Fourier transform.<br />\n<br
|
635
|
+
/>\n<script src=\"https://gist.github.com/0xfe/89a0c10da18fef15602af5b2d59a66d6.js\"></script>\n\n<br
|
636
|
+
/>\n<table align=\"center\" cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\"
|
637
|
+
style=\"margin-left: auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td
|
638
|
+
style=\"text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7-rQnX9r7yc1_mRLb0ffAINnImyrTJAbtAimvFgFb2u5GkTGK-jzfIVcq5NfQVRAlMd5UJaIq8ie42XhyphenhyphenG8dGHEzO2eo3AOnT3SiMUKChTA94ZI5LCfykpHQQbW1w7wrYlq-raA/s1600/Screen+Shot+2020-03-02+at+1.41.30+PM.png\"
|
639
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
640
|
+
data-original-height=\"1172\" data-original-width=\"1108\" height=\"640\"
|
641
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7-rQnX9r7yc1_mRLb0ffAINnImyrTJAbtAimvFgFb2u5GkTGK-jzfIVcq5NfQVRAlMd5UJaIq8ie42XhyphenhyphenG8dGHEzO2eo3AOnT3SiMUKChTA94ZI5LCfykpHQQbW1w7wrYlq-raA/s640/Screen+Shot+2020-03-02+at+1.41.30+PM.png\"
|
642
|
+
width=\"603\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
643
|
+
center;\">Real (blue) and Imaginary (green) Components</td></tr>\n</tbody></table>\n<br
|
644
|
+
/>\nIf you look at the weight matrix as a whole, you see the same pattern
|
645
|
+
we saw in the vertical bands of the spectrogram NN weights.<br />\n<br />\n<div
|
646
|
+
class=\"separator\" style=\"clear: both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMZpKg4uDe5zCWWXae8oWABbYGaVCSm-PeWz6UPu65ZpZ1lYzksc_LKPisXS91Ck8GbR7gKYRwiqyVcDO7fiow1UB28BuTe1FzpVrLBZlgqYCSXECqNaEBLFNuSiI8G6cmKbppLw/s1600/Screen+Shot+2020-03-02+at+1.59.01+PM.png\"
|
647
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
648
|
+
data-original-height=\"1376\" data-original-width=\"1440\" height=\"610\"
|
649
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMZpKg4uDe5zCWWXae8oWABbYGaVCSm-PeWz6UPu65ZpZ1lYzksc_LKPisXS91Ck8GbR7gKYRwiqyVcDO7fiow1UB28BuTe1FzpVrLBZlgqYCSXECqNaEBLFNuSiI8G6cmKbppLw/s640/Screen+Shot+2020-03-02+at+1.59.01+PM.png\"
|
650
|
+
width=\"640\" /></a></div>\n<div class=\"separator\" style=\"clear: both;
|
651
|
+
text-align: center;\">\n<br /></div>\nThere's a lot more we can explore in
|
652
|
+
these networks, but I should probably end here... this post is getting way
|
653
|
+
too long.<br />\n<br />\n<h3 style=\"text-align: left;\">\nFinal Thoughts</h3>\n<br
|
654
|
+
/>\nIt's impressive how well this works, and how quickly this works. The neural
|
655
|
+
networks we trained above are relatively crude, and there are techniques we
|
656
|
+
can explore to optimize them.<br />\n<br />\nFor example, with the spectrogram
|
657
|
+
networks -- instead of having it learn each FFT band independently for each
|
658
|
+
window, we could use a different network architecture (like recurrent networks),
|
659
|
+
or implement some kind of weight sharing strategy across multiple layers.<br
|
660
|
+
/>\n<br />\nEither way, let me be clear: using Neural Networks to perform
|
661
|
+
FFTs or generate spectrograms is <b style=\"font-style: italic;\">completely
|
662
|
+
impractical, </b>and you shouldn't do it. Really, don't do it! It is, however,
|
663
|
+
a great way to explore the guts of machine learning models as they learn to
|
664
|
+
perform complicated tasks.<br />\n<br />\n<br />\n<br /></div>\n<div style='clear:
|
665
|
+
both;'></div>\n</div>\n<div class='post-footer'>\n<div class='post-footer-line
|
666
|
+
post-footer-line-1'><span class='post-author vcard'>\n</span>\n<span class='post-timestamp'>\n</span>\n<span
|
667
|
+
class='post-comment-link'>\n<a class='comment-link' href='https://0xfe.blogspot.com/2020/03/generating-spectrograms-with-neural.html#comment-form'
|
668
|
+
onclick=''>0\ncomments</a>\n</span>\n<span class='post-icons'>\n</span>\n<div
|
669
|
+
class='post-share-buttons goog-inline-block'>\n</div>\n</div>\n<div class='post-footer-line
|
670
|
+
post-footer-line-2'></div>\n<div class='post-footer-line post-footer-line-3'></div>\n</div>\n</div>\n</div>\n\n
|
671
|
+
\ </div></div>\n \n\n <div class=\"date-outer\">\n
|
672
|
+
\ \n<h2 class='date-header'><span>Friday, February 28, 2020</span></h2>\n\n
|
673
|
+
\ <div class=\"date-posts\">\n \n<div class='post-outer'>\n<div
|
674
|
+
class='post hentry'>\n<a name='6954953458036352732'></a>\n<h3 class='post-title
|
675
|
+
entry-title'>\n<a href='https://0xfe.blogspot.com/2020/02/time-frequency-duality.html'>Time
|
676
|
+
Frequency Duality</a>\n</h3>\n<div class='post-header'>\n<div class='post-header-line-1'></div>\n</div>\n<div
|
677
|
+
class='post-body entry-content' id='post-body-6954953458036352732'>\n<div
|
678
|
+
dir=\"ltr\" style=\"text-align: left;\" trbidi=\"on\">\nAn particularly interesting
|
679
|
+
characteristic of Fourier transforms is <b><i>time-frequency duality.</i></b>
|
680
|
+
This duality exposes a beautiful deep symmetry between the time and frequency
|
681
|
+
domains of a signal.<br />\n<br />\nFor example, a sinusoid in the time domain
|
682
|
+
is an impulse in the frequency domain, <i><b>and vice versa</b></i>.<br />\n<br
|
683
|
+
/>\nHere's what a 1-second 20hz sine wave looks like. If you play this on
|
684
|
+
your audio device, you'll hear a 20hz tone.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/736216255e9bbcad5929350a29d1b6b6.js\"></script>\n\n<br
|
685
|
+
/>\n<table align=\"center\" cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\"
|
686
|
+
style=\"margin-left: auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td
|
687
|
+
style=\"text-align: center;\"><div class=\"separator\" style=\"clear: both;
|
688
|
+
text-align: center;\">\n<br /></div>\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfuInGS7azSrsPIEHFf8IRLzX092heOhMA-TvyOl4EWhzTGibkGhEUt317yNEu0jSSE0OlKEQFtxeWHcHPF2GeSyRlN2R41kLaE4xGnwG6qJYqFEt1CHzCBKwfWvJvhvhjIlv6Sg/s1600/Screen+Shot+2020-02-27+at+5.24.39+PM.png\"
|
689
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
690
|
+
data-original-height=\"388\" data-original-width=\"1212\" height=\"204\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfuInGS7azSrsPIEHFf8IRLzX092heOhMA-TvyOl4EWhzTGibkGhEUt317yNEu0jSSE0OlKEQFtxeWHcHPF2GeSyRlN2R41kLaE4xGnwG6qJYqFEt1CHzCBKwfWvJvhvhjIlv6Sg/s640/Screen+Shot+2020-02-27+at+5.24.39+PM.png\"
|
691
|
+
width=\"640\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
692
|
+
center;\">20hz Sine Wave</td></tr>\n</tbody></table>\n<br />\nWhen you take
|
693
|
+
the Fourier transform of the wave, and plot the frequency domain representation
|
694
|
+
of the signal, you get an impulse in the bin representing the 20hz. (Ignore
|
695
|
+
the <a href=\"https://en.wikipedia.org/wiki/Spectral_leakage\">tiny neighbours</a> for
|
696
|
+
now.)<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/d5b29e9f9b987eeefce1148ec3698b60.js\"></script>\n\n<br
|
697
|
+
/>\n<table align=\"center\" cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\"
|
698
|
+
style=\"margin-left: auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td
|
699
|
+
style=\"text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1Bs_O3s1cSwNgTZiRB0dTlIg5GsCMzVK3kCtvwJtNWtpjrmP1FLYNF5mGTLL5EgRHg8j5qvyBSvM0Fi9XTihdyXzNtBPcAsZqkDgOqJt9JnqvKg4UqK_NpVLF32Q-lI-2wNHS8g/s1600/Screen+Shot+2020-02-27+at+5.30.35+PM.png\"
|
700
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
701
|
+
data-original-height=\"396\" data-original-width=\"1224\" height=\"206\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1Bs_O3s1cSwNgTZiRB0dTlIg5GsCMzVK3kCtvwJtNWtpjrmP1FLYNF5mGTLL5EgRHg8j5qvyBSvM0Fi9XTihdyXzNtBPcAsZqkDgOqJt9JnqvKg4UqK_NpVLF32Q-lI-2wNHS8g/s640/Screen+Shot+2020-02-27+at+5.30.35+PM.png\"
|
702
|
+
width=\"640\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
703
|
+
center;\">Frequency Domain of 20hz Sine Wave</td></tr>\n</tbody></table>\n<br
|
704
|
+
/>\nIf you play this transformed representation out to your audio device,
|
705
|
+
you'll hear a click, generated from the single impulse pushing the speaker's
|
706
|
+
diaphragm. This is effectively an impulse signal.<br />\n<br />\nOkay, let's
|
707
|
+
create an impulse signal by hand -- a string of zeros, with a 1 somewhere
|
708
|
+
in the middle. Play this on your speaker, and, again, you'll hear a click.
|
709
|
+
This signal is no different from the the previous transformed signal, except
|
710
|
+
for maybe the position of the impulse.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/db8bbd3c1c8635e3b6fb8c2e28c93e20.js\"></script>\n\nSo,
|
711
|
+
check this out. If you take the the FFT of the impulse and plot the frequency
|
712
|
+
domain representation, you get... <i><b>a sinusoid</b></i>!<br />\n<br />\n<script
|
713
|
+
src=\"https://gist.github.com/0xfe/154b2a1b0be3fd92cdae206b829b38ba.js\"></script>\n<a
|
714
|
+
href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFWi8YhKu_nhS72RW8x_rcI2kuNQ4DqzZxYmd39x0KAtnA9PLrNaGEKSAOcTEuTvDOGnpM28adAauZqi26cMFi8MRJfCZJhVicEagWWkIlJXqP0ssEYHgdpDut5_hTAKckcJazMQ/s1600/Screen+Shot+2020-02-28+at+6.39.56+AM.png\"
|
715
|
+
imageanchor=\"1\"><img border=\"0\" data-original-height=\"392\" data-original-width=\"1208\"
|
716
|
+
height=\"208\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFWi8YhKu_nhS72RW8x_rcI2kuNQ4DqzZxYmd39x0KAtnA9PLrNaGEKSAOcTEuTvDOGnpM28adAauZqi26cMFi8MRJfCZJhVicEagWWkIlJXqP0ssEYHgdpDut5_hTAKckcJazMQ/s640/Screen+Shot+2020-02-28+at+6.39.56+AM.png\"
|
717
|
+
width=\"640\" /></a>\n<br />\n<br />\nThis works both ways. You can the the
|
718
|
+
<b><i>inverse FFT</i></b> of a sine wave in the frequency domain, to
|
719
|
+
produce an impulse in the time domain.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/fe317c6a7357dea6f23d9968e98dea2e.js\"></script>\n\n<br
|
720
|
+
/>\n<table align=\"center\" cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\"
|
721
|
+
style=\"margin-left: auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td
|
722
|
+
style=\"text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS4gwS_iQX8deUHZMtLPB4Vu4B5OzEvbDx2v8hHRzMbPEFeoEC2Qq6zDb863GrX3a09DYurSEjiAIELYexDHEjVndVw2fgQWVlp3kxft8SM1Cc8A7J0gkvL2rwfCquHr480IqMQQ/s1600/Screen+Shot+2020-02-28+at+6.42.45+AM.png\"
|
723
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
724
|
+
data-original-height=\"796\" data-original-width=\"1214\" height=\"261\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS4gwS_iQX8deUHZMtLPB4Vu4B5OzEvbDx2v8hHRzMbPEFeoEC2Qq6zDb863GrX3a09DYurSEjiAIELYexDHEjVndVw2fgQWVlp3kxft8SM1Cc8A7J0gkvL2rwfCquHr480IqMQQ/s400/Screen+Shot+2020-02-28+at+6.42.45+AM.png\"
|
725
|
+
width=\"400\" /></a>\n</td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
726
|
+
center;\">Inverse Fourier Transform of a Sine Wave</td></tr>\n</tbody></table>\n<br
|
727
|
+
/>\nThis is a wonderfully striking phenomenon, which I think reveals a lot
|
728
|
+
about our perception of nature.<br />\n<br />\nFor example, here's another
|
729
|
+
property of time-frequency duality -- <a href=\"https://en.wikipedia.org/wiki/Convolution\">convolutions</a> in
|
730
|
+
the time domain are multiplications in the frequency domain, and vice versa<i>.
|
731
|
+
</i>Because <i>multiplications require far fewer operations than convolutions</i>,
|
732
|
+
it's much simpler to operate on frequency domain representations of signals.<br
|
733
|
+
/>\n<br />\nYour inner ear consists of lots of tiny hairs that vary in thickness
|
734
|
+
and resonate at different frequencies sending frequency domain representations
|
735
|
+
of sound to your brain -- i.e., <i>your ear evolved a little DSP chip
|
736
|
+
in</i> it to make it easier on your brain.</div>\n<div style='clear: both;'></div>\n</div>\n<div
|
737
|
+
class='post-footer'>\n<div class='post-footer-line post-footer-line-1'><span
|
738
|
+
class='post-author vcard'>\n</span>\n<span class='post-timestamp'>\n</span>\n<span
|
739
|
+
class='post-comment-link'>\n<a class='comment-link' href='https://0xfe.blogspot.com/2020/02/time-frequency-duality.html#comment-form'
|
740
|
+
onclick=''>0\ncomments</a>\n</span>\n<span class='post-icons'>\n</span>\n<div
|
741
|
+
class='post-share-buttons goog-inline-block'>\n</div>\n</div>\n<div class='post-footer-line
|
742
|
+
post-footer-line-2'></div>\n<div class='post-footer-line post-footer-line-3'></div>\n</div>\n</div>\n</div>\n\n
|
743
|
+
\ </div></div>\n \n\n <div class=\"date-outer\">\n
|
744
|
+
\ \n<h2 class='date-header'><span>Saturday, February 22, 2020</span></h2>\n\n
|
745
|
+
\ <div class=\"date-posts\">\n \n<div class='post-outer'>\n<div
|
746
|
+
class='post hentry'>\n<a name='1894211342385136711'></a>\n<h3 class='post-title
|
747
|
+
entry-title'>\n<a href='https://0xfe.blogspot.com/2020/02/pitch-detection-with-convolutional.html'>Pitch
|
748
|
+
Detection with Convolutional Networks</a>\n</h3>\n<div class='post-header'>\n<div
|
749
|
+
class='post-header-line-1'></div>\n</div>\n<div class='post-body entry-content'
|
750
|
+
id='post-body-1894211342385136711'>\n<div dir=\"ltr\" style=\"text-align:
|
751
|
+
left;\" trbidi=\"on\">\nWhile working on <a href=\"https://pitchy.ninja/\">Pitchy
|
752
|
+
Ninja</a> and <a href=\"https://vexflow.com/\">Vexflow</a>, I explored
|
753
|
+
a variety of different techniques for pitch detection that would also work
|
754
|
+
well in a browser. Although, I settled on a relatively well-known algorithm,
|
755
|
+
the exploration took me down an interesting path -- I wondered if you could
|
756
|
+
build neural networks to classify pitches, intervals, and chords in recorded
|
757
|
+
audio.<br />\n<br />\nTurns out the answer is <b><i>yes. </i></b>To all
|
758
|
+
of them.<br />\n<br />\nThis post details some of the techniques I used to
|
759
|
+
build a pitch-detection neural network. Although I focus on single-note pitch
|
760
|
+
estimation, these methods seem to work well for multi-note chords too.<br
|
761
|
+
/>\n<br />\n<h3 style=\"text-align: left;\">\nOn Pitch Estimation</h3>\n<br
|
762
|
+
/>\n<a href=\"https://en.wikipedia.org/wiki/Pitch_detection_algorithm\">Pitch
|
763
|
+
detection</a> (also called <i>fundamental frequency estimation</i>) is not
|
764
|
+
an exact science. What your brain perceives as pitch is a function of lots
|
765
|
+
of different variables, from the physical materials that generate the sounds
|
766
|
+
to your body's physiological structure.<br />\n<br />\nOne would presume that
|
767
|
+
you can simply transform a signal to its frequency domain representation,
|
768
|
+
and look at the peak frequencies. This would work for a sine wave, but as
|
769
|
+
soon as you introduce any kind of timbre (e.g., when you sing, or play a note
|
770
|
+
on a guitar), the spectrum is flooded with <a href=\"https://en.wikipedia.org/wiki/Overtone\">overtones</a>
|
771
|
+
and <a href=\"https://en.wikipedia.org/wiki/Harmonic\">harmonic partials</a>.<br
|
772
|
+
/>\n<br />\nHere's a 33ms spectrogram of the note A4 (440hz) played on a piano.
|
773
|
+
You can see a peak at 440hz, and another around 1760hz.<br />\n<br />\n<div
|
774
|
+
class=\"separator\" style=\"clear: both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbd4u91YSFmMe3uUXT-tsjfSVygsHoRChlZKlFKRMkN11BNIFJsqrVhRRRxnC7hCeKEmkgiv3OYFjRo395zqpz_0zUBfl9R64ryL8Qdllg22ONbb4raIC5T1zW01BA3nOtzJG7Tw/s1600/Screen+Shot+2020-02-22+at+9.14.50+PM.png\"
|
775
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
776
|
+
data-original-height=\"534\" data-original-width=\"828\" height=\"257\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbd4u91YSFmMe3uUXT-tsjfSVygsHoRChlZKlFKRMkN11BNIFJsqrVhRRRxnC7hCeKEmkgiv3OYFjRo395zqpz_0zUBfl9R64ryL8Qdllg22ONbb4raIC5T1zW01BA3nOtzJG7Tw/s400/Screen+Shot+2020-02-22+at+9.14.50+PM.png\"
|
777
|
+
width=\"400\" /></a></div>\n<br />\n<br />\nHere's the same A4 (440hz), but
|
778
|
+
on a violin.<br />\n<br />\n<div class=\"separator\" style=\"clear: both;
|
779
|
+
text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj61E0xsI4lmYpxhsVxaUh2k7woDBN220o6d_TjgnkEfDdo-yQkpUHq5hr1TYR_GCiGvKzmh9M205x2qbmeziTOydR6pRwyHp9j4X_gv4p2nfuJLPDvuVck8DR49mII0jmEAAABdA/s1600/Screen+Shot+2020-02-22+at+9.15.03+PM.png\"
|
780
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
781
|
+
data-original-height=\"526\" data-original-width=\"820\" height=\"256\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj61E0xsI4lmYpxhsVxaUh2k7woDBN220o6d_TjgnkEfDdo-yQkpUHq5hr1TYR_GCiGvKzmh9M205x2qbmeziTOydR6pRwyHp9j4X_gv4p2nfuJLPDvuVck8DR49mII0jmEAAABdA/s400/Screen+Shot+2020-02-22+at+9.15.03+PM.png\"
|
782
|
+
width=\"400\" /></a></div>\n<br />\nAnd here's a trumpet.<br />\n<br />\n<div
|
783
|
+
class=\"separator\" style=\"clear: both; text-align: center;\">\n<a href=\"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\"
|
784
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
785
|
+
data-original-height=\"528\" data-original-width=\"826\" height=\"255\" src=\"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\"
|
786
|
+
width=\"400\" /></a></div>\n<br />\nNotice how the thicker instruments have
|
787
|
+
rich harmonic spectrums? These harmonics are what make them beautiful, and
|
788
|
+
also what make pitch detection hard.<br />\n<br />\n<h3 style=\"text-align:
|
789
|
+
left;\">\nEstimation Techniques</h3>\n<br />\nA lot of the well understood
|
790
|
+
pitch estimation algorithms resort to transformations and heuristics which
|
791
|
+
amplify the fundamental and cancel out the overtones. Some, more advanced
|
792
|
+
techniques work on (kind of) fingerprinting timbres, and then attempting to
|
793
|
+
correlate them with a signal.<br />\n<br />\nFor single tones, these techniques
|
794
|
+
work well, but they do break down in their own unique ways. After all, they're
|
795
|
+
heuristics that try to <i>estimate human perception</i>.<br />\n<br />\n<h3
|
796
|
+
style=\"text-align: left;\">\nConvolutional Networks</h3>\n<br />\nDeep <a
|
797
|
+
href=\"https://en.wikipedia.org/wiki/Convolutional_neural_network\">convolutional
|
798
|
+
networks</a> have been winning image labeling challenges for nearly a decade,
|
799
|
+
starting with <a href=\"https://en.wikipedia.org/wiki/AlexNet\">AlexNet in
|
800
|
+
2012</a>. The key insight in these architectures is that detecting objects
|
801
|
+
require some level of locality in pattern recognition, i.e., learned features
|
802
|
+
should be agnostic to translations, rotations, intensities, etc. Convolutional
|
803
|
+
networks learn multiple layers of filters, each capturing some perceptual
|
804
|
+
element.<br />\n<br />\n<div class=\"separator\" style=\"clear: both; text-align:
|
805
|
+
center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2LwwE33Uu5V1Kjy9nZxOQZEbh8LIe4nyUp728tznKUIym01KXtfscXhi8ASVftIu-7wJ8FKfBs4YDvDCTLSl7x2kmn7QPoubI9aBJ8zajZCg9IzGcbXvRDF8DM-pO1r_7IseNqw/s1600/Screen+Shot+2020-02-22+at+9.36.01+PM.png\"
|
806
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
807
|
+
data-original-height=\"488\" data-original-width=\"1458\" height=\"214\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2LwwE33Uu5V1Kjy9nZxOQZEbh8LIe4nyUp728tznKUIym01KXtfscXhi8ASVftIu-7wJ8FKfBs4YDvDCTLSl7x2kmn7QPoubI9aBJ8zajZCg9IzGcbXvRDF8DM-pO1r_7IseNqw/s640/Screen+Shot+2020-02-22+at+9.36.01+PM.png\"
|
808
|
+
width=\"640\" /></a></div>\n<br />\n<br />\nFor example, the bottom layer
|
809
|
+
of an image recognition network might detect edges and curves, the next might
|
810
|
+
detect simple shapes, and the next would detect objects, etc. Here's an example
|
811
|
+
of extracted features from various layers (via <a href=\"https://www.groundai.com/project/deepfeat-a-bottom-up-and-top-down-saliency-model-based-on-deep-features-of-convolutional-neural-nets/\">DeepFeat</a>.)<br
|
812
|
+
/>\n<br />\n<div class=\"separator\" style=\"clear: both; text-align: center;\">\n<a
|
813
|
+
href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijkByp7Wt8FbaM2YIKun1mLOehy2NOU5Oj8xgSKXrky_FeIHYqFkuxjEbtamdj_X_sC5pnC41QRUtTpUJjRXneSK7N5y4X8PiNQAKfB1Uoblcqo94HXEavYWlYQ2zYofVZHIWrdg/s1600/Screen+Shot+2020-02-22+at+9.40.06+PM.png\"
|
814
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
815
|
+
data-original-height=\"1092\" data-original-width=\"1096\" height=\"397\"
|
816
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijkByp7Wt8FbaM2YIKun1mLOehy2NOU5Oj8xgSKXrky_FeIHYqFkuxjEbtamdj_X_sC5pnC41QRUtTpUJjRXneSK7N5y4X8PiNQAKfB1Uoblcqo94HXEavYWlYQ2zYofVZHIWrdg/s400/Screen+Shot+2020-02-22+at+9.40.06+PM.png\"
|
817
|
+
width=\"400\" /></a></div>\n<br />\n<h3 style=\"text-align: left;\">\nConvolutional
|
818
|
+
Networks for Audio</h3>\n<br />\nFor audio feature extraction, <a href=\"https://en.wikipedia.org/wiki/Time_domain\">time
|
819
|
+
domain</a> representations don't seem to be very useful to convnets. However,
|
820
|
+
in the <a href=\"https://en.wikipedia.org/wiki/Frequency_domain\">frequency
|
821
|
+
domain</a>, convnets learn features <i><b>extremely well</b></i>. Once networks
|
822
|
+
start looking at spectrograms, all kinds of patterns start to emerge.<br />\n<br
|
823
|
+
/>\nIn the next few sections, we'll build a and train a simple convolutional
|
824
|
+
network to detect fundamental frequencies across six octaves.<br />\n<br />\n<h3
|
825
|
+
style=\"text-align: left;\">\nGetting Training Data</h3>\n<br />\nTo do this
|
826
|
+
well, we need data. Labeled. <b>Lots of it!</b> There are a few paths we can
|
827
|
+
take:<br />\n<br />\n<b>Option 1</b>: Go find a whole bunch of single-tone
|
828
|
+
music online, slice it up into little bits, transcribe and label.<br />\n<br
|
829
|
+
/>\n<b>Option 2</b>: Take out my trusty guitar, record, slice, and label.
|
830
|
+
Then my keyboard, and my trumpet, and my clarinet. And maybe sing too. Ugh!<br
|
831
|
+
/>\n<br />\n<b>Option 3</b>: Build synthetic samples with... code!<br />\n<br
|
832
|
+
/>\nSince, you know, the ultimate programmer virtue is laziness, let's go
|
833
|
+
with Option 3.<br />\n<br />\n<h3 style=\"text-align: left;\">\nTools of the
|
834
|
+
Trade</h3>\n<br />\nThe goal is to build a model that performs well, and <i>generalizes
|
835
|
+
well</i>, so we'll need to make sure that we account for enough of the variability
|
836
|
+
in real audio as we can -- which means using a variety of instruments, velocities,
|
837
|
+
effects, envelopes, and noise profiles.<br />\n<br />\nWith a good MIDI library,
|
838
|
+
a patch bank, and some savvy, we can get this done. Here's what we need:<br
|
839
|
+
/>\n<ul style=\"text-align: left;\">\n<li><a href=\"https://pypi.org/project/MIDIUtil/\">MIDIUtil</a>
|
840
|
+
- Python library to generate MIDI files.</li>\n<li><a href=\"http://www.fluidsynth.org/\">FluidSynth</a>
|
841
|
+
- Renders MIDI files to raw audio.</li>\n<li><a href=\"http://www.schristiancollins.com/generaluser.php\">GeneralUser
|
842
|
+
GS</a> - A bank of GM instrument patches for FluidSynth.</li>\n<li><a
|
843
|
+
href=\"http://sox.sourceforge.net/sox.html\">sox</a> - To post-process the
|
844
|
+
audio (resample, normalize, etc.)</li>\n<li><a href=\"https://www.scipy.org/\">scipy.io</a>
|
845
|
+
- For generating spectrograms</li>\n<li><a href=\"https://tensorflow.org/\">Tensorflow</a>
|
846
|
+
- For building and training the models.</li>\n</ul>\n<div>\n<br />\nAll of
|
847
|
+
these are open-source and freely available. Download and install them before
|
848
|
+
proceeding.</div>\n<h3 style=\"text-align: left;\">\n</h3>\n<h3 style=\"text-align:
|
849
|
+
left;\">\n</h3>\n<h3 style=\"text-align: left;\">\n<br /></h3>\n<h3 style=\"text-align:
|
850
|
+
left;\">\nSynthesizing the Data</h3>\n<br />\nWe start with picking a bunch
|
851
|
+
of instruments encompassing a variety of different timbres and tonalities.<br
|
852
|
+
/>\n<br />\n<script src=\"https://gist.github.com/0xfe/39f374a4ec66a1dc6af2a622851b2e74.js\"></script><br
|
853
|
+
/>\n<div>\n<br />\nPick the notes and octaves you want to be able to classify.
|
854
|
+
I used all 12 tones between octaves 2 and 8 (and added some random detunings.)
|
855
|
+
Here's a handy class to deal with note to MIDI value conversions.<br />\n<br
|
856
|
+
/>\n<br />\n<script src=\"https://gist.github.com/0xfe/0b8b66bf4e34caedcacb5894164e7c7c.js\"></script>\n\nThe
|
857
|
+
next section is where the meat of the synthesis happens. It does the following:<br
|
858
|
+
/>\n<ul>\n<li>Renders the MIDI files to raw audio (wav) using FluidSynth and
|
859
|
+
a free GM sound font.</li>\n<li>Resamples to single-channel, unsigned 16-bit,
|
860
|
+
at 44.1khz, normalized.</li>\n<li>Slices the sample up into its envelope components
|
861
|
+
(attack, sustain, decay.)</li>\n<li>Detunes some of the samples to cover more
|
862
|
+
of the harmonic surface.</li>\n</ul>\n<div>\n<script src=\"https://gist.github.com/0xfe/f01e3186b14543b42303e6af96286262.js\"></script>\n</div>\n<div>\n<br
|
863
|
+
/></div>\nFinally, we use the <b>Sample</b> class to generate thousands
|
864
|
+
of different 33ms long MIDI files, each playing a single note. The labels
|
865
|
+
are part of the filename, and include the note, octave, frequency, and envelope
|
866
|
+
component.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/29aedb7cc2733e7bde767eebcdd1d204.js\"></script>\n\n<br
|
867
|
+
/>\n<br />\n<h3 style=\"text-align: left;\">\nBuilding the Network</h3>\n<div>\n<br
|
868
|
+
/></div>\n<div>\nNow that we have the training data, let's design the network.<br
|
869
|
+
/>\n<br /></div>\n<div>\nI experimented with a variety of different architectures
|
870
|
+
before I got here, starting with simple dense (non-convolution) networks with
|
871
|
+
time-domain inputs, then moving on to one-dimensional LSTMs, then two-dimensional
|
872
|
+
convolutional networks (convnets) with frequency-domain inputs.</div>\n<div>\n<br
|
873
|
+
/></div>\n<div>\nAs you can guess, the 2D networks with frequency-domain inputs
|
874
|
+
worked <b>significantly better.</b> As soon as I got decent baseline performance
|
875
|
+
with them, I focused on incrementally improving accuracy by reducing validation
|
876
|
+
loss.</div>\n<div>\n<br /></div>\n<h3 style=\"text-align: left;\">\nModel
|
877
|
+
Inputs</h3>\n<div>\n<br /></div>\n<div>\nThe inputs to the network will be
|
878
|
+
<a href=\"https://en.wikipedia.org/wiki/Spectrogram\">spectrograms</a>, which
|
879
|
+
are 2D images representing a slice of audio. The X-axis is usually time, and
|
880
|
+
the Y-axis is frequency. They're great for visualizing audio spectrums, but
|
881
|
+
also for more advanced audio analysis.<br />\n<br />\n<table align=\"center\"
|
882
|
+
cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\" style=\"margin-left:
|
883
|
+
auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td style=\"text-align:
|
884
|
+
center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfBj4NcA-QJtuTsDM3Rv6rNxZJumr7qXjNgspGKe9lOnv9XRUjsjfqZZ3GSa50F76ayZn7BMIPcjV6MWOsihu8z4Pb0vBijwEQrV5x8VEs8zhe5llGV766O1lmXrJS0VidEAisJw/s1600/Screen+Shot+2020-02-22+at+9.17.27+PM.png\"
|
885
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
886
|
+
data-original-height=\"526\" data-original-width=\"840\" height=\"250\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfBj4NcA-QJtuTsDM3Rv6rNxZJumr7qXjNgspGKe9lOnv9XRUjsjfqZZ3GSa50F76ayZn7BMIPcjV6MWOsihu8z4Pb0vBijwEQrV5x8VEs8zhe5llGV766O1lmXrJS0VidEAisJw/s400/Screen+Shot+2020-02-22+at+9.17.27+PM.png\"
|
887
|
+
width=\"400\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
888
|
+
center;\">A Church Organ playing A4 (440hz)</td></tr>\n</tbody></table>\n<br
|
889
|
+
/></div>\n<div>\n<br /></div>\n<div>\nSpectrograms are typically generated
|
890
|
+
with <a href=\"https://en.wikipedia.org/wiki/Short-time_Fourier_transform\">Short
|
891
|
+
Time Fourier Transforms (STFTs)</a>. In short, the algorithm slides a window
|
892
|
+
over the audio, running <a href=\"https://en.wikipedia.org/wiki/Fast_Fourier_transform\">FFTs</a>
|
893
|
+
over the windowed data. Depending on the parameters of the STFT (and the associated
|
894
|
+
FFTs), the precision of the detected frequencies can be tweaked to match the
|
895
|
+
use case.</div>\n<br />\nFor this experiment, we're working with 44.1khz 16-bit
|
896
|
+
samples, 33ms long -- which is about 14,500 data points per sample. We first
|
897
|
+
<b>downsample</b> the audio to 16khz, yielding 5280 data points per sample.<br
|
898
|
+
/>\n<br />\n<script src=\"https://gist.github.com/0xfe/86a9c94f75bd06bde76cb6a612744d49.js\"></script>\n\nThe
|
899
|
+
spectrogram will be generated via STFT, using a window size of 256, an overlap
|
900
|
+
of 200, and a 1024 point FFT zero-padded on both sides. This yields one <b>513x90</b>
|
901
|
+
pixel image per sample.<br />\n<br />\nThe 1024-point FFT also <b><i>caps
|
902
|
+
the resolution</i></b> to about 19hz, which isn't perfect, but fine for distinguishing
|
903
|
+
pitches.<br />\n<br />\n<h3 style=\"text-align: left;\">\n\nThe Network Model</h3>\n<br
|
904
|
+
/>\nOur network consists of 4 convolutional layers, with 64, 128, 128, and
|
905
|
+
256 filters respectively, which are then immediately downsampled with <a href=\"https://computersciencewiki.org/index.php/Max-pooling_/_Pooling\">max-pooling</a>
|
906
|
+
layers. The input layer reshapes the input tensors by adding a <i>channels</i>
|
907
|
+
dimension for <a href=\"https://www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D\">Conv2D</a>.
|
908
|
+
We close out the model with two densely connected layers, and a final output
|
909
|
+
node for the floating-point frequency.<br />\n<br />\nTo prevent overfitting,
|
910
|
+
we <i><a href=\"https://en.wikipedia.org/wiki/Regularization_(mathematics)\">regularize</a></i>
|
911
|
+
by aggressively adding <a href=\"https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dropout\">dropout</a>
|
912
|
+
layers, including one right at the input which also doubles as an ad-hoc noise
|
913
|
+
generator.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/56f14b33ac8e09e94d65e77f50a61280.js\"></script>\n\n<br
|
914
|
+
/>\n<div>\n<br /></div>\nAlthough we use <a href=\"https://en.wikipedia.org/wiki/Mean_squared_error\">mean-squared-error</a>
|
915
|
+
as our loss function, it's the <a href=\"https://en.wikipedia.org/wiki/Mean_absolute_error\">mean-absolute-error</a>
|
916
|
+
that we need to watch, since it's easier to reason about. Let's take a look
|
917
|
+
at the model summary.<br />\n<br />\n<div class=\"separator\" style=\"clear:
|
918
|
+
both; text-align: center;\">\n<a href=\"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\"
|
919
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
920
|
+
data-original-height=\"1364\" data-original-width=\"1124\" height=\"640\"
|
921
|
+
src=\"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\"
|
922
|
+
width=\"524\" /></a></div>\n<br />\n<br />\nWow, <b>12 million</b> parameters!
|
923
|
+
Feels like a lot for an experiment, but it turns out we can build a model
|
924
|
+
in less than 10 minutes on a modern GPU. Let's start training.<br />\n<br
|
925
|
+
/>\n<script src=\"https://gist.github.com/0xfe/6a7234fe6e368bbd346f5fdddd88fa12.js\"></script>\n\n<br
|
926
|
+
/>\nAfter 100 epochs, we can achieve a validation MSE of 0.002, and a validation
|
927
|
+
MAE of 0.03.<br />\n<br />\n<div class=\"separator\" style=\"clear: both;
|
928
|
+
text-align: center;\">\n<a href=\"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\"
|
929
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
930
|
+
data-original-height=\"1066\" data-original-width=\"814\" height=\"400\" src=\"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\"
|
931
|
+
width=\"305\" /></a></div>\n<br />\nYou may be wondering why the validation
|
932
|
+
MAE is so much better than the training MAE. This is because of the aggressive
|
933
|
+
dropout regularization. Dropout layers are only activated during training,
|
934
|
+
not prediction.<br />\n<br />\nThese results are quite promising for an experiment!
|
935
|
+
For classification problems, we could use <a href=\"https://en.wikipedia.org/wiki/Confusion_matrix\">confusion
|
936
|
+
matrices</a> to see where the models mispredict. For regression problems (like
|
937
|
+
this one), we can explore the losses a bit more by plotting a graph of errors
|
938
|
+
by pitch.<br />\n<br />\n<script src=\"https://gist.github.com/0xfe/3e4d90436dce46ec121b9f94948001a1.js\"></script>\n\n<br
|
939
|
+
/>\n<div class=\"separator\" style=\"clear: both; text-align: center;\">\n<br
|
940
|
+
/></div>\n<div class=\"separator\" style=\"clear: both; text-align: center;\">\n<br
|
941
|
+
/></div>\n<table align=\"center\" cellpadding=\"0\" cellspacing=\"0\" class=\"tr-caption-container\"
|
942
|
+
style=\"margin-left: auto; margin-right: auto; text-align: center;\"><tbody>\n<tr><td
|
943
|
+
style=\"text-align: center;\"><a href=\"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\"
|
944
|
+
imageanchor=\"1\" style=\"margin-left: auto; margin-right: auto;\"><img border=\"0\"
|
945
|
+
data-original-height=\"512\" data-original-width=\"786\" height=\"260\" src=\"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\"
|
946
|
+
width=\"400\" /></a></td></tr>\n<tr><td class=\"tr-caption\" style=\"text-align:
|
947
|
+
center;\">Prediction Errors by Pitch</td></tr>\n</tbody></table>\n<br />\nAlready,
|
948
|
+
we can see that the prediction errors are on the highest octaves. This is
|
949
|
+
very likely due to our downsampling to 16khz, causing <a href=\"https://en.wikipedia.org/wiki/Aliasing\">aliasing</a>
|
950
|
+
in the harmonics and confusing the model.<br />\n<br />\nAfter discarding
|
951
|
+
the last octave, we can take the mean of the prediction error, and what do
|
952
|
+
we see?<br />\n<div style=\"background-color: #fffffe; font-family: monospace,
|
953
|
+
Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height:
|
954
|
+
19px; white-space: pre;\">\nnp.mean(np.nan_to_num(errors_by_key[<span style=\"color:
|
955
|
+
#09885a;\">0</span>:<span style=\"color: #09885a;\">80</span>]))</div>\n<span
|
956
|
+
style=\"background-color: white; color: #212121; font-family: monospace; font-size:
|
957
|
+
14px; white-space: pre;\">19.244542657486097</span><br />\n<br />\nPretty
|
958
|
+
much exactly the resolution of the FFT we used. It's very hard to do better
|
959
|
+
given the inputs.<br />\n<br />\n<h3 style=\"text-align: left;\">\nThe Real
|
960
|
+
Test</h3>\n<div>\n<br /></div>\nSo, how does this perform in the wild? To
|
961
|
+
answer this question, I recorded a few samples of myself playing single notes
|
962
|
+
on the guitar, and pulled some youtube videos of various instruments and sliced
|
963
|
+
them up for analysis. I also crossed my fingers and sacrificed a dozen goats.<br
|
964
|
+
/>\n<br />\nAs hoped, the predictions were <b><i>right within the tolerances</i></b>
|
965
|
+
of the model. Try it yourself and let me know how it works out.<br />\n<br
|
966
|
+
/>\n<h3 style=\"text-align: left;\">\nImprovements and Variations</h3>\n<br
|
967
|
+
/>\nThere's a few things we can do to improve what we have -- larger FFT and
|
968
|
+
window sizes, higher sample rates, better data, etc. We can also turn this
|
969
|
+
into a classification problem by using <a href=\"https://en.wikipedia.org/wiki/Softmax_function\">softmax</a>
|
970
|
+
at the bottom layer and training directly on musical pitches instead of frequencies.<br
|
971
|
+
/>\n<br />\nThis experiment was part of a whole suite of models I built for
|
972
|
+
music recognition. In a future post I'll describe a more complex set of models
|
973
|
+
I built to recognize roots, intervals, and 2-4 note chords.<br />\n<br />\nUntil
|
974
|
+
then, hope you enjoyed this post. If you did, drop me a note at <a href=\"https://twitter.com/11111110b\">@11111110b</a>.<br
|
975
|
+
/>\n<br />\nAll the source code for these experiments will be available on
|
976
|
+
<a href=\"https://github.com/0xfe\">my Github page</a> as soon as it's in
|
977
|
+
slightly better shape.<br />\n<br />\n<br /></div>\n</div>\n<div style='clear:
|
978
|
+
both;'></div>\n</div>\n<div class='post-footer'>\n<div class='post-footer-line
|
979
|
+
post-footer-line-1'><span class='post-author vcard'>\n</span>\n<span class='post-timestamp'>\n</span>\n<span
|
980
|
+
class='post-comment-link'>\n<a class='comment-link' href='https://0xfe.blogspot.com/2020/02/pitch-detection-with-convolutional.html#comment-form'
|
981
|
+
onclick=''>0\ncomments</a>\n</span>\n<span class='post-icons'>\n</span>\n<div
|
982
|
+
class='post-share-buttons goog-inline-block'>\n</div>\n</div>\n<div class='post-footer-line
|
983
|
+
post-footer-line-2'></div>\n<div class='post-footer-line post-footer-line-3'></div>\n</div>\n</div>\n</div>\n\n
|
984
|
+
\ </div></div>\n \n\n <div class=\"date-outer\">\n
|
985
|
+
\ \n<h2 class='date-header'><span>Wednesday, February 19, 2020</span></h2>\n\n
|
986
|
+
\ <div class=\"date-posts\">\n \n<div class='post-outer'>\n<div
|
987
|
+
class='post hentry'>\n<a name='541596065311835171'></a>\n<h3 class='post-title
|
988
|
+
entry-title'>\n<a href='https://0xfe.blogspot.com/2020/02/no-servers-just-buckets-hosting-static.html'>No
|
989
|
+
Servers, Just Buckets: Hosting Static Websites on the Cloud</a>\n</h3>\n<div
|
990
|
+
class='post-header'>\n<div class='post-header-line-1'></div>\n</div>\n<div
|
991
|
+
class='post-body entry-content' id='post-body-541596065311835171'>\n<div dir=\"ltr\"
|
992
|
+
style=\"text-align: left;\" trbidi=\"on\">\n<br />\nFor over two decades,
|
993
|
+
I've hosted websites on managed servers. Starting with web hosting providers,
|
994
|
+
going to dedicated machines, then dedicated VMs, then cloud VMs. Maintaining
|
995
|
+
these servers tend to come at a high cognitive cost -- machine and network
|
996
|
+
setup, OS patches, web server configuration, replication and high-availability,
|
997
|
+
TLS and cert management, security... the list goes on.<br />\n<br />\nLast
|
998
|
+
year, I moved [<a href=\"https://pitchy.ninja/\">almost</a>] [<a href=\"https://muthanna.com/\">all</a>] [<a
|
999
|
+
href=\"https://float64.dev/\">my</a>] [<a href=\"https://vexflow.com/\">websites</a>]
|
1000
|
+
to cloud buckets, and it has been amazing! Life just got simpler. With just
|
1001
|
+
a few commands I got:<br />\n<br />\n<ul style=\"text-align: left;\">\n<li>A
|
1002
|
+
HTTP(s) web-server hosting my content.</li>\n<li>Managed TLS certificates.</li>\n<li>Compression,
|
1003
|
+
Caching, and Content Delivery.</li>\n<li>Replication and High availability.</li>\n<li>IPv6!</li>\n<li>Fewer
|
1004
|
+
headaches, and more spending money. :-)</li>\n</ul>\n<br />\nIf you don't
|
1005
|
+
need tight control over how your data is served, I would strongly recommend
|
1006
|
+
that you host your sites on Cloud Buckets. (Yes, of course, servers are still
|
1007
|
+
involved, you just don't need to worry about them.)<br />\n<br />\nIn this
|
1008
|
+
post, I'll show you how I got the <a href=\"https://float64.dev/\">float64
|
1009
|
+
website</a> up and serving in almost no time.<br />\n<br />\n<h3 style=\"text-align:
|
1010
|
+
left;\">\nWhat are Cloud Buckets?</h3>\n<br />\nBuckets are a storage abstraction
|
1011
|
+
for blobs of data offered by cloud providers. E.g., <a href=\"https://cloud.google.com/storage\">Google
|
1012
|
+
Cloud Storage</a> or <a href=\"https://aws.amazon.com/s3/\">Amazon S3</a>.
|
1013
|
+
Put simply, they're a place in the cloud where you can store directories of
|
1014
|
+
files (typically called objects.)<br />\n<br />\nData in buckets are managed
|
1015
|
+
by cloud providers -- they take care of all the heavy lifting around storing
|
1016
|
+
the data, replicating, backing up, and serving. You can access this data with
|
1017
|
+
command line tools, via language APIs, or from the browser. You can also manage
|
1018
|
+
permissions, ownership, replication, retention, encryption, and audit controls.<br
|
1019
|
+
/>\n<br />\n<h3 style=\"text-align: left;\">\n</h3>\n<h3 style=\"text-align:
|
1020
|
+
left;\">\nHosting Websites on Cloud Buckets</h3>\n<br />\nMany cloud providers
|
1021
|
+
now allow you to serve files (sometimes called bucket objects) over the web,
|
1022
|
+
and let you distribute content over their respective CDNs. For this post,
|
1023
|
+
we'll upload a website to a <a href=\"https://cloud.google.com/storage\">Google
|
1024
|
+
Cloud Storage</a> bucket and serve it over the web.<br />\n<br />\nMake sure
|
1025
|
+
you have your <a href=\"https://cloud.google.com/\">Google Cloud</a> account
|
1026
|
+
setup, <a href=\"https://cloud.google.com/sdk\">command-line tools installed</a>,
|
1027
|
+
and are logged in on your terminal.<br />\n<br />\n<code>\ngcloud auth login<br
|
1028
|
+
/>\ngcloud config set project <your-project-id></code><br />\n<code><br
|
1029
|
+
/></code>\n\nCreate your storage bucket with <a href=\"https://cloud.google.com/storage/docs/creating-buckets\">gsutil
|
1030
|
+
mb</a>. Bucket names must be globally unique, so you'll have to pick something
|
1031
|
+
no one else has used. Here I'm using <i>float64</i> as my bucket name.<br
|
1032
|
+
/>\n<br />\n<code>gsutil mb gs://float64</code>\n<br />\n<code><br /></code>\nCopy
|
1033
|
+
your website content over to the bucket. We specify '-<a href=\"https://cloud.google.com/storage/docs/gsutil/commands/cp\">a
|
1034
|
+
public-read</a>' to make the objects world-readable.<br />\n<br />\n<code>gsutil
|
1035
|
+
cp -a public-read index.html style.css index.AF4C.js gs://float64</code>\n<br
|
1036
|
+
/>\n<code><br /></code>\nThat's it. Your content is now available at <i>https://storage.googleapis.com/<BUCKET>/index.html</i>.
|
1037
|
+
Like mine is here: <a href=\"https://storage.googleapis.com/float64/index.html\">https://storage.googleapis.com/float64/index.html</a>.<br
|
1038
|
+
/>\n<br />\n<h3 style=\"text-align: left;\">\n</h3>\n<h3 style=\"text-align:
|
1039
|
+
left;\">\nUsing your own Domain</h3>\n<br />\nTo serve data over your own
|
1040
|
+
domain using HTTPS, you need to create a <a href=\"https://cloud.google.com/load-balancing\">Cloud
|
1041
|
+
Load Balancer</a> (or use an existing one.) Go to the <a href=\"https://console.cloud.google.com/net-services/loadbalancing\">Load
|
1042
|
+
Balancer Console</a>, click \"Create Load Balancer\", and select the HTTP/HTTPS
|
1043
|
+
option.<br />\n<br />\n<div class=\"separator\" style=\"clear: both; text-align:
|
1044
|
+
center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhipsKbza7Qsm2JCe2LJwWdOlpRexWoh8IwF0JVLK9BsUaaiV9jaUctCtiSiG6FNjoHpspbjVPED27FoXPYh8L671CzC-azbBzqp3LQ0LyJtMvQw78R3yLdjX963KIRUSd87b3Azg/s1600/Screen+Shot+2020-02-19+at+8.28.36+AM.png\"
|
1045
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1046
|
+
data-original-height=\"652\" data-original-width=\"878\" height=\"295\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhipsKbza7Qsm2JCe2LJwWdOlpRexWoh8IwF0JVLK9BsUaaiV9jaUctCtiSiG6FNjoHpspbjVPED27FoXPYh8L671CzC-azbBzqp3LQ0LyJtMvQw78R3yLdjX963KIRUSd87b3Azg/s400/Screen+Shot+2020-02-19+at+8.28.36+AM.png\"
|
1047
|
+
width=\"400\" /></a></div>\n<br />\nThe balancer configuration has three main
|
1048
|
+
parts: backend, routing rules, and frontend.<br />\n<br />\nFor the backend,
|
1049
|
+
select \"backend buckets\", and pick the bucket that you just created. Check
|
1050
|
+
the 'Enable CDN' box if you want your content cached and delivered over Google's
|
1051
|
+
worldwide Content Delivery Network.<br />\n<br />\n<div class=\"separator\"
|
1052
|
+
style=\"clear: both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9YZ38AmPpWnQ2YCL3PGI9xzJSbPD4GgyN776Ha5bZ82BubM7CjwmJ7qexivA9WywQpWBsEmKoFYRLrlvIMA3tfRkt95YDp7bZUnQbMC_8vw_OxGg5xq0J7MGNC8FyLZyT5jq3wQ/s1600/Screen+Shot+2020-02-19+at+8.31.48+AM.png\"
|
1053
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1054
|
+
data-original-height=\"100\" data-original-width=\"280\" height=\"71\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9YZ38AmPpWnQ2YCL3PGI9xzJSbPD4GgyN776Ha5bZ82BubM7CjwmJ7qexivA9WywQpWBsEmKoFYRLrlvIMA3tfRkt95YDp7bZUnQbMC_8vw_OxGg5xq0J7MGNC8FyLZyT5jq3wQ/s200/Screen+Shot+2020-02-19+at+8.31.48+AM.png\"
|
1055
|
+
width=\"200\" /></a></div>\n<br />\n<br />\nFor the routing rules, simply
|
1056
|
+
use your domain name (float64.dev) in the host field, your bucket (float64)
|
1057
|
+
in the backends field, and <code><b>/*</b></code> in Paths to say that all
|
1058
|
+
paths get routed to your bucket.<br />\n<br />\nFinally, for the frontend,
|
1059
|
+
add a new IP address, and point your domain's <i>A</i> record at it. If you're
|
1060
|
+
with the times, you can also add an IPv6 address, and point your domain's
|
1061
|
+
<i>AAAA</i> record at it.<br />\n<br />\n<div class=\"separator\" style=\"clear:
|
1062
|
+
both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZYmJHBFv8QyXiXv30K_XVUf62MvxDLSmb4GijnBsHnc2vouiVM2bAqEtM0qpjfoj7_cCcg1p17ZVXntlwmSzctH0cWHKXAFRa38IfK5yS28Am_UCy1B140zhdfmzkB6AtPM0cKA/s1600/Screen+Shot+2020-02-19+at+8.29.55+AM.png\"
|
1063
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1064
|
+
data-original-height=\"272\" data-original-width=\"962\" height=\"179\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZYmJHBFv8QyXiXv30K_XVUf62MvxDLSmb4GijnBsHnc2vouiVM2bAqEtM0qpjfoj7_cCcg1p17ZVXntlwmSzctH0cWHKXAFRa38IfK5yS28Am_UCy1B140zhdfmzkB6AtPM0cKA/s640/Screen+Shot+2020-02-19+at+8.29.55+AM.png\"
|
1065
|
+
width=\"640\" /></a></div>\n<br />\n<br />\nIf you're serving over HTTPS,
|
1066
|
+
you can create a new <a href=\"https://cloud.google.com/load-balancing/docs/ssl-certificates\">managed
|
1067
|
+
certificate</a>. These certs are issued by Let's Encrypt and managed by Google
|
1068
|
+
(i.e., Goole takes care of attaching, verifying, and renewing them.) The certificates
|
1069
|
+
take about 30 minutes to propagate.<br />\n<br />\nSave and apply your changes,
|
1070
|
+
and your custom HTTPS website is up! A few more odds and ends before we call
|
1071
|
+
it a day.<br />\n<br />\n<h3 style=\"text-align: left;\">\nSetup Index and
|
1072
|
+
Error Pages</h3>\n<div>\n<br /></div>\n<div>\nYou probably don't want your
|
1073
|
+
users typing in the name of the index HTML file (<a href=\"https://float64.dev/index.html\">https://float64.dev/index.html</a>)
|
1074
|
+
every time they visit your site. You also probably want invalid URLs showing
|
1075
|
+
a pretty error page.</div>\n<br />\nYou can use <a href=\"https://cloud.google.com/storage/docs/gsutil/commands/web\"><b>gsutil
|
1076
|
+
web</b></a> to configure the index and 404 pages for the bucket.<br />\n<br
|
1077
|
+
/>\n<code>gsutil web set gs://my-super-bucket -m index.html -e 404.html</code><br
|
1078
|
+
/>\n<br />\n<h3 style=\"text-align: left;\">\nCaching, Compression, and Content
|
1079
|
+
Delivery</h3>\n<br />\nTo take advantage of Google's CDN (or even simply to
|
1080
|
+
improve bandwidth usage and latency), you should set the <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control\">Cache-Control
|
1081
|
+
headers</a> on your files. I like to keep the expiries for the index page
|
1082
|
+
short, and everything else long (of course, also adding content hashes to
|
1083
|
+
frequently modified files.)<br />\n<br />\nWe also want to make sure that
|
1084
|
+
text files are served with <i>gzip</i> compression enabled. The <code><b>-z</b></code>
|
1085
|
+
flag compresses the file, and sets the <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding\">content-encoding</a>
|
1086
|
+
to <i>gzip</i> while serving over HTTP(s).<br />\n<br />\n<code>\ngsutil -h
|
1087
|
+
\"Cache-control:public,max-age=86400\" -m \\</code><br />\n<code> cp
|
1088
|
+
-a public-read -z js,map,css,svg \\<br />\n $DIST/*.js $DIST/*.map
|
1089
|
+
$DIST/*.css \\<br />\n $DIST/*.jpg $DIST/*.svg $DIST/*.png $DIST/*.ico
|
1090
|
+
\\<br />\n gs://float64</code><br />\n<code><br />\ngsutil -h
|
1091
|
+
\"Cache-control:public,max-age=300\" -m \\</code><br />\n<code> cp -a
|
1092
|
+
public-read -z html \\</code><br />\n<code> $DIST/index.html gs://float64</code><br
|
1093
|
+
/>\n<code><br /></code>\nIf you've made it this far, you now have a (nearly)
|
1094
|
+
production-ready website up and running. Congratulations!<br />\n<br />\n<h3
|
1095
|
+
style=\"text-align: left;\">\nSo, how much does it cost?</h3>\n<br />\nI have
|
1096
|
+
about 8 different websites running on different domains, all using managed
|
1097
|
+
certificates and the CDN, and I pay about $20 a month.<br />\n<br />\nI use
|
1098
|
+
a single load balancer ($18/mo) and one IP address ($2/mo) for all of them.
|
1099
|
+
I get about 10 - 20k requests a day across all my sites, and bandwidth costs
|
1100
|
+
are in the pennies.<br />\n<br />\nNot cheap, but not expensive either given
|
1101
|
+
the cognitive savings. And there are cheaper options (as you'll see in the
|
1102
|
+
next section).<br />\n<br />\n<h3 style=\"text-align: left;\">\nAlternatives</h3>\n<div>\n<br
|
1103
|
+
/></div>\nThere are many ways to serve web content out of storage buckets,
|
1104
|
+
and this is just one. Depending on your traffic, the number of sites you're
|
1105
|
+
running, and what kinds of tradeoffs you're willing to make, you can optimize
|
1106
|
+
costs further.<br />\n<br />\n<a href=\"https://firebase.google.com/docs/hosting\">Firebase
|
1107
|
+
Hosting</a> sticks all of this into one pretty package, with a lower
|
1108
|
+
upfront cost (however, the bandwidth costs are higher as your traffic increases.)<br
|
1109
|
+
/>\n<br />\n<a href=\"https://www.cloudflare.com/\">Cloudflare</a> has
|
1110
|
+
a free plan and lets you stick an SSL server and CDN in front of your
|
1111
|
+
Cloud Storage bucket. However if you want dedicated certificates, they charge
|
1112
|
+
you $5 each. Also, the minimum TTL on the free plan is 2 hours, which is not
|
1113
|
+
great if you're building static Javascript applications.<br />\n<br />\nAnd
|
1114
|
+
there's <a href=\"https://aws.amazon.com/cloudfront/\">CloudFront</a>, <a
|
1115
|
+
href=\"http://www.fastly.com/\">Fastly</a>, <a href=\"https://netlify.com/\">Netlify</a>,
|
1116
|
+
all which provide various levels of managed infrastructure, still all better
|
1117
|
+
than running your own servers.<br />\n<br />\n<h3 style=\"text-align: left;\">\nCaveats</h3>\n<div>\n<br
|
1118
|
+
/></div>\n<div>\nObviously, there's no free lunch, and good engineering requires
|
1119
|
+
making tradeoffs, and here are a few things to consider before you decide
|
1120
|
+
to migrate from servers to buckets:</div>\n<div>\n<br /></div>\n<div>\n<ul
|
1121
|
+
style=\"text-align: left;\">\n<li><b>Vendor lock-in.</b> Are you okay with
|
1122
|
+
using proprietary technologies for your stack. If not, you're better off running
|
1123
|
+
your own servers.</li>\n<li><b>Control and Flexibility.</b> Do you want advanced
|
1124
|
+
routing, URL rewriting, or other custom behavior? If so you're better off
|
1125
|
+
running your own servers.</li>\n<li><b>Cost transparency.</b> Although both
|
1126
|
+
Google and Amazon do great jobs with billing and detailed price breakdowns,
|
1127
|
+
they are super complicated and can change on a whim.</li>\n</ul>\n<div>\nFor
|
1128
|
+
a lot of what I do, these downsides are well worth it. The vendor lock-in
|
1129
|
+
troubles me the most, however it's not hard to migrate this stuff to other
|
1130
|
+
providers if I need to.</div>\n</div>\n<div>\n<br /></div>\n<div>\nIf you
|
1131
|
+
liked this, check out some of <a href=\"https://0xfe.blogspot.com/\">my other
|
1132
|
+
stuff</a> on this blog.</div>\n<br />\n<br />\n<br /></div>\n<div style='clear:
|
1133
|
+
both;'></div>\n</div>\n<div class='post-footer'>\n<div class='post-footer-line
|
1134
|
+
post-footer-line-1'><span class='post-author vcard'>\n</span>\n<span class='post-timestamp'>\n</span>\n<span
|
1135
|
+
class='post-comment-link'>\n<a class='comment-link' href='https://0xfe.blogspot.com/2020/02/no-servers-just-buckets-hosting-static.html#comment-form'
|
1136
|
+
onclick=''>1 comments</a>\n</span>\n<span class='post-icons'>\n</span>\n<div
|
1137
|
+
class='post-share-buttons goog-inline-block'>\n</div>\n</div>\n<div class='post-footer-line
|
1138
|
+
post-footer-line-2'></div>\n<div class='post-footer-line post-footer-line-3'></div>\n</div>\n</div>\n</div>\n\n
|
1139
|
+
\ </div></div>\n \n\n <div class=\"date-outer\">\n
|
1140
|
+
\ \n<h2 class='date-header'><span>Tuesday, July 12, 2016</span></h2>\n\n
|
1141
|
+
\ <div class=\"date-posts\">\n \n<div class='post-outer'>\n<div
|
1142
|
+
class='post hentry'>\n<a name='421690605020819859'></a>\n<h3 class='post-title
|
1143
|
+
entry-title'>\n<a href='https://0xfe.blogspot.com/2016/07/new-in-vexflow-es6-visual-regression.html'>New
|
1144
|
+
in VexFlow: ES6, Visual Regression Tests, and more!</a>\n</h3>\n<div class='post-header'>\n<div
|
1145
|
+
class='post-header-line-1'></div>\n</div>\n<div class='post-body entry-content'
|
1146
|
+
id='post-body-421690605020819859'>\n<div dir=\"ltr\" style=\"text-align: left;\"
|
1147
|
+
trbidi=\"on\">\nLots of developments since the last time I posted about VexFlow.<br
|
1148
|
+
/>\n<h3 style=\"text-align: left;\">\n</h3>\n<div>\n<br /></div>\n<h3 style=\"text-align:
|
1149
|
+
left;\">\nVexFlow is ES6</h3>\n<div>\n<br /></div>\nThanks to the heroics
|
1150
|
+
of <a href=\"http://github.com/SilverWolf90\">SilverWolf90</a> and <a href=\"http://github.com/AaronMars\">AaronMars</a>,
|
1151
|
+
and the help from many others, VexFlow's entire <code>src/</code> tree has
|
1152
|
+
been migrated to ES6. This is a huge benefit to the project and to the health
|
1153
|
+
of the codebase. Some of the wins are:<br />\n<br />\n<ul style=\"text-align:
|
1154
|
+
left;\">\n<li>Real modules, which allows us to extract explicit dependency
|
1155
|
+
information and generate graphs like <a href=\"https://github.com/0xfe/vexflow/wiki/VexFlow-Dependency-Graph\">this</a>.</li>\n<li>Const-correctness
|
1156
|
+
and predictable variable scoping with <code>const</code> and <code>let</code>.</li>\n<li>Classes,
|
1157
|
+
lambda functions, and lots of other structural enhancements that vastly improve
|
1158
|
+
the clarity and conciseness of the codebase.</li>\n</ul>\n<div>\n<div class=\"separator\"
|
1159
|
+
style=\"clear: both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3aiqkRZhH7qzKgGfHCA1yXvmcc4CvQwetLQEHvrQej3ugBVywShEIL3tPMIFw93rJU4Qf7yyerY2Ha60AgIvadGwq98fZLMrTLbzRg0h0y9v1XG_sIyFh4lVEmi2tq-HbikKDMg/s1600/Screen+Shot+2016-07-12+at+10.06.20+AM.png\"
|
1160
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1161
|
+
height=\"390\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3aiqkRZhH7qzKgGfHCA1yXvmcc4CvQwetLQEHvrQej3ugBVywShEIL3tPMIFw93rJU4Qf7yyerY2Ha60AgIvadGwq98fZLMrTLbzRg0h0y9v1XG_sIyFh4lVEmi2tq-HbikKDMg/s640/Screen+Shot+2016-07-12+at+10.06.20+AM.png\"
|
1162
|
+
width=\"640\" /></a></div>\n<br />\n<br />\nPart of the migration effort also
|
1163
|
+
involved making everything lint-clean, improving the overall style and consistency
|
1164
|
+
of the codebase -- see <a href=\"http://github.com/SilverWolf90\">SilverWolf90</a>'s
|
1165
|
+
brief document on how <a href=\"https://github.com/0xfe/vexflow/wiki/Migrating-to-ESLint\">here</a>.</div>\n<h3
|
1166
|
+
style=\"text-align: left;\">\n</h3>\n<div>\n<br /></div>\n<h3 style=\"text-align:
|
1167
|
+
left;\">\nVisual Regression Tests</h3>\n<div>\n<br /></div>\nVexFlow now has
|
1168
|
+
a visual regression test system, and all image-generating QUnit tests are
|
1169
|
+
automatically included.<br />\n<br />\nThe goal of this system is to detect
|
1170
|
+
differences in the rendered output without having to rely on human eyeballs,
|
1171
|
+
especially given the huge number of tests that exist today. It does this by
|
1172
|
+
calculating a perceptual hash (PHASH) of each test image and comparing it
|
1173
|
+
with the hash of a good known blessed image. The larger the arithmetic distance
|
1174
|
+
between the hashes, the more different are the two images.<br />\n<br />\nThe
|
1175
|
+
system also generates a diff image, which is an overlay of the two images,
|
1176
|
+
with the differences highlighted, to ease debugging. Here's an example of
|
1177
|
+
a failing test:<br />\n<br />\n<div class=\"separator\" style=\"clear: both;
|
1178
|
+
text-align: center;\">\n<a href=\"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\"
|
1179
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1180
|
+
height=\"640\" src=\"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\"
|
1181
|
+
width=\"600\" /></a></div>\n<br />\n<br />\n<div class=\"separator\" style=\"clear:
|
1182
|
+
both; text-align: center;\">\n</div>\nThese tests are run automatically for
|
1183
|
+
all PRs, commits, and releases. Props to <a href=\"http://github.com/panarch\">Taehoon
|
1184
|
+
Moon</a> for migrating the regression tests from NodeJS to SlimerJS, giving
|
1185
|
+
us headless support and Travis CI integration. To find out more, read the
|
1186
|
+
Wiki page on <a href=\"https://github.com/0xfe/vexflow/wiki/Visual-Regression-Tests\">Visual
|
1187
|
+
Regression Tests</a>.<br />\n<h3 style=\"text-align: left;\">\n</h3>\n<div>\n<br
|
1188
|
+
/></div>\n<h3 style=\"text-align: left;\">\nNative SVG</h3>\n<div>\n<br /></div>\nThanks
|
1189
|
+
to the awesome contribution of <a href=\"http://github.com/gristow\">Gregory
|
1190
|
+
Ristow</a>, VexFlow now has a native SVG rendering backend, and the <a href=\"http://raphaeljs.com/\">RaphaelJS</a>
|
1191
|
+
backend has been deprecated. This not only reduces the overall size and bloat,
|
1192
|
+
but also hugely improves rendering performance.<br />\n<br />\nThe new backend
|
1193
|
+
is called <code>Rendering.Backends.SVG</code> with the code at <a href=\"https://github.com/0xfe/vexflow/blob/master/src/svgcontext.js\">Vex.Flow.SVGContext</a>.
|
1194
|
+
Here is a quick example of how to use the new backend: <a href=\"https://jsfiddle.net/nL0cn3vL/2/\">https://jsfiddle.net/nL0cn3vL/2/</a>.<br
|
1195
|
+
/>\n<br />\n<h3 style=\"text-align: left;\">\nImproved Microtonal Support</h3>\n<br
|
1196
|
+
/>\nVexFlow now has better support for Arabic, Turkish, and other microtonal
|
1197
|
+
music via accidentals and key signatures. Thanks to <a href=\"http://github.com/infojunkie\">infojunkie</a>
|
1198
|
+
for a lot of the heavy lifting here, and to all the contributors in the <a
|
1199
|
+
href=\"https://github.com/0xfe/vexflow/issues/318\">GitHub issue</a>.<br />\n<br
|
1200
|
+
/>\n<div class=\"separator\" style=\"clear: both; text-align: center;\">\n<a
|
1201
|
+
href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKqsOnWIHkdB5VVqFfq6gj2wgiwJXzN1Sk8XR2qJX_6EZ-2xnrzBYL5eZy23MiDbm5GCvgNA_TBzjFQ8JOCoA2JlOVMD7rw3jQxk4YphClucw6TQGtnc1-ZJgNhWYtX18BKHKMcQ/s1600/Screen+Shot+2016-07-12+at+9.50.40+AM.png\"
|
1202
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1203
|
+
height=\"212\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKqsOnWIHkdB5VVqFfq6gj2wgiwJXzN1Sk8XR2qJX_6EZ-2xnrzBYL5eZy23MiDbm5GCvgNA_TBzjFQ8JOCoA2JlOVMD7rw3jQxk4YphClucw6TQGtnc1-ZJgNhWYtX18BKHKMcQ/s640/Screen+Shot+2016-07-12+at+9.50.40+AM.png\"
|
1204
|
+
width=\"640\" /></a></div>\n<br />\n<div class=\"separator\" style=\"clear:
|
1205
|
+
both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYh59DufskkVVEVorHw4Of9VzlaEjU2i2GG8p8q3-PpWfFZrbEcWs8rvfyiEvJ1gfJwvRoEjzkW88AzO4O2LUo-Vs3PU97AmA6ZINyvz5zm4vwfU_s89qNFNrcf56tys6hDXVN6w/s1600/Screen+Shot+2016-07-12+at+9.50.52+AM.png\"
|
1206
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1207
|
+
height=\"272\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYh59DufskkVVEVorHw4Of9VzlaEjU2i2GG8p8q3-PpWfFZrbEcWs8rvfyiEvJ1gfJwvRoEjzkW88AzO4O2LUo-Vs3PU97AmA6ZINyvz5zm4vwfU_s89qNFNrcf56tys6hDXVN6w/s640/Screen+Shot+2016-07-12+at+9.50.52+AM.png\"
|
1208
|
+
width=\"640\" /></a></div>\n<br />\nMicrotonal support is by no means complete,
|
1209
|
+
but this is a noteworthy step forward in the space.<br />\n<br />\n<h3 style=\"text-align:
|
1210
|
+
left;\">\nOther Stuff</h3>\n<br />\nLots of other stuff worth mentioning:<br
|
1211
|
+
/>\n<br />\n<ul style=\"text-align: left;\">\n<li>Support for user interactivity
|
1212
|
+
in SVG notation. You can attach event-handlers to elements (or groups of elements)
|
1213
|
+
and dynamically modify various properties of the score.</li>\n<li>Improved
|
1214
|
+
bounding-box support.</li>\n<li>Alignment of clef, timesignature, and other
|
1215
|
+
stave modifiers during mid-measure changes.</li>\n<li>Lots of improvements
|
1216
|
+
to the build system and Travis CI integration.</li>\n<li>Lots of bug fixes
|
1217
|
+
related to beaming, tuplets, annotations, etc.</li>\n</ul>\n<div>\n<br /></div>\n<div>\nMany
|
1218
|
+
thanks to all the contributors involved!</div>\n</div>\n<div style='clear:
|
1219
|
+
both;'></div>\n</div>\n<div class='post-footer'>\n<div class='post-footer-line
|
1220
|
+
post-footer-line-1'><span class='post-author vcard'>\n</span>\n<span class='post-timestamp'>\n</span>\n<span
|
1221
|
+
class='post-comment-link'>\n<a class='comment-link' href='https://0xfe.blogspot.com/2016/07/new-in-vexflow-es6-visual-regression.html#comment-form'
|
1222
|
+
onclick=''>2\ncomments</a>\n</span>\n<span class='post-icons'>\n</span>\n<div
|
1223
|
+
class='post-share-buttons goog-inline-block'>\n</div>\n</div>\n<div class='post-footer-line
|
1224
|
+
post-footer-line-2'></div>\n<div class='post-footer-line post-footer-line-3'></div>\n</div>\n</div>\n</div>\n\n
|
1225
|
+
\ </div></div>\n \n\n <div class=\"date-outer\">\n
|
1226
|
+
\ \n<h2 class='date-header'><span>Friday, May 02, 2014</span></h2>\n\n
|
1227
|
+
\ <div class=\"date-posts\">\n \n<div class='post-outer'>\n<div
|
1228
|
+
class='post hentry'>\n<a name='2970930225553638408'></a>\n<h3 class='post-title
|
1229
|
+
entry-title'>\n<a href='https://0xfe.blogspot.com/2014/05/new-in-vexflow.html'>New
|
1230
|
+
in VexFlow (May 2014)</a>\n</h3>\n<div class='post-header'>\n<div class='post-header-line-1'></div>\n</div>\n<div
|
1231
|
+
class='post-body entry-content' id='post-body-2970930225553638408'>\nLots
|
1232
|
+
of commits into the repository lately. Thanks to Cyril Silverman for may of
|
1233
|
+
these. Here are some of the highlights:\n\n<p/>\n\n<h3>Chord Symbols</h3>\nThis
|
1234
|
+
includes subscript/superscript support in <code>TextNote</code> and support
|
1235
|
+
for common symbols (dim, half-dim, maj7, etc.)\n<p/>\n<div class=\"separator\"
|
1236
|
+
style=\"clear: both; text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhARWln9YaHdtVL5iBAiFKZHcGI85eDMyNLhN3USobfFRbFuq6FAZPsXK746YLJHrXEcb0Hpkbza1giiZ8sCb6I-1BVwuPTCNwkY8boODT0d1x7hYEnbys1OzHbdVRmwGwEYMSaSw/s1600/Screen+Shot+2014-05-02+at+10.51.12+AM.png\"
|
1237
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1238
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhARWln9YaHdtVL5iBAiFKZHcGI85eDMyNLhN3USobfFRbFuq6FAZPsXK746YLJHrXEcb0Hpkbza1giiZ8sCb6I-1BVwuPTCNwkY8boODT0d1x7hYEnbys1OzHbdVRmwGwEYMSaSw/s400/Screen+Shot+2014-05-02+at+10.51.12+AM.png\"
|
1239
|
+
/></a></div>\n\n<p/>\n\n<h3>Stave Line Arrows</h3>\nThis is typically used
|
1240
|
+
in instructional material.\n<p/>\n<div class=\"separator\" style=\"clear:
|
1241
|
+
both; text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNZTalVSVd5H_a4RBTaURK2A7MhCwpKTZDwqEC6j3ZALkCB3TFv4jfVQyX6GoiALkwsTkxnRK_Ntw7HuRm3S7n6ehmw8M1xa1qqysNo7HVxG06zc1y2TNE09PNp41kaGqiHo32tg/s1600/Screen+Shot+2014-05-02+at+10.50.57+AM.png\"
|
1242
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1243
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNZTalVSVd5H_a4RBTaURK2A7MhCwpKTZDwqEC6j3ZALkCB3TFv4jfVQyX6GoiALkwsTkxnRK_Ntw7HuRm3S7n6ehmw8M1xa1qqysNo7HVxG06zc1y2TNE09PNp41kaGqiHo32tg/s400/Screen+Shot+2014-05-02+at+10.50.57+AM.png\"
|
1244
|
+
/></a></div>\n\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1245
|
+
center;\"><a href=\"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\"
|
1246
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1247
|
+
src=\"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\"
|
1248
|
+
/></a></div>\n\n<p/>\n\n<h3>Slurs</h3>\nFinally, we have slurs. This uses
|
1249
|
+
a new VexFlow class called <code>Curve</code>. Slurs are highly configurable.\n<p/>\n<div
|
1250
|
+
class=\"separator\" style=\"clear: both; text-align: center;\"><a href=\"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\"
|
1251
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1252
|
+
src=\"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\"
|
1253
|
+
/></a></div>\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1254
|
+
center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhatECDRLVFWQyBskqVOcVUOZg66Jcywok5t9qyAfIVcqHoUfCZa1J2Ga1Gxrp3dpiFA7Zbm6I1XGGRrWBMX6fcZ5oZDxlWZu0hR1UVipMdAgmEf-96gHbWgppln4Dp2DOsBhIAxg/s1600/Screen+Shot+2014-05-02+at+10.51.29+AM.png\"
|
1255
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1256
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhatECDRLVFWQyBskqVOcVUOZg66Jcywok5t9qyAfIVcqHoUfCZa1J2Ga1Gxrp3dpiFA7Zbm6I1XGGRrWBMX6fcZ5oZDxlWZu0hR1UVipMdAgmEf-96gHbWgppln4Dp2DOsBhIAxg/s400/Screen+Shot+2014-05-02+at+10.51.29+AM.png\"
|
1257
|
+
/></a></div>\n\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1258
|
+
center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgc_eVlyXPeFIevIo9au9bCa75PKtHctY2oRecgdQl15Gspn0s_i7uZK_7t6UclD-UAqCXFCJrozYETXUqg45BobYrgRGs0EZkTpCMriR21P0CdLfaH5sDfSVljgqHokOQwHM7Kbw/s1600/Screen+Shot+2014-05-02+at+10.51.36+AM.png\"
|
1259
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1260
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgc_eVlyXPeFIevIo9au9bCa75PKtHctY2oRecgdQl15Gspn0s_i7uZK_7t6UclD-UAqCXFCJrozYETXUqg45BobYrgRGs0EZkTpCMriR21P0CdLfaH5sDfSVljgqHokOQwHM7Kbw/s400/Screen+Shot+2014-05-02+at+10.51.36+AM.png\"
|
1261
|
+
/></a></div>\n\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1262
|
+
center;\"><a href=\"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\"
|
1263
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1264
|
+
src=\"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\"
|
1265
|
+
/></a></div>\n\n<p/>\n\n<h3>Improved auto-positioning of Annotations and Articulations</h3>\nAnnotations
|
1266
|
+
and Articulations now self-position based on note, stem, and beam configuration.\n<p/>\n<div
|
1267
|
+
class=\"separator\" style=\"clear: both; text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU13FCOX7XCOL9mnXofqMUGuOmGJbJvbCtUIixCdxqASZmSCLZNmPQ773x2hY5lWiPHorbJak3YtUg1BuhcV1HajHha3ryLrlId4AczhzrqALlOhdvm2wyBiz7ha8SZwRliBj11A/s1600/Screen+Shot+2014-05-02+at+10.54.03+AM.png\"
|
1268
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1269
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU13FCOX7XCOL9mnXofqMUGuOmGJbJvbCtUIixCdxqASZmSCLZNmPQ773x2hY5lWiPHorbJak3YtUg1BuhcV1HajHha3ryLrlId4AczhzrqALlOhdvm2wyBiz7ha8SZwRliBj11A/s400/Screen+Shot+2014-05-02+at+10.54.03+AM.png\"
|
1270
|
+
/></a></div>\n\n<p/>\n\n<h3>Grace Notes</h3>\nVexFlow now has full support
|
1271
|
+
for Grace Notes. Grace Note groups can contain complex rhythmic elements,
|
1272
|
+
and are formatted using the same code as regular notes.\n\n<p/>\n<div class=\"separator\"
|
1273
|
+
style=\"clear: both; text-align: center;\"><a href=\"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\"
|
1274
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1275
|
+
src=\"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\"
|
1276
|
+
/></a></div>\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1277
|
+
center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZOxD9_LTKefUh0Vs9iqFpzlLI55B7tYSYKx79Zv43GL0BhyMqmfi63i7ulWmY1FrXmN_5HnFmORvMqCNA0yUX-jQEi6MJOv4cO3HBBvde5m1VOeRPzEXxvEO_FwR2qceVl1Zixg/s1600/Screen+Shot+2014-05-02+at+10.54.37+AM.png\"
|
1278
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1279
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZOxD9_LTKefUh0Vs9iqFpzlLI55B7tYSYKx79Zv43GL0BhyMqmfi63i7ulWmY1FrXmN_5HnFmORvMqCNA0yUX-jQEi6MJOv4cO3HBBvde5m1VOeRPzEXxvEO_FwR2qceVl1Zixg/s400/Screen+Shot+2014-05-02+at+10.54.37+AM.png\"
|
1280
|
+
/></a></div>\n\n<p/>\n\n<h3>Auto-Beam Imnprovements</h3>\nLots more beaming
|
1281
|
+
options, including beaming over rests, stemlet rendering, and time-signature
|
1282
|
+
aware beaming.\n\n<p/>\n<div class=\"separator\" style=\"clear: both; text-align:
|
1283
|
+
center;\"><a href=\"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\"
|
1284
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1285
|
+
src=\"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\"
|
1286
|
+
/></a></div>\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1287
|
+
center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgovZ5gaOWJyLcc9DxyQX0yI54NK-hy3hSgukICa5-gMrGTaFbVR1OXhoWjS7TkX5CDtoiBRQC4S88QBNv_IHsnRU5U8nKB0H8xHPEuU2o3goALytt_ayQgkMyCNicq7dCJ8qmgQg/s1600/Screen+Shot+2014-05-02+at+10.55.15+AM.png\"
|
1288
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1289
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgovZ5gaOWJyLcc9DxyQX0yI54NK-hy3hSgukICa5-gMrGTaFbVR1OXhoWjS7TkX5CDtoiBRQC4S88QBNv_IHsnRU5U8nKB0H8xHPEuU2o3goALytt_ayQgkMyCNicq7dCJ8qmgQg/s400/Screen+Shot+2014-05-02+at+10.55.15+AM.png\"
|
1290
|
+
/></a></div>\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1291
|
+
center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihdUTDYtKZhcd-HQjXBxzNPfL0VycfORZBI7xoQWZ3Re4VpaLb9Ka59EL1WivBVfmt1cERK5RhN7zs0mbvxFwzbucMkfL50UYk7epEViA7L3UDk5ihFNJIGVdHmNTgZQ0gjPGrHg/s1600/Screen+Shot+2014-05-02+at+10.55.25+AM.png\"
|
1292
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1293
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihdUTDYtKZhcd-HQjXBxzNPfL0VycfORZBI7xoQWZ3Re4VpaLb9Ka59EL1WivBVfmt1cERK5RhN7zs0mbvxFwzbucMkfL50UYk7epEViA7L3UDk5ihFNJIGVdHmNTgZQ0gjPGrHg/s400/Screen+Shot+2014-05-02+at+10.55.25+AM.png\"
|
1294
|
+
/></a></div>\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1295
|
+
center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7hzcAqJ2cabV3VXA9JCKovDSwx5yKTJB8lKEOmOmHZ_VKoM9uTktsiIR6f2ulKVShwrOm7v7dUuPvNPM3E5EZ-YupxVdfz9EXHRr_FMaOlhsoP5At99tKHoVBmU9CX9-YpIMjhA/s1600/Screen+Shot+2014-05-02+at+10.55.35+AM.png\"
|
1296
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1297
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7hzcAqJ2cabV3VXA9JCKovDSwx5yKTJB8lKEOmOmHZ_VKoM9uTktsiIR6f2ulKVShwrOm7v7dUuPvNPM3E5EZ-YupxVdfz9EXHRr_FMaOlhsoP5At99tKHoVBmU9CX9-YpIMjhA/s400/Screen+Shot+2014-05-02+at+10.55.35+AM.png\"
|
1298
|
+
/></a></div>\n\n<p/>\n<h3>Tab-Stem Features</h3>\n\nYou can (optionally) render
|
1299
|
+
Tab Stems through stave lines.\n<p/>\n\n<div class=\"separator\" style=\"clear:
|
1300
|
+
both; text-align: center;\"><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfsQjfpN66Lpyq_IC4dTlXCypbUy3s8wEOE1Tf-j0hRUS3JkAYk2C5topEC4P0cA2ZgQ2A7gxP1djMAp8ecedWzJgaO_lHOELzrFZq8EebqIO3Y7QZxa67UAKluCDHNhv-QgNkWQ/s1600/Screen+Shot+2014-05-02+at+10.57.42+AM.png\"
|
1301
|
+
imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"><img border=\"0\"
|
1302
|
+
src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfsQjfpN66Lpyq_IC4dTlXCypbUy3s8wEOE1Tf-j0hRUS3JkAYk2C5topEC4P0cA2ZgQ2A7gxP1djMAp8ecedWzJgaO_lHOELzrFZq8EebqIO3Y7QZxa67UAKluCDHNhv-QgNkWQ/s400/Screen+Shot+2014-05-02+at+10.57.42+AM.png\"
|
1303
|
+
/></a></div>\n\n<p/>\n\nThat's all, Folks!\n<div style='clear: both;'></div>\n</div>\n<div
|
1304
|
+
class='post-footer'>\n<div class='post-footer-line post-footer-line-1'><span
|
1305
|
+
class='post-author vcard'>\n</span>\n<span class='post-timestamp'>\n</span>\n<span
|
1306
|
+
class='post-comment-link'>\n<a class='comment-link' href='https://0xfe.blogspot.com/2014/05/new-in-vexflow.html#comment-form'
|
1307
|
+
onclick=''>19\ncomments</a>\n</span>\n<span class='post-icons'>\n</span>\n<div
|
1308
|
+
class='post-share-buttons goog-inline-block'>\n</div>\n</div>\n<div class='post-footer-line
|
1309
|
+
post-footer-line-2'></div>\n<div class='post-footer-line post-footer-line-3'></div>\n</div>\n</div>\n</div>\n\n
|
1310
|
+
\ </div></div>\n \n\n <div class=\"date-outer\">\n
|
1311
|
+
\ \n<h2 class='date-header'><span>Monday, January 02, 2012</span></h2>\n\n
|
1312
|
+
\ <div class=\"date-posts\">\n \n<div class='post-outer'>\n<div
|
1313
|
+
class='post hentry'>\n<a name='6880188793332322250'></a>\n<h3 class='post-title
|
1314
|
+
entry-title'>\n<a href='https://0xfe.blogspot.com/2012/01/more-k-means-clustering-experiments-on.html'>More
|
1315
|
+
K-Means Clustering Experiments on Images</a>\n</h3>\n<div class='post-header'>\n<div
|
1316
|
+
class='post-header-line-1'></div>\n</div>\n<div class='post-body entry-content'
|
1317
|
+
id='post-body-6880188793332322250'>\nI spent a little more time experimenting
|
1318
|
+
with <a href=\"http://0xfe.blogspot.com/2011/12/k-means-clustering-and-art.html\">k-means
|
1319
|
+
clustering</a> on images and realized that I could use these clusters to recolor
|
1320
|
+
the image in interesting ways.\n<p/>\nI wrote the function <code>save_recolor</code>
|
1321
|
+
to replace pixels from the given clusters (<code>replacements</code>) with
|
1322
|
+
new ones of equal intensity, as specified by the <code>rgb_factors</code>
|
1323
|
+
vector. For example, the following code will convert pixels of the first two
|
1324
|
+
clusters to greyscale.\n<p/>\n<pre class=\"prettyprint\">\n> save_recolor(\"baby.jpeg\",
|
1325
|
+
\"baby_new.jpg\", replacements=c(1,2),\n rgb_factors=c(1/3,
|
1326
|
+
1/3, 1/3))\n</pre>\n<p/>\nIt's greyscale because the <code>rgb_factors</code>
|
1327
|
+
distributes the pixel intensity evenly among the channels. A factor of <code>c(20/100,
|
1328
|
+
60/100, 20/100)</code> would make pixels from the cluster 60% more green.\n<p/>\nLet's
|
1329
|
+
get to some examples. Here's an unprocessed image, alongside its color clusters.
|
1330
|
+
I picked <code>k=10</code>. You can set <code>k</code> by specifying the <code>palette_size</code>
|
1331
|
+
parameter to <code>save_recolor</code>.\n\n<div class=\"separator\" style=\"clear:
|
1332
|
+
both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnRdj14UpXHdCnqTwLonrOWUuqLfH09kzSImmQpt8gaPgq8udoZZCtbCcvejK2mvW0u9RKzC355jFpINb2sxNvMccNBy5z8j477Vx0HLeTB87Mww1nldLP6aQ7qZn5mOalIi7-Tw/s1600/Screen+shot+2012-01-01+at+12.16.45+PM.png\"
|
1333
|
+
imageanchor=\"1\" style=\"margin-left:1em; margin-right:1em\"><img border=\"0\"
|
1334
|
+
height=\"274\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnRdj14UpXHdCnqTwLonrOWUuqLfH09kzSImmQpt8gaPgq8udoZZCtbCcvejK2mvW0u9RKzC355jFpINb2sxNvMccNBy5z8j477Vx0HLeTB87Mww1nldLP6aQ7qZn5mOalIi7-Tw/s400/Screen+shot+2012-01-01+at+12.16.45+PM.png\"
|
1335
|
+
width=\"400\" /></a></div>\n\n<p/>\nHere's what happens when I remove the
|
1336
|
+
red (the first cluster).\n\n<div class=\"separator\" style=\"clear: both;
|
1337
|
+
text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj21ggbKmvK0T2hmbK5tWykQeThSTgl-n-NtJnxUF0v7xQf0DAQV6IXutPz2DuPxFVqBNim_dAvmKQ3xWcGOXrN1tc9NSb-LM2rj-1sAMVV_W6YLkX3dVST_CnlzaE6D4IzfwMUFw/s1600/arkin_recolor_nored.jpg\"
|
1338
|
+
imageanchor=\"1\" style=\"margin-left:1em; margin-right:1em\"><img border=\"0\"
|
1339
|
+
height=\"400\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj21ggbKmvK0T2hmbK5tWykQeThSTgl-n-NtJnxUF0v7xQf0DAQV6IXutPz2DuPxFVqBNim_dAvmKQ3xWcGOXrN1tc9NSb-LM2rj-1sAMVV_W6YLkX3dVST_CnlzaE6D4IzfwMUFw/s400/arkin_recolor_nored.jpg\"
|
1340
|
+
width=\"300\" /></a></div>\n\n<pre class=\"prettyprint\">\n> save_recolor(\"baby.jpeg\",
|
1341
|
+
\"baby_new.jpg\", replacements=1)\n</pre>\n\n\n<p/>\nIn the next image, I
|
1342
|
+
keep the red, and remove everything else.\n\n<div class=\"separator\" style=\"clear:
|
1343
|
+
both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA27bcS661NHy3BNusm97m9j8-jR-DYdF-rRYXgjwfZcSE08rY8qwFakOGzHk35tbuanWAJBf4QRySbcDvzE0vmDvlM0S4l1AOGHb8J6NIjCeBWAaDIZNfp8WH3puF_uqPN7K4DA/s1600/arkin_recolor_red.jpeg\"
|
1344
|
+
imageanchor=\"1\" style=\"margin-left:1em; margin-right:1em\"><img border=\"0\"
|
1345
|
+
height=\"400\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA27bcS661NHy3BNusm97m9j8-jR-DYdF-rRYXgjwfZcSE08rY8qwFakOGzHk35tbuanWAJBf4QRySbcDvzE0vmDvlM0S4l1AOGHb8J6NIjCeBWAaDIZNfp8WH3puF_uqPN7K4DA/s400/arkin_recolor_red.jpeg\"
|
1346
|
+
width=\"300\" /></a></div>\n\n<pre class=\"prettyprint\">\n> save_recolor(\"baby.jpeg\",
|
1347
|
+
\"baby_new.jpg\", replacements=2:10)\n</pre>\n\n\n<p/>\nBelow, I replace the
|
1348
|
+
red cluster pixels, with green ones of corresponding intensity.\n\n<div class=\"separator\"
|
1349
|
+
style=\"clear: both; text-align: center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwxVeNhcer-um9mLCAKIdpVqaI5GsohQzeHECRjZ7bI6gDEHzsp45SuCy7GjANE9IYAXdI0qBmf4ZEO8yWwgPWWy8MOW_O8MGnr1BDR1A3P4U90SXaYy1fmBlQU8pRiSNHXZdKug/s1600/arkin_recolor_green.jpg\"
|
1350
|
+
imageanchor=\"1\" style=\"margin-left:1em; margin-right:1em\"><img border=\"0\"
|
1351
|
+
height=\"400\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwxVeNhcer-um9mLCAKIdpVqaI5GsohQzeHECRjZ7bI6gDEHzsp45SuCy7GjANE9IYAXdI0qBmf4ZEO8yWwgPWWy8MOW_O8MGnr1BDR1A3P4U90SXaYy1fmBlQU8pRiSNHXZdKug/s400/arkin_recolor_green.jpg\"
|
1352
|
+
width=\"300\" /></a></div>\n\n<pre class=\"prettyprint\">\n> save_recolor(\"baby.jpeg\",
|
1353
|
+
\"baby_new.jpg\", replacements=1,\n rgb_factors=c(10/100, 80/100,
|
1354
|
+
10/100))\n</pre>\n\n<p/>\nAnd this is a fun one: Get rid of everything, keep
|
1355
|
+
just the grass.\n\n<div class=\"separator\" style=\"clear: both; text-align:
|
1356
|
+
center;\">\n<a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSuSiJ8ulDFAstlCFQkYTAJIRIhYSVl_jrUYMaC-G4D9hmpIEBMnkl3DI4B54Kvi28b5q4qJPBS8M9w0OBPqLebGd5-nNN32MUJioCg-cFx3hhoEwARmexWb9kLyd20u8NIAu_Aw/s1600/arkin_recolor_grass.jpg\"
|
1357
|
+
imageanchor=\"1\" style=\"margin-left:1em; margin-right:1em\"><img border=\"0\"
|
1358
|
+
height=\"400\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSuSiJ8ulDFAstlCFQkYTAJIRIhYSVl_jrUYMaC-G4D9hmpIEBMnkl3DI4B54Kvi28b5q4qJPBS8M9w0OBPqLebGd5-nNN32MUJioCg-cFx3hhoEwARmexWb9kLyd20u8NIAu_Aw/s400/arkin_recolor_grass.jpg\"
|
1359
|
+
width=\"300\" /></a></div>\n\n\n<pre class=\"prettyprint\">\n> save_recolor(\"baby.jpeg\",
|
1360
|
+
\"baby_new.jpg\", replacements=c(1,3:10))\n</pre>\n\n<p/>\nI tried this on
|
1361
|
+
various images, using different cluster sizes, replacements, and RGB factors,
|
1362
|
+
with lots of interesting results. Anyhow, you should experiment with this
|
1363
|
+
yourselves and let me know what you find.\n<p/>\nI should point out that nothing
|
1364
|
+
here is novel or new -- it's all well known in image processing circles. It's
|
1365
|
+
still pretty impressive what you can do when you apply simple machine learning
|
1366
|
+
algorithms to other areas.\n\n<p>\nOkay, as in all my posts, the code is available
|
1367
|
+
in my <a href=\"http://github.com/0xfe\">GitHub repository</a>:\n<p/>\n<a
|
1368
|
+
href=\"https://github.com/0xfe/experiments/blob/master/r/recolor.rscript\">https://github.com/0xfe/experiments/blob/master/r/recolor.rscript</a>\n\n<p/>\nHappy
|
1369
|
+
new year!\n<div style='clear: both;'></div>\n</div>\n<div class='post-footer'>\n<div
|
1370
|
+
class='post-footer-line post-footer-line-1'><span class='post-author vcard'>\n</span>\n<span
|
1371
|
+
class='post-timestamp'>\n</span>\n<span class='post-comment-link'>\n<a class='comment-link'
|
1372
|
+
href='https://0xfe.blogspot.com/2012/01/more-k-means-clustering-experiments-on.html#comment-form'
|
1373
|
+
onclick=''>3\ncomments</a>\n</span>\n<span class='post-icons'>\n</span>\n<div
|
1374
|
+
class='post-share-buttons goog-inline-block'>\n</div>\n</div>\n<div class='post-footer-line
|
1375
|
+
post-footer-line-2'></div>\n<div class='post-footer-line post-footer-line-3'></div>\n</div>\n</div>\n</div>\n\n
|
1376
|
+
\ </div></div>\n \n</div>\n<div class='blog-pager' id='blog-pager'>\n<span
|
1377
|
+
id='blog-pager-older-link'>\n<a class='blog-pager-older-link' href='https://0xfe.blogspot.com/search?updated-max=2012-01-02T13:34:00-05:00&max-results=8'
|
1378
|
+
id='Blog1_blog-pager-older-link' title='Older Posts'>Older Posts</a>\n</span>\n<a
|
1379
|
+
class='home-link' href='https://0xfe.blogspot.com/'>Home</a>\n</div>\n<div
|
1380
|
+
class='clear'></div>\n<div class='blog-feeds'>\n<div class='feed-links'>\nSubscribe
|
1381
|
+
to:\n<a class='feed-link' href='https://0xfe.blogspot.com/feeds/posts/default'
|
1382
|
+
target='_blank' type='application/atom+xml'>Posts (Atom)</a>\n</div>\n</div>\n</div></div>\n</div>\n</div>\n<div
|
1383
|
+
class='column-left-outer'>\n<div class='column-left-inner'>\n<aside>\n</aside>\n</div>\n</div>\n<div
|
1384
|
+
class='column-right-outer'>\n<div class='column-right-inner'>\n<aside>\n<div
|
1385
|
+
class='sidebar section' id='sidebar-right-1'><div class='widget LinkList'
|
1386
|
+
data-version='1' id='LinkList2'>\n<div class='widget-content'>\n<ul>\n<li><a
|
1387
|
+
href='http://github.com/0xfe'>github/0xfe</a></li>\n</ul>\n<div class='clear'></div>\n</div>\n</div></div>\n</aside>\n</div>\n</div>\n</div>\n<div
|
1388
|
+
style='clear: both'></div>\n<!-- columns -->\n</div>\n<!-- main -->\n</div>\n</div>\n<div
|
1389
|
+
class='main-cap-bottom cap-bottom'>\n<div class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n<footer>\n<div
|
1390
|
+
class='footer-outer'>\n<div class='footer-cap-top cap-top'>\n<div class='cap-left'></div>\n<div
|
1391
|
+
class='cap-right'></div>\n</div>\n<div class='fauxborder-left footer-fauxborder-left'>\n<div
|
1392
|
+
class='fauxborder-right footer-fauxborder-right'></div>\n<div class='region-inner
|
1393
|
+
footer-inner'>\n<div class='foot no-items section' id='footer-1'></div>\n<!--
|
1394
|
+
outside of the include in order to lock Attribution widget -->\n<div class='foot
|
1395
|
+
section' id='footer-3'><div class='widget Attribution' data-version='1' id='Attribution1'>\n<div
|
1396
|
+
class='widget-content' style='text-align: center;'>\nCopyright 2011 Mohit
|
1397
|
+
Muthanna Cheppudira. Powered by <a href='https://draft.blogger.com' target='_blank'>Blogger</a>.\n</div>\n<div
|
1398
|
+
class='clear'></div>\n</div></div>\n</div>\n</div>\n<div class='footer-cap-bottom
|
1399
|
+
cap-bottom'>\n<div class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n</footer>\n<!--
|
1400
|
+
content -->\n</div>\n</div>\n<div class='content-cap-bottom cap-bottom'>\n<div
|
1401
|
+
class='cap-left'></div>\n<div class='cap-right'></div>\n</div>\n</div>\n</div>\n<script
|
1402
|
+
type='text/javascript'>\n window.setTimeout(function() {\n document.body.className
|
1403
|
+
= document.body.className.replace('loading', '');\n }, 10);\n </script>\n\n<script
|
1404
|
+
type=\"text/javascript\" src=\"https://www.blogger.com/static/v1/widgets/3000588928-widgets.js\"></script>\n<script
|
1405
|
+
type='text/javascript'>\nwindow['__wavt'] = 'AOuZoY7MPoJeyKYqO6N9mdnvbWds903W9g:1753780911572';_WidgetManager._Init('//draft.blogger.com/rearrange?blogID\\x3d19544619','//0xfe.blogspot.com/','19544619');\n_WidgetManager._SetDataContext([{'name':
|
1406
|
+
'blog', 'data': {'blogId': '19544619', 'title': '0xFE - 11111110b - 0376',
|
1407
|
+
'url': 'https://0xfe.blogspot.com/', 'canonicalUrl': 'http://0xfe.blogspot.com/',
|
1408
|
+
'homepageUrl': 'https://0xfe.blogspot.com/', 'searchUrl': 'https://0xfe.blogspot.com/search',
|
1409
|
+
'canonicalHomepageUrl': 'http://0xfe.blogspot.com/', 'blogspotFaviconUrl':
|
1410
|
+
'https://0xfe.blogspot.com/favicon.ico', 'bloggerUrl': 'https://draft.blogger.com',
|
1411
|
+
'hasCustomDomain': false, 'httpsEnabled': true, 'enabledCommentProfileImages':
|
1412
|
+
true, 'gPlusViewType': 'FILTERED_POSTMOD', 'adultContent': false, 'analyticsAccountNumber':
|
1413
|
+
'', 'encoding': 'UTF-8', 'locale': 'en', 'localeUnderscoreDelimited': 'en',
|
1414
|
+
'languageDirection': 'ltr', 'isPrivate': false, 'isMobile': false, 'isMobileRequest':
|
1415
|
+
false, 'mobileClass': '', 'isPrivateBlog': false, 'isDynamicViewsAvailable':
|
1416
|
+
true, 'feedLinks': '\\x3clink rel\\x3d\\x22alternate\\x22 type\\x3d\\x22application/atom+xml\\x22
|
1417
|
+
title\\x3d\\x220xFE - 11111110b - 0376 - Atom\\x22 href\\x3d\\x22https://0xfe.blogspot.com/feeds/posts/default\\x22
|
1418
|
+
/\\x3e\\n\\x3clink rel\\x3d\\x22alternate\\x22 type\\x3d\\x22application/rss+xml\\x22
|
1419
|
+
title\\x3d\\x220xFE - 11111110b - 0376 - RSS\\x22 href\\x3d\\x22https://0xfe.blogspot.com/feeds/posts/default?alt\\x3drss\\x22
|
1420
|
+
/\\x3e\\n\\x3clink rel\\x3d\\x22service.post\\x22 type\\x3d\\x22application/atom+xml\\x22
|
1421
|
+
title\\x3d\\x220xFE - 11111110b - 0376 - Atom\\x22 href\\x3d\\x22https://draft.blogger.com/feeds/19544619/posts/default\\x22
|
1422
|
+
/\\x3e\\n', 'meTag': '\\x3clink rel\\x3d\\x22me\\x22 href\\x3d\\x22https://draft.blogger.com/profile/11179501091623983192\\x22
|
1423
|
+
/\\x3e\\n', 'adsenseHostId': 'ca-host-pub-1556223355139109', 'adsenseHasAds':
|
1424
|
+
false, 'adsenseAutoAds': false, 'boqCommentIframeForm': true, 'loginRedirectParam':
|
1425
|
+
'', 'view': '', 'dynamicViewsCommentsSrc': '//www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js',
|
1426
|
+
'dynamicViewsScriptSrc': '//www.blogblog.com/dynamicviews/aec9a2fc4235c5ac',
|
1427
|
+
'plusOneApiSrc': 'https://apis.google.com/js/platform.js', 'disableGComments':
|
1428
|
+
true, 'interstitialAccepted': false, 'sharing': {'platforms': [{'name': 'Get
|
1429
|
+
link', 'key': 'link', 'shareMessage': 'Get link', 'target': ''}, {'name':
|
1430
|
+
'Facebook', 'key': 'facebook', 'shareMessage': 'Share to Facebook', 'target':
|
1431
|
+
'facebook'}, {'name': 'BlogThis!', 'key': 'blogThis', 'shareMessage': 'BlogThis!',
|
1432
|
+
'target': 'blog'}, {'name': 'X', 'key': 'twitter', 'shareMessage': 'Share
|
1433
|
+
to X', 'target': 'twitter'}, {'name': 'Pinterest', 'key': 'pinterest', 'shareMessage':
|
1434
|
+
'Share to Pinterest', 'target': 'pinterest'}, {'name': 'Email', 'key': 'email',
|
1435
|
+
'shareMessage': 'Email', 'target': 'email'}], 'disableGooglePlus': true, 'googlePlusShareButtonWidth':
|
1436
|
+
0, 'googlePlusBootstrap': '\\x3cscript type\\x3d\\x22text/javascript\\x22\\x3ewindow.___gcfg
|
1437
|
+
\\x3d {\\x27lang\\x27: \\x27en\\x27};\\x3c/script\\x3e'}, 'hasCustomJumpLinkMessage':
|
1438
|
+
false, 'jumpLinkMessage': 'Read more', 'pageType': 'index', 'pageName': '',
|
1439
|
+
'pageTitle': '0xFE - 11111110b - 0376'}}, {'name': 'features', 'data': {}},
|
1440
|
+
{'name': 'messages', 'data': {'edit': 'Edit', 'linkCopiedToClipboard': 'Link
|
1441
|
+
copied to clipboard!', 'ok': 'Ok', 'postLink': 'Post Link'}}, {'name': 'template',
|
1442
|
+
'data': {'name': 'custom', 'localizedName': 'Custom', 'isResponsive': false,
|
1443
|
+
'isAlternateRendering': false, 'isCustom': true}}, {'name': 'view', 'data':
|
1444
|
+
{'classic': {'name': 'classic', 'url': '?view\\x3dclassic'}, 'flipcard': {'name':
|
1445
|
+
'flipcard', 'url': '?view\\x3dflipcard'}, 'magazine': {'name': 'magazine',
|
1446
|
+
'url': '?view\\x3dmagazine'}, 'mosaic': {'name': 'mosaic', 'url': '?view\\x3dmosaic'},
|
1447
|
+
'sidebar': {'name': 'sidebar', 'url': '?view\\x3dsidebar'}, 'snapshot': {'name':
|
1448
|
+
'snapshot', 'url': '?view\\x3dsnapshot'}, 'timeslide': {'name': 'timeslide',
|
1449
|
+
'url': '?view\\x3dtimeslide'}, 'isMobile': false, 'title': '0xFE - 11111110b
|
1450
|
+
- 0376', 'description': '', 'url': 'https://0xfe.blogspot.com/', 'type': 'feed',
|
1451
|
+
'isSingleItem': false, 'isMultipleItems': true, 'isError': false, 'isPage':
|
1452
|
+
false, 'isPost': false, 'isHomepage': true, 'isArchive': false, 'isLabelSearch':
|
1453
|
+
false}}]);\n_WidgetManager._RegisterWidget('_HeaderView', new _WidgetInfo('Header1',
|
1454
|
+
'header', document.getElementById('Header1'), {}, 'displayModeFull'));\n_WidgetManager._RegisterWidget('_BlogView',
|
1455
|
+
new _WidgetInfo('Blog1', 'main', document.getElementById('Blog1'), {'cmtInteractionsEnabled':
|
1456
|
+
false, 'lightboxEnabled': true, 'lightboxModuleUrl': 'https://www.blogger.com/static/v1/jsbin/249874-lbx.js',
|
1457
|
+
'lightboxCssUrl': 'https://www.blogger.com/static/v1/v-css/123180807-lightbox_bundle.css'},
|
1458
|
+
'displayModeFull'));\n_WidgetManager._RegisterWidget('_LinkListView', new
|
1459
|
+
_WidgetInfo('LinkList2', 'sidebar-right-1', document.getElementById('LinkList2'),
|
1460
|
+
{}, 'displayModeFull'));\n_WidgetManager._RegisterWidget('_AttributionView',
|
1461
|
+
new _WidgetInfo('Attribution1', 'footer-3', document.getElementById('Attribution1'),
|
1462
|
+
{}, 'displayModeFull'));\n</script>\n</body>\n<script language=\"javascript\"
|
1463
|
+
src=\"//google-code-prettify.googlecode.com/svn/trunk/src/prettify.js\" type=\"text/javascript\"></script>\n<script
|
1464
|
+
language=\"javascript\" src=\"//google-code-prettify.googlecode.com/svn/trunk/src/lang-css.js\"
|
1465
|
+
type=\"text/javascript\"></script>\n<script type='text/javascript'>\nfunction
|
1466
|
+
addLoadEvent(func) {\n var oldonload = window.onload;\n if (typeof window.onload
|
1467
|
+
!= \"function\") {\n window.onload = func;\n } else {\n window.onload
|
1468
|
+
= function() {\n if (oldonload) {\n oldonload();\n }\n func();\n
|
1469
|
+
\ }\n }\n}\naddLoadEvent(prettyPrint);\n</script>\n</html>"
|
1470
|
+
recorded_at: Tue, 29 Jul 2025 09:54:37 GMT
|
1471
|
+
recorded_with: VCR 6.3.1
|