<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-19544619</id><updated>2012-01-05T11:48:10.717-05:00</updated><category term='vim'/><category term='vexflow'/><category term='webaudio'/><category term='haskell'/><title type='text'>0xFE - 11111110b - 0376 - 254 - b9#9</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>57</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-19544619.post-6880188793332322250</id><published>2012-01-02T13:34:00.000-05:00</published><updated>2012-01-02T13:34:24.100-05:00</updated><title type='text'>More K-Means Clustering Experiments on Images</title><content type='html'>I spent a little more time experimenting with &lt;a href="http://0xfe.blogspot.com/2011/12/k-means-clustering-and-art.html"&gt;k-means clustering&lt;/a&gt; on images and realized that I could use these clusters to recolor the image in interesting ways.&lt;p/&gt;I wrote the function &lt;code&gt;save_recolor&lt;/code&gt; to replace pixels from the given clusters (&lt;code&gt;replacements&lt;/code&gt;) with new ones of equal intensity, as specified by the &lt;code&gt;rgb_factors&lt;/code&gt; vector. For example, the following code will convert pixels of the first two clusters to greyscale.&lt;p/&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;gt; save_recolor("baby.jpeg", "baby_new.jpg", replacements=c(1,2),&lt;br /&gt;               rgb_factors=c(1/3, 1/3, 1/3))&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;It's greyscale because the &lt;code&gt;rgb_factors&lt;/code&gt; distributes the pixel intensity evenly among the channels. A factor of &lt;code&gt;c(20/100, 60/100, 20/100)&lt;/code&gt; would make pixels from the cluster 60% more green.&lt;p/&gt;Let's get to some examples. Here's an unprocessed image, alongside its color clusters. I picked &lt;code&gt;k=10&lt;/code&gt;. You can set &lt;code&gt;k&lt;/code&gt; by specifying the &lt;code&gt;palette_size&lt;/code&gt; parameter to &lt;code&gt;save_recolor&lt;/code&gt;.&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-KxMuakdpsxs/TwHzUFcubUI/AAAAAAAAbNc/EI3k5Qs9caU/s1600/Screen%2Bshot%2B2012-01-01%2Bat%2B12.16.45%2BPM.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="274" width="400" src="http://2.bp.blogspot.com/-KxMuakdpsxs/TwHzUFcubUI/AAAAAAAAbNc/EI3k5Qs9caU/s400/Screen%2Bshot%2B2012-01-01%2Bat%2B12.16.45%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p/&gt;Here's what happens when I remove the red (the first cluster).&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-m4-Ho9HL878/TwHzsL2J_qI/AAAAAAAAbNo/8svaFr_tiEE/s1600/arkin_recolor_nored.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="300" src="http://1.bp.blogspot.com/-m4-Ho9HL878/TwHzsL2J_qI/AAAAAAAAbNo/8svaFr_tiEE/s400/arkin_recolor_nored.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;gt; save_recolor("baby.jpeg", "baby_new.jpg", replacements=1)&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;In the next image, I keep the red, and remove everything else.&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-YCtO8fMPAJk/TwHz7B_osOI/AAAAAAAAbN0/Nzwa-HCx494/s1600/arkin_recolor_red.jpeg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="300" src="http://4.bp.blogspot.com/-YCtO8fMPAJk/TwHz7B_osOI/AAAAAAAAbN0/Nzwa-HCx494/s400/arkin_recolor_red.jpeg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;gt; save_recolor("baby.jpeg", "baby_new.jpg", replacements=2:10)&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;Below, I replace the red cluster pixels, with green ones of corresponding intensity.&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-P3Oh3XybyMk/TwH0FT1n0II/AAAAAAAAbOA/SHAe7wTdWzg/s1600/arkin_recolor_green.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="300" src="http://4.bp.blogspot.com/-P3Oh3XybyMk/TwH0FT1n0II/AAAAAAAAbOA/SHAe7wTdWzg/s400/arkin_recolor_green.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;gt; save_recolor("baby.jpeg", "baby_new.jpg", replacements=1,&lt;br /&gt;               rgb_factors=c(10/100, 80/100, 10/100))&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;And this is a fun one: Get rid of everything, keep just the grass.&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-EKN_jmM1Z-w/TwH0KV66CVI/AAAAAAAAbOM/CxuW2MiLf1s/s1600/arkin_recolor_grass.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="300" src="http://3.bp.blogspot.com/-EKN_jmM1Z-w/TwH0KV66CVI/AAAAAAAAbOM/CxuW2MiLf1s/s400/arkin_recolor_grass.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;gt; save_recolor("baby.jpeg", "baby_new.jpg", replacements=c(1,3:10))&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;I tried this on various images, using different cluster sizes, replacements, and RGB factors, with lots of interesting results. Anyhow, you should experiment with this yourselves and let me know what you find.&lt;p/&gt;I should point out that nothing here is novel or new -- it's all well known in image processing circles. It's still pretty impressive what you can do when you apply simple machine learning algorithms to other areas.&lt;p&gt;Okay, as in all my posts, the code is available in my &lt;a href="http://github.com/0xfe"&gt;GitHub repository&lt;/a&gt;:&lt;p/&gt;&lt;a href="https://github.com/0xfe/experiments/blob/master/r/recolor.rscript"&gt;https://github.com/0xfe/experiments/blob/master/r/recolor.rscript&lt;/a&gt;&lt;p/&gt;Happy new year!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-6880188793332322250?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/6880188793332322250/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2012/01/more-k-means-clustering-experiments-on.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6880188793332322250'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6880188793332322250'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2012/01/more-k-means-clustering-experiments-on.html' title='More K-Means Clustering Experiments on Images'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-KxMuakdpsxs/TwHzUFcubUI/AAAAAAAAbNc/EI3k5Qs9caU/s72-c/Screen%2Bshot%2B2012-01-01%2Bat%2B12.16.45%2BPM.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-8650024762996646704</id><published>2011-12-31T10:27:00.000-05:00</published><updated>2011-12-31T14:14:34.568-05:00</updated><title type='text'>K-Means Clustering and Art</title><content type='html'>&lt;i&gt;Cross posted from &lt;a href="https://plus.google.com/u/0/111867441083313519234/posts/dxp5w3R7ts3"&gt;Google+&lt;/a&gt;.&lt;/i&gt;&lt;p/&gt;My coworker at Google, Tony Rippy, has for a while been working on a fascinating problem. Take all the pixels of a photograph, and rearrange them so that the final image looks like an artist's palette -- something to which you can take a paintbrush and recreate the original image.&lt;p/&gt;He's got some really good looking solutions which he might post if you ask him nicely. :-)&lt;p/&gt;This turns out to be a tricky problem, and its hard to come up with an objective measure of the quality of any given solution. In fact, the quality is very subjective.&lt;p/&gt;Anyhow, while studying the &lt;a href="http://en.wikipedia.org/wiki/K-means_clustering"&gt;K-means clustering algorithm&lt;/a&gt; from &lt;a href="http://www.ml-class.org"&gt;ml-class&lt;/a&gt;, it struck me that &lt;i&gt;k-means&lt;/i&gt; could be used to help with extracting a small palette of colors from an image. For example, by using each of the RGB channels as features, and euclidian distance as the similarity metric, one could run stock &lt;i&gt;k-means&lt;/i&gt; to generate clusters of similar colors.&lt;p/&gt;I coded up a quick R script to test this and got some interesting results. Here is an example of an image with its potential palette. Recall that the second image is simply the first image with the pixels rearranged.&lt;p/&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Ti6N9fTByjM/Tv8odz2P8BI/AAAAAAAAbMQ/IOWtSRSDfoM/s1600/Screen%2Bshot%2B2011-12-30%2Bat%2B1.58.13%2BPM.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="180" width="400" src="http://4.bp.blogspot.com/-Ti6N9fTByjM/Tv8odz2P8BI/AAAAAAAAbMQ/IOWtSRSDfoM/s400/Screen%2Bshot%2B2011-12-30%2Bat%2B1.58.13%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p/&gt;I experimented with various values of &lt;i&gt;k&lt;/i&gt; (number of clusters) for the different images. It turns out that it's pretty hard to algorithmically pre-determine this number (although there are various techniques that do exist.) The water villa pic above has 15 clusters, the nursery pic below has 20, and the cartoon has 6.&lt;p/&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-GaVc74JaOqY/Tv8o6Nqi7eI/AAAAAAAAbMc/iyHsL17HV3U/s1600/Screen%2Bshot%2B2011-12-30%2Bat%2B1.57.12%2BPM.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="235" width="400" src="http://4.bp.blogspot.com/-GaVc74JaOqY/Tv8o6Nqi7eI/AAAAAAAAbMc/iyHsL17HV3U/s400/Screen%2Bshot%2B2011-12-30%2Bat%2B1.57.12%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p/&gt;Note that this is only one subproblem of the original one; there is also the subproblem of placement, which I skirted around by simply arranging the colors in vertical bands across the final image. I'm pretty sure no artist's palette looks like this.&lt;p/&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-aQ6rcShXe44/Tv8pDe7nsuI/AAAAAAAAbMo/ZKwGn2n_LxQ/s1600/Screen%2Bshot%2B2011-12-30%2Bat%2B1.57.49%2BPM.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="182" width="400" src="http://2.bp.blogspot.com/-aQ6rcShXe44/Tv8pDe7nsuI/AAAAAAAAbMo/ZKwGn2n_LxQ/s400/Screen%2Bshot%2B2011-12-30%2Bat%2B1.57.49%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p/&gt;Also, these palettes aren't very "clean". Since the original pictures themselves are noisy, some of this noise arbitrarily creep into the various clusters. Working with a filtered version of the picture would be cheating, so we won't do that. But we might be able to extract the noisy pixels, put them in a special cluster, and run &lt;i&gt;k-means&lt;/i&gt; on the remaining pixels.&lt;p/&gt;Okay, enough talk. Here's the code: &lt;a href="https://github.com/0xfe/experiments/blob/master/r/palette.rscript"&gt;https://github.com/0xfe/experiments/blob/master/r/palette.rscript&lt;/a&gt;&lt;p/&gt;First install &lt;code&gt;cclust&lt;/code&gt; and &lt;code&gt;ReadImages&lt;/code&gt; packages from &lt;a href="http://cran.r-project.org"&gt;CRAN&lt;/a&gt;, and try out the algorithm in an R console:&lt;p/&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;gt; source('/path/to/palette.rscript')&lt;br /&gt;&amp;gt; plot_palette('/path/to/some/image.jpg')&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;This will produce a plot with the original image and the transformed one next to each other, like the attached pics below. It uses 10 clusters by default, for a palette of 10 colors. You can change this by passing the cluster count as the second parameter to &lt;code&gt;plot_palette&lt;/code&gt;.&lt;p/&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;gt; plot_palette('/path/to/some/image.jpg', 20)&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;That's all folks!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-8650024762996646704?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/8650024762996646704/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/12/k-means-clustering-and-art.html#comment-form' title='18 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8650024762996646704'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8650024762996646704'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/12/k-means-clustering-and-art.html' title='K-Means Clustering and Art'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-Ti6N9fTByjM/Tv8odz2P8BI/AAAAAAAAbMQ/IOWtSRSDfoM/s72-c/Screen%2Bshot%2B2011-12-30%2Bat%2B1.58.13%2BPM.png' height='72' width='72'/><thr:total>18</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7852661428465635210</id><published>2011-08-21T11:15:00.000-04:00</published><updated>2011-08-21T21:37:53.266-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='webaudio'/><title type='text'>A Web Audio Spectrum Analyzer</title><content type='html'>In my last post, I went over some of the &lt;a href="http://0xfe.blogspot.com/2011/08/generating-tones-with-web-audio-api.html"&gt;basics of the Web Audio API&lt;/a&gt; and showed you how to generate &lt;a href="http://0xfe.muthanna.com/tone"&gt;sine waves&lt;/a&gt; of various frequencies and amplitudes. We were introduced to some key &lt;a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html"&gt;Web Audio classes&lt;/a&gt;, such as &lt;code&gt;AudioContext&lt;/code&gt;, &lt;code&gt;AudioNode&lt;/code&gt;, and &lt;code&gt;JavaScriptAudioNode&lt;/code&gt;.&lt;p/&gt;This time, I'm going to go take things a little further and build a realtime spectrum analyzer with Web Audio and HTML5 Canvas. The final product plays a remote music file, and displays the frequency spectrum overlaid with a time domain graph.&lt;p/&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-OpeqMjWF6yc/TlEb9Mo35DI/AAAAAAAALlk/ImIgBdWK7Qs/s1600/Screen%2Bshot%2B2011-08-20%2Bat%2B11.47.10%2BAM.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="234" width="400" src="http://3.bp.blogspot.com/-OpeqMjWF6yc/TlEb9Mo35DI/AAAAAAAALlk/ImIgBdWK7Qs/s400/Screen%2Bshot%2B2011-08-20%2Bat%2B11.47.10%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p/&gt;The demo is here: &lt;a href="http://0xfe.muthanna.com/wavebox"&gt;JavaScript Spectrum Analyzer&lt;/a&gt;. The code for the demo is in my &lt;a href="https://github.com/0xfe/experiments/tree/master/www/wavebox"&gt;GitHub&lt;/a&gt; repository.&lt;p/&gt;&lt;h3&gt;The New Classes&lt;/h3&gt;&lt;p/&gt;In this post we introduce three new Web Audio classes: &lt;code&gt;AudioBuffer&lt;/code&gt;, &lt;code&gt;AudioBufferSourceNode&lt;/code&gt;, and &lt;code&gt;RealtimeAnalyzerNode&lt;/code&gt;.&lt;p/&gt;An &lt;code&gt;AudioBuffer&lt;/code&gt; represents an in-memory audio asset. It is usually used to store short audio clips and can contain multiple channels.&lt;p/&gt;An &lt;code&gt;AudioBufferSourceNode&lt;/code&gt; is a specialization of &lt;code&gt;AudioNode&lt;/code&gt; that serves audio from &lt;code&gt;AudioBuffer&lt;/code&gt;s.&lt;p/&gt;A &lt;code&gt;RealtimeAnalyzerNode&lt;/code&gt; is an &lt;code&gt;AudioNode&lt;/code&gt; that returns time- and frequency-domain analysis information in real time.&lt;p/&gt;&lt;h3&gt;The Plumbing&lt;/h3&gt;&lt;p/&gt;To begin, we need to acquire some audio. The API supports a number of different formats, including MP3 and raw PCM-encoded audio. In our demo, we retrieve a remote audio asset (an MP3 file) using AJAX, and use it to populate a new &lt;code&gt;AudioBuffer&lt;/code&gt;. This is implemented in the &lt;code&gt;RemoteAudioPlayer&lt;/code&gt; class (&lt;a href="https://github.com/0xfe/experiments/blob/master/www/wavebox/js/remoteaudioplayer.js"&gt;js/remoteaudioplayer.js&lt;/a&gt;) like so:&lt;pre class="prettyprint"&gt;&lt;br /&gt;RemoteAudioPlayer.prototype.load = function(callback) {&lt;br /&gt;  var request = new XMLHttpRequest();&lt;br /&gt;  var that = this;&lt;br /&gt;  request.open("GET", this.url, true);&lt;br /&gt;  request.responseType = "arraybuffer";&lt;br /&gt;  request.onload = function() {&lt;br /&gt;    that.buffer = that.context.createBuffer(request.response, true);&lt;br /&gt;    that.reload();&lt;br /&gt;    callback(request.response);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  request.send();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Notice that the &lt;i&gt;jQuery&lt;/i&gt;'s AJAX calls aren't used here. This is because jQuery does not support the &lt;i&gt;arraybuffer&lt;/i&gt; response type, which is required for loading binary data from the server. The &lt;code&gt;AudioBuffer&lt;/code&gt; is created with the &lt;code&gt;AudioContext&lt;/code&gt;'s &lt;code&gt;createBuffer&lt;/code&gt; function. The second parameter, &lt;code&gt;true&lt;/code&gt;, tells it to mix down all the channels to a single mono channel.&lt;p/&gt;The &lt;code&gt;AudioBuffer&lt;/code&gt; is then provided to an &lt;code&gt;AudioBufferSourceNode&lt;/code&gt;, which will be the context's audio source. This source node is then connected to a &lt;code&gt;RealTimeAnalyzerNode&lt;/code&gt;, which in turn is connected to the context's destination, i.e, the computer's output device.&lt;pre class="prettyprint"&gt;&lt;br /&gt;var source_node = context.createBufferSource();&lt;br /&gt;source_node.buffer = audio_buffer;&lt;br /&gt;&lt;br /&gt;var analyzer = context.createAnalyser();&lt;br /&gt;analyzer.fftSize = 2048; // 2048-point FFT&lt;br /&gt;source_node.connect(analyzer);&lt;br /&gt;analyzer.connect(context.destination);&lt;br /&gt;&lt;/pre&gt;To start playing the music, call the &lt;code&gt;noteOn&lt;/code&gt; method of the source node. &lt;code&gt;noteOn&lt;/code&gt; takes one parameter: a timestamp indicating when to start playing. If set to &lt;code&gt;0&lt;/code&gt;, it plays immediately. To start playing the music 0.5 seconds from now, you can use &lt;code&gt;context.currentTime&lt;/code&gt; to get the reference point.&lt;pre class="prettyprint"&gt;&lt;br /&gt;// Play music 0.5 seconds from now&lt;br /&gt;source_node.noteOn(context.currentTime + 0.5);&lt;br /&gt;&lt;/pre&gt;It's also worth noting that we specified the granularity of the FFT to 2048 by setting the &lt;code&gt;analyzer.fftSize&lt;/code&gt; variable. For those unfamiliar with DSP theory, this breaks the frequency spectrum of the audio into 2048 points, each point representing the magnitude of the &lt;i&gt;n/2048th&lt;/i&gt; frequency bin.&lt;p/&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-lVHIXlEb7U8/TlEcJnWtKvI/AAAAAAAALls/Rh3cZ7ebf2I/s1600/Screen%2Bshot%2B2011-08-20%2Bat%2B11.38.45%2BAM.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="150" width="400" src="http://3.bp.blogspot.com/-lVHIXlEb7U8/TlEcJnWtKvI/AAAAAAAALls/Rh3cZ7ebf2I/s400/Screen%2Bshot%2B2011-08-20%2Bat%2B11.38.45%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p/&gt;&lt;h3&gt;The Pretty Graphs&lt;/h3&gt;&lt;p/&gt;Okay, it's now all wired up -- how do I get the pretty graphs? The general strategy is to poll the analyzer every few milliseconds (e.g., with &lt;code&gt;window.setInterval&lt;/code&gt;), request the time- or frequency-domain data, and then render it onto a HTML5 Canvas element. The analyzer exports a few different methods to access the analysis data: &lt;code&gt;getFloatFrequencyData&lt;/code&gt;, &lt;code&gt;getByteFrequencyData&lt;/code&gt;, &lt;code&gt;getByteTimeDomainData&lt;/code&gt;. Each of these methods populate a given &lt;code&gt;ArrayBuffer&lt;/code&gt; with the appropriate analysis data.&lt;p/&gt;In the below snippet, we schedule an &lt;code&gt;update()&lt;/code&gt; function every 50ms, which breaks the frequency-domain data points into 30 bins, and renders a bar representing the average magnitude of the points in each bin.&lt;pre class="prettyprint"&gt;&lt;br /&gt;canvas = document.getElementById(canvas_id);&lt;br /&gt;canvas_context = canvas.getContext("2d");&lt;br /&gt;&lt;br /&gt;function update() {&lt;br /&gt;  // This graph has 30 bars.&lt;br /&gt;  var num_bars = 30;&lt;br /&gt;&lt;br /&gt;  // Get the frequency-domain data&lt;br /&gt;  var data = new Uint8Array(2048);&lt;br /&gt;  analyzer.getByteFrequencyData(data);&lt;br /&gt;&lt;br /&gt;  // Clear the canvas&lt;br /&gt;  canvas_context.clearRect(0, 0, this.width, this.height);&lt;br /&gt;&lt;br /&gt;  // Break the samples up into bins&lt;br /&gt;  var bin_size = Math.floor(length / num_bars);&lt;br /&gt;  for (var i=0; i &lt; num_bars; ++i) {&lt;br /&gt;    var sum = 0;&lt;br /&gt;    for (var j=0; j &lt; bin_size; ++j) {&lt;br /&gt;      sum += data[(i * bin_size) + j];&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // Calculate the average frequency of the samples in the bin&lt;br /&gt;    var average = sum / bin_size;&lt;br /&gt;&lt;br /&gt;    // Draw the bars on the canvas&lt;br /&gt;    var bar_width = canvas.width / num_bars;&lt;br /&gt;    var scaled_average = (average / 256) * canvas.height;&lt;br /&gt;&lt;br /&gt;    canvas_context.fillRect(i * bar_width, canvas.height, bar_width - 2,&lt;br /&gt;                         -scaled_average);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Render every 50ms&lt;br /&gt;window.setInterval(update, 50);&lt;br /&gt;&lt;br /&gt;// Start the music&lt;br /&gt;source_node.noteOn(0);&lt;br /&gt;&lt;/pre&gt;A similar strategy can be employed for time-domain data, except for a few minor differences: Time-domain data is usually rendered as waves, so you might want to use lot more bins and plot pixels instead of drawing bars. The code that renders the time and frequency domain graphs in the demo is encapsulated in the &lt;code&gt;SpectrumBox&lt;/code&gt; class in &lt;a href="https://github.com/0xfe/experiments/blob/master/www/wavebox/js/spectrum.js"&gt;js/spectrum.js&lt;/a&gt;.&lt;p/&gt;&lt;h3&gt;The Minutiae&lt;/h3&gt;&lt;p/&gt;I glossed over a number of things in this post, mostly with respect to the details of the demo. You can learn it all from the source code, but here's a summary for the impatient: &lt;p/&gt;The graphs are actually two HTML5 Canvas elements overlaid using CSS absolute positioning. Each element is used by its own &lt;code&gt;SpectrumBox&lt;/code&gt; class, one which displays the frequency spectrum, the other which displays the time-domain wave. &lt;p/&gt;The routing of the nodes is done in the &lt;code&gt;onclick&lt;/code&gt; handler to the &lt;code&gt;#play&lt;/code&gt; button -- it takes the &lt;code&gt;AudioSourceNode&lt;/code&gt; from the &lt;code&gt;RemoteAudioPlayer&lt;/code&gt;, routes it to node of the frequency analyzer, routes &lt;i&gt;that&lt;/i&gt; to the node of the time-domain analyzer, and then finally to the destination.&lt;p/&gt;&lt;h3&gt;Bonus: Another Demo&lt;/h3&gt;&lt;p/&gt;That's all folks! You now have the knowhow to build yourself a fancy new graphical spectrum analyzer. If all you want to do is play with the waves and stare at the graphs, check out my other demo: The &lt;a href="http://0xfe.muthanna.com/analyzer"&gt;Web Audio Tone Analyzer&lt;/code&gt; (&lt;a href="https://github.com/0xfe/experiments/tree/master/www/analyzer"&gt;source&lt;/a&gt;). This is really just the same spectrum analyzer from the first demo, connected to the tone generator from the &lt;a href="https://github.com/0xfe/experiments/tree/master/www/wavebox"&gt;last post&lt;/a&gt;.&lt;p/&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-4iUEC2D9w7M/TlEXCKuROuI/AAAAAAAALlc/J5RcurbSw2U/s1600/Screen%2Bshot%2B2011-08-20%2Bat%2B10.13.15%2BAM.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="127" width="320" src="http://1.bp.blogspot.com/-4iUEC2D9w7M/TlEXCKuROuI/AAAAAAAALlc/J5RcurbSw2U/s320/Screen%2Bshot%2B2011-08-20%2Bat%2B10.13.15%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p/&gt;&lt;h3&gt;References&lt;/h3&gt;&lt;/p&gt;As a reminder, all the code for my posts is available at my GitHub repository: &lt;a href="http://github.com/0xfe"&gt;github.com/0xfe&lt;/a&gt;.&lt;p/&gt;The audio track used in the demo is a discarded take of  &lt;a href="http://captainstarr.bandcamp.com/track/who-da-man"&gt;Who-Da-Man&lt;/a&gt;, which I recorded with my previous band &lt;a href="http://captainstarr.bandcamp.com/album/ep"&gt;Captain Starr&lt;/a&gt; many many years ago.&lt;p/&gt;Finally, don't forget to read the &lt;a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html"&gt;Web Audio API&lt;/a&gt; draft specification for more information.&lt;p/&gt;Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-7852661428465635210?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/7852661428465635210/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/08/web-audio-spectrum-analyzer.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7852661428465635210'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7852661428465635210'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/08/web-audio-spectrum-analyzer.html' title='A Web Audio Spectrum Analyzer'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-OpeqMjWF6yc/TlEb9Mo35DI/AAAAAAAALlk/ImIgBdWK7Qs/s72-c/Screen%2Bshot%2B2011-08-20%2Bat%2B11.47.10%2BAM.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6394151762251429282</id><published>2011-08-13T20:58:00.005-04:00</published><updated>2011-08-22T07:12:12.828-04:00</updated><title type='text'>Generating Tones with the Web Audio API</title><content type='html'>The Web Audio API is a W3C draft standard interface for building in-browser audio applications. Although the draft is well specified, it is almost impossible to find useful documentation on building applications with it. &lt;p/&gt;In my quest to deeper understand HTML5 audio, I spent some time figuring out how the API works, and decided to write up this quick tutorial on doing useful things with it. &lt;p/&gt;We will build a sine wave tone-generator entirely in JavaScript. The final product looks like this: &lt;a href="http://0xfe.muthanna.com/tone"&gt;Web Audio Tone Generator&lt;/a&gt;. &lt;p/&gt;The full code is available in my GitHub repository: &lt;a href="https://github.com/0xfe/experiments/tree/master/www/tone"&gt;https://github.com/0xfe/experiments/tree/master/www/tone&lt;/a&gt; &lt;p/&gt;&lt;h3&gt;Caveats&lt;/h3&gt;&lt;p/&gt;The Web Audio API is draft, is likely to change, and does not work on all browsers. Right now, only the latest versions of Chrome and Safari support it. &lt;p/&gt;&lt;h3&gt;Onwards We Go&lt;/h3&gt;&lt;p/&gt;Getting started making sounds with the Web Audio API is straightforward so long as you take the time to study the plumbing, most of which exists to allow for real-time audio processing and synthesis. The complete specification is available on the &lt;a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html"&gt;W3C Web Audio API&lt;/a&gt; page, and I'd strongly recommend that you read it thoroughly if you're interested in building advanced applications with the API. &lt;p/&gt;To produce any form of sound, you need an &lt;code&gt;AudioContext&lt;/code&gt; and a few &lt;code&gt;AudioNode&lt;/code&gt;s. The &lt;code&gt;AudioContext&lt;/code&gt; is sort of like an environment for audio processing -- it's where various attributes such as the sample rate, the clock status, and other environment-global state reside. Most applications will need no more than a single instance of &lt;code&gt;AudioContext&lt;/code&gt;. &lt;p/&gt;The &lt;code&gt;AudioNode&lt;/code&gt; is probably the most important component in the API, and is responsible for synthesizing or processing audio. An &lt;code&gt;AudioNode&lt;/code&gt; instance can be an input source, an output destination, or a mid-stream processor. These nodes can be linked together to form processing pipelines to render a complete audio stream. &lt;p/&gt;One kind of &lt;code&gt;AudioNode&lt;/code&gt; is &lt;code&gt;JavaScriptAudioNode&lt;/code&gt;, which is used to generate sounds in JavaScript. This is what what we will use in this tutorial to build a tone generator. &lt;p/&gt;Let us begin by instantiating an &lt;code&gt;AudioContext&lt;/code&gt; and creating a &lt;code&gt;JavaScriptAudioNode&lt;/code&gt;.  &lt;pre class="prettyprint"&gt;var context = new webkitAudioContext();&lt;br /&gt;var node = context.createJavaScriptNode(1024, 1, 1);&lt;br /&gt;&lt;/pre&gt;The parameters to &lt;code&gt;createJavaScriptNode&lt;/code&gt; refer to the buffer size, the number of input channels, and the number of output channels. The buffer size must be in units of sample frames, i.e., one of: 256, 512, 1024, 2048, 4096, 8192, or 16384. It controls the frequency of callbacks asking for a buffer refill. Smaller sizes allow for lower latency and higher for better overall quality. &lt;p/&gt;We're going to use the &lt;code&gt;JavaScriptNode&lt;/code&gt; as the source node along with a bit of code to create sine waves. To actually &lt;i&gt;hear&lt;/i&gt;&amp;nbsp;anything, it must be connected to an output node. It turns out that &lt;code&gt;context.destination&lt;/code&gt; gives us just that -- a node that maps to the speaker on your machine. &lt;p/&gt;&lt;h3&gt;The SineWave Class&lt;/h3&gt;&lt;p/&gt;To start off our tone generator, we create a &lt;code&gt;SineWave&lt;/code&gt; class, which wraps the &lt;code&gt;AudioNode&lt;/code&gt; and wave generation logic into one cohesive package. This class will be responsible for creating the &lt;code&gt;JavaScriptNode&lt;/code&gt; instances, generating the sine waves, and managing the connection to the destination node. &lt;pre class="prettyprint"&gt;SineWave = function(context) {&lt;br /&gt;  var that = this;&lt;br /&gt;  this.x = 0; // Initial sample number&lt;br /&gt;  this.context = context;&lt;br /&gt;  this.node = context.createJavaScriptNode(1024, 1, 1);&lt;br /&gt;  this.node.onaudioprocess = function(e) { that.process(e) };&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;SineWave.prototype.process = function(e) {&lt;br /&gt;  var data = e.outputBuffer.getChannelData(0);&lt;br /&gt;  for (var i = 0; i &amp;lt; data.length; ++i) {&lt;br /&gt;    data[i] = Math.sin(this.x++);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;SineWave.prototype.play = function() {&lt;br /&gt;  this.node.connect(this.context.destination);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;SineWave.prototype.pause = function() {&lt;br /&gt;  this.node.disconnect();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Upon instantiation, this class creates a &lt;code&gt;JavaScriptAudioNode&lt;/code&gt; and attaches an event handler to &lt;code&gt;onaudioprocess&lt;/code&gt; for buffer refills. The event handler requests a reference to the output buffer for the first channel, and fills it with a sine wave. Notice that the handler does not know the buffer size in advance, and gets it from &lt;code&gt;data.length&lt;/code&gt;. &lt;p/&gt;The buffer is of type &lt;code&gt;ArrayBuffer&lt;/code&gt; which is a JavaScript Typed Array. These arrays allow for high throughput processing of raw binary data. To learn more about Typed Arrays, check out the &lt;a href="https://developer.mozilla.org/en/javascript_typed_arrays"&gt;Mozilla Developer Documentation on Typed Arrays&lt;/a&gt;. &lt;p/&gt;To try out a quick demo of the &lt;code&gt;SineWave&lt;/code&gt; class, add the following code to the &lt;code&gt;onload&lt;/code&gt; handler for your page: &lt;pre class="prettyprint"&gt;var context = new webkitAudioContext();&lt;br /&gt;var sinewave = new SineWave(context);&lt;br /&gt;sinewave.play();&lt;br /&gt;&lt;/pre&gt;&lt;div&gt;Notice that &lt;code&gt;sinewave.play()&lt;/code&gt; works by wiring up the node to the &lt;code&gt;AudioContext&lt;/code&gt;'s destination (the speakers). To stop the tone, call &lt;code&gt;sinewave.pause()&lt;/code&gt;, which unplugs this connection.&lt;/div&gt;&lt;p/&gt;&lt;div&gt;&lt;h3&gt;Generating Specific Tones&lt;/h3&gt;&lt;/div&gt;&lt;p/&gt; &lt;div&gt;So, now you have yourself a tone. Are we done yet?&lt;/div&gt;&lt;p/&gt;&lt;div&gt;Not quite. How does one know what the frequency of the generated wave is? How does one generate tones of arbitrary frequencies?&lt;/div&gt;&lt;p/&gt;&lt;div&gt;To answer these questions, we must find out the sample rate of the audio. Each data value we stuff into the buffer in out handler is a sample, and the sample rate is the number of samples processed per second. We can calculate the frequency of the tone by dividing the sample rate by the length of a full wave cycle.&lt;/div&gt;&lt;p/&gt;&lt;div&gt;How do we get the sample rate? Via the &lt;code&gt;getSampleRate()&lt;/code&gt; method of &lt;code&gt;AudioContext&lt;/code&gt;. On my machine, the default sample rate is 44KHz, i.e., 44100 samples per second. This means that the frequency of the generated tone in our above code is:&lt;/div&gt;&lt;pre class="prettyprint"&gt;freq = context.getSampleRate() / 2 * Math.PI&lt;br /&gt;&lt;/pre&gt;&lt;div&gt;That's about 7KHz. Ouch! Lets use our newfound knowledge to generate less spine-curdling tones. To generate a tone of a specific frequency, you can change &lt;code&gt;SineWave.process&lt;/code&gt; to:&lt;/div&gt;&lt;pre class="prettyprint"&gt;SineWave.prototype.process = function(e) {&lt;br /&gt;  var data = e.outputBuffer.getChannelData(0);&lt;br /&gt;  for (var i = 0; i &amp;lt; data.length; ++i) {&lt;br /&gt;    data[i] = Math.sin(this.x++ / (this.sample_rate / 2 * Math.PI * this.frequency));&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Also make sure you add the following two lines to &lt;code&gt;SineWave&lt;/code&gt;'s constructor:  &lt;pre class="prettyprint"&gt;this.sample_rate = this.context.getSampleRate();&lt;br /&gt;this.frequency = 440;&lt;br /&gt;&lt;/pre&gt;This initializes the frequency to &lt;i&gt;pitch standard A440&lt;/i&gt;, i.e., the A above &lt;i&gt;middle C.&lt;/i&gt; &lt;p/&gt;&lt;h3&gt;The Theremin Effect&lt;/h3&gt;&lt;p/&gt;Now that we can generate tones of arbitrary frequencies, it's only natural that we connect our class to some sort of slider widget so we can experience the entire spectrum right in our browsers. Turns out that &lt;a href="http://jqueryui.com/"&gt;JQueryUI&lt;/a&gt; already has such a &lt;a href="http://jqueryui.com/demos/slider/"&gt;slider&lt;/a&gt;, leaving us only a little plumbing to do. &lt;p/&gt;We add a setter function to our &lt;code&gt;SineWave&lt;/code&gt; class, and call it from our slider widget's change handler. &lt;pre class="prettyprint"&gt;SineWave.prototype.setFrequency = function(freq) {&lt;br /&gt;  this.next_frequency = freq;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;A JQueryUI snippet would look like this: &lt;pre class="prettyprint"&gt;$("#slider").slider({&lt;br /&gt;    value: 440,&lt;br /&gt;    min: 1,&lt;br /&gt;    max: 2048,&lt;br /&gt;    slide: function(event, ui) { sinewave.setFrequency(ui.value); }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;Going up to Eleven&lt;/h3&gt;&lt;p/&gt;Adding support for volume is straightforward. Add an amplitude member to the &lt;code&gt;SineWave&lt;/code&gt; constructor along with a setter method, just like we did for frequency, and change &lt;code&gt;SineWave.process&lt;/code&gt; to:  &lt;pre class="prettyprint"&gt;SineWave.prototype.process = function(e) {&lt;br /&gt;  var data = e.outputBuffer.getChannelData(0);&lt;br /&gt;  for (var i = 0; i &amp;lt; data.length; ++i) {&lt;br /&gt;    data[i] = this.amplitude * Math.sin(this.x++ / (this.sample_rate / 2 * Math.PI * this.frequency));&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Folks, we now have a full fledged sine wave generator! &lt;p/&gt;&lt;h3&gt;Boo Hiss Crackle&lt;/h3&gt;&lt;p/&gt;But, we're not done yet. You've probably noticed that changing the frequency causes mildly annoying crackling sounds. This happens because when the frequency changes, discontinuity occurs in the wave, causing a high-frequency &lt;i&gt;pop&lt;/i&gt;&amp;nbsp;in the audio stream. &lt;p/&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Sx6PvmKi4Y8/Tkca9ECRD0I/AAAAAAAALh4/ix4r9oExlzg/s1600/Screen+shot+2011-08-13+at+8.45.06+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="137" src="http://4.bp.blogspot.com/-Sx6PvmKi4Y8/Tkca9ECRD0I/AAAAAAAALh4/ix4r9oExlzg/s320/Screen+shot+2011-08-13+at+8.45.06+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Discontinuity when Changing Frequencies&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p/&gt;We try to eliminate the discontinuity by only shifting frequencies when the cycle of the previous frequency completes, i.e., the sample value is (approximately) zero. (There are better ways to do this, e.g., windowing, LPFs, etc., but these techniques are out of the scope of this tutorial.) &lt;p/&gt;Although this complicates the code a little bit, waiting for the cycle to end significantly reduces the noise upon frequency shifts.  &lt;pre class="prettyprint"&gt;SineWave.prototype.setFrequency = function(freq) {&lt;br /&gt;  this.next_frequency = freq;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;SineWave.prototype.process = function(e) {&lt;br /&gt;  // Get a reference to the output buffer and fill it up.&lt;br /&gt;  var data = e.outputBuffer.getChannelData(0);&lt;br /&gt;&lt;br /&gt;  // We need to be careful about filling up the entire buffer and not&lt;br /&gt;  // overflowing.&lt;br /&gt;  for (var i = 0; i &amp;lt; data.length; ++i) {&lt;br /&gt;    data[i] = this.amplitude * Math.sin(&lt;br /&gt;        this.x++ / (this.sampleRate / (this.frequency * 2 * Math.PI)));&lt;br /&gt;&lt;br /&gt;    // This reduces high-frequency blips while switching frequencies. It works&lt;br /&gt;    // by waiting for the sine wave to hit 0 (on it's way to positive territory)&lt;br /&gt;    // before switching frequencies.&lt;br /&gt;    if (this.next_frequency != this.frequency) {&lt;br /&gt;      // Figure out what the next point is.&lt;br /&gt;      next_data = this.amplitude * Math.sin(&lt;br /&gt;        this.x / (this.sampleRate / (this.frequency * 2 * Math.PI)));&lt;br /&gt;&lt;br /&gt;      // If the current point approximates 0, and the direction is positive,&lt;br /&gt;      // switch frequencies.&lt;br /&gt;      if (data[i] &amp;lt; 0.001 &amp;amp;&amp;amp; data[i] &amp;gt; -0.001 &amp;amp;&amp;amp; data[i] &amp;lt; next_data) {&lt;br /&gt;        this.frequency = this.next_frequency;&lt;br /&gt;        this.x = 0;&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p/&gt;&lt;div&gt;&lt;h3&gt;The End&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;&lt;p/&gt;&lt;/div&gt;&lt;div&gt;As mentioned in the beginning of this tutorial, a demo of the full code is available at&amp;nbsp;&lt;a href="http://0xfe.muthanna.com/tone/"&gt;http://0xfe.muthanna.com/tone/&lt;/a&gt;&amp;nbsp;and the entire source code is available at&amp;nbsp;&lt;a href="https://github.com/0xfe/experiments/tree/master/www/tone"&gt;https://github.com/0xfe/experiments/tree/master/www/tone&lt;/a&gt;.&lt;/div&gt;&lt;p/&gt; &lt;div&gt;Do check out the &lt;a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html"&gt;W3C Web Audio API&lt;/a&gt; specification. Do check out the &lt;a href="https://developer.mozilla.org/en/javascript_typed_arrays"&gt;Mozilla document on JavaScript Typed Arrays&lt;/a&gt;.&lt;/div&gt;&lt;p/&gt; &lt;div&gt;Comments, criticism, and error reports welcome. Enjoy!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-6394151762251429282?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/6394151762251429282/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/08/generating-tones-with-web-audio-api.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6394151762251429282'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6394151762251429282'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/08/generating-tones-with-web-audio-api.html' title='Generating Tones with the Web Audio API'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-Sx6PvmKi4Y8/Tkca9ECRD0I/AAAAAAAALh4/ix4r9oExlzg/s72-c/Screen+shot+2011-08-13+at+8.45.06+PM.png' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-8941953535018258860</id><published>2011-06-30T11:23:00.001-04:00</published><updated>2011-08-05T19:37:02.094-04:00</updated><title type='text'>I'm on Google+</title><content type='html'>And I'm quite digging it.&lt;br /&gt;&lt;br /&gt;Yes, it isn't lost on me that as a Googler, I'm biased.&lt;br /&gt;&lt;br /&gt;You can add me to your circles -- I'm &lt;code&gt;&lt;a href="https://plus.google.com/111867441083313519234/posts"&gt;right here&lt;/a&gt;.&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;To make up for this post's utter lack of content, I have you a haiku:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;in my machine world,&lt;br /&gt;all the digits of my toes,&lt;br /&gt;are ones and zeroes.&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;I should stick to what I know.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-8941953535018258860?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/8941953535018258860/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/06/im-on-google.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8941953535018258860'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8941953535018258860'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/06/im-on-google.html' title='I&apos;m on Google+'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1841038669655438377</id><published>2011-03-31T13:50:00.000-04:00</published><updated>2011-03-31T13:50:44.471-04:00</updated><title type='text'>A Music Theory API</title><content type='html'>Most of my work last week consisted of writing music theory code. &lt;a href="http://vexflow.com/"&gt;VexFlow&lt;/a&gt; now has a neat little music theory API, that gives you answers to questions like the following:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;What note is a minor 3rd above a B?&lt;/li&gt;&lt;li&gt;What are the scale tones of a Gb Harmonic Minor?&lt;/li&gt;&lt;li&gt;What relation is the C# note to an A Major scale? (Major 3rd)&lt;/li&gt;&lt;li&gt;What accidentals should be displayed for the perfect 4th note of a G Major scale?&lt;/li&gt;&lt;li&gt;etc.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The API is part of VexFlow, and can be used independently of the rendering API. Take a look at &lt;a href="https://github.com/0xfe/vexflow/blob/master/src/music.js"&gt;music.js&lt;/a&gt; in the &lt;a href="http://github.com/0xfe/vexflow"&gt;VexFlow GitHub repository&lt;/a&gt; for the complete reference. There's also a handy key management library for building scores in &lt;a href="https://github.com/0xfe/vexflow/blob/master/src/keymanager.js"&gt;keymanager.js&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I'm currently working on updating the&amp;nbsp;&lt;a href="http://vexflow.com/docs/tutorial.html"&gt;VexFlow Tutorial&lt;/a&gt;&amp;nbsp;with a quickstart on the music theory API, but meanwhile, here are some teasers (pulled straight out of the&amp;nbsp;&lt;a href="https://github.com/0xfe/vexflow/blob/master/tests/music_tests.js"&gt;tests&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;// What does C note consist of?&lt;br /&gt;var parts = music.getNoteParts("c");&lt;br /&gt;equals(parts.root, "c");&lt;br /&gt;equals(parts.accidental, null);&lt;br /&gt;&lt;br /&gt;// What does C# note consist of?&lt;br /&gt;var parts = music.getNoteParts("c#");&lt;br /&gt;equals(parts.root, "c");&lt;br /&gt;equals(parts.accidental, "#");&lt;br /&gt;&lt;br /&gt;// What is a flat-5th above C?&lt;br /&gt;var value = music.getRelativeNoteValue(music.getNoteValue("c"),&lt;br /&gt;                                       music.getIntervalValue("b5"));&lt;br /&gt;equals(value, music.getNoteValue("gb");&lt;br /&gt;equals(value, music.getNoteValue("f#");&lt;br /&gt;&lt;br /&gt;// What is the C quality of a Db?&lt;br /&gt;equals(music.getRelativeNoteName("c", music.getNoteValue("db")), "c#");&lt;br /&gt;&lt;br /&gt;// What are the tones of a C major scale?&lt;br /&gt;var c_major = music.getScaleTones(&lt;br /&gt;      music.getNoteValue("c"), Vex.Flow.Music.scales.major);&lt;br /&gt;// result: ["c", "d", "e", "f", "g", "a", "b"]&lt;br /&gt;&lt;br /&gt;// What is the interval between a C and a D?&lt;br /&gt;equals(music.getCanonicalIntervalName(music.getIntervalBetween(&lt;br /&gt;     music.getNoteValue("c"), music.getNoteValue("d"))), "M2");&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="line-height: 16px;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;Thanks to the theory support, we now have smarter Accidentals in the standard notation stave that VexTab generates.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-3Yg0PjZg5m0/TZS6UIsBWvI/AAAAAAAAD6Y/-9XLvPElksA/s1600/Screen+shot+2011-03-31+at+1.03.07+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-3Yg0PjZg5m0/TZS6UIsBWvI/AAAAAAAAD6Y/-9XLvPElksA/s1600/Screen+shot+2011-03-31+at+1.03.07+PM.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Smarter Accidentals&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Notice how the accidentals are correctly picked according to the rules of standard notation? Yep, so do I.&lt;br /&gt;&lt;br /&gt;We also have lots more tests -- over 750 of them! &lt;a href="http://vexflow.com/tests"&gt;Try running them on your browser&lt;/a&gt; and tell me how long it takes.&lt;br /&gt;&lt;br /&gt;That's all folks!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-1841038669655438377?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/1841038669655438377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/03/music-theory-api.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1841038669655438377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1841038669655438377'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/03/music-theory-api.html' title='A Music Theory API'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-3Yg0PjZg5m0/TZS6UIsBWvI/AAAAAAAAD6Y/-9XLvPElksA/s72-c/Screen+shot+2011-03-31+at+1.03.07+PM.png' height='72' width='72'/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6538091093974991528</id><published>2011-03-27T14:48:00.000-04:00</published><updated>2011-03-27T14:48:07.944-04:00</updated><title type='text'>Prettier Tablature</title><content type='html'>Spot the difference:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-ZbrjjD4XROk/TY-Di26eeNI/AAAAAAAAD5w/bKx_fIm4smI/s1600/Screen+shot+2011-03-27+at+2.34.14+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-ZbrjjD4XROk/TY-Di26eeNI/AAAAAAAAD5w/bKx_fIm4smI/s1600/Screen+shot+2011-03-27+at+2.34.14+PM.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Before&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-FxBgV1Mq0zs/TY-Doo3XTrI/AAAAAAAAD50/pbrCy_90ucg/s1600/Screen+shot+2011-03-27+at+2.32.23+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-FxBgV1Mq0zs/TY-Doo3XTrI/AAAAAAAAD50/pbrCy_90ucg/s1600/Screen+shot+2011-03-27+at+2.32.23+PM.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;After&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;Still can't tell? Let me help you out. We have:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Slightly greater spacing between the tablature stave lines. This makes it more consistent in appearance with printed tablature.&lt;/li&gt;&lt;li&gt;Stave lines are cleared before fret numbers are rendered, vastly improving readability.&lt;/li&gt;&lt;li&gt;Font sizes for fret numbers and annotations are bigger.&lt;/li&gt;&lt;li&gt;Associated notation and tablature staves are connected with a vertical bar on the left.&lt;/li&gt;&lt;li&gt;Micro-changes in spacing between fret numbers, effects, annotations, etc.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;All these changes have been incorporated into &lt;a href="http://vexflow.com/tabdiv"&gt;TabDiv&lt;/a&gt;, and pushed to &lt;a href="http://github.com/0xfe/vexflow"&gt;GitHub&lt;/a&gt;. See more on the &lt;a href="http://vexflow.com/vextab/tutorial.html"&gt;VexTab Tutorial&lt;/a&gt; page. Enjoy!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-6538091093974991528?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/6538091093974991528/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/03/prettier-tablature.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6538091093974991528'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6538091093974991528'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/03/prettier-tablature.html' title='Prettier Tablature'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-ZbrjjD4XROk/TY-Di26eeNI/AAAAAAAAD5w/bKx_fIm4smI/s72-c/Screen+shot+2011-03-27+at+2.34.14+PM.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1929109118059060998</id><published>2011-03-24T16:44:00.000-04:00</published><updated>2011-03-24T16:44:47.737-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='vexflow'/><title type='text'>The VexFlow Tutorial (...and other goodies)</title><content type='html'>Finally... finally... finally... we have the humble beginnings of what could be considered "documentation".&lt;br /&gt;&lt;br /&gt;I present to you &lt;a href="http://vexflow.com/docs/tutorial.html"&gt;The VexFlow Tutorial&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Although still in its infancy, the tutorial covers everything you need to &lt;i&gt;start&lt;/i&gt; using VexFlow in your own code. My plan for the next few weeks is to make this document as comprehensive as possible, and write up a separate API reference.&lt;br /&gt;&lt;br /&gt;I hope that this tutorial will help developers understand VexFlow better, and enable them to build new and interesting libraries, parsers, and applications.&lt;br /&gt;&lt;br /&gt;The entire tutorial is stored in the &lt;a href="https://github.com/0xfe/vexflow/blob/master/docs/tutorial.html"&gt;Git repo&lt;/a&gt;; feel free to send me your corrections or other updates.&lt;br /&gt;&lt;br /&gt;About time, I know.&lt;br /&gt;&lt;br /&gt;In other news, we have had a few contributions to both VexFlow and VexTab. A big thanks to &lt;a href="https://github.com/airfrog"&gt;airfrog&lt;/a&gt;, &lt;a href="https://github.com/wiseleyb"&gt;wiseleyb&lt;/a&gt;, and &lt;a href="https://github.com/adamf"&gt;adamf&lt;/a&gt; for getting these done.&lt;br /&gt;&lt;br /&gt;First, we have the ability to render dotted notes.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-_6ap18PtjUM/TYuqXl_qfKI/AAAAAAAAD5I/H7rGvYH1ibc/s1600/Screen+shot+2011-03-24+at+4.29.01+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="https://lh4.googleusercontent.com/-_6ap18PtjUM/TYuqXl_qfKI/AAAAAAAAD5I/H7rGvYH1ibc/s1600/Screen+shot+2011-03-24+at+4.29.01+PM.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Dotted Notes&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Then we have key signatures.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/--vft509bs9g/TYuqfZb2zRI/AAAAAAAAD5Q/FWEwl2rGnlg/s1600/Screen+shot+2011-03-24+at+4.30.42+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="131" src="https://lh3.googleusercontent.com/--vft509bs9g/TYuqfZb2zRI/AAAAAAAAD5Q/FWEwl2rGnlg/s320/Screen+shot+2011-03-24+at+4.30.42+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Key Signatures&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;We also have time signatures.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh6.googleusercontent.com/-y4tt_zqG1GA/TYuqm1T_rVI/AAAAAAAAD5U/8svVodZZbSk/s1600/Screen+shot+2011-03-24+at+4.30.53+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="https://lh6.googleusercontent.com/-y4tt_zqG1GA/TYuqm1T_rVI/AAAAAAAAD5U/8svVodZZbSk/s1600/Screen+shot+2011-03-24+at+4.30.53+PM.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Time Signatures&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;This includes the really crazy time signatures too.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh6.googleusercontent.com/-kLZARpxyxs8/TYuqs0_j8uI/AAAAAAAAD5Y/A1_swBCl_gc/s1600/Screen+shot+2011-03-24+at+4.31.03+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="https://lh6.googleusercontent.com/-kLZARpxyxs8/TYuqs0_j8uI/AAAAAAAAD5Y/A1_swBCl_gc/s1600/Screen+shot+2011-03-24+at+4.31.03+PM.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Whacky Time Signatures&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Finally, we have support for different types of clefs.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh6.googleusercontent.com/-3uf_T9N9gJM/TYuq0ptEOII/AAAAAAAAD5c/0T7Gy_cJQbE/s1600/Screen+shot+2011-03-24+at+4.31.40+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="92" src="https://lh6.googleusercontent.com/-3uf_T9N9gJM/TYuq0ptEOII/AAAAAAAAD5c/0T7Gy_cJQbE/s320/Screen+shot+2011-03-24+at+4.31.40+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Alternate Clefs&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;But wait... there's more.&amp;nbsp;All of this is supported in &lt;a href="http://vexflow.com/vextab/tutorial.html"&gt;VexTab&lt;/a&gt; by way of new &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;tabstave&lt;/span&gt; parameters. Take a look at the updated &lt;a href="http://vexflow.com/vextab/tutorial.html"&gt;VexTab Tutorial&lt;/a&gt; for the details.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-1929109118059060998?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/1929109118059060998/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/03/vexflow-tutorial-and-other-goodies.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1929109118059060998'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1929109118059060998'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/03/vexflow-tutorial-and-other-goodies.html' title='The VexFlow Tutorial (...and other goodies)'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-_6ap18PtjUM/TYuqXl_qfKI/AAAAAAAAD5I/H7rGvYH1ibc/s72-c/Screen+shot+2011-03-24+at+4.29.01+PM.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6639885962680967449</id><published>2011-03-23T13:26:00.000-04:00</published><updated>2011-09-10T11:05:46.322-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='vim'/><title type='text'>Editing XML and HTML in Vim</title><content type='html'>I just discovered the Vim &lt;a href="https://github.com/sukima/xmledit/"&gt;xmledit&lt;/a&gt; plugin.&lt;br /&gt;&lt;br /&gt;With features like tag-completion, auto-wrapping and unwrapping, quick navigation, etc., it has, in a matter of minutes, measurably decreased my level of frustration while editing markup in Vim.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Installation&lt;/h3&gt;&lt;p/&gt;&lt;div&gt;The quickest way to install the plugin is by downloading the latest .&lt;code&gt;vba&lt;/code&gt; from the &lt;a href="http://vim.sourceforge.net/scripts/script.php?script_id=301"&gt;plugin site&lt;/a&gt;, and run the following commands:&lt;/div&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ vim xmledit.vba&lt;br /&gt;:so %&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You also need to edit your&amp;nbsp;&lt;code&gt;.vimrc&lt;/code&gt; and add the following:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;filetype plugin on&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div&gt;This installs the plugin into your &lt;code&gt;.vim/ftplugin&lt;/code&gt; directory, and enables it for &lt;code&gt;.xml&lt;/code&gt; files. To enable it for other file types, create a link to the file with the new extension name in the same directory. (Copying the file also works.)&lt;/div&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ cd ~/.vim/ftplugin&lt;br /&gt;$ ln -s xml.vim html.vim&lt;br /&gt;$ ln -s xml.vim xhtml.vim&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Usage&lt;/h3&gt;&lt;br /&gt;The plugin supports the various Vim modes in interesting ways.&lt;br /&gt;&lt;br /&gt;In insert mode, when you finish a tag (with the &lt;code&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&amp;nbsp;character), it will be autocompleted and the cursor placed in-between the tags.&lt;br /&gt;&lt;br /&gt;If you immediately type another &lt;code&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;, it will close the tag on its own line, and place the cursor on a new line right in between.&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;%&lt;/code&gt; key jumps between the start and end of a tag.&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;\%&lt;/code&gt; combination jumps between opening and closing tags. (Note that backslash is the default key-prefix for scripts and plugins to use. You can change this prefix with the &lt;code&gt;mapleader&lt;/code&gt; setting.)&lt;br /&gt;&lt;br /&gt;If you select text (for example with &lt;code&gt;v&lt;/code&gt;), and type &lt;code&gt;\x&lt;/code&gt;, it will prompt you to wrap the text with a custom tag.&lt;br /&gt;&lt;br /&gt;Typing in &lt;code&gt;\d&lt;/code&gt; unwraps surrounding tags from the cursor.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Learning More&lt;/h3&gt;&lt;br /&gt;Type &lt;code&gt;:help xml-plugin&lt;/code&gt; for help and more information.&lt;br /&gt;&lt;br /&gt;Yay for another awesome Vim plugin. You can see my entire Vim profile in my &lt;a href="https://github.com/0xfe/evil/tree/master/dotfiles/vim_local"&gt;Evil Tomato GitHub Repository&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-6639885962680967449?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/6639885962680967449/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/03/editing-xml-and-html-in-vim.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6639885962680967449'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6639885962680967449'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/03/editing-xml-and-html-in-vim.html' title='Editing XML and HTML in Vim'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-4947829838430309691</id><published>2011-03-20T08:43:00.000-04:00</published><updated>2011-03-20T08:43:13.268-04:00</updated><title type='text'>On Twitter</title><content type='html'>It turns out I'm on twitter. I have absolutely no idea what I'm going to do with it.&lt;br /&gt;&lt;br /&gt;Maybe I'll tweet every time time the compiler yells at me... or when my kernel panics... or when my browser &lt;a href="http://4.bp.blogspot.com/_IBgS1al-1-g/SL__vUEp0uI/AAAAAAAABqs/HPy_jHV0y20/s400/aw,+snap.png"&gt;frowns&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Whatever it is, I'm on twitter: &lt;a href="http://twitter.com/11111110b"&gt;twitter.com/11111110b&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;That's seven ones and a zero, followed by a bee.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-4947829838430309691?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/4947829838430309691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2011/03/on-twitter.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/4947829838430309691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/4947829838430309691'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2011/03/on-twitter.html' title='On Twitter'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-5987592118110172726</id><published>2010-09-13T11:03:00.000-04:00</published><updated>2010-09-13T11:03:44.098-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Regex Substitution in Haskell</title><content type='html'>I'm shocked and appalled at the fact that there is no generic regex substitution function in the GHC libraries. All I'm looking for is a simple function equivalent to perl's &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;s/.../.../&lt;/span&gt; expression.&lt;br /&gt;&lt;br /&gt;After digging around a bit, I found &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;subRegex&lt;/span&gt; in &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;regex-compat&lt;/span&gt;. While this works well, it does not use PCRE, and as far as I can tell, there's no support for &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;ByteString&lt;/span&gt;s.&lt;br /&gt;&lt;br /&gt;Grrr.&lt;br /&gt;&lt;br /&gt;Anyhow, I took the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;subRegex&lt;/span&gt; implementation from &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;regex-compat&lt;/span&gt; and mangled it slightly to work with &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Text.Regex.PCRE&lt;/span&gt;. I also added the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;(=~$)&lt;/span&gt; function which feels a bit more familiar to perl users. For example:&lt;br /&gt;&lt;pre class="prettyprint"&gt;Prelude PCRESub&amp;gt; "me boo" =~$ ("(me) boo", "he \\1")&lt;br /&gt;"he me"&lt;/pre&gt;The above is equivalent to perl's:&lt;br /&gt;&lt;pre class="prettyprint"&gt;$text = "me boo";&lt;br /&gt;$text =~ s/(me) boo/he $1/;&lt;/pre&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;(~=$)&lt;/span&gt; is implemented with &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;reSub&lt;/span&gt; (which is also exported by &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;PCRESub&lt;/span&gt;). &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;reSub&lt;/span&gt; allows you to provide your own &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;CompOption&lt;/span&gt; and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;ExecOption&lt;/span&gt; options.&lt;br /&gt;&lt;br /&gt;Here's the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;PCRESub&lt;/span&gt; module:&lt;br /&gt;&lt;pre class="prettyprint"&gt;-- PCRE-based Regex Substitution&lt;br /&gt;-- Mohit Muthanna Cheppudira&lt;br /&gt;--&lt;br /&gt;-- Based off code by Chris Kuklewicz from regex-compat library.&lt;br /&gt;--&lt;br /&gt;-- Requires Text.Regex.PCRE from regex-pcre.&lt;br /&gt;&lt;br /&gt;module PCRESub(&lt;br /&gt;  (=~$),&lt;br /&gt;  reSub&lt;br /&gt;) where&lt;br /&gt;&lt;br /&gt;import Data.Array((!))&lt;br /&gt;import Text.Regex.PCRE&lt;br /&gt;&lt;br /&gt;subRegex :: Regex                          -- ^ Search pattern&lt;br /&gt;         -&gt; String                         -- ^ Input string&lt;br /&gt;         -&gt; String                         -- ^ Replacement text&lt;br /&gt;         -&gt; String                         -- ^ Output string&lt;br /&gt;subRegex _ "" _ = ""&lt;br /&gt;subRegex regexp inp repl =&lt;br /&gt;  let compile _i str [] = \ _m -&gt;  (str++)&lt;br /&gt;      compile i str (("\\",(off,len)):rest) =&lt;br /&gt;        let i' = off+len&lt;br /&gt;            pre = take (off-i) str&lt;br /&gt;            str' = drop (i'-i) str&lt;br /&gt;        in if null str' then \ _m -&gt; (pre ++) . ('\\':)&lt;br /&gt;             else \  m -&gt; (pre ++) . ('\\' :) . compile i' str' rest m&lt;br /&gt;      compile i str ((xstr,(off,len)):rest) =&lt;br /&gt;        let i' = off+len&lt;br /&gt;            pre = take (off-i) str&lt;br /&gt;            str' = drop (i'-i) str&lt;br /&gt;            x = read xstr&lt;br /&gt;        in if null str' then \ m -&gt; (pre++) . ((fst (m!x))++)&lt;br /&gt;             else \ m -&gt; (pre++) . ((fst (m!x))++) . compile i' str' rest m&lt;br /&gt;      compiled :: MatchText String -&gt; String -&gt; String&lt;br /&gt;      compiled = compile 0 repl findrefs where&lt;br /&gt;        bre = makeRegexOpts defaultCompOpt execBlank "\\\\(\\\\|[0-9]+)"&lt;br /&gt;        findrefs = map (\m -&gt; (fst (m!1),snd (m!0))) (matchAllText bre repl)&lt;br /&gt;      go _i str [] = str&lt;br /&gt;      go i str (m:ms) =&lt;br /&gt;        let (_,(off,len)) = m!0&lt;br /&gt;            i' = off+len&lt;br /&gt;            pre = take (off-i) str&lt;br /&gt;            str' = drop (i'-i) str&lt;br /&gt;        in if null str' then pre ++ (compiled m "")&lt;br /&gt;             else pre ++ (compiled m (go i' str' ms))&lt;br /&gt;  in go 0 inp (matchAllText regexp inp)&lt;br /&gt;&lt;br /&gt;-- Substitue re with sub in str using options copts and eopts.&lt;br /&gt;reSub :: String -&gt; String -&gt; String -&gt; CompOption -&gt; ExecOption -&gt; String&lt;br /&gt;reSub str re sub copts eopts = subRegex (makeRegexOpts copts eopts re) str sub&lt;br /&gt;&lt;br /&gt;-- Substitute re with sub in str, e.g.,&lt;br /&gt;--&lt;br /&gt;-- The perl expression:&lt;br /&gt;--&lt;br /&gt;--   $text = "me boo";&lt;br /&gt;--   $text =~ s/(me) boo/he $1/;&lt;br /&gt;--&lt;br /&gt;-- can be written as:&lt;br /&gt;--&lt;br /&gt;--   text = "me boo" =~$ ("(me) boo", "he \\1")&lt;br /&gt;--&lt;br /&gt;(=~$) :: String -&gt; (String, String) -&gt; String&lt;br /&gt;(=~$) str (re, sub) = reSub str re sub defaultCompOpt defaultExecOpt&lt;/pre&gt;Example usage:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;import PCRESub&lt;br /&gt;&lt;br /&gt;main = do&lt;br /&gt;  let text = "me boo" =~$ ("(me) boo", "he \\1")&lt;br /&gt;  print text&lt;/pre&gt;Paste this code in, or browse the source at my GitHub repo: &lt;a href="http://github.com/0xfe/experiments/blob/master/haskell/PCRESub.hs"&gt;PCRESub.hs&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Someone please make this work across all the regex backends (and add support for &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;ByteString&lt;/span&gt;s)!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-5987592118110172726?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/5987592118110172726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/09/regex-substitution-in-haskell.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/5987592118110172726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/5987592118110172726'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/09/regex-substitution-in-haskell.html' title='Regex Substitution in Haskell'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-5809791720055478982</id><published>2010-09-12T12:24:00.000-04:00</published><updated>2010-09-12T12:24:22.308-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='vexflow'/><title type='text'>VexFlow Google Group</title><content type='html'>I've been out of touch for a while, and it took me way too long to set this up; but hey - better late than never. :-)&lt;br /&gt;&lt;br /&gt;After looking into various options for the VexFlow mailing list, I eventually decided to use Google Groups. It's super easy to setup and manage, and has all the features I'll ever need.&lt;br /&gt;&lt;br /&gt;If you're interested in hacking, discussing, or simply keeping up with VexFlow, sign up here:&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;a href="http://groups.google.com/group/vexflow"&gt;http://groups.google.com/group/vexflow&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Unfortunately, I haven't had the time lately to hack on VexFlow, but I assure you that it's only temporary. I have a bunch of partial changes in the works, along with some interesting ideas floating around.&amp;nbsp;More later.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-5809791720055478982?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/5809791720055478982/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/09/vexflow-google-group.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/5809791720055478982'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/5809791720055478982'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/09/vexflow-google-group.html' title='VexFlow Google Group'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3059590296733853422</id><published>2010-08-03T18:04:00.000-04:00</published><updated>2010-08-03T18:04:26.454-04:00</updated><title type='text'>VexFlow is Open Source</title><content type='html'>&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;That's right folks! All the &lt;a href="http://www.vexflow.com/"&gt;VexFlow&lt;/a&gt; code is now available in the &lt;a href="http://github.com/0xfe/vexflow"&gt;VexFlow GitHub Repository&lt;/a&gt;.&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;It's distributed under the OSI approved MIT License, so feel free to tinker, tweak, hack, fix, fork, and redistribute it.&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TFiMglBsB2I/AAAAAAAAB1g/djXto7n0Y5Y/s1600/Screen+shot+2010-08-03+at+5.36.09+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="187" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TFiMglBsB2I/AAAAAAAAB1g/djXto7n0Y5Y/s320/Screen+shot+2010-08-03+at+5.36.09+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;A lot of the core infrastructure (e.g., contexts, formatting, etc.) is ready and stable, and most of the work that needs to be done is adding support for various types of modifiers, effects, and annotations. I've worked on some of the trickier ones, like accidentals and beams, and have left the easier ones out so interested coders can learn by contributing.&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;This said, algorithms-enthusiasts need not feel left out - there are some hard problems to solve as well :-)&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;Here's where I would like help from the community:&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Dots (Easy)&lt;/li&gt;&lt;li&gt;Trills (Easy)&lt;/li&gt;&lt;li&gt;Grace Notes (Moderate)&lt;/li&gt;&lt;li&gt;Slurs (Easy)&lt;/li&gt;&lt;li&gt;Glyphs for time signatures (Easy)&lt;/li&gt;&lt;li&gt;Key signature (Easy if you reuse the accidental placement code from accidentals.js)&lt;/li&gt;&lt;li&gt;Guitar effects: Palm Muting, Scratches, Whammy, Harmonics, etc. (Easy)&lt;/li&gt;&lt;li&gt;Chord Stave with Rhythm Slashes (Moderate to Hard)&lt;/li&gt;&lt;li&gt;Lyrics (Easy)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;Here's what I'm working on right now (and also wouldn't mind some help with):&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Tuplets / Triplets&lt;/li&gt;&lt;li&gt;VexTab parser support for rests, alternate keys, and multiple voices.&lt;/li&gt;&lt;li&gt;Alternate tunings and support for arbitrary-string instruments.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;There isn't much developer documentation right now, but a good place to start is by going through the &lt;a href="http://github.com/0xfe/vexflow/tree/master/tests/"&gt;code&lt;/a&gt; for &lt;a href="http://vexflow.com/tests/"&gt;the tests&lt;/a&gt;. You may notice that some files are commented better than others - a great way to help is by adding better comments along with more thorough tests.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you're not a coder and would like to help, you can do so by testing and &lt;a href="http://github.com/0xfe/vexflow/issues"&gt;reporting bugs&lt;/a&gt;, helping with documentation, spending $7 on a &lt;a href="http://vexflow.com/tabdiv/"&gt;TabDiv license&lt;/a&gt;, or simply spreading the word.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thanks for all the support and help over the past few months. &lt;a href="http://github.com/0xfe/vexflow"&gt;Dive in&lt;/a&gt; and enjoy!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-3059590296733853422?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/3059590296733853422/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/08/vexflow-is-open-source.html#comment-form' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3059590296733853422'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3059590296733853422'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/08/vexflow-is-open-source.html' title='VexFlow is Open Source'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_Zfnd5XZP80Y/TFiMglBsB2I/AAAAAAAAB1g/djXto7n0Y5Y/s72-c/Screen+shot+2010-08-03+at+5.36.09+PM.png' height='72' width='72'/><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7961142667231282175</id><published>2010-07-20T21:54:00.000-04:00</published><updated>2010-07-20T21:54:23.905-04:00</updated><title type='text'>More Durations and Better Beaming</title><content type='html'>I finally got most of the duration and beaming support worked out last weekend, and I gotta say that the generated scores by &lt;a href="http://vexflow.com/"&gt;VexFlow&lt;/a&gt; (and &lt;a href="http://vexflow.com/vextab"&gt;VexTab&lt;/a&gt;) are starting to look pretty good.&lt;br /&gt;&lt;br /&gt;In VexTab notation, you can now set the duration of the subsequent notes using the colon (&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;:&lt;/span&gt;) character. By default, note durations are set to eighth notes.&lt;br /&gt;&lt;br /&gt;Here's an example of a line that generates a half-note followed by two quarter-notes.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TEZNQw7MZRI/AAAAAAAABwM/ZGhQfsV7guE/s1600/Screen+shot+2010-07-20+at+9.17.44+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="328" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TEZNQw7MZRI/AAAAAAAABwM/ZGhQfsV7guE/s400/Screen+shot+2010-07-20+at+9.17.44+PM.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Basic Duration Support&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Valid duration values (currently) are: &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;w&lt;/span&gt;, &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;h&lt;/span&gt;, &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;q&lt;/span&gt;, &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;8&lt;/span&gt;, &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;16&lt;/span&gt;, and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;32&lt;/span&gt;. Support for dots and tuplets/triplets is not yet implemented.&lt;br /&gt;&lt;br /&gt;Durations can be specified inside slides, bends, and other types of ties by prefixing the fret with the duration value enclosed within colon characters.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TEZNRJa-saI/AAAAAAAABwQ/9ZHO_7UMf_E/s1600/Screen+shot+2010-07-20+at+9.18.50+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="330" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TEZNRJa-saI/AAAAAAAABwQ/9ZHO_7UMf_E/s400/Screen+shot+2010-07-20+at+9.18.50+PM.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Durations within Ties&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Also, I spent time working on some of the tricker beam configurations, where notes with varying durations are beamed.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TEZNRlCAgPI/AAAAAAAABwU/P-fNb1H18HU/s1600/Screen+shot+2010-07-20+at+9.20.49+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="127" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TEZNRlCAgPI/AAAAAAAABwU/P-fNb1H18HU/s200/Screen+shot+2010-07-20+at+9.20.49+PM.png" width="200" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Crazy Beaming&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;In VexTab, you can create beams by enclosing your notes within brackets (separated by spaces).&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TEZNR7yZZsI/AAAAAAAABwY/2cRP0qH_zQQ/s1600/Screen+shot+2010-07-20+at+9.25.47+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="332" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TEZNR7yZZsI/AAAAAAAABwY/2cRP0qH_zQQ/s400/Screen+shot+2010-07-20+at+9.25.47+PM.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Beaming in VexTab&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Anyhow, I've pushed out the latest revision, with support for standard notation, durations, and beaming to the&amp;nbsp;&lt;a href="http://vexflow.com/tabdiv"&gt;TabDiv website&lt;/a&gt;. Feel free to &lt;a href="http://vexflow.com/vextab/tutorial.html"&gt;toy with it&lt;/a&gt; and report any issues you come across.&lt;br /&gt;&lt;br /&gt;Here's a screenshot of a bluesy guitar lick written in VexTab.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TEZSfloFljI/AAAAAAAABwg/y2yG5bVlRw8/s1600/Screen+shot+2010-07-20+at+9.50.07+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="640" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TEZSfloFljI/AAAAAAAABwg/y2yG5bVlRw8/s640/Screen+shot+2010-07-20+at+9.50.07+PM.png" width="408" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A Blues Lick in VexTab&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;That's all for this week, folks! There are a lot more interesting things coming up. Check out the &lt;a href="http://vexflow.com/vextab/tutorial.html"&gt;VexTab tutorial&lt;/a&gt; to play around in the sandboxes, and stay in touch.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-7961142667231282175?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/7961142667231282175/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/07/more-durations-and-better-beaming.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7961142667231282175'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7961142667231282175'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/07/more-durations-and-better-beaming.html' title='More Durations and Better Beaming'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_Zfnd5XZP80Y/TEZNQw7MZRI/AAAAAAAABwM/ZGhQfsV7guE/s72-c/Screen+shot+2010-07-20+at+9.17.44+PM.png' height='72' width='72'/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7789963467582961010</id><published>2010-07-14T11:51:00.000-04:00</published><updated>2010-07-14T11:51:00.426-04:00</updated><title type='text'>Migrating VexFlow to SCons</title><content type='html'>I love tools. Tools keep my projects predictable and smooth. Tools help me code fast and release fast.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.vexflow.com/"&gt;VexFlow&lt;/a&gt; has gone through many iterations of design, implementation, and deployment, and I couldn't have brought it this far so quickly without my tools. (Well, automated testing had a lot to do with it too, but that's for another post.)&lt;br /&gt;&lt;br /&gt;Yesterday, I added a new tool to my toolbox - &lt;a href="http://www.scons.org/"&gt;SCons&lt;/a&gt;. I migrated all my building, packaging, test driving, and deployment code to SCons. Compared to the ugly shell scripts I previously used, SCons is a lot cleaner, a lot faster, and significantly easier to manage.&lt;br /&gt;&lt;br /&gt;I chose SCons for two reasons:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Simplicity. It's Python-based and super-easy to work with.&lt;/li&gt;&lt;li&gt;Familiarity. I've used it before, so already know my way around it.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;Since I use the &lt;a href="http://code.google.com/closure/compiler/"&gt;Google Closure Compiler&lt;/a&gt; to build and minimize my JavaScript code, I had to write a new builder for SCons. That turned out to be pretty straightforward to implement.&lt;/div&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;def js_builder(target, source, env):&lt;br /&gt;  """ A JavaScript builder using Google Closure Compiler. """&lt;br /&gt;&lt;br /&gt;  cmd = env.subst(&lt;br /&gt;      "$JAVA -jar $JS_COMPILER --compilation_level $JS_COMPILATION_LEVEL");&lt;br /&gt;&lt;br /&gt;  # Add defines to the command&lt;br /&gt;  for define in env['JS_DEFINES'].keys():&lt;br /&gt;    cmd += " --define=\"%s=%s\"" % (define, env['JS_DEFINES'][define])&lt;br /&gt;&lt;br /&gt;  # Add the source files&lt;br /&gt;  for file in source:&lt;br /&gt;    cmd += " --js " + str(file)&lt;br /&gt;&lt;br /&gt;  # Add the output file&lt;br /&gt;  cmd += " --js_output_file " + str(target[0])&lt;br /&gt;&lt;br /&gt;  # Log the command and run&lt;br /&gt;  print env.subst(cmd)&lt;br /&gt;  os.system(env.subst(cmd))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I also needed a new builder to stamp my output with the relevant build information. So, I created a &lt;i&gt;Stamper, &lt;/i&gt;which is just a builder that runs some string substitution on files with &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;sed&lt;/span&gt;. The stamper looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;def vexflow_stamper(target, source, env):&lt;br /&gt;  """ A Build Stamper for VexFlow """&lt;br /&gt;&lt;br /&gt;  cmd =  "sed "&lt;br /&gt;  cmd += " -e s/__VEX_BUILD_PREFIX__/$VEX_BUILD_PREFIX/"&lt;br /&gt;  cmd += " -e s/__VEX_VERSION__/$VEX_VERSION/"&lt;br /&gt;  cmd += ' -e "s/__VEX_BUILD_DATE__/${VEX_BUILD_DATE}/"'&lt;br /&gt;  cmd += " -e s/__VEX_GIT_SHA1__/`git rev-list --max-count=1 HEAD`/ "&lt;br /&gt;  cmd += ("%s &amp;gt; %s" % (source[0], target[0]))&lt;br /&gt;&lt;br /&gt;  print env.subst(cmd)&lt;br /&gt;  os.system(env.subst(cmd))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Before you can use these builders, you need to add them to your environment:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;env.Append(BUILDERS = {'JavaScript': Builder(action = js_builder),&lt;br /&gt;                       'VexFlowStamp': Builder(action = vexflow_stamper)})&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Once this is done, you can add build JavaScript targets with the &lt;i&gt;JavaScript&lt;/i&gt; command.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;env['JAVA'] = "/usr/bin/java"&lt;br /&gt;env['JS_COMPILER'] = "support/compiler.jar"&lt;br /&gt;env['JS_DEFINES' ] = {&lt;br /&gt;  "Vex.Debug": "true",&lt;br /&gt;  "Vex.LogLevel": "4"&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sources = ["src1.js", "src2.js", "src3.js"]&lt;br /&gt;&lt;br /&gt;env.JavaScript("src.min.js", sources)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This really is just scratching the surface. There's a lot more you can do with SCons to automate and streamline your builds. To learn more, take a look at the &lt;a href="http://www.scons.org/doc/2.0.0.final.0/HTML/scons-user/index.html"&gt;user guide&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I added support for testing, packaging, and deployment (of the web pages and demos) to my SCons scripts in a matter of hours, and finally purged all my nasty shell scripts from the VexFlow codebase.&lt;br /&gt;&lt;br /&gt;Give it a try. I guarantee you'll be happier.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-7789963467582961010?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/7789963467582961010/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/07/migrating-vexflow-to-scons.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7789963467582961010'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7789963467582961010'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/07/migrating-vexflow-to-scons.html' title='Migrating VexFlow to SCons'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6514910752490146447</id><published>2010-07-12T14:50:00.000-04:00</published><updated>2010-07-12T14:50:31.885-04:00</updated><title type='text'>Durations, Code, and Posters</title><content type='html'>The last few weeks have been relatively quiet on the VexFlow side. I've been vacationing in Cape Cod with my wife and 3-month-old.&lt;br /&gt;&lt;br /&gt;Obviously, vacation is never fun without a few good coding sprints. I started work on incorporating standard notation into &lt;a href="http://www.vexflow.com/vextab"&gt;VexTab&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The first thing I needed to do was create a class to convert fret-string pairs to notes. In order to support alternate tunings, I created a &lt;i&gt;Tuning&lt;/i&gt;&amp;nbsp;class, whose sole responsibility is to return the correct note for a given fret-string pair, based on the instrument type and tuning.&lt;br /&gt;&lt;br /&gt;So, to convert the fret-string pair "5/2" on a 5-string bass to standard notation, all I need to do is:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;var tuning = new Vex.Flow.Tuning("G/4,D/4,A/3,E/3,B/2");&lt;br /&gt;var note = tuning.getNoteForFret(5, 2);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The next part was augmenting the language to render standard notation when requested. I modified &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;tabstave&lt;/span&gt; to accept &lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;i&gt;key=value&lt;/i&gt;&lt;/span&gt; parameters, and added a parameter called &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;notation&lt;/span&gt;. When set to &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;true&lt;/span&gt;, it renders standard notation above the guitar tab.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TDti2s17SfI/AAAAAAAABvk/jlntRyKLWjM/s1600/Screen+shot+2010-07-12+at+2.20.47+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="276" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TDti2s17SfI/AAAAAAAABvk/jlntRyKLWjM/s320/Screen+shot+2010-07-12+at+2.20.47+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;VexTab with Standard Notation&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;I also started work on basic duration support and auto-beaming. I don't have much to show for this yet, because they're currently a bit intertwined, and automatic beaming is harder than I anticipated (yet again!)&lt;br /&gt;&lt;br /&gt;In other news, I &lt;a href="http://github.com/0xfe/vex/blob/master/vextab/vextab.js"&gt;open sourced&lt;/a&gt; the VexTab parser, so you can learn more about the language or use it in your own rendering engines. It is currently slightly coupled to VexFlow, but pretty trivial to decouple. (I'm going to fully decouple it as this project progresses.)&lt;br /&gt;&lt;br /&gt;The parser is licensed under the &lt;a href="http://www.opensource.org/licenses/mit-license.php"&gt;MIT license&lt;/a&gt;, and is available on GitHub at: &lt;a href="http://github.com/0xfe/vex/tree/master/vextab/"&gt;http://github.com/0xfe/vex/tree/master/vextab/&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Finally, some readers who liked my previous &lt;a href="http://0xfe.blogspot.com/2009/12/google-chrome-poster-from-source-code.html"&gt;Chrome Poster from Source Code&lt;/a&gt; post requested posters for other open-source projects. I generated posters for Firefox, Linux, and FreeBSD, and made them available on my other side project: &lt;a href="http://wickedmeanposters.com/"&gt;Wicked Mean Posters&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TDtjDLWXf0I/AAAAAAAABvo/LBpuZ15dnTQ/s1600/Screen+shot+2010-07-12+at+2.43.14+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="198" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TDtjDLWXf0I/AAAAAAAABvo/LBpuZ15dnTQ/s320/Screen+shot+2010-07-12+at+2.43.14+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Firefox Poster from Source Code&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;More next time!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-6514910752490146447?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/6514910752490146447/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/07/durations-code-and-posters.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6514910752490146447'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6514910752490146447'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/07/durations-code-and-posters.html' title='Durations, Code, and Posters'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_Zfnd5XZP80Y/TDti2s17SfI/AAAAAAAABvk/jlntRyKLWjM/s72-c/Screen+shot+2010-07-12+at+2.20.47+PM.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-8369373862453509821</id><published>2010-06-25T17:25:00.000-04:00</published><updated>2011-09-10T11:20:07.098-04:00</updated><title type='text'>Encrypted Incremental Backups to S3</title><content type='html'>I spent some time this week trying to get secure online backups working for all my machines.&lt;br /&gt;&lt;br /&gt;So far, I've been managing most of my data and workspaces with replicated Git repositories. I have scripts that allow me to maintain roaming profiles across my machines (almost) seamlessly, and these scripts try to ensure that these profiles are consistently replicated. My profiles include things like dot files (&lt;code&gt;.vimrc&lt;/code&gt;, &lt;code&gt;.screenrc&lt;/code&gt;, etc.), startup scripts, tools, workspaces, repositories, and other odds and ends.&lt;br /&gt;&lt;br /&gt;Because I tend to be ultra-paranoid about security and reliability, the replicas are encrypted and distributed across different machines in different locations. For encryption, I use &lt;a href="https://launchpad.net/ecryptfs"&gt;ecryptfs&lt;/a&gt; on Linux machines, and FileVault on the Mac.&lt;br /&gt;&lt;br /&gt;Anyhow, this week I lost my Mac to a hardware failure, and my co-located Linux machine to a service-provider &lt;i&gt;dismantling&lt;/i&gt;. That left me with one replica... just waiting to fail.&lt;br /&gt;&lt;br /&gt;I decided that I needed another replica, but didn't want to pay for, or have to setup another co-located server. After spending some time researching various online-backup providers, I decided to go with &lt;a href="http://aws.amazon.com/s3/"&gt;Amazon's S3 service&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I chose S3 because - it's cheap, it's tried and tested, it's built on an internal distributed and replicated database, and there are some great tools that work with it.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Brackup&lt;/h3&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/brackup/"&gt;Brackup&lt;/a&gt;, by Brad Fitzpatrick, is one of those tools. It allows you to make encrypted incremental backups to S3, without a lot of hair-pulling or teeth-gnashing.&lt;br /&gt;&lt;br /&gt;To get Brackup running on your machine, you need to have GPG, Perl 5, and the Net::Amazon::S3 Perl module installed. On the Mac, you also need to get MacPorts.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Installation&lt;/h3&gt;&lt;br /&gt;Most modern distributions come with Perl 5 pre-installed, but not with GPG. The package name you want on both MacPorts and Ubuntu, is &lt;code&gt;gnupg&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;The first thing you need to do, if you don't already have a GPG key, is to generate one.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ gpg --gen-keys&lt;/pre&gt;&lt;br /&gt;If you need to backup multiple machines, export your public key to a text file, and import it on the other machines.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;hostA$ gpg --export -a "User Name" &amp;gt; public.key&lt;br /&gt;hostA$ scp public.key hostB:/tmp&lt;br /&gt;hostB$ gpg --import /tmp/public.key&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Remember that all your backups will be encrypted with your public key, so if you lose your private key, the only thing you can do with your backups is generate white noise. Export your private key and save it in a safe place. (I suggest &lt;a href="http://vexcrypto.appspot.com/"&gt;VexCrypto&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ gpg --export-secret-key -a "User Name" &amp;gt; private.key&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now that you have your keys setup, download and install Brackup. The easiest way to do this is by using the &lt;code&gt;cpan&lt;/code&gt; tool.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ sudo cpan Net::Amazon::S3&lt;br /&gt;$ sudo cpan Brackup&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that it's better (and way faster) to use your distribution's package for Net::Amazon::S3. On Ubuntu the package is &lt;code&gt;libnet-amazon-s3-perl&lt;/code&gt;, and on MacPorts, the package is &lt;code&gt;p5-amazon-s3&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Configuration&lt;/h3&gt;&lt;br /&gt;Once this is done, you can generate a template configuration file by typing in &lt;code&gt;brackup&lt;/code&gt; on the command line. This file is stored in &lt;code&gt;$HOME/.brackup&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ &lt;b&gt;brackup&lt;/b&gt;&lt;br /&gt;Error: Your config file needs tweaking.  I put a commented-out template at: /home/muthanna/.brackup.conf&lt;br /&gt;&lt;br /&gt;brackup --from=[source_name] --to=[target_name] [--output=&lt;backup_metafile.brackup&gt;]&lt;br /&gt;brackup --help&lt;br /&gt;&lt;/backup_metafile.brackup&gt;&lt;/pre&gt;&lt;br /&gt;Edit the configuration file and create your sources and targets. You will likely have multiple sources, and one target. Here's a snip of my configuration:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;[TARGET:amazon]&lt;br /&gt;type = Amazon&lt;br /&gt;aws_access_key_id  = XXXXXXXXXXX&lt;br /&gt;aws_secret_access_key = XXXXXXXXXXXXXX&lt;br /&gt;keep_backups = 10&lt;br /&gt;&lt;br /&gt;[SOURCE:mac_repos]&lt;br /&gt;path = /Users/0xfe/Local&lt;br /&gt;chunk_size = 5m&lt;br /&gt;gpg_recipient = 79E44165&lt;br /&gt;ignore = ^.*\.(swp|swo|hi|o|a|pyc|svn|class|DS_Store|Trash|Trashes)$/&lt;br /&gt;&lt;br /&gt;[SOURCE:mac_desktop_books]&lt;br /&gt;path = /Users/0xfe/Desktop/Books&lt;br /&gt;gpg_recipient = 79E44165&lt;br /&gt;ignore = ^.*\.(swp|swo|hi|o|a|pyc|svn|class|DS_Store|Trash|Trashes)$/&lt;br /&gt;&lt;br /&gt;[SOURCE:mac_desktop_workspace]&lt;br /&gt;path = /Users/0xfe/Desktop/Workspace&lt;br /&gt;gpg_recipient = 79E44165&lt;br /&gt;ignore = ^.*\.(swp|swo|hi|o|a|pyc|svn|class|DS_Store|Trash|Trashes)$/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The configuration keys are pretty self explanatory. I should point out that &lt;code&gt;gpg_recipient&lt;/code&gt; is your public key ID, as shown by &lt;code&gt;gpg --list-keys&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ gpg --list-keys&lt;br /&gt;/Users/0xfe/.gnupg/pubring.gpg&lt;br /&gt;-----------------------------------&lt;br /&gt;pub   2048R/79E44165 2010-06-24&lt;br /&gt;uid                  My Username &amp;lt;snip@snip.com&amp;gt;&lt;br /&gt;sub   2048R/43AD4B72 2010-06-24&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For more details on the various parameters, see &lt;a href="http://search.cpan.org/~bradfitz/Brackup/lib/Brackup/Manual/Overview.pod"&gt;The Brackup Manual&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Start a Backup&lt;/h3&gt;&lt;br /&gt;To backup one of your sources, use the &lt;code&gt;brackup&lt;/code&gt; command, as so:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ brackup -v --from=mac_repos --to=amazon&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you now take a look at your &lt;a href="http://aws.amazon.com/"&gt;AWS Dashboard&lt;/a&gt;, you should see the buckets and chunks created for your backup data.&lt;br /&gt;&lt;br /&gt;Notice that &lt;code&gt;brackup&lt;/code&gt; creates an output file (with the extension &lt;code&gt;.brackup&lt;/code&gt;) in the current directory. This file serves as an index, and maintains pointers to the S3 chunks for each backed-up file. You will need this file to locate and restore your data, and a copy of it is maintained on S3.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Restoring&lt;/h3&gt;&lt;br /&gt;"&lt;i&gt;Test restores regularly.&lt;/i&gt;" -- a wise man.&lt;br /&gt;&lt;br /&gt;To restore your Brackup backups, you will need to have your private key handy on the machine that you're restoring to. Brackup accesses your private key via &lt;code&gt;gpg-agent&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ sudo port install gpg-agent&lt;br /&gt;$ eval $(gpg-agent --daemon)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The &lt;code&gt;brackup-restore&lt;/code&gt; command restores a source tree to a path specified on the command line. It makes use of the output file that brackup generated during the initial backup to locate and restore your data. If you don't have a local copy of the output file, you can use &lt;code&gt;brackup-target&lt;/code&gt; to retrieve a copy from S3.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ brackup-restore -v --from=mac_repos-20100624.brackup \&lt;br /&gt;  --to=/Users/0xfe/temp/mac_repos --all&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You will be prompted for you AWS key, your AWS secret key, and your GPG private key passphrase. Make sure that that the restore completed successfully and correctly. Comparing the SHA1 hashes of the restored data with those of the original data is a good way to validate correctness.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Garbage Collection&lt;/h3&gt;&lt;br /&gt;You will need to prune and garbage collect your data regularly to keep backups from piling up and using up space in S3. The following commands delete old backed up chunks based on the &lt;i&gt;keep_files &lt;/i&gt;configuration value of the target.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;$ brackup-target amazon prune&lt;br /&gt;$ brackup-target amazon gc&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's all folks! Secure, on-line, off-site, incremental, buzz-word-ridden backups. Code safely!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-8369373862453509821?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/8369373862453509821/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/06/encrypted-incremental-backups-to-s3.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8369373862453509821'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8369373862453509821'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/06/encrypted-incremental-backups-to-s3.html' title='Encrypted Incremental Backups to S3'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3313904850292755497</id><published>2010-06-21T14:12:00.000-04:00</published><updated>2010-06-21T14:12:11.700-04:00</updated><title type='text'>On Parsing and Licenses</title><content type='html'>So I spent this weekend rewriting the &lt;a href="http://vexflow.com/tabdiv/tutorial.html"&gt;VexTab&lt;/a&gt; parser. The original version, though it served its purpose as a quick prototype for the language, was severely limited due to it being built primarily out of regular expressions.&lt;br /&gt;&lt;br /&gt;The new parser uses a recursive-descent algorithm, and fully supports the original grammar. Adding new syntactic elements to the language is now simple, as is adding support for more complex grammars.&lt;br /&gt;&lt;br /&gt;Some new features I added to the language are support for slides, hammer-ons, pull-offs, and tapping. Here's a blues lick written in VexTab:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TB-kXi70SwI/AAAAAAAABsY/U6n_P4PLmSY/s1600/Picture+11.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="262" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TB-kXi70SwI/AAAAAAAABsY/U6n_P4PLmSY/s320/Picture+11.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Blues Lick in VexTab&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Some readers have asked me about durations and how to specify rhythms in VexTab. Although the VexFlow core has full support for durations and timing, I still need a good way to represent them in the language. I'm open to ideas here if you have any.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Enter TabDiv&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I also spent this weekend working on the release of the first VexFlow-based product:&amp;nbsp;&lt;a href="http://vexflow.com/tabdiv/index.html"&gt;TabDiv&lt;/a&gt;. TabDiv lets you easily embed guitar tablature into your website or blog.&lt;br /&gt;&lt;br /&gt;After you've included the TabDiv &lt;code&gt;.js&lt;/code&gt; and &lt;code&gt;.css&lt;/code&gt; files in your HTML document (or blog template), you can add tabs by simply creating DIV elements and setting the class to vex-tabdiv.&lt;br /&gt;&lt;br /&gt;You can get TabDiv here:&amp;nbsp;&lt;a href="http://vexflow.com/tabdiv/index.html"&gt;http://vexflow.com/tabdiv/index.html&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Why not Open-Source?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I'm no stranger to open-source. I've been writing, maintaining, and contributing to open-source software for over a decade.&lt;br /&gt;&lt;br /&gt;Although I hope to eventually open-source all the VexFlow source code, I'm going to hold off on it until I figure out where I want to take this product. I've invested a lot of time and effort into making VexFlow a fast high-quality renderer, and I'd like to find a way to cater to both a commercial-audience, and the open-source community.&lt;br /&gt;&lt;br /&gt;So, how does one find and maintain this delicate balance? Do I completely open-source it? Should I keep it closed and charge for it? Dual-license maybe?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-3313904850292755497?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/3313904850292755497/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/06/on-parsing-and-licenses.html#comment-form' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3313904850292755497'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3313904850292755497'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/06/on-parsing-and-licenses.html' title='On Parsing and Licenses'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_Zfnd5XZP80Y/TB-kXi70SwI/AAAAAAAABsY/U6n_P4PLmSY/s72-c/Picture+11.png' height='72' width='72'/><thr:total>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7599469828136185502</id><published>2010-06-17T15:01:00.000-04:00</published><updated>2010-06-17T15:01:57.156-04:00</updated><title type='text'>Benchmarking VexFlow</title><content type='html'>I have about 340 tests now for VexFlow, and one of the things I find really impressive is the speed at which browsers currently load, execute, and render web-pages.&lt;br /&gt;&lt;br /&gt;Since the code exercises the browser on a few different dimensions (heavy JavaScript, lots of DOM manipulation, a few new HTML5 features), I decided to pit the major browsers against each other and run a few benchmarks.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TBpuw46E4pI/AAAAAAAABrw/SqG8B7qolSs/s1600/Picture+9.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="127" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TBpuw46E4pI/AAAAAAAABrw/SqG8B7qolSs/s320/Picture+9.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Test Suite on Chrome 5.0.375&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;I ran the 340 tests in a loop a 1000 times on each browser, and calculated the mean runtime in milliseconds. Here are the results:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Chrome 5.0.375: &lt;b&gt;754ms&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Safari 4.0.4: &lt;b&gt;1118ms&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Opera 10.53: &lt;b&gt;1511ms&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Firefox 3.6.3: &lt;b&gt;3209ms&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;The difference between the Chrome and Firefox numbers is quite surprising.&lt;/div&gt;&lt;br /&gt;I also ran some SVG vs. Canvas benchmarks and found that SVG was about 3 times slower than Canvas. This factor increased significantly as the number of elements in the SVG image grew. That said, SVG rendered much more consistently across the different browsers.&lt;br /&gt;&lt;br /&gt;The test machine used was a dual-core MacBook Pro with a 2.53 GHz Intel Core 2 Duo processor and 4GB of DDR3 RAM.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-7599469828136185502?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/7599469828136185502/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/06/benchmarking-vexflow.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7599469828136185502'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7599469828136185502'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/06/benchmarking-vexflow.html' title='Benchmarking VexFlow'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_Zfnd5XZP80Y/TBpuw46E4pI/AAAAAAAABrw/SqG8B7qolSs/s72-c/Picture+9.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1010250446328724203</id><published>2010-06-13T21:37:00.000-04:00</published><updated>2010-06-13T21:37:32.242-04:00</updated><title type='text'>Fonts, Tablature, SVG, and a Demo</title><content type='html'>This was a pretty productive weekend. I got a lot working, and a lot to show off.&lt;br /&gt;&lt;br /&gt;First, a credit to Simon Tatham, who created the awesome &lt;a href="http://www.chiark.greenend.org.uk/~sgtatham/gonville/"&gt;Gonville&lt;/a&gt; font, which I use for the renderer. Before finding Gonville, I tried to draw many of the glyphs myself and suffice to say, it didn't work out too well.&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TBWApLyHjsI/AAAAAAAABrQ/Tpw3uJO7GKA/s1600/Picture+2.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TBWApLyHjsI/AAAAAAAABrQ/Tpw3uJO7GKA/s1600/Picture+2.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Gonville by Simon Tatham&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Thank you Simon for creating this brilliant work of art!&lt;br /&gt;&lt;br /&gt;This week was all about the tablature. First, I added support for bend-and-release.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TBWApmGL-DI/AAAAAAAABrY/2FCC-RHU63Y/s1600/Picture+4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="109" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TBWApmGL-DI/AAAAAAAABrY/2FCC-RHU63Y/s320/Picture+4.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Then, I added support for soft and harsh vibratos.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TBWAp4i81KI/AAAAAAAABrc/rlo0K8GJFkg/s1600/Picture+5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="83" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TBWAp4i81KI/AAAAAAAABrc/rlo0K8GJFkg/s320/Picture+5.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I added support for custom note annotations.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TBWAqLZw-4I/AAAAAAAABrg/R6JS90E0WF4/s1600/Picture+6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TBWAqLZw-4I/AAAAAAAABrg/R6JS90E0WF4/s1600/Picture+6.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;There are a lot of things you can do with note annotations.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TBWAqbxxlKI/AAAAAAAABrk/8e0gfSVlUts/s1600/Picture+7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TBWAqbxxlKI/AAAAAAAABrk/8e0gfSVlUts/s1600/Picture+7.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I also added full support for SVG. Yep, full support. With a little help from &lt;a href="http://raphaeljs.com/"&gt;raphael.js&lt;/a&gt;, it was actually pretty straightforward to implement. I created a &lt;i&gt;RaphaelContext&lt;/i&gt;&amp;nbsp;class that implements the HTML5 Canvas API, and delegates all the drawing calls to Raphael. So now, the library uses HTML5 Canvas by default, unless you explicitly include the Raphael JavaScript library in your script sources.&lt;br /&gt;&lt;br /&gt;I created the beginnings of simple tablature language to render guitar tab. It's called VexTab and it's very&amp;nbsp;very alpha. Here's an example:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TBWE01GDNgI/AAAAAAAABro/HNAP8sIMbHI/s1600/Picture+8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="286" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TBWE01GDNgI/AAAAAAAABro/HNAP8sIMbHI/s320/Picture+8.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Anyhow, all this while you've been seeing screenshots of rendered notation, and no real HTML5-based demos. So, this time, I put up a new demo. All JavaScript. All SVG. Here it is:&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;a href="http://vexflow.com/tabdiv/tutorial.html"&gt;A VexFlow Demo: The VexTab Tutorial Page&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;Hope you like it, and keep those comments coming.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-1010250446328724203?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/1010250446328724203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/06/fonts-tablature-svg-and-demo.html#comment-form' title='20 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1010250446328724203'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1010250446328724203'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/06/fonts-tablature-svg-and-demo.html' title='Fonts, Tablature, SVG, and a Demo'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_Zfnd5XZP80Y/TBWApLyHjsI/AAAAAAAABrQ/Tpw3uJO7GKA/s72-c/Picture+2.png' height='72' width='72'/><thr:total>20</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-5119301442673681732</id><published>2010-06-07T13:37:00.000-04:00</published><updated>2010-06-07T13:37:49.541-04:00</updated><title type='text'>On Beams</title><content type='html'>Beams are interesting. Engraving rules for beams are extremely inconsistent. In essence, the ideal rendering involves striking a balance between the direction of the music, the angle of the beam, and the length of the stems. You don't want beams whose absolute slopes are too high, and you also don't want to compromise on the slopes too much by making the stems too long.&lt;br /&gt;&lt;br /&gt;There is one well-known rule, however: Never shorten a stem. &lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0kz8dm8nI/AAAAAAAABqY/XccZP75uRxw/s1600/Picture+1.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="126" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0kz8dm8nI/AAAAAAAABqY/XccZP75uRxw/s320/Picture+1.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Beams on Both Voices&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The algorithm I currently use to render beams seems to work pretty well. I use an approach where I iteratively update the slope and y-shift of the beam, based on the height of each stem.&lt;br /&gt;&lt;br /&gt;In retrospect, this algorithm seems pretty obvious, but I really had to experiment a fair bit to derive it. The first few beams were just all over the place.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0k0RNpJiI/AAAAAAAABqg/gfg4mHW3_NQ/s1600/Picture+3.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="123" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0k0RNpJiI/AAAAAAAABqg/gfg4mHW3_NQ/s320/Picture+3.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Beam Confusion&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0k0v4DaPI/AAAAAAAABqk/lZHHUEDCy0w/s1600/Picture+4.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="134" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0k0v4DaPI/AAAAAAAABqk/lZHHUEDCy0w/s320/Picture+4.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;More Beam Confusion&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;My debugging code places colored dots at various control points to help me visualize the errors better. It took me a bit of effort before I got to the rendering below. The beams still aren't shifted far enough to both accommodate for the stem heights.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/TA0k1owcBTI/AAAAAAAABqs/Kwww6-BTZfc/s1600/Picture+6.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="130" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/TA0k1owcBTI/AAAAAAAABqs/Kwww6-BTZfc/s320/Picture+6.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Close... But Still Wrong!&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;After ironing out the iterative algorithm, I got this:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TA0k14e-fAI/AAAAAAAABqw/-ga87n-LytU/s1600/Picture+7.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="124" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TA0k14e-fAI/AAAAAAAABqw/-ga87n-LytU/s320/Picture+7.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Decently Rendered Beams&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Perfect! The stems are only extended where necessary, and the angles of the beams adequately reflect the direction of the music.&lt;br /&gt;&lt;br /&gt;Thanks to the context-based architecture, note-modifiers such as accidentals don't disturb beam positioning.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0oXEj31-I/AAAAAAAABq0/Y3JnL2IuzzU/s1600/Picture+8.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="116" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0oXEj31-I/AAAAAAAABq0/Y3JnL2IuzzU/s320/Picture+8.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Beam With Accidentals&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;That said, something about the above rendering does not look right to me. The group of eighth notes don't look like they're positioned very evenly. If anyone knows how to render beamed notes with accidentals correctly, please let me know.&lt;br /&gt;&lt;br /&gt;Anyhow, I also got slides and ties working again. These were gone after the rewrite, and I had to work them in using the new framework.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TA0rKI09n8I/AAAAAAAABq4/6OGjADrq9MA/s1600/Picture+9.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="77" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TA0rKI09n8I/AAAAAAAABq4/6OGjADrq9MA/s320/Picture+9.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Tab Slides&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TA0rKrPX-UI/AAAAAAAABrA/EahLoHYeSx0/s1600/Picture+11.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="78" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TA0rKrPX-UI/AAAAAAAABrA/EahLoHYeSx0/s320/Picture+11.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Hammer-Ons&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;These actually make use of &lt;i&gt;annotation-contexts&lt;/i&gt;&amp;nbsp;which help determine the position of the annotation text above the stave.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TA0rK735DxI/AAAAAAAABrE/fZNJJzkeGG4/s1600/Picture+12.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/TA0rK735DxI/AAAAAAAABrE/fZNJJzkeGG4/s1600/Picture+12.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Random Annotation&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;I'm still a bit unsure of how to render ties across different note steps, with accidentals. What I have now looks like this:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TA0rLSCgA6I/AAAAAAAABrM/RCYxDMXahog/s1600/Picture+14.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/TA0rLSCgA6I/AAAAAAAABrM/RCYxDMXahog/s1600/Picture+14.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Chord Ties&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Although this is a really uncommon case, it would be nice if there were a generic way to deal with it. Thoughts? Ideas?&lt;br /&gt;&lt;br /&gt;That's all for now folks, and thanks very much for the comments. Keep 'em coming!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-5119301442673681732?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/5119301442673681732/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/06/on-beams.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/5119301442673681732'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/5119301442673681732'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/06/on-beams.html' title='On Beams'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_Zfnd5XZP80Y/TA0kz8dm8nI/AAAAAAAABqY/XccZP75uRxw/s72-c/Picture+1.png' height='72' width='72'/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7212499643209581753</id><published>2010-05-23T17:40:00.000-04:00</published><updated>2010-05-23T17:40:25.097-04:00</updated><title type='text'>The Thing about Accidentals...</title><content type='html'>...is that they can be a real pain.&lt;br /&gt;&lt;br /&gt;Last weekend, I mentioned that I threw away most of the layout and formatting code, which I started rewriting from scratch. Well, this weekend I threw away everything else, and started abstracting away many of the general properties that are shared between different types of notation.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYdEtH60I/AAAAAAAABpU/b4r5hUhSE00/s1600/Picture+10.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="77" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYdEtH60I/AAAAAAAABpU/b4r5hUhSE00/s320/Picture+10.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Testing Note-Head Displacement&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;One of the big design flaws in the previous version was the coupling of notes and tabs. I essentially had a single &lt;i&gt;Note&lt;/i&gt; class that encapsulated everything about a specific note, including the string/fret information, attached lyrics, attached accidentals, and other modifiers. This class had a big &lt;i&gt;render()&lt;/i&gt; method that drew everything (the tabs, the accidentals, etc.) in one go.&lt;br /&gt;&lt;br /&gt;This made the code hard to follow, hard to test, and very hard to add features to.&lt;br /&gt;&lt;br /&gt;In the new version, I have different classes for different types of staves, notes, accidentals, and other modifiers, which are managed by a hierarchy of contexts. This means that adding support for a new type of instrument, say a Didgeridoo, is a matter of creating a new &lt;i&gt;DidgeridooStave&lt;/i&gt; class to render its stave, and maybe a &lt;i&gt;DidgeridooNote&lt;/i&gt; class to render any Didgeridoo notational symbols.&lt;br /&gt;&lt;br /&gt;Yes, even the tests were rewritten. I wrote about 150 tests. All new, all fresh. It wasn't much fun, but I do have a significant level of confidence in the code.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_mYe4sRkRI/AAAAAAAABpY/ErvQqgTb6UE/s1600/Picture+11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_mYe4sRkRI/AAAAAAAABpY/ErvQqgTb6UE/s1600/Picture+11.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;So what did I gain from all of this? Just the following...&lt;br /&gt;&lt;br /&gt;Accidentals are tricky. In the image below, notice how the flat is nicely shifted to make room for the flat.&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S_mYLBXqobI/AAAAAAAABo4/lj6_dLxE-gc/s1600/Picture+3.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S_mYLBXqobI/AAAAAAAABo4/lj6_dLxE-gc/s1600/Picture+3.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Rendering Accidentals&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;And this is how it deals with a chord on steroids.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYMhHvD8I/AAAAAAAABo8/HjOQlUNlYA0/s1600/Picture+4.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYMhHvD8I/AAAAAAAABo8/HjOQlUNlYA0/s1600/Picture+4.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Accidentals Line Up Correctly&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;I tried hard to break it.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_mYOXhuEGI/AAAAAAAABpA/EPii2q_FcVw/s1600/Picture+5.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_mYOXhuEGI/AAAAAAAABpA/EPii2q_FcVw/s1600/Picture+5.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Whoa there, Fella!&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;It gets a bit confused when you bring in a second voice, but still quite respectable.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S_mYSKS1VQI/AAAAAAAABpE/mCHqz6J0w5s/s1600/Picture+6.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S_mYSKS1VQI/AAAAAAAABpE/mCHqz6J0w5s/s1600/Picture+6.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Accidentals with Multiple Voices&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The formatter doesn't just work with accidentals. It works with all kinds of modifiers, such as bends. Last week, I showed you how the modifier handled multiple bent notes on the same chord. Below, notice how full and half bends share the text annotation area. (This excited me a wee bit.)&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYU324Q8I/AAAAAAAABpI/yTqgnQZ6Wm8/s1600/Picture+7.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYU324Q8I/AAAAAAAABpI/yTqgnQZ6Wm8/s1600/Picture+7.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Bends with Text Annotations&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The new layout code allows for justification. Below, the top stave is rendered without justification, while the bottom two are justified to different widths.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYZB-TKMI/AAAAAAAABpM/neDmBCx210M/s1600/Picture+8.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="316" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYZB-TKMI/AAAAAAAABpM/neDmBCx210M/s320/Picture+8.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Layout Justification&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The justification works across staves too, so you can have multiple staves playing different parts, all lined up in the notation.&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S_mYa4khrxI/AAAAAAAABpQ/1EkGxikswtw/s1600/Picture+9.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="291" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S_mYa4khrxI/AAAAAAAABpQ/1EkGxikswtw/s320/Picture+9.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Justification across Staves&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;That's all I have to show for this week, folks! Since this is all new code, there are a few things that I lost support for, in particular: beams and ties. Hopefully it'll all be back next week.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-7212499643209581753?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/7212499643209581753/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/05/thing-about-accidentals.html#comment-form' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7212499643209581753'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7212499643209581753'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/05/thing-about-accidentals.html' title='The Thing about Accidentals...'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_Zfnd5XZP80Y/S_mYdEtH60I/AAAAAAAABpU/b4r5hUhSE00/s72-c/Picture+10.png' height='72' width='72'/><thr:total>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3851969526978923221</id><published>2010-05-16T21:53:00.000-04:00</published><updated>2010-05-16T21:53:33.436-04:00</updated><title type='text'>Cleaning up the Cruft</title><content type='html'>I spent most of this weekend cleaning up the &lt;a href="http://0xfe.blogspot.com/2010/05/music-notation-with-html5-canvas.html"&gt;HTML5 music notation&lt;/a&gt; code - tests, refactoring, style fixes, etc. While doing this, I threw away lots of cruft, and started rewriting almost all the layout and formatting code. After looking at and studying&amp;nbsp;&lt;i&gt;lots&lt;/i&gt;&amp;nbsp;of scores and tabs, it turned out that I really had to rethink the architecture of the layout management pieces, to allow for the correct formatting and placement of modifiers, accidentals, multi-voice note-heads etc.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S_CfDdgvWII/AAAAAAAABno/gXRJPqI-A7c/s1600/Picture+2.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S_CfDdgvWII/AAAAAAAABno/gXRJPqI-A7c/s1600/Picture+2.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Hammer-ons&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;I now have a much nicer design that should&amp;nbsp;accommodate&amp;nbsp;for these intricacies nicely, and am part way through implementing it. The design involves maintaining separate contexts for groups of &lt;i&gt;Tickables&lt;/i&gt;&amp;nbsp;(which is an abstraction for anything with a duration, such as notes, tabs, lyric-pieces, or chords) and&amp;nbsp;&lt;i&gt;Modifiers &lt;/i&gt;(an abstraction which includes accidentals, dots, bends, etc.) Higher-level formatters can be plugged in that work within these contexts to size, kern, and position the glyphs.&lt;br /&gt;&lt;br /&gt;On the testing front, I have about 75 tests in, which its still barely enough to test the different scenarios and edge cases that occur in musical notation. I tend to add the tests for different components as I have a clearer picture of what the API looks like, primarily to avoid having to incessantly rejiggle the test code. One thing's for sure - I love &lt;a href="http://docs.jquery.com/QUnit"&gt;QUnit&lt;/a&gt;, which is jQuery's JavaScript unit-testing suite.&amp;nbsp;It's functional, elegant, and extremely simple to use.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S_CY__KhceI/AAAAAAAABnU/G5pVUhoSG4g/s1600/Picture+1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S_CY__KhceI/AAAAAAAABnU/G5pVUhoSG4g/s1600/Picture+1.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;All you need to do is write your tests, and QUnit takes care of managing, running, and reporting.&lt;br /&gt;&lt;br /&gt;Anyhow, I do have a few things to show for all of this. First, is the support for note bends in tablature. Notice how the lower bend is pushed further to the right? This is because they're in the same &lt;i&gt;ModifierContext&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_CeDFk78vI/AAAAAAAABnY/CZM6J8itoOg/s1600/Picture+14.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_CeDFk78vI/AAAAAAAABnY/CZM6J8itoOg/s1600/Picture+14.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;String Bends&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;I also added slides, hammerons, and pulloffs.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_CeWXYSWzI/AAAAAAAABnc/HJDTOVxnVwo/s1600/Picture+15.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_CeWXYSWzI/AAAAAAAABnc/HJDTOVxnVwo/s1600/Picture+15.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Slides&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;I fixed the size of the accidentals. (I hope this is correct.)&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_CfO-sE6xI/AAAAAAAABns/Fi3VLWvK-t8/s1600/Picture+18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_CfO-sE6xI/AAAAAAAABns/Fi3VLWvK-t8/s1600/Picture+18.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;And finally, note-heads are correctly displaced for notes that are right next to each other.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_CfBV89xnI/AAAAAAAABnk/nPG61IXe5Nc/s1600/Picture%2019.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S_CfBV89xnI/AAAAAAAABnk/nPG61IXe5Nc/s1600/Picture%2019.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Displaced Note Heads&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;That's all for this week, folks!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-3851969526978923221?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/3851969526978923221/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/05/cleaning-up-cruft.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3851969526978923221'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3851969526978923221'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/05/cleaning-up-cruft.html' title='Cleaning up the Cruft'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_Zfnd5XZP80Y/S_CfDdgvWII/AAAAAAAABno/gXRJPqI-A7c/s72-c/Picture+2.png' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6971023733465831511</id><published>2010-05-12T08:13:00.000-04:00</published><updated>2010-05-12T08:13:02.117-04:00</updated><title type='text'>A Question of Formats</title><content type='html'>Firstly, a big &lt;b&gt;Thank You&lt;/b&gt; to everyone for the comments and advice to my &lt;a href="http://0xfe.blogspot.com/2010/05/music-notation-with-html5-canvas.html"&gt;previous post&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I went through almost all of the links that were posted, and glanced over the various options for languages, rendering, and formatting.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-qaYujnfqI/AAAAAAAABnM/hugqmniWaXA/s1600/Picture%2013.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-qaYujnfqI/AAAAAAAABnM/hugqmniWaXA/s1600/Picture%2013.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Question 1. Canvas or SVG?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This is actually a no-brainer. The library was designed such that alternate rendering backends could be easily plugged in. I went over some of the SVG specification documents, and it appears (at first glance) that I could add SVG support pretty trivially.&lt;br /&gt;&lt;br /&gt;So, the answer to this is: Yes, there will be SVG support.&lt;br /&gt;&lt;br /&gt;Now, that said, I had two primary goals for this project. One was the ability to render pleasing-to-the-eye musical scores, and the other was to be able to support interactive scores (to allow for building tools such as score editors.)&lt;br /&gt;&lt;br /&gt;These goals are potentially conflicting, because certain aspects of rendering perfect scores are actually computationally expensive, requiring multiple passes on the notation. This could affect the &lt;i&gt;snappy-factor&lt;/i&gt; of interactive scores. There's also the question of whether SVG is flexible enough for animations and interactive graphics.&lt;br /&gt;&lt;br /&gt;Anyhow, I'll dig into this some more and report my findings. Worst case, SVG will be used for rendering static scores, and Canvas for interactivity.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-qaW4QbrUI/AAAAAAAABnI/JHKWWkgdDLE/s1600/Picture+12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-qaW4QbrUI/AAAAAAAABnI/JHKWWkgdDLE/s1600/Picture+12.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Question 2. MusicXML or ABC Notation or JSON or Lilypond?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I eschew XML. It's clunky. It's difficult to parse. It's difficult to read. It makes my head hurt. So I'll let someone else build a &lt;a href="http://www.recordare.com/xml.html"&gt;MusicXML&lt;/a&gt; importer for the API. (Yes, I do realize that SVG is XML too, but generating XML is far simpler than parsing it.)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://abcnotation.com/"&gt;ABC Notation&lt;/a&gt;. Love it. It's going in. I'll have to figure out a way to lump in easy tablature support.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.json.org/"&gt;JSON&lt;/a&gt;. This is a good idea in theory, but getting the schema right is the tricky part. I'm going to hold off on this for now and defer to the API.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://lilypond.org/"&gt;Lilypond&lt;/a&gt;. Although the generated scores are beautiful, I'm not particularly a fan of the TeX-based language. Like I mentioned before, I'd like to be accessible to non-geeks.&lt;br /&gt;&lt;br /&gt;Nevertheless, I'm working on cleaning up the API such that it is friendly to compiler-writers, and interested parties can write their own front-ends.&amp;nbsp;A fringe benefit of a compiler-friendly API is the ability to run layout and positioning algorithms on top of generated scores to fine tune the presentation. Kind of like a &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;-O3&lt;/span&gt; for music notation.&lt;br /&gt;&lt;br /&gt;Note that these opinions aren't very strong (except for maybe the MusicXML one), and I'm open to other thoughts and suggestions you may have. So... suggest away.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-6971023733465831511?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/6971023733465831511/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/05/question-of-formats.html#comment-form' title='34 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6971023733465831511'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6971023733465831511'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/05/question-of-formats.html' title='A Question of Formats'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-qaYujnfqI/AAAAAAAABnM/hugqmniWaXA/s72-c/Picture%2013.png' height='72' width='72'/><thr:total>34</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-8979982059011824648</id><published>2010-05-11T14:16:00.001-04:00</published><updated>2010-05-11T14:17:29.605-04:00</updated><title type='text'>Music Notation with HTML5 Canvas</title><content type='html'>I spent a few weekends trying to scratch an itch. While looking for online tools to create and render music notation, I was mildly disappointed. The only real options were crummy flash based sites or clumsy server-side image renderers. Neither of these were particularly appealing.&lt;br /&gt;&lt;br /&gt;So I pulled up my sleeves, and cooked up a full-fledged JavaScript API to engrave musical notation directly in the browser with the HTML5 Canvas element.&lt;br /&gt;&lt;br /&gt;Here's a demo: &lt;a href="http://0xfe.muthanna.com/jsnotation/demo.html"&gt;HTML5 Music Notation Demo&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The library has no external dependencies, and all the glyphs, scores, beams, ties, etc. are positioned and rendered entirely in JavaScript.&lt;br /&gt;&lt;br /&gt;One of the cool things about this is that I can now build interactive music sheets that respond to user events like score editors.&lt;br /&gt;&lt;br /&gt;So here are all the features I have so far.&lt;br /&gt;&lt;br /&gt;1. Basic staff and chord support.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-mYERQiOWI/AAAAAAAABmw/WjzhoxbNDYg/s1600/Picture+6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-mYERQiOWI/AAAAAAAABmw/WjzhoxbNDYg/s1600/Picture+6.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;2. Accidentals and beams.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S-mYWV50dMI/AAAAAAAABm0/-4ufnagoMrc/s1600/Picture+7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="127" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S-mYWV50dMI/AAAAAAAABm0/-4ufnagoMrc/s320/Picture+7.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;3. Support for all note values with correct rhythmic positioning.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S-mYwvIwMzI/AAAAAAAABm4/51q4lmcE3U0/s1600/Picture+8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S-mYwvIwMzI/AAAAAAAABm4/51q4lmcE3U0/s1600/Picture+8.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;4. Support for rests, ties, and multiple voices. (Note the positioning of the notes.)&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S-mZK64HvkI/AAAAAAAABm8/_VHALY9_fTg/s1600/Picture+9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="104" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S-mZK64HvkI/AAAAAAAABm8/_VHALY9_fTg/s320/Picture+9.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;5. Support for guitar tablature.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S-mZWNjsdqI/AAAAAAAABnA/rcRiSg0XYjQ/s1600/Picture+10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S-mZWNjsdqI/AAAAAAAABnA/rcRiSg0XYjQ/s1600/Picture+10.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;6. Multiple voices in both staff and tab scores.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-mZrKYHn4I/AAAAAAAABnE/8j8RLQpAeKo/s1600/Picture+11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="183" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-mZrKYHn4I/AAAAAAAABnE/8j8RLQpAeKo/s320/Picture+11.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;7. Broken bars, odd-timings, custom glyphs, basic interactivity, and a full suite of unit tests (I love QUnit).&lt;br /&gt;&lt;br /&gt;I have a fair bit of work to do before I can make it available. In particular, I want to build a simple language that allows users to easily create scores. Something along the lines of the following:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;score {&lt;br /&gt;  title: Hip Tune&lt;br /&gt;  artist: Hip Person&lt;br /&gt;&lt;br /&gt;  bar { v8 C4 D4 E4 F4 (C4 E4 G4) } &lt;br /&gt;  bar { v8 C4 D4 E4 F4 (C4 E4 G4) } repeat 3&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above is just an idea - I haven't really given it much thought. But the intent is to make the language easy to read and accessible to non-geeks. I'm open to suggestions related to what the language should look like.&lt;br /&gt;&lt;br /&gt;Let me know what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-8979982059011824648?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/8979982059011824648/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/05/music-notation-with-html5-canvas.html#comment-form' title='158 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8979982059011824648'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8979982059011824648'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/05/music-notation-with-html5-canvas.html' title='Music Notation with HTML5 Canvas'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_Zfnd5XZP80Y/S-mYERQiOWI/AAAAAAAABmw/WjzhoxbNDYg/s72-c/Picture+6.png' height='72' width='72'/><thr:total>158</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1935812612428314325</id><published>2010-04-21T10:50:00.006-04:00</published><updated>2010-04-21T14:40:06.256-04:00</updated><title type='text'>Desktop Notifications with WebKit</title><content type='html'>Chrome now supports desktop notifications using WebKit's &lt;code&gt;webkitNotifications&lt;/code&gt; API. (Try the &lt;a href="http://0xfe.muthanna.com/notifyme.html"&gt;demo&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;Why is this awesome? Because it gives online tools like e-mail clients, calendering software, task managers, monitoring systems, etc. an elegant and consistent way to notify users &lt;i&gt;without having to resort to annoying and invasive hacks like &lt;code&gt;window.alert&lt;/code&gt;&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S88MibEHsXI/AAAAAAAABhs/qqbx__m6Fpk/s1600/Picture+4.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="249" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S88MibEHsXI/AAAAAAAABhs/qqbx__m6Fpk/s320/Picture+4.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Stacked Desktop Notifications&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Although the API is sparsely documented, it is simple and flexible and supports features such as domain based permissions, stacking of notification windows, embedded HTML, etc.&lt;br /&gt;&lt;br /&gt;To use desktop notifications in your applications, you need the following three API calls:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;window.webkitNotifications.requestPermission(callback)&lt;/code&gt; - Request access to Desktop Notifications for this domain.&lt;/li&gt;&lt;li&gt;&lt;code&gt;window.webkitNotifications.checkPermission()&lt;/code&gt; - Returns &lt;code&gt;0&lt;/code&gt; if this domain has Desktop Notification access.&lt;/li&gt;&lt;li&gt;&lt;code&gt;window.webkitNotifications.createNotification&lt;/code&gt; - Returns a popup notification instance, which you can display by calling &lt;code&gt;show()&lt;/code&gt; on it.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;A straightforward way to check for Desktop Notification support is by testing if &lt;code&gt;window.webkitNotifications&lt;/code&gt; is defined. Take a look at &lt;code&gt;Notifier.HasSupport()&lt;/code&gt; in the API wrapper below.&lt;br /&gt;&lt;br /&gt;When you request permission with &lt;code&gt;requestPermission(callback)&lt;/code&gt;, Chrome slides out a small permissions dialog immediately above the page. (&lt;i&gt;Click the image below to magnify.&lt;/i&gt;)&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S88VBKC6RJI/AAAAAAAABhw/hXENnYZ3yew/s1600/Picture+5.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="150" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S88VBKC6RJI/AAAAAAAABhw/hXENnYZ3yew/s400/Picture+5.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Permissions Dialog for Desktop Notifications&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;In the above image, licking &lt;i&gt;"Allow" &lt;/i&gt;tells Chrome that all pages hosted on &lt;i&gt;0xfe.muthanna.com &lt;/i&gt;can send notifications to the desktop. You can remove this access by clicking the &lt;i&gt;"Options"&lt;/i&gt;&amp;nbsp;link on any notification from a site.&lt;br /&gt;&lt;br /&gt;Here's a simple wrapper to the API (which is &lt;a href="http://github.com/0xfe/experiments/raw/master/www/notifyme.html"&gt;embedded&lt;/a&gt; in the &lt;a href="http://0xfe.muthanna.com/notifyme.html"&gt;demo&lt;/a&gt; below):  &lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;function Notifier() {}&lt;br /&gt;&lt;br /&gt;// Returns "true" if this browser supports notifications.&lt;br /&gt;Notifier.prototype.HasSupport = function() {&lt;br /&gt;  if (window.webkitNotifications) {&lt;br /&gt;    return true;&lt;br /&gt;  } else {&lt;br /&gt;    return false;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Request permission for this page to send notifications. If allowed,&lt;br /&gt;// calls function "cb" with "true" as the first argument.&lt;br /&gt;Notifier.prototype.RequestPermission = function(cb) {&lt;br /&gt;  window.webkitNotifications.requestPermission(function() {&lt;br /&gt;    if (cb) { cb(window.webkitNotifications.checkPermission() == 0); }&lt;br /&gt;  });&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Popup a notification with icon, title, and body. Returns false if&lt;br /&gt;// permission was not granted.&lt;br /&gt;Notifier.prototype.Notify = function(icon, title, body) {&lt;br /&gt;  if (window.webkitNotifications.checkPermission() == 0) {&lt;br /&gt;    var popup = window.webkitNotifications.createNotification(&lt;br /&gt;      icon, title, body);&lt;br /&gt;    popup.show();&lt;br /&gt;    return true;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  return false;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Take a look at the &lt;a href="http://0xfe.muthanna.com/notifyme.html"&gt;WebKit Desktop Notification Demo&lt;/a&gt; for a working demo. [ &lt;a href="http://github.com/0xfe/experiments/raw/master/www/notifyme.html"&gt;View Source&lt;/a&gt; ]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-1935812612428314325?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/1935812612428314325/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/04/desktop-notifications-with-webkit.html#comment-form' title='31 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1935812612428314325'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1935812612428314325'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/04/desktop-notifications-with-webkit.html' title='Desktop Notifications with WebKit'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_Zfnd5XZP80Y/S88MibEHsXI/AAAAAAAABhs/qqbx__m6Fpk/s72-c/Picture+4.png' height='72' width='72'/><thr:total>31</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3926924448865658645</id><published>2010-04-16T10:51:00.000-04:00</published><updated>2010-04-16T10:51:17.176-04:00</updated><title type='text'>My Coding Font</title><content type='html'>A few readers have asked me what coding font I use. It's &lt;a href="http://www.levien.com/type/myfonts/inconsolata.html"&gt;Inconsolata&lt;/a&gt; and it's wickedly awesome.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://www.levien.com/type/myfonts/incoshow.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="168" src="http://www.levien.com/type/myfonts/incoshow.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Inconsolata&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Mad props to &lt;a href="http://www.levien.com/"&gt;Raph Levien&lt;/a&gt; for this elegant, easy-on-the-eyes, monospaced, beauty.&lt;br /&gt;&lt;br /&gt;Inconsolata is distributed under the &lt;a href="http://en.wikipedia.org/wiki/SIL_Open_Font_License"&gt;Open Font License&lt;/a&gt;, and is available for download on Raph's &lt;a href="http://www.levien.com/type/myfonts/inconsolata.html"&gt;Inconsolata font&lt;/a&gt; page.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-3926924448865658645?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/3926924448865658645/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/04/my-coding-font.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3926924448865658645'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3926924448865658645'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/04/my-coding-font.html' title='My Coding Font'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-8760640420815732664</id><published>2010-04-15T00:31:00.002-04:00</published><updated>2010-04-15T13:49:34.263-04:00</updated><title type='text'>Improved Git-enabled Shell Prompt</title><content type='html'>In my &lt;a href="http://0xfe.blogspot.com/2010/04/adding-git-status-information-to-your.html"&gt;previous post&lt;/a&gt;, I wrote about how you could bolster your shell prompt with Git status information.&lt;br /&gt;&lt;br /&gt;Today, I added two more Git indicators that I find extremely useful while working on shared code.&lt;br /&gt;&lt;br /&gt;The first is &lt;i&gt;unmerged branches&lt;/i&gt;, which is the list of local branches that are not yet merged into the current branch.&lt;br /&gt;&lt;pre class="prettyprint lang-sh"&gt;# Returns "|unmerged:N" where N is the number of unmerged local and remote&lt;br /&gt;# branches (if any).&lt;br /&gt;function parse_git_unmerged {&lt;br /&gt;  local unmerged=`expr $(git branch --no-color -a --no-merged | wc -l)`&lt;br /&gt;  if [ "$unmerged" != "0" ]&lt;br /&gt;  then&lt;br /&gt;    echo "|unmerged:$unmerged"&lt;br /&gt;  fi&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;The second is &lt;i&gt;unpushed branches&lt;/i&gt;, which is the list of remote branches that are not synced to the local HEAD.&lt;br /&gt;&lt;pre class="prettyprint lang-sh"&gt;# Returns "|unpushed:N" where N is the number of unpushed local and remote&lt;br /&gt;# branches (if any).&lt;br /&gt;function parse_git_unpushed {&lt;br /&gt;  local unpushed=`expr $( (git branch --no-color -r --contains HEAD; \&lt;br /&gt;    git branch --no-color -r) | sort | uniq -u | wc -l )`&lt;br /&gt;  if [ "$unpushed" != "0" ]&lt;br /&gt;  then&lt;br /&gt;    echo "|unpushed:$unpushed"&lt;br /&gt;  fi&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;I now have the perfect git-enabled shell prompt!&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S8aWajTLCaI/AAAAAAAABhQ/XNzYHXu-Ilk/s1600/Picture+3.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="340" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S8aWajTLCaI/AAAAAAAABhQ/XNzYHXu-Ilk/s400/Picture+3.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Git-enabled Shell Prompt&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The code to the full prompt is available here: &lt;a href="http://github.com/0xfe/evil/raw/master/lib/prompt.sh"&gt;prompt.sh&lt;/a&gt; (also see &lt;a href="http://github.com/0xfe/evil/raw/master/lib/evilgit.sh"&gt;evilgit.sh&lt;/a&gt; for the git support functions). Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-8760640420815732664?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/8760640420815732664/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/04/improved-git-enabled-shell-prompt.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8760640420815732664'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8760640420815732664'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/04/improved-git-enabled-shell-prompt.html' title='Improved Git-enabled Shell Prompt'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_Zfnd5XZP80Y/S8aWajTLCaI/AAAAAAAABhQ/XNzYHXu-Ilk/s72-c/Picture+3.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7840520484653411320</id><published>2010-04-13T14:41:00.001-04:00</published><updated>2010-04-13T14:51:39.852-04:00</updated><title type='text'>Adding Git Status Information to your Terminal Prompt</title><content type='html'>If you're a heavy git user, you'll find this simple prompt customization quite useful. It adds the name of the current branch to the shell prompt along with an asterisk as a &lt;i&gt;dirty&lt;/i&gt; indicator. It also displays the number of states you have stashed away with &lt;i&gt;git stash&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;Start by defining the following functions and placing it into your shell profile (&lt;i&gt;e.g., .bashrc&lt;/i&gt;).&lt;br /&gt;&lt;pre class="prettyprint lang-sh"&gt;# Returns "*" if the current git branch is dirty.&lt;br /&gt;function parse_git_dirty {&lt;br /&gt;  [[ $(git diff --shortstat 2&gt; /dev/null | tail -n1) != "" ]] &amp;amp;&amp;amp; echo "*"&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;# Returns "|shashed:N" where N is the number of stashed states (if any).&lt;br /&gt;function parse_git_stash {&lt;br /&gt;  local stash=`expr $(git stash list 2&gt;/dev/null| wc -l)`&lt;br /&gt;  if [ "$stash" != "0" ]&lt;br /&gt;  then&lt;br /&gt;    echo "|stashed:$stash"&lt;br /&gt;  fi&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;# Get the current git branch name (if available)&lt;br /&gt;git_prompt() {&lt;br /&gt;  local ref=$(git symbolic-ref HEAD 2&gt;/dev/null | cut -d'/' -f3)&lt;br /&gt;  if [ "$ref" != "" ]&lt;br /&gt;  then&lt;br /&gt;    echo "($ref$(parse_git_dirty)$(parse_git_stash)) "&lt;br /&gt;  fi&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then customize your prompt by adding &lt;i&gt;$(git_prompt)&lt;/i&gt; to the &lt;i&gt;PS1&lt;/i&gt; environment variable, for example:&lt;br /&gt;&lt;pre class="prettyprint lang-sh"&gt;export PS1="\u@\h:\w $(git_prompt)$ "&lt;/pre&gt;&lt;br /&gt;Here's what my prompt looks like in action:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S8S8ZPTdVvI/AAAAAAAABg8/dfnIlEXmDJM/s1600/Picture+2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 241px;" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/S8S8ZPTdVvI/AAAAAAAABg8/dfnIlEXmDJM/s400/Picture+2.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5459695790070781682" /&gt;&lt;/a&gt;&lt;br /&gt;And, here is the code to my prompt. (It relies on the functions defined above.)&lt;br /&gt;&lt;pre class="prettyprint lang-sh"&gt;# A fancy colorful prompt&lt;br /&gt;function color_prompt&lt;br /&gt;{&lt;br /&gt;  local none="\[\033[0m\]"&lt;br /&gt;&lt;br /&gt;  local black="\[\033[0;30m\]"&lt;br /&gt;  local dark_gray="\[\033[1;30m\]"&lt;br /&gt;  local blue="\[\033[0;34m\]"&lt;br /&gt;  local light_blue="\[\033[1;34m\]"&lt;br /&gt;  local green="\[\033[0;32m\]"&lt;br /&gt;  local light_green="\[\033[1;32m\]"&lt;br /&gt;  local cyan="\[\033[0;36m\]"&lt;br /&gt;  local light_cyan="\[\033[1;36m\]"&lt;br /&gt;  local red="\[\033[0;31m\]"&lt;br /&gt;  local light_red="\[\033[1;31m\]"&lt;br /&gt;  local purple="\[\033[0;35m\]"&lt;br /&gt;  local light_purple="\[\033[1;35m\]"&lt;br /&gt;  local brown="\[\033[0;33m\]"&lt;br /&gt;  local yellow="\[\033[1;33m\]"&lt;br /&gt;  local light_gray="\[\033[0;37m\]"&lt;br /&gt;  local white="\[\033[1;37m\]"&lt;br /&gt;&lt;br /&gt;  local current_tty=`tty | sed -e "s/\/dev\/\(.*\)/\1/"`&lt;br /&gt;&lt;br /&gt;  local u_color=$purple&lt;br /&gt;  id -u &gt; /dev/null 2&gt;&amp;amp;1 &amp;amp;&amp;amp;           #Cross-platform hack.&lt;br /&gt;&lt;br /&gt;  if [ `id -u` -eq 0 ] ; then&lt;br /&gt;    local u_color=$yellow&lt;br /&gt;  fi&lt;br /&gt;&lt;br /&gt;  PS1="$light_blue&gt; $current_tty $u_color\u$brown@${purple}\&lt;br /&gt;    \h$brown:$light_blue\w\n$light_blue&gt; $light_red\$\&lt;br /&gt;    ? $cyan\$(git_prompt)$brown"'\$'"$none "&lt;br /&gt;&lt;br /&gt;  PS2="$dark_gray&gt;$none "&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's all folks! I'm interested in hearing about any useful shell prompt customizations that you have.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-7840520484653411320?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/7840520484653411320/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/04/adding-git-status-information-to-your.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7840520484653411320'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7840520484653411320'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/04/adding-git-status-information-to-your.html' title='Adding Git Status Information to your Terminal Prompt'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_Zfnd5XZP80Y/S8S8ZPTdVvI/AAAAAAAABg8/dfnIlEXmDJM/s72-c/Picture+2.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-2117448405019308120</id><published>2010-04-12T19:44:00.002-04:00</published><updated>2010-04-13T07:49:38.562-04:00</updated><title type='text'>My Vim Configuration</title><content type='html'>I've posted a number of articles on configuring Vim, and a few readers have asked me to post my complete &lt;i&gt;.vimrc&lt;/i&gt; file.&lt;br /&gt;&lt;br /&gt;My configuration includes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Incremental search&lt;/li&gt;&lt;li&gt;Status bar at the bottom of the screen&lt;/li&gt;&lt;li&gt;&lt;a href="http://0xfe.blogspot.com/2009/04/simple-code-folding-in-vim.html"&gt;Code folding based on indentation level&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Syntax highlighting&lt;/li&gt;&lt;li&gt;Spell checking&lt;/li&gt;&lt;li&gt;&lt;a href="http://0xfe.blogspot.com/2010/04/digraphs-in-vim.html"&gt;Digraphs for whitespace characters&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://0xfe.blogspot.com/2009/04/abbreviations-in-vim.html"&gt;Signature abbreviations&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Incremental search&lt;/li&gt;&lt;li&gt;&lt;a href="http://0xfe.blogspot.com/2010/04/macvim-is-plain-awesome.html"&gt;MacVim customizations&lt;/a&gt;&lt;/li&gt;&lt;li&gt;... and more.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;So, without further ado.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;" VIM Configuration - Mohit Muthanna&lt;br /&gt;&lt;br /&gt;" Enable syntax highlighting.&lt;br /&gt;syntax on&lt;br /&gt;&lt;br /&gt;" Do awesome stuff on MacVim&lt;br /&gt;if has("gui_macvim")&lt;br /&gt;" Disable toolbar on MacVim&lt;br /&gt;set go-=T&lt;br /&gt;&lt;br /&gt;" MacVim font and color scheme&lt;br /&gt;set guifont=Inconsolata:h13&lt;br /&gt;colorscheme torte&lt;br /&gt;endif&lt;br /&gt;&lt;br /&gt;" Enable spell checking. For source code, it only checks comments.&lt;br /&gt;" set spell spelllang=en_us&lt;br /&gt;&lt;br /&gt;" Set title on X window&lt;br /&gt;set title&lt;br /&gt;&lt;br /&gt;" Hide buffer instead of abandoning when unloading&lt;br /&gt;set hidden ruler wmnu&lt;br /&gt;&lt;br /&gt;" Set GUI font and options&lt;br /&gt;" set guifont=Monospace 8&lt;br /&gt;" set guioptions=agimrLt&lt;br /&gt;" set imi=0&lt;br /&gt;&lt;br /&gt;" I'm usually on a light-on-dark terminal&lt;br /&gt;set background=dark&lt;br /&gt;&lt;br /&gt;" Live incremental search&lt;br /&gt;set incsearch&lt;br /&gt;&lt;br /&gt;" Highlight current line (Vim 7)&lt;br /&gt;" set cursorline&lt;br /&gt;&lt;br /&gt;" Tabs and indentation. Yes, I like 2-space tabs.&lt;br /&gt;set tabstop=2&lt;br /&gt;set shiftwidth=2&lt;br /&gt;set expandtab&lt;br /&gt;set autoindent&lt;br /&gt;filetype indent on&lt;br /&gt;&lt;br /&gt;" Allow extended digraphs&lt;br /&gt;set encoding=utf-8&lt;br /&gt;&lt;br /&gt;" Show status line (help statusline)&lt;br /&gt;set statusline=%&lt;%2*%f%h%1*%m%2*%r%h%w%y\ %*%{&amp;ff}\ %=\ col:%c%V\ asc:%B\ pos:%o\ lin:%l\,%L\ %P&lt;br /&gt;set laststatus=2&lt;br /&gt;&lt;br /&gt;" Set status line colors&lt;br /&gt;highlight ModeMsg cterm=bold ctermfg=cyan ctermbg=black&lt;br /&gt;highlight StatusLine cterm=bold ctermfg=brown ctermbg=blue&lt;br /&gt;highlight StatusLineNC cterm=bold ctermfg=black ctermbg=green&lt;br /&gt;&lt;br /&gt;" Filename in white.&lt;br /&gt;highlight User2 cterm=bold ctermfg=gray ctermbg=blue&lt;br /&gt;&lt;br /&gt;" Modified flag on statusline.&lt;br /&gt;highlight User1 ctermfg=red ctermbg=blue&lt;br /&gt;&lt;br /&gt;" Highlight trailing whitespace and tab characters. Note that the foreground&lt;br /&gt;" colors are overridden here, so this only works with the "set list" settings&lt;br /&gt;" below.&lt;br /&gt;autocmd ColorScheme * highlight ExtraWhitespace ctermfg=red guifg=red&lt;br /&gt;highlight ExtraWhitespace ctermfg=red guifg=red cterm=bold gui=bold&lt;br /&gt;match ExtraWhitespace /\s\+$\|\t/&lt;br /&gt;&lt;br /&gt;" Show tabs and trailing spaces.&lt;br /&gt;" Ctrl-K &gt;&gt; for »&lt;br /&gt;" Ctrl-K .M for ·&lt;br /&gt;" Ctrk-K 0M for ●&lt;br /&gt;" Ctrk-K sB for ▪&lt;br /&gt;" (use :dig for list of digraphs)&lt;br /&gt;set list listchars=tab:»»,trail:·&lt;br /&gt;&lt;br /&gt;" I like to stick to lines under 80 columns&lt;br /&gt;au BufWinEnter * let w:m2=matchadd('ErrorMsg', '\%&gt;80v.\+', -1)&lt;br /&gt;&lt;br /&gt;" Enable folding by indentation&lt;br /&gt;" Use: zc, zo, zC, zO, zR, zM&lt;br /&gt;" Ctrl-K .3 for ⋯&lt;br /&gt;set foldmethod=indent&lt;br /&gt;highlight Folded ctermfg=red&lt;br /&gt;highlight FoldColumn ctermfg=white&lt;br /&gt;set fillchars=fold:⋯&lt;br /&gt;&lt;br /&gt;" My information&lt;br /&gt;iab xdate &lt;c-r&gt;=strftime("%Y/%m/%d %H:%M:%S")&lt;cr&gt;&lt;br /&gt;iab xname Mohit Muthanna Cheppudira&lt;br /&gt;iab xsigp Mohit Muthanna Cheppudira &lt;mohit@muthanna.com&gt;&lt;br /&gt;iab xsigw Mohit Muthanna Cheppudira &lt;mmuthanna@google.com&gt;&lt;br /&gt;&lt;br /&gt;" Strip trailing whitespace in whole file&lt;br /&gt;func! StripTrailingWS()&lt;br /&gt;%s/\s\+$//&lt;br /&gt;endfunc&lt;br /&gt;command! StripTrailingWS call StripTrailingWS()&lt;/pre&gt;&lt;br /&gt;This file is continually evolving, and you can always get the latest version here: &lt;a href="http://github.com/0xfe/evil/raw/master/dotfiles/vim_local/vimrc"&gt;.vimrc&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-2117448405019308120?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/2117448405019308120/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/04/my-vim-configuration.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/2117448405019308120'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/2117448405019308120'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/04/my-vim-configuration.html' title='My Vim Configuration'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-596207744346244392</id><published>2010-04-11T09:46:00.007-04:00</published><updated>2010-04-13T07:50:54.294-04:00</updated><title type='text'>Digraphs in Vim</title><content type='html'>Ever wondered how to use &lt;a href="http://en.wikipedia.org/wiki/Digraph_(computing)"&gt;digraphs&lt;/a&gt; to insert characters such as ⇐ or ⅛ into text files with Vim?&lt;br /&gt;&lt;br /&gt;Simple - use the key combination &lt;i&gt;Ctrl-K&lt;b&gt; &lt;/b&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;followed by the two-letter digraph code directly in &lt;/span&gt;insert&lt;span class="Apple-style-span" style="font-style: normal;"&gt; mode.&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;For example, &lt;/span&gt;Ctrl-K DE&lt;/i&gt; generates ∆ and &lt;i&gt;Ctrl-K 23&lt;/i&gt; generates ⅔.&lt;br /&gt;&lt;br /&gt;For a list of all the available digraphs and their key codes, use the command &lt;i&gt;":dig"&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S8HO7W65dtI/AAAAAAAABgU/ibopmtKqZAo/s1600/Picture%203.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="158" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/S8HO7W65dtI/AAAAAAAABgU/ibopmtKqZAo/s320/Picture%203.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sample Digraph Codes&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The ability to insert digraphs directly into the editor opens up a lot of possibilities for customization. For example, to make tabs and trailing whitespace characters visible in code, you can add something like this to your &lt;i&gt;.vimrc.&lt;/i&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;" Show tabs and trailing spaces.&lt;br /&gt;" Ctrl-K &amp;gt;&amp;gt; for »&lt;br /&gt;" Ctrl-K .M for ·&lt;br /&gt;" (use :dig for list of digraphs)&lt;br /&gt;set list listchars=tab:»»,trail:·&lt;/pre&gt;&lt;br /&gt;Here's what it looks like:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S8HQ5AMv9wI/AAAAAAAABgc/kaYOKvl6OIU/s1600/Picture+5.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/S8HQ5AMv9wI/AAAAAAAABgc/kaYOKvl6OIU/s1600/Picture+5.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Using Digraphs to see Whitespace Characters&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Digraphs are fun. Vim is fun. Digraphs + Vim = ∏☺♣☆は℞ψ. For more on digraphs in Vim, visit the &lt;a href="http://vimdoc.sourceforge.net/htmldoc/digraph.html"&gt;vim digraph page&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-596207744346244392?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/596207744346244392/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/04/digraphs-in-vim.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/596207744346244392'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/596207744346244392'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/04/digraphs-in-vim.html' title='Digraphs in Vim'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_Zfnd5XZP80Y/S8HO7W65dtI/AAAAAAAABgU/ibopmtKqZAo/s72-c/Picture%203.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3948078741969054124</id><published>2010-04-04T09:32:00.003-04:00</published><updated>2010-04-04T09:49:22.272-04:00</updated><title type='text'>MacVim is Plain Awesome</title><content type='html'>Every once in a while, dissatisfied with my current editor, I start scouring the web for an editor more Mac friendly. You know - an editor that respects conventional keyboard shortcuts, an editor that works predictably with the native copy-paste buffers, an editor that works with the mouse (even though I rarely use the mouse).&lt;br /&gt;&lt;br /&gt;I want an editor that just &lt;i&gt;feels right&lt;/i&gt; on the Mac.&lt;br /&gt;&lt;br /&gt;And usually, after exploring popular editors like TextMate, TextWrangler, and jEdit, for the umpteenth time my dissatisfaction inevitably leads me back to Vim.&lt;br /&gt;&lt;br /&gt;Not this time.&lt;br /&gt;&lt;br /&gt;It's been about three months since I've been using &lt;a href="http://code.google.com/p/macvim/"&gt;MacVim&lt;/a&gt;, and I have two words for it - Awesome. Major kudos go out to Bjorne Winckler and the MacVim team for &lt;i&gt;getting&lt;/i&gt; the Mac environment and releasing such a fantastic product.&lt;br /&gt;&lt;br /&gt;What's so great about MacVim? Oh, only the following:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;A full Vim implementation, that fully supports my &lt;i&gt;.vimrc&lt;/i&gt; (including all my crazy plugins.)&lt;/li&gt;&lt;li&gt;Native support for tabs with conventional key-bindings (&lt;i&gt;Cmd-T&lt;/i&gt;, &lt;i&gt;Cmd-W&lt;/i&gt;, etc.) for managing and navigating tabs.&lt;/li&gt;&lt;li&gt;Supports the native OS X paste-buffer. You can select with the mouse, and copy it into the native paste-buffer with &lt;i&gt;Cmd-C. &lt;/i&gt;You can also use &lt;i&gt;Cmd-V&lt;/i&gt; to paste into the editor without losing formatting and without requiring "&lt;span class="Apple-style-span"  style="font-family:'Courier New', Courier, monospace;"&gt;set paste&lt;/span&gt;".&lt;/li&gt;&lt;li&gt;"&lt;span class="Apple-style-span"  style="font-family:'Courier New', Courier, monospace;"&gt;mvim filename&lt;/span&gt;" on the commandline spawns a new MacVim session (within an existing process, if any) in background mode. This helps you save significantly on superfluous terminal or &lt;i&gt;screen&lt;/i&gt; sessions.&lt;/li&gt;&lt;li&gt;Supports the scroll-wheel and native pagination keys.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Shift-Cmd-F&lt;/i&gt; for fullscreen mode is the shyza.&lt;/li&gt;&lt;li&gt;The toolbar is simple and customizable, and the application menus simplify the process of spell-checking, code-folding, visual-diffing, etc.&lt;/li&gt;&lt;li&gt;The &lt;i&gt;Print &lt;/i&gt;menu-item (or pressing &lt;i&gt;Cmd-P&lt;/i&gt;) converts the current buffer to a &lt;i&gt;PDF&lt;/i&gt; document, and brings it up in &lt;i&gt;Preview&lt;/i&gt;.&lt;/li&gt;&lt;li&gt;It's pretty to look at and extremely usable.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Gratuitous screenshot:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S7iYtuzt93I/AAAAAAAABdE/fJ0KEl_3YLc/s1600/Picture+7.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 307px; height: 400px;" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S7iYtuzt93I/AAAAAAAABdE/fJ0KEl_3YLc/s400/Picture+7.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5456278859985844082" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;You can get &lt;a href="http://code.google.com/p/macvim/"&gt;MacVim&lt;/a&gt; at &lt;a href="http://code.google.com/p/macvim/"&gt;http://code.google.com/p/macvim&lt;/a&gt;. Install it, toy with it, and tell me what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-3948078741969054124?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/3948078741969054124/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/04/macvim-is-plain-awesome.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3948078741969054124'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3948078741969054124'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/04/macvim-is-plain-awesome.html' title='MacVim is Plain Awesome'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_Zfnd5XZP80Y/S7iYtuzt93I/AAAAAAAABdE/fJ0KEl_3YLc/s72-c/Picture+7.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-6831286558041466608</id><published>2010-02-28T13:08:00.004-05:00</published><updated>2010-02-28T13:30:14.774-05:00</updated><title type='text'>Meditation Timer</title><content type='html'>For a while now, I've been relying on audio files for use as meditation timers. These files essentially consist of a recorded bell in the beginning, followed by a duration of silence, and another bell at the end. There are many of these available on the web, and are not bad. The problem is that you're limited to the duration of the files available, i.e., if you want to meditate for 45 minutes, then you need a 45-minute long audio file.&lt;br /&gt;&lt;br /&gt;Mildly frustrated by this, I spent some time this morning writing an on-line meditation timer. Simply set the duration, and click &lt;span style="font-style:italic;"&gt;Start&lt;/span&gt;. You should hear a bell when the timer begins and ends.&lt;br /&gt;&lt;br /&gt;Here it is: &lt;a href="http://vexmeditation.appspot.com/"&gt;Online Meditation Timer&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Using Google App Engine and JQuery, it took me about 45 minutes to build this. That includes the time it took to prettify the site and type in this blog post. Gotta love simple, well-designed application frameworks!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-6831286558041466608?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/6831286558041466608/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2010/02/meditation-timer.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6831286558041466608'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/6831286558041466608'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2010/02/meditation-timer.html' title='Meditation Timer'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-427433043874529109</id><published>2009-12-26T16:56:00.009-05:00</published><updated>2010-04-13T07:48:31.114-04:00</updated><title type='text'>The Mandelbrot Set in JavaScript</title><content type='html'>JavaScript turns out to be a great language for toying with graphics and visualization. This morning, I spent a few hours learning about the Mandelbrot set and trying to code up a working implementation. The link below is one of the fruits of my labor:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://0xfe.muthanna.com/mandelbrot/mandelbrot.html"&gt;Mandelbrot Set Generator&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Mathematically, the Mandelbrot set is the set of points in the complex plane generated by iterating through the formula &lt;span style="font-style:italic;"&gt;z = z^2 + c&lt;/span&gt;. For different initial values of &lt;i&gt;c&lt;/i&gt;, the iteration can either produce a bounded sequence, or tend to infinity. Plotting the graph is a matter of placing a black dot for the values that generate a bounded sequence.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/SzaGy2sgnYI/AAAAAAAABK8/y7chmCuaxeg/s1600-h/Picture+1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 189px; height: 126px;" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/SzaGy2sgnYI/AAAAAAAABK8/y7chmCuaxeg/s400/Picture+1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5419667409820753282" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The next part of the problem is figuring out how to test for unbounded sequences. A good approximation can be reached by running through &lt;i&gt;n&lt;/i&gt; iterations and quitting if the magnitude of &lt;i&gt;z&lt;/i&gt; exceeds 2. The magnitude of a complex number with real part &lt;i&gt;a&lt;/i&gt; and imaginary part &lt;i&gt;b&lt;/i&gt; is &lt;i&gt;sqrt(a^2 + b^2)&lt;/i&gt;. For simple graphs, a reasonable value for &lt;i&gt;n&lt;/i&gt; is 100.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/SzaJSDFyp-I/AAAAAAAABLE/1RnEPMcU1eE/s1600-h/Picture+2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 164px; height: 101px;" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/SzaJSDFyp-I/AAAAAAAABLE/1RnEPMcU1eE/s400/Picture+2.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5419670144747218914" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Here's what a simple Mandelbrot set grapher in JavaScript looks like:&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;for (var x = 1; x &lt; this.width; x++) {&lt;br /&gt;    for (var y = 1; y &lt; this.height; y++) {&lt;br /&gt;      var count = 0;&lt;br /&gt;      var size = 0;&lt;br /&gt;      var cx = xcorner + ((x * zoom) / this.width);&lt;br /&gt;      var cy = ycorner + ((y * zoom) / this.height);&lt;br /&gt;&lt;br /&gt;      var zx = 0;&lt;br /&gt;      var zy = 0;&lt;br /&gt;&lt;br /&gt;      while (count &lt; 100 &amp;&amp; size &lt;= 4) {&lt;br /&gt;        count += 1;&lt;br /&gt;        temp = (zx * zx) - (zy * zy);&lt;br /&gt;        zy = (2 * zx * zy) + cy&lt;br /&gt;        zx = temp + cx;&lt;br /&gt;        size = (zx * zx) + (zy * zy);&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      this.Plot(x, y, count); // count serves as a good color hint.&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;By setting the &lt;i&gt;xcorner&lt;/i&gt;, &lt;i&gt;ycorner&lt;/i&gt;, and &lt;i&gt;zoom&lt;/i&gt; variables, the above code allows you to pan across and zoom into various points in the set. To accommodate for pretty visualizations, you can use &lt;i&gt;count&lt;/i&gt; to determine the color of the pixel.&lt;br /&gt;&lt;br /&gt;A barebones implementation is available here: &lt;a href="http://0xfe.muthanna.com/mandelbrot/mandelbrot.js"&gt;mandelbrot.js&lt;/a&gt;. Although it is not a very efficient implementation, it is very simple, and if you squint hard enough, you can see how the code relates to the visualization.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/SzaMYjsEDNI/AAAAAAAABLM/K23-eTNyB3Y/s1600-h/Picture+3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 195px; height: 99px;" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/SzaMYjsEDNI/AAAAAAAABLM/K23-eTNyB3Y/s400/Picture+3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5419673555111775442" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://0xfe.muthanna.com/mandelbrot/mandelbrot.html"&gt;Mandelbrot Set Generator&lt;/a&gt; is a harness I built with &lt;i&gt;mandelbrot.js&lt;/i&gt;. Play with the parameters and let me know if you find anything interesting. It also turns out to be a good browser speed test.&lt;br /&gt;&lt;br /&gt;To learn more about the Mandelbrot Set, see the &lt;a href="http://en.wikipedia.org/wiki/Mandelbrot_set"&gt;Wikipedia article on the Mandelbrot Set&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-427433043874529109?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/427433043874529109/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2009/12/mandelbrot-set-in-javascript.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/427433043874529109'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/427433043874529109'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2009/12/mandelbrot-set-in-javascript.html' title='The Mandelbrot Set in JavaScript'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_Zfnd5XZP80Y/SzaGy2sgnYI/AAAAAAAABK8/y7chmCuaxeg/s72-c/Picture+1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-5001699129714448502</id><published>2009-12-24T11:56:00.008-05:00</published><updated>2009-12-24T13:42:22.915-05:00</updated><title type='text'>Google Chrome Poster from Source Code</title><content type='html'>This holiday weekend, I decided to compose the Google Chrome logo using the browser's own source code. I used a combination of Ruby, ImageMagick, and a heavily hacked PDF library to generate this poster.&lt;br /&gt;&lt;br /&gt;This is the icon that I used:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/SzOqen857fI/AAAAAAAABKA/2EU3N31pjC0/s1600-h/chromelogo2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 194px; height: 173px;" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/SzOqen857fI/AAAAAAAABKA/2EU3N31pjC0/s400/chromelogo2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5418862219754073586" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Here's what it looks like zoomed in:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Zfnd5XZP80Y/SzOqjpPfr3I/AAAAAAAABKI/ajI-BWKr3Hk/s1600-h/chromelogo_middle.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 356px; height: 130px;" src="http://3.bp.blogspot.com/_Zfnd5XZP80Y/SzOqjpPfr3I/AAAAAAAABKI/ajI-BWKr3Hk/s400/chromelogo_middle.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5418862305999826802" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And zoomed in some more:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_Zfnd5XZP80Y/SzOrR0LwmDI/AAAAAAAABKY/VQFkbz88Mv0/s1600-h/chromelogo_large.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 131px;" src="http://1.bp.blogspot.com/_Zfnd5XZP80Y/SzOrR0LwmDI/AAAAAAAABKY/VQFkbz88Mv0/s400/chromelogo_large.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5418863099210930226" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://muthanna.com/downloads/ChromePoster.pdf"&gt;Download the PDF in all it's vectorized glory.&lt;/a&gt; (Approx 1.5MB. Please don't link directly to the poster.)&lt;br /&gt;&lt;br /&gt;Take it to your favourite print shop. Since it is based on vector graphics, it can be scaled up trivially without losing quality.&lt;br /&gt;&lt;br /&gt;Happy Holidays!&lt;br /&gt;&lt;br /&gt;P.S. If you ask me nicely, I might send you a 24-page letter-sized split of the poster so you can print it at home. (At nearly 50MB, it's too big for me to host here.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-5001699129714448502?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/5001699129714448502/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2009/12/google-chrome-poster-from-source-code.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/5001699129714448502'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/5001699129714448502'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2009/12/google-chrome-poster-from-source-code.html' title='Google Chrome Poster from Source Code'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_Zfnd5XZP80Y/SzOqen857fI/AAAAAAAABKA/2EU3N31pjC0/s72-c/chromelogo2.png' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7492440751485587470</id><published>2009-10-26T17:59:00.003-04:00</published><updated>2009-10-26T22:10:53.808-04:00</updated><title type='text'>Vex Crypto</title><content type='html'>So, I was looking a place to store some of my private data, such that I could access it conveniently when I needed to. By "conveniently", I mean over a web browser at a friend's place, or in an Internet cafe.&lt;br /&gt;&lt;br /&gt;It turns out that outside of setting up an encrypted filesystem on a remote server (or doing something similarly convoluted), there really aren't any other options.&lt;br /&gt;&lt;br /&gt;The type of information that I need to store is usually of the personal kind, e.g., passport numbers, emergency contacts, financial or health information, etc. Basically, I'd like to have easy access to any information that comes in handy during an emergency but can't be easily carried around.&lt;br /&gt;&lt;br /&gt;I also really wanted to do something cool with Google App Engine and jQuery.&lt;br /&gt;&lt;br /&gt;So, I wrote this: &lt;a href="http://vexcrypto.appspot.com"&gt;Vex Crypto&lt;/a&gt; (you need a Google account to log in.)&lt;br /&gt;&lt;br /&gt;Vex Crypto allows you to store snippets of encrypted text in the Google "cloud". It performs all the encryption and decryption in JavaScript (inside the browser), so that no plain-text information is stored on disk or transferred over the wire.&lt;br /&gt;&lt;br /&gt;This essentially means that even people who have access to the stored data will not be able to decode it without the "decryption password".&lt;br /&gt;&lt;br /&gt;Try it out and let me know what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-7492440751485587470?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/7492440751485587470/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2009/10/vex-crypto.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7492440751485587470'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7492440751485587470'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2009/10/vex-crypto.html' title='Vex Crypto'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-1092899964842019575</id><published>2009-09-25T19:46:00.005-04:00</published><updated>2010-04-13T07:51:46.733-04:00</updated><title type='text'>Everybody Loves Colors</title><content type='html'>Colors on terminal scripts are fun. They're also easy.&lt;br /&gt;&lt;pre class="prettyprint lang-python"&gt;class Colors:&lt;br /&gt;  ESC = "\033["&lt;br /&gt;  PURPLE = ESC + "95m"&lt;br /&gt;  GREEN = ESC + "92m" &lt;br /&gt;  YELLOW = ESC + "93m"&lt;br /&gt;  RESET = ESC + "0m"&lt;br /&gt;&lt;br /&gt;print Colors.YELLOW + "Warning: Colors enabled."&lt;br /&gt;print Colors.RESET&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-1092899964842019575?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/1092899964842019575/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2009/09/everybody-loves-colors.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1092899964842019575'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/1092899964842019575'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2009/09/everybody-loves-colors.html' title='Everybody Loves Colors'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3307744252905588746</id><published>2009-04-11T19:09:00.005-04:00</published><updated>2010-04-13T07:52:20.559-04:00</updated><title type='text'>Abbreviations in Vim</title><content type='html'>There are a few bits of text that are common to almost every file I edit with Vim. One of them is, of course, my name. The others are my e-mail address, and the date.&lt;br /&gt;&lt;br /&gt;Vim allows you to define simple text abbreviations to make entering such strings super-easy. Here's a snip of my &lt;span style="font-style: italic;"&gt;.vimrc&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;" Some handy abbreviations.&lt;br /&gt;"&lt;br /&gt;" Insert date.&lt;br /&gt;iab xdate &lt;c-r&gt;=strftime("%Y/%m/%d %H:%M:%S")&lt;cr&gt;&lt;br /&gt;&lt;br /&gt;" What's my name?&lt;br /&gt;iab xname Mohit Muthanna Cheppudira&lt;br /&gt;&lt;br /&gt;" My personal sig.&lt;br /&gt;iab xsigp Mohit Muthanna Cheppudira &lt;mohit@muthanna-hates-spam.com&gt;&lt;br /&gt;&lt;br /&gt;" My work sig.&lt;br /&gt;iab xsigw Mohit Muthanna Cheppudira &lt;mmuthanna@google-hates-spam.com&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With the above in my Vim configuration, the text &lt;span style="font-style:italic;"&gt;xdate&lt;/span&gt; gets auto-replaced to today's date.&lt;br /&gt;&lt;br /&gt;Slick.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-3307744252905588746?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/3307744252905588746/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2009/04/abbreviations-in-vim.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3307744252905588746'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3307744252905588746'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2009/04/abbreviations-in-vim.html' title='Abbreviations in Vim'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-7436745865499096847</id><published>2009-04-05T11:12:00.006-04:00</published><updated>2010-04-13T08:20:35.705-04:00</updated><title type='text'>Simple Code Folding in Vim</title><content type='html'>I've been on a Vim customization frenzy the last few days, and I just discovered an uber-cool feature: &lt;span style="font-style: italic;"&gt;code folding&lt;/span&gt;. Folding is the process of rolling up a block of lines in the buffer, to a single line.&lt;br /&gt;&lt;br /&gt;Here's what folded code looks like:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/SdjMUM0PySI/AAAAAAAAA2s/WkhzSi0G0Eo/s1600-h/Picture+2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 368px; height: 180px;" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/SdjMUM0PySI/AAAAAAAAA2s/WkhzSi0G0Eo/s400/Picture+2.png" alt="" id="BLOGGER_PHOTO_ID_5321227607148054818" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;So, to unfold a block, simply move the cursor to the block and hit "zO". (All the fold commands begin with "z"). Here's what an unfolded block looks like:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_Zfnd5XZP80Y/SdjR_YY8AyI/AAAAAAAAA28/ohXdGafZdgA/s1600-h/Picture+1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 329px;" src="http://4.bp.blogspot.com/_Zfnd5XZP80Y/SdjR_YY8AyI/AAAAAAAAA28/ohXdGafZdgA/s400/Picture+1.png" alt="" id="BLOGGER_PHOTO_ID_5321233846547252002" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;There are many ways to fold code, but the simplest way to enable it is by setting the &lt;span style="font-style: italic;"&gt;foldmethod&lt;/span&gt;  option to &lt;span style="font-style: italic;"&gt;indent&lt;/span&gt;. This folds code based on its indentation.&lt;br /&gt;&lt;br /&gt;If you're looking for syntax-aware folding, you can set &lt;span style="font-style: italic;"&gt;fold-method&lt;/span&gt; to &lt;span style="font-style: italic;"&gt;syntax&lt;/span&gt;. This generally takes a little more tweaking to get the right fold behaviour.&lt;br /&gt;&lt;br /&gt;Add the following to your &lt;span style="font-style: italic;"&gt;.vimrc&lt;/span&gt;, to get simple straightforward indentation-based code folding:&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;" Enable folding by indentation&lt;br /&gt;" Use: zc, zo, zC, zO, zR, zM&lt;br /&gt;" Ctrl-K .3 for ⋯&lt;br /&gt;set foldmethod=indent&lt;br /&gt;highlight Folded ctermfg=red&lt;br /&gt;highlight FoldColumn ctermfg=white&lt;br /&gt;set fillchars=fold:⋯&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For more information about code folding, and to get a list of all the commands, see &lt;a href="http://www.vim.org/htmldoc/usr_28.html"&gt;http://www.vim.org/htmldoc/usr_28.html&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-7436745865499096847?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/7436745865499096847/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2009/04/simple-code-folding-in-vim.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7436745865499096847'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/7436745865499096847'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2009/04/simple-code-folding-in-vim.html' title='Simple Code Folding in Vim'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_Zfnd5XZP80Y/SdjMUM0PySI/AAAAAAAAA2s/WkhzSi0G0Eo/s72-c/Picture+2.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3064617622275188290</id><published>2009-03-29T20:12:00.015-04:00</published><updated>2010-04-13T07:54:09.631-04:00</updated><title type='text'>Generating Circular Primes</title><content type='html'>I've been having way too much fun with &lt;a href="http://projecteuler.net"&gt;Project Euler&lt;/a&gt; (and Haskell) lately. Below is a listing of my solution to problem 35: "&lt;span style="font-style: italic;"&gt;How many circular primes are there below a million?"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It solves in about 2 seconds on my laptop (using -O2). This isn't great, but not bad given that the only optimizations in the code are: a memoized prime number generator, and the use of Data.Set for quick primality tests.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-js"&gt;-- Project Euler: Problem 35&lt;br /&gt;--&lt;br /&gt;-- The number, 197, is called a circular prime because all rotations of the&lt;br /&gt;-- digits: 197, 971, and 719, are themselves prime.&lt;br /&gt;--&lt;br /&gt;-- There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37,&lt;br /&gt;-- 71, 73, 79, and 97.&lt;br /&gt;--&lt;br /&gt;-- How many circular primes are there below one million?&lt;br /&gt;&lt;br /&gt;import Data.Set (Set)&lt;br /&gt;import qualified Data.Set as Set&lt;br /&gt;import System (getArgs)&lt;br /&gt;&lt;br /&gt;-- How many circular primes do you want?&lt;br /&gt;limit = 1000000&lt;br /&gt;&lt;br /&gt;-- Memoized prime number generator.&lt;br /&gt;primes :: [Integer]&lt;br /&gt;primes = 2 : 3 : 5 : filter (not . hasFactor) [7..]&lt;br /&gt;  where hasFactor n = any (divides n) $ takeWhile (&lt;= lowestFactor n) primes&lt;br /&gt;        divides n m = n `mod` m == 0&lt;br /&gt;        lowestFactor = ceiling . sqrt . fromIntegral&lt;br /&gt;&lt;br /&gt;-- Given an integer, return the number of digits in it.&lt;br /&gt;digits :: Integer -&gt; Int&lt;br /&gt;digits n = ceiling $ logBase 10 $ fromIntegral (n + 1)&lt;br /&gt;&lt;br /&gt;-- Rotate number right:&lt;br /&gt;--   rotate 456 = 645&lt;br /&gt;--   rotate 3498 = 8349&lt;br /&gt;--&lt;br /&gt;-- Note that this does not work for numbers that have zeroes in them. But&lt;br /&gt;-- that's okay because any number with a zero in it is not a circular prime.&lt;br /&gt;-- Also, right rotation catches the zero before it disappears into the &lt;br /&gt;-- significant digit.&lt;br /&gt;rotate :: Integer -&gt; Integer&lt;br /&gt;rotate n = strip_last_digit + (last_digit * tens_place)&lt;br /&gt;  where last_digit = n `mod` 10&lt;br /&gt;        strip_last_digit = (n - last_digit) `div` 10&lt;br /&gt;        tens_place = (10 ^ (digits n - 1))&lt;br /&gt;&lt;br /&gt;-- Set of million primes. This enables fast lookup in isCircularPrime.&lt;br /&gt;primeSet :: Set Integer&lt;br /&gt;primeSet = Set.fromList $ takeWhile (&lt; limit) primes&lt;br /&gt;&lt;br /&gt;-- Returns True if number is prime.&lt;br /&gt;isPrime :: Integer -&gt; Bool&lt;br /&gt;isPrime n = Set.member n primeSet&lt;br /&gt;&lt;br /&gt;-- Returns True if the number is a circular prime.&lt;br /&gt;isCircularPrime :: Integer -&gt; Bool&lt;br /&gt;isCircularPrime n = all isPrime $ take (digits n) $ iterate rotate n&lt;br /&gt;&lt;br /&gt;-- Go.&lt;br /&gt;main :: IO ()&lt;br /&gt;main = print $ length $ filter isCircularPrime $ takeWhile (&lt; limit) primes&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So... how can I optimize this some more?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-3064617622275188290?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/3064617622275188290/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2009/03/generating-circular-primes.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3064617622275188290'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3064617622275188290'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2009/03/generating-circular-primes.html' title='Generating Circular Primes'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-651197904320857211</id><published>2009-03-28T17:30:00.001-04:00</published><updated>2009-03-28T17:32:50.673-04:00</updated><title type='text'>Project Euler</title><content type='html'>I can't believe I never knew about this before. &lt;a href="http://projecteuler.net"&gt;Project Euler&lt;/a&gt; is very addictive. You've been warned.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-651197904320857211?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/651197904320857211/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2009/03/project-euler.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/651197904320857211'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/651197904320857211'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2009/03/project-euler.html' title='Project Euler'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-8820617461020386455</id><published>2008-10-17T13:30:00.002-04:00</published><updated>2008-10-17T13:44:49.940-04:00</updated><title type='text'>Tackling the Awkward Squad</title><content type='html'>It's been a long time since I've blogged a new post.&lt;br /&gt;&lt;br /&gt;Some of the time it's because I start a topic, and never finish it, leaving behind a mess of draft posts. But most of the time, it's because I have nothing interesting to say.&lt;br /&gt;&lt;br /&gt;Today, I have something interesting to say:&lt;br /&gt;&lt;br /&gt;Haskell is full of lazy-sugary-pure-functional goodness.&lt;br /&gt;&lt;br /&gt;There. I said it.&lt;br /&gt;&lt;br /&gt;If you have never used Haskell, now would be a great time to start. I'm not going to pimp it too much, because there's a huge community out there who are already doing that quite well.&lt;br /&gt;&lt;br /&gt;Haskell will bend your mind. Haskell will make you feel young again. Haskell will save the cheerleader.&lt;br /&gt;&lt;br /&gt;And if you&lt;span style="font-style: italic;"&gt; have &lt;/span&gt;already been learning Haskell, and plan to do serious work with it, you need to know how to tackle the awkward squad: monadic input/output, concurrency, exceptions, and foreign-language calls.&lt;br /&gt;&lt;br /&gt;I'll defer to Simon Peyton Jones from Microsoft Research, Cambridge in his awesomely awesome paper: &lt;a href="http://research.microsoft.com/Users/simonpj/papers/marktoberdorf/"&gt;Tackling the Awkward Squad&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-8820617461020386455?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/8820617461020386455/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2008/10/tackling-awkward-squad.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8820617461020386455'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/8820617461020386455'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2008/10/tackling-awkward-squad.html' title='Tackling the Awkward Squad'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-9076280720548554133</id><published>2007-02-10T14:50:00.008-05:00</published><updated>2010-04-13T08:23:40.591-04:00</updated><title type='text'>Scheduling on SMP Hardware</title><content type='html'>Ever wondered how the Linux scheduler schedules a job on another processor, after the idle task has halted the CPU?&lt;br /&gt;&lt;br /&gt;On x86 systems, the answer lies in InterProcessor Interrupts (IPI). An IPI is a type of custom interrupt that can be sent from one processor to another, using an &lt;a href="http://en.wikipedia.org/wiki/Intel_APIC_Architecture"&gt;Advanced Programmable Interrupt Controller&lt;/a&gt; (APIC).&lt;br /&gt;&lt;br /&gt;One of the customers of IPIs happens to be the OS task scheduler, where a processor can be woken up after a task is added to it's per-processor run queue.&lt;br /&gt;&lt;br /&gt;Let me add some context.&lt;br /&gt;&lt;br /&gt;On a uni-processor machine, the &lt;i&gt;idle&lt;/i&gt; task is scheduled when the OS has absolutely nothing to do. The main purpose of this task is to shut down the CPU by first making sure that interrupts are enabled, and then sending the &lt;i&gt;hlt&lt;/i&gt; instruction. This happens in the &lt;i&gt;default_idle()&lt;/i&gt; function within &lt;i&gt;arch/i386/process.c&lt;/i&gt;, via a call to &lt;i&gt;safe_halt()&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-cpp"&gt;&lt;br /&gt;100 void default_idle(void)&lt;br /&gt;101 {&lt;br /&gt;102         local_irq_enable();&lt;br /&gt;103&lt;br /&gt;104         if (!hlt_counter &amp;amp;&amp;amp; boot_cpu_data.hlt_works_ok) {&lt;br /&gt;105                 clear_thread_flag(TIF_POLLING_NRFLAG);&lt;br /&gt;106                 smp_mb__after_clear_bit();&lt;br /&gt;107                 while (!need_resched()) {&lt;br /&gt;108                         local_irq_disable();&lt;br /&gt;109                         if (!need_resched())&lt;br /&gt;110                                 safe_halt();&lt;br /&gt;111                         else&lt;br /&gt;112                                 local_irq_enable();&lt;br /&gt;113                 }&lt;br /&gt;114                 set_thread_flag(TIF_POLLING_NRFLAG);&lt;br /&gt;115         } else {&lt;br /&gt;116                 while (!need_resched())&lt;br /&gt;117                         cpu_relax();&lt;br /&gt;118         }&lt;br /&gt;119 }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The &lt;i&gt;safe_halt()&lt;/i&gt; function, is not really a function, but a preprocessor macro that calls the &lt;b&gt;sti&lt;/b&gt; and &lt;b&gt;hlt&lt;/b&gt; instructions, and is defined in &lt;i&gt;include/asm-i386/system.h&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-cpp"&gt;#define safe_halt()             __asm__ __volatile__("sti; hlt": : :"memory")&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Of course, since interrupts are enabled, the CPU is immediately woken up when an interrupt is received. This could be from any IO device, the timer, or even another processor. On an idle machine, the timer interrupt vector gates into the scheduler, which may examine it's per-processor run-queues, and try to find a task to execute. If nothing is found, the idle task gets rescheduled.&lt;br /&gt;&lt;br /&gt;This works well on uniprocessor systems, but on an SMP system, the timer interrupt only wakes up the primary CPU. So what happens when a new task is forked, and the scheduler decides that it needs to run on another CPU?&lt;br /&gt;&lt;br /&gt;The answer lies within the scheduler code (&lt;i&gt;kernel/sched.c&lt;/i&gt;), in the &lt;i&gt;resched_task()&lt;/i&gt; function.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-cpp"&gt;&lt;br /&gt;820 #ifdef CONFIG_SMP&lt;br /&gt;821 static void resched_task(task_t *p)&lt;br /&gt;822 {&lt;br /&gt;823         int cpu;&lt;br /&gt;824&lt;br /&gt;825         assert_spin_locked(&amp;amp;task_rq(p)-&gt;lock);&lt;br /&gt;826&lt;br /&gt;827         if (unlikely(test_tsk_thread_flag(p, TIF_NEED_RESCHED)))&lt;br /&gt;828                 return;&lt;br /&gt;829&lt;br /&gt;830         set_tsk_thread_flag(p, TIF_NEED_RESCHED);&lt;br /&gt;831&lt;br /&gt;832         cpu = task_cpu(p);&lt;br /&gt;833         if (cpu == smp_processor_id())&lt;br /&gt;834                 return;&lt;br /&gt;835&lt;br /&gt;836         /* NEED_RESCHED must be visible before we test POLLING_NRFLAG */&lt;br /&gt;837         smp_mb();&lt;br /&gt;838         if (!test_tsk_thread_flag(p, TIF_POLLING_NRFLAG))&lt;br /&gt;839                 smp_send_reschedule(cpu);&lt;br /&gt;840 }&lt;br /&gt;841 #else&lt;br /&gt;842 static inline void resched_task(task_t *p)&lt;br /&gt;843 {&lt;br /&gt;844         assert_spin_locked(&amp;amp;task_rq(p)-&gt;lock);&lt;br /&gt;845         set_tsk_need_resched(p);&lt;br /&gt;846 }&lt;br /&gt;847 #endif&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;As you can see there are two versions of this function, one for uniprocessor systems, and the other for SMP systems. On a uniprocessor, the task is rescheduled by setting the &lt;i&gt;TIF_NEED_RESCHED&lt;/i&gt; flag, via a call to &lt;i&gt;set_tsk_need_resched()&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;On a multiprocessor system, the same flag is set, and a check is made to see if the task should be running on a different processor from the current one. If not, it just returns, and lets the scheduler take care of rescheduling the task. Otherwise, it wakes up the target processor by calling the &lt;i&gt;smp_send_reschedule()&lt;/i&gt; function.&lt;br /&gt;&lt;br /&gt;The &lt;i&gt;smp_send_reschedule()&lt;/i&gt; function (defined in &lt;i&gt;arch/i386/kernel/smp.c&lt;/i&gt;) simply sends an IPI to the specified processor, by calling &lt;i&gt;send_IPI_mask()&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-cpp"&gt;&lt;br /&gt;472 /*&lt;br /&gt;473  * This function sends a 'reschedule' IPI to another CPU.&lt;br /&gt;474  * it goes straight through and wastes no time serializing&lt;br /&gt;475  * anything. Worst case is that we lose a reschedule ...&lt;br /&gt;476  */&lt;br /&gt;477 void smp_send_reschedule(int cpu)&lt;br /&gt;478 {&lt;br /&gt;479         WARN_ON(cpu_is_offline(cpu));&lt;br /&gt;480         send_IPI_mask(cpumask_of_cpu(cpu), RESCHEDULE_VECTOR);&lt;br /&gt;481 }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The &lt;i&gt;send_IPI_mask()&lt;/i&gt; function uses the APIC to fire an interrupt off to the target processor. It calls &lt;i&gt;send_IPI_mask_bitmask()&lt;/i&gt;, which in-turn programs the APIC using the functions in &lt;i&gt;include/asm-i386/apic.h&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-cpp"&gt;&lt;br /&gt;160 void send_IPI_mask_bitmask(cpumask_t cpumask, int vector)&lt;br /&gt;161 {&lt;br /&gt;162         unsigned long mask = cpus_addr(cpumask)[0];&lt;br /&gt;163         unsigned long cfg;&lt;br /&gt;164         unsigned long flags;&lt;br /&gt;165&lt;br /&gt;166         local_irq_save(flags);&lt;br /&gt;167         WARN_ON(mask &amp;amp; ~cpus_addr(cpu_online_map)[0]);&lt;br /&gt;168         /*&lt;br /&gt;169          * Wait for idle.&lt;br /&gt;170          */&lt;br /&gt;171         apic_wait_icr_idle();&lt;br /&gt;172                &lt;br /&gt;173         /*&lt;br /&gt;174          * prepare target chip field&lt;br /&gt;175          */&lt;br /&gt;176         cfg = __prepare_ICR2(mask);&lt;br /&gt;177         apic_write_around(APIC_ICR2, cfg);&lt;br /&gt;178                &lt;br /&gt;179         /*&lt;br /&gt;180          * program the ICR&lt;br /&gt;181          */&lt;br /&gt;182         cfg = __prepare_ICR(0, vector);&lt;br /&gt;183                        &lt;br /&gt;184         /*&lt;br /&gt;185          * Send the IPI. The write to APIC_ICR fires this off.&lt;br /&gt;186          */&lt;br /&gt;187         apic_write_around(APIC_ICR, cfg);&lt;br /&gt;188&lt;br /&gt;189         local_irq_restore(flags);&lt;br /&gt;190 }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The specific IPI-gate is defined as &lt;i&gt;RESCHEDULE_VECTOR&lt;/i&gt;, and is installed by the &lt;i&gt;smp_intr_init()&lt;/i&gt; function in &lt;i&gt;arch/i386/kernel/smpboot.c&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-cpp"&gt;&lt;br /&gt;1454 void __init smp_intr_init(void)&lt;br /&gt;1455 {&lt;br /&gt;1456         /*&lt;br /&gt;1457          * IRQ0 must be given a fixed assignment and initialized,&lt;br /&gt;1458          * because it's used before the IO-APIC is set up.&lt;br /&gt;1459          */&lt;br /&gt;1460         set_intr_gate(FIRST_DEVICE_VECTOR, interrupt[0]);&lt;br /&gt;1461&lt;br /&gt;1462         /*&lt;br /&gt;1463          * The reschedule interrupt is a CPU-to-CPU reschedule-helper&lt;br /&gt;1464          * IPI, driven by wakeup.&lt;br /&gt;1465          */&lt;br /&gt;1466         set_intr_gate(RESCHEDULE_VECTOR, reschedule_interrupt);&lt;br /&gt;1467&lt;br /&gt;1468         /* IPI for invalidation */&lt;br /&gt;1469         set_intr_gate(INVALIDATE_TLB_VECTOR, invalidate_interrupt);&lt;br /&gt;1470&lt;br /&gt;1471         /* IPI for generic function call */&lt;br /&gt;1472         set_intr_gate(CALL_FUNCTION_VECTOR, call_function_interrupt);&lt;br /&gt;1473 }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;These gates are installed at boot time, and are responsible for handling the various interrupts on multiprocessor systems. An APIC is required on all SMP systems for various reasons, and scheduling is one of them.&lt;br /&gt;&lt;br /&gt;There is one small detail that I left out. You may have wondered about the &lt;i&gt;TIF_POLLING_NRFLAG&lt;/i&gt; scattered about in the code. If you provide &lt;b&gt;"idle=poll"&lt;/b&gt; as a kernel parameter at boot time, the idle function points to &lt;i&gt;poll_idle()&lt;/i&gt;, instead of &lt;i&gt;default_idle()&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;The &lt;i&gt;poll_idle()&lt;/i&gt; function does not halt the processor, but spins around checking to see if a task is ready to run. As you may have guessed, although performance may improve, it's not very power-friendly. You certainly don't want this on your laptop.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-cpp"&gt;&lt;br /&gt;124 /*&lt;br /&gt;125  * On SMP it's slightly faster (but much more power-consuming!)&lt;br /&gt;126  * to poll the -&gt;work.need_resched flag instead of waiting for the&lt;br /&gt;127  * cross-CPU IPI to arrive. Use this option with caution.&lt;br /&gt;128  */&lt;br /&gt;129 static void poll_idle (void)&lt;br /&gt;130 {&lt;br /&gt;131         local_irq_enable();&lt;br /&gt;132&lt;br /&gt;133         asm volatile(&lt;br /&gt;134                 "2:"&lt;br /&gt;135                 "testl %0, %1;"&lt;br /&gt;136                 "rep; nop;"&lt;br /&gt;137                 "je 2b;"&lt;br /&gt;138                 : : "i"(_TIF_NEED_RESCHED), "m" (current_thread_info()-&gt;flags));&lt;br /&gt;139 }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you go back and read the &lt;i&gt;resched_task()&lt;/i&gt; function, you will notice that the IPI is only sent if &lt;i&gt;TIF_POLLING_NRFLAG&lt;/i&gt; is not set. Since the CPU is already running, it is not necessary to interrupt it.&lt;br /&gt;&lt;br /&gt;A final word about IPIs. Waking up a halted processor is not all that it is used for. It is also used in the memory management code, to maintain cache coherency, in certian synchronization code, and many other places in the scheduler.&lt;br /&gt;&lt;br /&gt;If you found this interesting and want to learn more, listed below are a few places you can go digging around. Happy hacking!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Note&lt;/b&gt;: All file paths are relative to the base of the Linux kernel source tree. The code snippets in this article are from version 2.6.20 of the kernel.&lt;br /&gt;&lt;br /&gt;kernel/sched.c&lt;br /&gt;kernel/cpu.c&lt;br /&gt;arch/i386/kernel/smp.c&lt;br /&gt;arch/i386/kernel/smpboot.c&lt;br /&gt;arch/i386/kernel/process.c&lt;br /&gt;arch/i386/kernel/head.S&lt;br /&gt;include/asm-i386/apic.h&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Intel_APIC_Architecture"&gt;Intel APIC Architecture&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-9076280720548554133?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/9076280720548554133/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2007/02/scheduling-on-smp-hardware.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/9076280720548554133'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/9076280720548554133'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2007/02/scheduling-on-smp-hardware.html' title='Scheduling on SMP Hardware'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-3915277811568566919</id><published>2007-02-04T15:43:00.000-05:00</published><updated>2007-02-04T16:48:29.085-05:00</updated><title type='text'>The Game of Life in JavaScript</title><content type='html'>I just discovered the &lt;span style="font-weight: bold;"&gt;canvas&lt;/span&gt; tag. &lt;span style="font-weight: bold;"&gt;&amp;lt;canvas&lt;span style="font-weight: bold;"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt; is a HTML element used for embedding vector graphics within your web pages. It was first introduced by  Apple for Dashboard Widgets, and was later implemented in Safari.&lt;br /&gt;&lt;br /&gt;It is now supported by most major browsers, except Internet Explorer. Fortunately, Google has a compatibility layer called &lt;a href="http://code.google.com/p/explorercanvas/"&gt;ExplorerCanvas&lt;/a&gt; that brings the &lt;span style="font-weight: bold;"&gt;canvas&lt;/span&gt; tag to IE.&lt;br /&gt;&lt;br /&gt;To learn more about &lt;span style="font-weight: bold;"&gt;&amp;lt;canvas&amp;gt;&lt;/span&gt;, Mozilla has a &lt;a href="http://developer.mozilla.org/en/docs/Canvas_tutorial"&gt;very good tutorial&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Anyhow, for a quick demo of what it can do, check out my interpretation of John Conway's Game of Life, in JavaScript, aptly named &lt;a href="http://muthanna.com/gol/gol.html"&gt;Game of Death&lt;/a&gt;. I coded it on a Saturday afternoon, slightly hungover, to teach myself JavaScript and familiarize myself with &lt;span style="font-weight: bold;"&gt;&amp;lt;canvas&amp;gt;&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Here's the &lt;a href="http://muthanna.com/gol/js/GoL.js"&gt;source code&lt;/a&gt;. &lt;span style="font-weight: bold;"&gt;Warning&lt;/span&gt;: No fancy algorithms within.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-3915277811568566919?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/3915277811568566919/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2007/02/game-of-life-in-javascript.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3915277811568566919'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/3915277811568566919'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2007/02/game-of-life-in-javascript.html' title='The Game of Life in JavaScript'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-115828497708417482</id><published>2006-09-14T20:47:00.000-04:00</published><updated>2006-11-15T02:30:26.153-05:00</updated><title type='text'>Why I Like Working for Google</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/blogger/4924/1936/1600/card.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://photos1.blogger.com/blogger/4924/1936/400/card.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I don't really care for the free food, the toys, the microkitchens, or the ridculously-massively-distributed infrastructure. It's not the pay, the on-site massages, the flexible work hours, or the free books. Guido van Rossum, Vint Cerf and Rob Pike are great, but they're no Angelina Jolie. &lt;br /&gt;&lt;br /&gt;It's the business cards... they really are quite cool.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-115828497708417482?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/115828497708417482/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/09/why-i-like-working-for-google.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/115828497708417482'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/115828497708417482'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/09/why-i-like-working-for-google.html' title='Why I Like Working for Google'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114355962301559944</id><published>2006-03-28T09:17:00.001-05:00</published><updated>2010-04-13T08:16:24.705-04:00</updated><title type='text'>Q&amp;A: How OS X Executes Applications</title><content type='html'>After writing my previous article, &lt;a href="http://0xfe.blogspot.com/2006/03/how-os-x-executes-applications.html"&gt;How OS X Executes Applications&lt;/a&gt;, I received quite a few comments and e-mails with some good questions. I will attempt to answer some of them here, and continue to update this entry as questions arise.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Question 1. What is &lt;span style="font-style:italic;"&gt;libSystem.B.dylib&lt;/span&gt;?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~/Temp mohit$ otool -L /bin/ls&lt;br /&gt;/bin/ls:&lt;br /&gt;        /usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)&lt;br /&gt;        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.0.0)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The System library, found in &lt;span style="font-style:italic;"&gt;/usr/lib/libSystem.dylib&lt;/span&gt;, is simply a collection of core libraries that are used by most Darwin applications. A few libraries worth mentioning that are in &lt;span style="font-style:italic;"&gt;libSystem.dylib&lt;/span&gt; are: &lt;ul&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;libc&lt;/span&gt; : The standard C library.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;libdl&lt;/span&gt; : The dynamic loader library.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;libm&lt;/span&gt; : The math library.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;libpthread&lt;/span&gt; : The POSIX threads library.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;libinfo&lt;/span&gt; : The NetInfo library.&lt;/li&gt;&lt;/ul&gt;To get a complete list of modules and symbols within the library, use the &lt;span style="font-style:italic;"&gt;-Tv&lt;/span&gt; switch of the &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; command.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Question 2. Is there an &lt;span style="font-style:italic;"&gt;objdump&lt;/span&gt; for OS X?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Yes there is, and it supports Mach-O binaries. It's just not distributed with Darwin / OS X. &lt;a href="http://www.math.utah.edu/~beebe/unix/o/objdump.html"&gt;This link&lt;/a&gt; on &lt;a href="http://www.math.utah.edu/~beebe/unix/everywhere-commands.html"&gt;this site&lt;/a&gt;, tells you what systems &lt;span style="font-style:italic;"&gt;objdump&lt;/span&gt; is distributed with.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Question 3. Are executable code and readonly data in the same _TEXT segment? If so, how can they mark part of it executable and part not executable (normal security practice nowadays)?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I actually updated the article with the answer to this, but its a good question, and I'll answer it here again.&lt;br /&gt;&lt;br /&gt;Segments may be sub-divided into sections. Within the &lt;span style="font-style:italic;"&gt;__TEXT&lt;/span&gt; segment, only certain sections, e.g., &lt;span style="font-style:italic;"&gt;__text&lt;/span&gt;, or &lt;span style="font-style:italic;"&gt;__picsymbol_stub&lt;/span&gt;, can contain executable code.&lt;br /&gt;&lt;br /&gt;To determine which sections contain executable code, use the &lt;span style="font-style:italic;"&gt;-lv&lt;/span&gt; parameter with &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt;, and look at the attribute named &lt;span style="font-style:italic;"&gt;attributes&lt;/span&gt;.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~/Temp mohit$ otool -lv /bin/ls | egrep '(sectname|attributes)'&lt;br /&gt;  sectname __text&lt;br /&gt;attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS&lt;br /&gt;  sectname __picsymbol_stub&lt;br /&gt;attributes PURE_INSTRUCTIONS&lt;br /&gt;  sectname __symbol_stub&lt;br /&gt;attributes PURE_INSTRUCTIONS&lt;br /&gt;  sectname __picsymbolstub1&lt;br /&gt;attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS&lt;br /&gt;  sectname __cstring&lt;br /&gt;attributes (none)&lt;br /&gt;  sectname __symbol_stub1&lt;br /&gt;attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS&lt;br /&gt;  sectname __literal8&lt;br /&gt;attributes (none)&lt;br /&gt;  sectname __eh_frame&lt;br /&gt;attributes NO_TOC STRIP_STATIC_SYMS LIVE_SUPPORT&lt;br /&gt;  sectname __data&lt;br /&gt;attributes (none)&lt;br /&gt;  sectname __nl_symbol_ptr&lt;br /&gt;attributes (none)&lt;br /&gt;  sectname __la_symbol_ptr&lt;br /&gt;attributes (none)&lt;br /&gt;  sectname __dyld&lt;br /&gt;attributes (none)&lt;br /&gt;  sectname __common&lt;br /&gt;attributes (none)&lt;br /&gt;  sectname __bss&lt;br /&gt;attributes (none)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The sections with &lt;span style="font-style:italic;"&gt;attributes&lt;/span&gt; set to &lt;span style="font-style:italic;"&gt;PURE_INSTRUCTIONS&lt;/span&gt; contain executable code.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Question 4. How do I dechipher the constants in the &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; output?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There are two ways to do this: One way is to examine the header files in &lt;span style="font-style:italic;"&gt;/usr/include/mach&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;/usr/include/mach-o&lt;/span&gt;; and the other, simpler, way is to just add &lt;span style="font-style:italic;"&gt;-v&lt;/span&gt; to your &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; commands.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~/Temp mohit$ otool -vh /bin/ls      &lt;br /&gt;/bin/ls:&lt;br /&gt;Mach header&lt;br /&gt;      magic cputype cpusubtype   filetype ncmds sizeofcmds      flags&lt;br /&gt;   MH_MAGIC     PPC        ALL    EXECUTE    11       1608   NOUNDEFS DYLDLINK TWOLEVEL&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Question 5. What are Two-Level Namespaces?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It is a feature included since OS X 10.1, that prevents collisions with symbol names   in dynamic libraries. It works by associating library names with symbol names at compile time.&lt;br /&gt;&lt;br /&gt;Suppose you have an application that is linked against &lt;span style="font-style:italic;"&gt;libfirst&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;libsecond&lt;/span&gt;. &lt;span style="font-style:italic;"&gt;libfirst&lt;/span&gt; exports a function called &lt;span style="font-style:italic;"&gt;dothis()&lt;/span&gt;. At a later time, a new version of &lt;span style="font-style:italic;"&gt;libsecond&lt;/span&gt; comes out with its own &lt;span style="font-style:italic;"&gt;dothis()&lt;/span&gt; function. Now, the application may execute whichever &lt;span style="font-style:italic;"&gt;dothis()&lt;/span&gt; function it loads first, which may not be the one that was intended.&lt;br /&gt;&lt;br /&gt;With two-level namespaces (enabled by default), the linker associates &lt;span style="font-style:italic;"&gt;dothis()&lt;/span&gt; with &lt;span style="font-style:italic;"&gt;libfirst&lt;/span&gt; at compile time. This prevents the chances of symbol collisions in future versions of linked libraries.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Question 6. Is Steve Jobs going to have you executed for reverse engineering this information?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Yes he is.&lt;br /&gt;&lt;br /&gt;Seriously though, all this information is public knowledge. I did not "reverse engineeer" anything. All I did was put together the most relevant parts of the documents mentioned at the end of the article. And I would suggest reading them for a deeper understanding of the OS X runtime environments.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114355962301559944?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114355962301559944/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/03/qa-how-os-x-executes-applications.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114355962301559944'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114355962301559944'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/03/qa-how-os-x-executes-applications.html' title='Q&amp;A: How OS X Executes Applications'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114338745476477157</id><published>2006-03-26T10:16:00.004-05:00</published><updated>2010-04-13T08:15:04.822-04:00</updated><title type='text'>How OS X Executes Applications</title><content type='html'>Being a long-time UNIX user, I generally have a common set of tools that I work with while trying to troubleshoot system problems. More recently, I have been developing software that adds Apple's OS X to the list of supported operating systems; and unlike traditional UNIX variants, OS X does not support many of the tools that relate to loading, linking and executing programs.&lt;br /&gt;&lt;br /&gt;For example, when I come across library relocation problems, the first thing I do is run &lt;span style="font-style:italic;"&gt;ldd&lt;/span&gt; on the executable. The &lt;span style="font-style:italic;"&gt;ldd&lt;/span&gt; tool lists the dependent shared libraries that the executable requires, along with their paths if found.&lt;br /&gt;&lt;br /&gt;On OS X though, here's what happens when you try to run &lt;span style="font-style:italic;"&gt;ldd&lt;/span&gt;.&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-js"&gt;evil:~ mohit$ ldd /bin/ls&lt;br /&gt;-bash: ldd: command not found&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Not Found? But it's on all the common UNIX flavours. I wonder if &lt;span style="font-style:italic;"&gt;objdump&lt;/span&gt; works.&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-js"&gt;$ objdump -x /bin/ls&lt;br /&gt;-bash: objdump: command not found&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Command not found. What's going on?&lt;br /&gt;&lt;br /&gt;The problem is that unlike Linux, Solaris, HP-UX, and many other UNIX variants, OS X does not use ELF binaries. In addition, OS X is not part of the GNU project, which is home to tools like &lt;span style="font-style:italic;"&gt;ldd&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;objdump&lt;/span&gt;. &lt;br /&gt;&lt;br /&gt;In order to get a list of dependencies for an executable on OS X, you need to use &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt;.&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-js"&gt;evil:~ mohit$ otool /bin/ls&lt;br /&gt;otool: one of -fahlLtdoOrTMRIHScis must be specified&lt;br /&gt;Usage: otool [-fahlLDtdorSTMRIHvVcXm] object_file ...&lt;br /&gt;        -f print the fat headers&lt;br /&gt;        -a print the archive header&lt;br /&gt;        -h print the mach header&lt;br /&gt;        -l print the load commands&lt;br /&gt;        -L print shared libraries used&lt;br /&gt;        -D print shared library id name&lt;br /&gt;        -t print the text section (disassemble with -v)&lt;br /&gt;        -p &lt;routine name&gt;  start dissassemble from routine name&lt;br /&gt;        -s &lt;segname&gt; &lt;sectname&gt; print contents of section&lt;br /&gt;        -d print the data section&lt;br /&gt;        -o print the Objective-C segment&lt;br /&gt;        -r print the relocation entries&lt;br /&gt;        -S print the table of contents of a library&lt;br /&gt;        -T print the table of contents of a dynamic shared library&lt;br /&gt;        -M print the module table of a dynamic shared library&lt;br /&gt;        -R print the reference table of a dynamic shared library&lt;br /&gt;        -I print the indirect symbol table&lt;br /&gt;        -H print the two-level hints table&lt;br /&gt;        -v print verbosely (symbolicly) when possible&lt;br /&gt;        -V print disassembled operands symbolicly&lt;br /&gt;        -c print argument strings of a core file&lt;br /&gt;        -X print no leading addresses or headers&lt;br /&gt;        -m don't use archive(member) syntax&lt;br /&gt;evil:~ mohit$ otool -L /bin/ls&lt;br /&gt;/bin/ls:&lt;br /&gt;        /usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)&lt;br /&gt;        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.0.0)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Much better. I can see that &lt;span style="font-style:italic;"&gt;/bin/ls&lt;/span&gt; references two dynamic libraries. Though, the filename extensions don't look at all familiar.&lt;br /&gt;&lt;br /&gt;I'm quite sure that many UNIX / Linux users have had similar experiences while working on OS X systems, so I decided to write a little on what I have learnt so far about OS X executable files.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The OS X Runtime Architecture&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A runtime environment is a framework for code execution on OS X. It consists of a set of conventions that define how code is loaded, managed and executed. When an application is launched, the relevant runtime environment loads the program into memory, resolves references to external libraries, and prepares the code for execution.&lt;br /&gt;&lt;br /&gt;OS X supports three runtime environments: &lt;ul&gt;&lt;li&gt;Dyld Runtime Environment: The preferred runtime environment based on the &lt;span style="font-style:italic;"&gt;dyld&lt;/span&gt; library manager.&lt;/li&gt;&lt;li&gt;CFM Runtime Environment: A legacy environment inherited from OS 9. This is really designed for applications that want to use some of the newer OS X features, but have not been completely ported to &lt;span style="font-style:italic;"&gt;dyld&lt;/span&gt; yet.&lt;/li&gt;&lt;li&gt;The Classic Environment: This environment makes it possible for unmodified OS 9 (9.1 or 9.2) applications to run on OS X.&lt;/li&gt;&lt;/ul&gt; This article will primarily focus on the Dyld Runtime Environment.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The Mach-O Executable File Format&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In OS X, almost all files containing executable code, e.g., applications, frameworks, libraries, kernel extensions etc., are implemented as &lt;span style="font-style:italic;"&gt;Mach-O&lt;/span&gt; files. &lt;span style="font-style:italic;"&gt;Mach-O&lt;/span&gt; is a file format and an ABI (Application Binary Interface) that describes how an executable is to be loaded and run by the kernel. To be more specific, it tells the OS: &lt;ul&gt;&lt;li&gt;Which dynamic loader to use.&lt;/li&gt;&lt;li&gt;Which shared libraries to load.&lt;/li&gt;&lt;li&gt;How to organize the process address space.&lt;/li&gt;&lt;li&gt;Where the function entry-point is, and more.&lt;/li&gt;&lt;/ul&gt;Mach-O is not new. It was originally designed by the Open Software Foundation (OSF) for their OSF/1 operating system, which was based on the Mach microkernel. It was later adapted to x86 systems with OpenStep.&lt;br /&gt;&lt;br /&gt;To support the Dyld Runtime Environment, all files must be built using the Mach-O executable format.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;How Mach-O Files are Organized&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Mach-O files are divided into three regions: a &lt;span style="font-style:italic;"&gt;header&lt;/span&gt;, a &lt;span style="font-style:italic;"&gt;load commands&lt;/span&gt; region, and the &lt;span style="font-style:italic;"&gt;raw segment data&lt;/span&gt;. The &lt;span style="font-style:italic;"&gt;header&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;load commands&lt;/span&gt; regions describe the features, layout and other characteristics of the file, while the &lt;span style="font-style:italic;"&gt;raw segment data&lt;/span&gt; region contains ranges of bytes that are referenced by the load commands.&lt;br /&gt;&lt;br /&gt;To investigate and examine the various parts of Mach-O files, OS X comes with a useful program called &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; located in &lt;span style="font-style:italic;"&gt;/usr/bin&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;In the following sections, we will use &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; to learn more about how Mach-O files are organized.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The Header&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To view the the Mach-O header of a file, use the &lt;span style="font-style:italic;"&gt;-h&lt;/span&gt; parameter of the &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; command.&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-js"&gt;evil:~ mohit$ otool -h /bin/ls&lt;br /&gt;/bin/ls:&lt;br /&gt;Mach header&lt;br /&gt;      magic cputype cpusubtype   filetype ncmds sizeofcmds      flags&lt;br /&gt; 0xfeedface      18          0          2    11       1608 0x00000085&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The first thing specified in the header is the magic number. The magic number identifies the file as either a 32-bit or a 64-bit Mach-O file. It also identifies the &lt;a href="http://en.wikipedia.org/wiki/Endianness"&gt;endianness&lt;/a&gt; of the CPU that it was intended for. To decipher the magic number, have a look at &lt;span style="font-style:italic;"&gt;/usr/include/mach-o/loader.h&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;The header also specifies the target architecture for the file. This allows the kernel to ensure that the code is not run on a processor-type that it was not written for. For example, in the above output, &lt;span style="font-style:italic;"&gt;cputype&lt;/span&gt; is set to 18, which is &lt;span style="font-style:italic;"&gt;CPU_TYPE_POWERPC&lt;/span&gt;, as defined in &lt;span style="font-style:italic;"&gt;/usr/include/mach/machine.h&lt;/span&gt;. &lt;br /&gt;&lt;br /&gt;From these two entries alone, we can infer that this binary was intended for 32-bit PowerPC based systems.&lt;br /&gt;&lt;br /&gt;Sometimes binaries can contain code for more than one architecture. These are known as Universal Binaries, and generally begin with an additional header called the &lt;span style="font-style:italic;"&gt;fat_header&lt;/span&gt;. To examine the contents of the &lt;span style="font-style:italic;"&gt;fat_header&lt;/span&gt;, use the &lt;span style="font-style:italic;"&gt;-f&lt;/span&gt; switch of the &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; command.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-style:italic;"&gt;cpusubtype&lt;/span&gt; attribute specifies the exact model of the CPU, and is generally set to CPU_SUBTYPE_POWERPC_ALL or CPU_SUBTYPE_I386_ALL.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-style:italic;"&gt;filetype&lt;/span&gt; signifies how the file is to be aligned and used. It usually tells you if the file is a library, a standard executable, a core file etc. The &lt;span style="font-style:italic;"&gt;filetype&lt;/span&gt; above equates to MH_EXECUTE, which signifies a demand paged executable file. Below is a snip from &lt;span style="font-style:italic;"&gt;/usr/include/mach-o/loader.h&lt;/span&gt; that lists the different file-types as of this writing.&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-cpp"&gt;#define MH_OBJECT 0x1   /* relocatable object file */&lt;br /&gt;#define MH_EXECUTE  0x2   /* demand paged executable file */&lt;br /&gt;#define MH_FVMLIB 0x3   /* fixed VM shared library file */&lt;br /&gt;#define MH_CORE   0x4   /* core file */&lt;br /&gt;#define MH_PRELOAD  0x5   /* preloaded executable file */&lt;br /&gt;#define MH_DYLIB  0x6   /* dynamically bound shared library */&lt;br /&gt;#define MH_DYLINKER 0x7   /* dynamic link editor */&lt;br /&gt;#define MH_BUNDLE 0x8   /* dynamically bound bundle file */&lt;br /&gt;#define MH_DYLIB_STUB 0x9   /* shared library stub for static */&lt;br /&gt;          /*  linking only, no section contents */&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The next two attributes refer to the &lt;span style="font-style:italic;"&gt;load commands&lt;/span&gt; section, and specify the number and size of the commands. &lt;br /&gt;&lt;br /&gt;And finally, we have &lt;span style="font-style:italic;"&gt;flags&lt;/span&gt;, that specify various features that the kernel may use while loading and executing Mach-O files.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Load Commands&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-style:italic;"&gt;load commands&lt;/span&gt; region contains a list of commands that tell the kernel how to load the various raw segments within the file. They basically describe how each segment is aligned, protected and laid out in memory.&lt;br /&gt;&lt;br /&gt;To see a the list of load commands within a file, use the &lt;span style="font-style:italic;"&gt;-l&lt;/span&gt; switch of the &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; command.&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-js"&gt;evil:~/Temp mohit$ otool -l /bin/ls&lt;br /&gt;/bin/ls:&lt;br /&gt;Load command 0&lt;br /&gt;      cmd LC_SEGMENT&lt;br /&gt;  cmdsize 56&lt;br /&gt;  segname __PAGEZERO&lt;br /&gt;   vmaddr 0x00000000&lt;br /&gt;   vmsize 0x00001000&lt;br /&gt;  fileoff 0&lt;br /&gt; filesize 0&lt;br /&gt;  maxprot 0x00000000&lt;br /&gt; initprot 0x00000000&lt;br /&gt;   nsects 0&lt;br /&gt;    flags 0x4&lt;br /&gt;Load command 1&lt;br /&gt;      cmd LC_SEGMENT&lt;br /&gt;  cmdsize 600&lt;br /&gt;  segname __TEXT&lt;br /&gt;   vmaddr 0x00001000&lt;br /&gt;   vmsize 0x00006000&lt;br /&gt;  fileoff 0&lt;br /&gt; filesize 24576&lt;br /&gt;  maxprot 0x00000007&lt;br /&gt; initprot 0x00000005&lt;br /&gt;   nsects 8&lt;br /&gt;    flags 0x0&lt;br /&gt;Section&lt;br /&gt;  sectname __text&lt;br /&gt;   segname __TEXT&lt;br /&gt;      addr 0x00001ac4&lt;br /&gt;      size 0x000046e8&lt;br /&gt;    offset 2756&lt;br /&gt;     align 2^2 (4)&lt;br /&gt;    reloff 0&lt;br /&gt;    nreloc 0&lt;br /&gt;     flags 0x80000400&lt;br /&gt; reserved1 0&lt;br /&gt; reserved2 0&lt;br /&gt;&lt;br /&gt;[ ___SNIPPED FOR BREVITY___ ]&lt;br /&gt;&lt;br /&gt;Load command 4&lt;br /&gt;          cmd LC_LOAD_DYLINKER&lt;br /&gt;      cmdsize 28&lt;br /&gt;         name /usr/lib/dyld (offset 12)&lt;br /&gt;Load command 5&lt;br /&gt;          cmd LC_LOAD_DYLIB&lt;br /&gt;      cmdsize 56&lt;br /&gt;         name /usr/lib/libncurses.5.4.dylib (offset 24)&lt;br /&gt;   time stamp 1111407638 Mon Mar 21 07:20:38 2005&lt;br /&gt;      current version 5.4.0&lt;br /&gt;compatibility version 5.4.0&lt;br /&gt;Load command 6&lt;br /&gt;          cmd LC_LOAD_DYLIB&lt;br /&gt;      cmdsize 52&lt;br /&gt;         name /usr/lib/libSystem.B.dylib (offset 24)&lt;br /&gt;   time stamp 1111407267 Mon Mar 21 07:14:27 2005&lt;br /&gt;      current version 88.0.0&lt;br /&gt;compatibility version 1.0.0&lt;br /&gt;Load command 7&lt;br /&gt;     cmd LC_SYMTAB&lt;br /&gt; cmdsize 24&lt;br /&gt;  symoff 28672&lt;br /&gt;   nsyms 101&lt;br /&gt;  stroff 31020&lt;br /&gt; strsize 1440&lt;br /&gt;Load command 8&lt;br /&gt;            cmd LC_DYSYMTAB&lt;br /&gt;        cmdsize 80&lt;br /&gt;      ilocalsym 0&lt;br /&gt;      nlocalsym 0&lt;br /&gt;     iextdefsym 0&lt;br /&gt;     nextdefsym 18&lt;br /&gt;      iundefsym 18&lt;br /&gt;      nundefsym 83&lt;br /&gt;         tocoff 0&lt;br /&gt;           ntoc 0&lt;br /&gt;      modtaboff 0&lt;br /&gt;        nmodtab 0&lt;br /&gt;   extrefsymoff 0&lt;br /&gt;    nextrefsyms 0&lt;br /&gt; indirectsymoff 30216&lt;br /&gt;  nindirectsyms 201&lt;br /&gt;      extreloff 0&lt;br /&gt;        nextrel 0&lt;br /&gt;      locreloff 0&lt;br /&gt;        nlocrel 0&lt;br /&gt;Load command 9&lt;br /&gt;     cmd LC_TWOLEVEL_HINTS&lt;br /&gt; cmdsize 16&lt;br /&gt;  offset 29884&lt;br /&gt;  nhints 83&lt;br /&gt;Load command 10&lt;br /&gt;        cmd LC_UNIXTHREAD&lt;br /&gt;    cmdsize 176&lt;br /&gt;     flavor PPC_THREAD_STATE&lt;br /&gt;      count PPC_THREAD_STATE_COUNT&lt;br /&gt;    r0  0x00000000 r1  0x00000000 r2  0x00000000 r3   0x00000000 r4   0x00000000&lt;br /&gt;    r5  0x00000000 r6  0x00000000 r7  0x00000000 r8   0x00000000 r9   0x00000000&lt;br /&gt;    r10 0x00000000 r11 0x00000000 r12 0x00000000 r13  0x00000000 r14  0x00000000&lt;br /&gt;    r15 0x00000000 r16 0x00000000 r17 0x00000000 r18  0x00000000 r19  0x00000000&lt;br /&gt;    r20 0x00000000 r21 0x00000000 r22 0x00000000 r23  0x00000000 r24  0x00000000&lt;br /&gt;    r25 0x00000000 r26 0x00000000 r27 0x00000000 r28  0x00000000 r29  0x00000000&lt;br /&gt;    r30 0x00000000 r31 0x00000000 cr  0x00000000 xer  0x00000000 lr   0x00000000&lt;br /&gt;    ctr 0x00000000 mq  0x00000000 vrsave 0x00000000 srr0 0x00001ac4 srr1 0x00000000&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above file has 11 load commands located directly below the header, numbered 0 to 10.&lt;br /&gt;&lt;br /&gt;The first four commands (LC_SEGMENT), numbered 0 to 3, define how segments within the file are to be mapped into memory. A segment defines a range of bytes in the Mach-O binary, and can contain zero or more sections. We will talk more about segments later.&lt;br /&gt;&lt;br /&gt;Load command 4 (LC_LOAD_DYLINKER) specifies which dynamic linker to use. This is almost always set to &lt;span style="font-style:italic;"&gt;/usr/lib/dyld&lt;/span&gt;, which is the default OS X dynamic library linker.&lt;br /&gt;&lt;br /&gt;Commands 5 and 6 (LC_LOAD_DYLIB) specify the shared libraries that this file links against. These are loaded by the dynamic loader specified in command 4.&lt;br /&gt;&lt;br /&gt;Commands 7 and 8 (LC_SYMTAB, LC_DYNSYMTAB) specify the symbol tables used by the file and the dynamic linker respectively. Command 9 (LC_TWOLEVEL_HINTS) contains the hint table for the two-level namespace.&lt;br /&gt;&lt;br /&gt;And finally, command 10 (LC_UNIXTHREAD), defines the initial state of the main thread of the process. This command is only included in executable files. &lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Segments and Sections&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Most of the load commands mentioned above make references to segments within the file. A segment is a range of bytes within a Mach-O file that maps directly into virtual memory by the kernel and the dynamic linker. The &lt;span style="font-style:italic;"&gt;header&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;load commands&lt;/span&gt; regions are considered as the first segment of the file. &lt;br /&gt;&lt;br /&gt;An typical OS X executable generally has five segments:&lt;ul&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__PAGEZERO&lt;/span&gt; : Located at virtual memory address 0 and has no protection rights. This segment occupies no space in the file, and causes access to &lt;span style="font-style:italic;"&gt;NULL&lt;/span&gt; to immediately crash.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__TEXT&lt;/span&gt; : Contains read-only data and executable code.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__DATA&lt;/span&gt; : Contains writable data. These sections are generally marked &lt;span style="font-style:italic;"&gt;copy-on-write&lt;/span&gt; by the kernel.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__OBJC&lt;/span&gt; : Contains data used by the Objective C language runtime.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__LINKEDIT&lt;/span&gt; : Contains raw data used by the dynamic linker.&lt;/li&gt;&lt;/ul&gt;The &lt;span style="font-style:italic;"&gt;__TEXT&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;__DATA&lt;/span&gt; segments may contain zero or more sections. Each section consists of specific types of data, e.g., executable code, constants, C strings etc.&lt;br /&gt;&lt;br /&gt;To see the contents of a section, use the &lt;span style="font-style:italic;"&gt;-s&lt;/span&gt; option with the &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; command.&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-js"&gt;evil:~/Temp mohit$ otool -sv __TEXT __cstring /bin/ls&lt;br /&gt;/bin/ls:&lt;br /&gt;Contents of (__TEXT,__cstring) section&lt;br /&gt;00006320 00000000 5f5f6479 6c645f6d 6f645f74 &lt;br /&gt;00006330 65726d5f 66756e63 73000000 5f5f6479 &lt;br /&gt;00006340 6c645f6d 616b655f 64656c61 7965645f &lt;br /&gt;00006350 6d6f6475 6c655f69 6e697469 616c697a &lt;br /&gt;__SNIP__&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To disassemble the &lt;span style="font-style:italic;"&gt;__text&lt;/span&gt; section, use the &lt;span style="font-style:italic;"&gt;-tv&lt;/span&gt; switch.&lt;br /&gt;&lt;pre name="code" class="prettyprint lang-js"&gt;evil:~/Temp mohit$ otool -tv /bin/ls&lt;br /&gt;/bin/ls:&lt;br /&gt;(__TEXT,__text) section&lt;br /&gt;00001ac4        or      r26,r1,r1&lt;br /&gt;00001ac8        addi    r1,r1,0xfffc&lt;br /&gt;00001acc        rlwinm  r1,r1,0,0,26&lt;br /&gt;00001ad0        li      r0,0x0&lt;br /&gt;00001ad4        stw     r0,0x0(r1)&lt;br /&gt;00001ad8        stwu    r1,0xffc0(r1)&lt;br /&gt;00001adc        lwz     r3,0x0(r26)&lt;br /&gt;00001ae0        addi    r4,r26,0x4&lt;br /&gt;__SNIP__&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Within the &lt;span style="font-style:italic;"&gt;__TEXT&lt;/span&gt; segment, there are four major sections: &lt;ul&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__text&lt;/span&gt; : The compiled machine code for the executable.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__const&lt;/span&gt; : General constants data.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__cstring&lt;/span&gt; : Literal string constants.&lt;/li&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;__picsymbol_stub&lt;/span&gt; : Position-independent code stub routines used by the dynamic linker.&lt;/li&gt;&lt;/ul&gt; This keeps the executable and non-executable code clearly separated within the segment.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Running an Application&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now that we know what a Mach-O file looks like, let us see how OS X loads and runs an application.&lt;br /&gt;&lt;br /&gt;When you run an application, the shell first calls the &lt;span style="font-style:italic;"&gt;fork()&lt;/span&gt; system call.  &lt;span style="font-style:italic;"&gt;Fork&lt;/span&gt; creates a logical copy of the calling process (the shell) and schedules it for execution. This child process then calls the &lt;span style="font-style:italic;"&gt;execve()&lt;/span&gt; system call providing the path of the program to be executed.&lt;br /&gt;&lt;br /&gt;The kernel loads the specified file, and examines its header to verify that it is a valid Mach-O file. It then starts interpreting the load commands, replacing the child process's address space with segments from the file. &lt;br /&gt;&lt;br /&gt;At the same time, the kernel also executes the dynamic linker specified by the binary, which proceeds to load and link all the dependent libraries. After it binds just enough symbols that are necessary for running the file, it calls the entry-point function.&lt;br /&gt;&lt;br /&gt;The entry-point function is usually a standard function statically linked in from &lt;span style="font-style:italic;"&gt;/usr/lib/crt1.o&lt;/span&gt; at build time. This function initializes the kernel environment and calls the executable's &lt;span style="font-style:italic;"&gt;main()&lt;/span&gt; function.&lt;br /&gt;&lt;br /&gt;The application is now running.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The Dynamic Linker&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The OS X dynamic linker, &lt;span style="font-style:italic;"&gt;/usr/lib/dyld&lt;/span&gt;, is responsible for loading dependent shared libraries, importing the various symbols and functions, and &lt;span style="font-style:italic;"&gt;binding &lt;/span&gt; them into the current process. &lt;br /&gt;&lt;br /&gt;When the process is first started, all the linker does is import the shared libraries into the address space of the process. Depending on how the program was built, the actual binding may be performed at different stages of its execution. &lt;ul&gt;&lt;li&gt;Immediately after loading, as in &lt;span style="font-style:italic;"&gt;load-time&lt;/span&gt; binding.&lt;/li&gt;&lt;li&gt;When a symbol is referenced, as in &lt;span style="font-style:italic;"&gt;just-in-time&lt;/span&gt; binding.&lt;/li&gt;&lt;li&gt;Before the process is even executed, an optimization technique known as &lt;span style="font-style:italic;"&gt;pre-binding&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;If a binding type is not specified, the &lt;span style="font-style:italic;"&gt;just-in-time&lt;/span&gt; binding is used.&lt;br /&gt;&lt;br /&gt;An application can only continue to run when all the &lt;span style="font-style:italic;"&gt;required&lt;/span&gt; symbols and segments from all the different object files can be resolved. In order to find libraries and frameworks, the standard dynamic linker, &lt;span style="font-style:italic;"&gt;/usr/bin/dyld&lt;/span&gt;, searches a predefined set of directories. To override these directories, or to provide fallback paths, the DYLD_LIBRARY_PATH or DYLD_FALLBACK_LIBRARY_PATH environment variables can be set a colon-separated list of directories.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Finally&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;As you can see, executing a process in OS X is a complex affair, and I have tried to cover as much as is necessary for a useful debugging session. &lt;br /&gt;&lt;br /&gt;To learn more about Mach-O executables, &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt;, and the OS X kernel in general, here are a list of references that I would recommend:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/index.html#//apple_ref/doc/uid/TP40000895"&gt;Mac OS X ABI Mach-O File Format Reference&lt;/a&gt;&lt;br /&gt;&lt;a href="http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachOTopics/index.html"&gt;Executing Mach-O Files&lt;/a&gt;&lt;br /&gt;&lt;a href="http://developer.apple.com/documentation/DeveloperTools/Conceptual/DynamicLibraries/index.html#//apple_ref/doc/uid/TP40001869"&gt;Overview of Dynamic Libraries&lt;/a&gt;&lt;br /&gt;The &lt;span style="font-style:italic;"&gt;otool&lt;/span&gt; man page&lt;br /&gt;The &lt;span style="font-style:italic;"&gt;dyld&lt;/span&gt; man page&lt;br /&gt;/usr/include/mach/machines.h&lt;br /&gt;/usr/include/mach-o/loader.h&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Updates&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;2006/03/28&lt;/span&gt; - Looks like this article was &lt;a href="http://apple.slashdot.org/apple/06/03/27/1452244.shtml"&gt;Slashdotted&lt;/a&gt; and &lt;a href="http://digg.com/apple/How_OS_X_Executes_Applications"&gt;Dugg&lt;/a&gt;. It has been slightly modified since, thanks to a few readers who pointed out errors and typos within.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;2006/03/28&lt;/span&gt; - I have answered some of your questions and comments regarding this article here: &lt;a href="http://0xfe.blogspot.com/2006/03/qa-how-os-x-executes-applications.html"&gt;Q&amp;A: How OS X Executes Applications&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114338745476477157?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114338745476477157/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/03/how-os-x-executes-applications.html#comment-form' title='41 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114338745476477157'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114338745476477157'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/03/how-os-x-executes-applications.html' title='How OS X Executes Applications'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>41</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114303054204797200</id><published>2006-03-22T07:05:00.001-05:00</published><updated>2010-04-13T08:39:44.096-04:00</updated><title type='text'>Using Spotlight from the OS X Commandline</title><content type='html'>One significant productivity-enhancing feature that arrived with Tiger was Spotlight.  On its own, it changed the way some (if not most) Mac users use their desktops. A simple &lt;span style="font-style:italic;"&gt;command-space&lt;/span&gt; pops up the Spotlight window, where you can enter a query string, and in a matter of seconds, get a list of files matching your query.&lt;br /&gt;&lt;br /&gt;Spotlight has many advantages over traditional file-searching tools. For one thing, it's not a tool. It is a complete indexing and search framework that is tightly integrated into the Operating System. In addition to filenames and paths, it also indexes by file metadata and content. So Spotlight returns query results based on &lt;span style="font-style:italic;"&gt;what's inside the file&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Spotlight benefits can also be enjoyed on the commandline, and this article explains how you can take full advantage of it from inside the OS X Terminal window.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The Old Way&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Most commandline users are familiar with the ubiquitous &lt;span style="font-style:italic;"&gt;find&lt;/span&gt; command.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~/Desktop mohit$ find / -name '*Rails*'&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle/Syntaxes/HTML (Rails).plist&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle/Syntaxes/Ruby on Rails.plist&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle/Syntaxes/SQL (Rails).plist&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Find&lt;/span&gt; is old-school. You give it a search path, and it begins its search by tediously recursing directories and finding matches  to the query string. On even an average-sized filesystem, &lt;span style="font-style:italic;"&gt;find&lt;/span&gt; can take a frustratingly long time.&lt;br /&gt;&lt;br /&gt;Then there's the &lt;span style="font-style:italic;"&gt;locate&lt;/span&gt; tool. &lt;span style="font-style:italic;"&gt;Locate&lt;/span&gt; is much faster because it maintains a periodically-updated index of filenames and their locations.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~/Desktop mohit$ locate Rails&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle/Commands&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle/Commands/Open test-case.plist&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle/info.plist&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle/Snippets&lt;br /&gt;/Applications/TextMate.app/Contents/SharedSupport/Bundles/Rails.tmbundle/Snippets/170 eruby forin.plist&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The problem with &lt;span style="font-style:italic;"&gt;locate&lt;/span&gt; is that the index is not updated dynamically. On OS X systems, it is updated weekly by code residing in &lt;span style="font-style:italic;"&gt;/etc/weekly&lt;/span&gt;. Also, locate does not index the contents of the files, nor does it know anything about file metadata.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Enter Spotlight&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Spotlight consists of a &lt;span style="font-style:italic;"&gt;metadata-store&lt;/span&gt; and a &lt;span style="font-style:italic;"&gt;content index&lt;/span&gt; that is dynamically updated by various &lt;span style="font-style:italic;"&gt;importer plugins&lt;/span&gt; within the system. &lt;br /&gt;&lt;br /&gt;The metadata that Spotlight maintains can be very application-specific. For example, images can contain metadata such as, "Dimensions" and "Color Space". Or music files can contain metadata such as, "Genre", "Bit-Rate" or "Encoding".&lt;br /&gt;&lt;br /&gt;Spotlight indexes data by way of various importer plugins. These plugins know how to handle various kinds of data, such as iChat Transcripts, iTunes Music, e-mail etc.&lt;br /&gt;&lt;br /&gt;Below is a snippet of the top processes on my PowerBook.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt; 1352 top         10.4%  0:01.41   1    18    22   620K   416K  1.05M  27.0M &lt;br /&gt; 1337 mdimport     0.0%  0:00.45   4    62    55  1.06M  3.98M  3.15M  39.9M&lt;br /&gt; 1294 mdimport     0.0%  0:00.21   3    61    46   776K  2.82M  2.28M  38.9M&lt;br /&gt; 1283 mdimport     0.0%  0:00.35   3    61    47   748K  3.19M  2.36M  39.4M&lt;br /&gt; 1281 lookupd      0.0%  0:00.17   2    34    39   440K   912K  1.20M  28.5M&lt;br /&gt; 1258 iTunes       2.4%  2:32.63   4   226   376  17.0M  26.8M  41.7M   227M&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice the three &lt;span style="font-style:italic;"&gt;mdimport&lt;/span&gt; processes. The &lt;span style="font-style:italic;"&gt;mdimport&lt;/span&gt; daemon is responsible for working with the importer plugins and updating the Spotlight index.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 1. A Basic Spotlight Query&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The commandline version of Spotlight is &lt;span style="font-style:italic;"&gt;mdfind&lt;/span&gt;. Simply provide your search query as a parameter and let it run.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~/Desktop mohit$ mdfind Rails&lt;br /&gt;/Users/mohit/Documents/Rails4Days.pdf&lt;br /&gt;/Users/mohit/Documents/Agile Development with Rails.pdf&lt;br /&gt;/Users/mohit/Library/Mail/POP-foobar@mail.snip.com/INBOX.mbox/Messages/20455.emlx&lt;br /&gt;/Users/mohit/Local/rails&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/cache/rails-1.0.0.gem&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/gems/rails-1.0.0&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/gems/rails-1.0.0/bin/rails&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/gems/rails-1.0.0/builtin/controllers/rails_info_controller.rb&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/gems/rails-1.0.0/html/index.html&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/gems/rails-1.0.0/html/images/rails.png&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The files that are listed also include files with content and metadata that matches the query expression.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 2. Limiting Your Search to a Specific Directory&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-style:italic;"&gt;-onlyin&lt;/span&gt; parameter limits the scope of the search to the directory specified.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~/Desktop mohit$ mdfind -onlyin ~/Desktop Rails&lt;br /&gt;/Users/mohit/Desktop/Downloads/Linux/Documents/Work/Verizon Data/Tekelec/Tekelec_Alarm_Docs.pdf&lt;br /&gt;/Users/mohit/Desktop/Projects/Client/nABLE Event Manager - High Level Architecture.doc&lt;br /&gt;/Users/mohit/Desktop/Projects/Client/nABLE EM.doc&lt;br /&gt;/Users/mohit/Desktop/Projects/Client/to-timesheet-2006-01.pdf&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 3. Displaying File Metadata&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Earlier, I mentioned that Spotlight also indexes file metadata. The &lt;span style="font-style:italic;"&gt;mdls&lt;/span&gt; tool lets you examine the metadata for a specified file.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~/Desktop/Projects/Tierone mohit$ mdls SomeDocument.doc &lt;br /&gt;nABLE EM.doc -------------&lt;br /&gt;kMDItemAttributeChangeDate     = 2006-01-23 08:12:42 -0500&lt;br /&gt;kMDItemAuthors                 = ("Homer Simpson")&lt;br /&gt;kMDItemContentCreationDate     = 2006-01-23 08:12:40 -0500&lt;br /&gt;kMDItemContentModificationDate = 2006-01-23 08:12:40 -0500&lt;br /&gt;kMDItemContentType             = "com.microsoft.word.doc"&lt;br /&gt;kMDItemContentTypeTree         = ("com.microsoft.word.doc", "public.data", "public.item")&lt;br /&gt;kMDItemDisplayName             = "SomeDocument.doc"&lt;br /&gt;kMDItemFSContentChangeDate     = 2006-01-23 08:12:40 -0500&lt;br /&gt;kMDItemFSCreationDate          = 2006-01-23 08:12:40 -0500&lt;br /&gt;kMDItemFSCreatorCode           = 1297307460&lt;br /&gt;kMDItemFSFinderFlags           = 0&lt;br /&gt;kMDItemFSInvisible             = 0&lt;br /&gt;kMDItemFSIsExtensionHidden     = 0&lt;br /&gt;kMDItemFSLabel                 = 0&lt;br /&gt;kMDItemFSName                  = "SomeDocument.doc"&lt;br /&gt;kMDItemFSNodeCount             = 0&lt;br /&gt;kMDItemFSOwnerGroupID          = 20&lt;br /&gt;kMDItemFSOwnerUserID           = 501&lt;br /&gt;kMDItemFSSize                  = 92160&lt;br /&gt;kMDItemFSTypeCode              = 1463304782&lt;br /&gt;kMDItemID                      = 2821259&lt;br /&gt;kMDItemKind                    = "Microsoft Word document"&lt;br /&gt;kMDItemLastUsedDate            = 2006-01-23 08:12:40 -0500&lt;br /&gt;kMDItemTitle                   = "Document:"&lt;br /&gt;kMDItemUsedDates               = (2006-01-23 08:12:40 -0500)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The metadata consists of various attributes specific to the file. These attributes can be used with &lt;span style="font-style:italic;"&gt;mdfind&lt;/span&gt; to limit the scope of your search.&lt;br /&gt;&lt;br /&gt;A &lt;a href="http://developer.apple.com/documentation/Carbon/Reference/MetadataAttributesRef/index.html"&gt;good reference&lt;/a&gt; for these metadata attributes can be found at the Apple Developer Connection site.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 4. Finding Files by a Specific Author&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This time, we limit our search to all files by a given author. The attribute we use is &lt;span style="font-style:italic;"&gt;kMDItemAuthors&lt;/span&gt;.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~ mohit$ mdfind "kMDItemAuthors == '*Homer*'"&lt;br /&gt;/Users/mohit/Documents/SomeDocument.doc&lt;br /&gt;/Users/mohit/Documents/Microsoft User Data/AutoRecovery save of SomeDocument.doc&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice that the query was double quoted, while the text-pattern was single quoted.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 5. Finding Music by Artist&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Spotlight query expressions can be quite sophisticated. It allows for various kinds of conditional operators and patterns. Below, we search for all music by John Scofield.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:~ mohit$ mdfind "kMDItemAuthors == 'John Scofield' &amp;&amp; kMDItemContentType == 'public.mp3'"&lt;br /&gt;/Users/mohit/Music/iTunes/iTunes Music/John Scofield/A Go Go/07 Green Tea.mp3&lt;br /&gt;/Users/mohit/Music/iTunes/iTunes Music/John Scofield/A Go Go/06 Kubrick.mp3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Great! But I seem to be missing some files. Where are my AACs?&lt;br /&gt;&lt;br /&gt;Spotlight organizes ContentTypes within ContentTypeTrees, so in this case, &lt;span style="font-style:italic;"&gt;public.mp3&lt;/span&gt; falls under &lt;span style="font-style:italic;"&gt;public.audio&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Knowing this, lets refine our search query to include all audio files.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;&lt;br /&gt;evil:~ mohit$ mdfind "kMDItemAuthors == 'John Scofield' &amp;&amp; kMDItemContentTypeTree == 'public.audio'"&lt;br /&gt;/Users/mohit/Music/iTunes/iTunes Music/John Scofield/A Go Go/07 Green Tea.mp3&lt;br /&gt;/Users/mohit/Music/iTunes/iTunes Music/John Scofield/A Go Go/06 Kubrick.mp3&lt;br /&gt;/Users/mohit/Music/iTunes/iTunes Music/John Scofield/That's What I Say_ John Scofield Plays The Music of Ray Charles/01 Busted.m4a&lt;br /&gt;/Users/mohit/Music/iTunes/iTunes Music/John Scofield/That's What I Say_ John Scofield Plays The Music of Ray Charles/02 What'd I Say.m4a&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Much better. But how did I know what to search for? &lt;br /&gt;&lt;br /&gt;This is where &lt;span style="font-style:italic;"&gt;mdls&lt;/span&gt; comes in handy again.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;&lt;br /&gt;evil:~ mohit$ mdls "/Users/mohit/Music/iTunes/iTunes Music/John Scofield/A Go Go/02 Chank.mp3"&lt;br /&gt;/Users/mohit/Music/iTunes/iTunes Music/John Scofield/A Go Go/02 Chank.mp3 -------------&lt;br /&gt;kMDItemAlbum                   = "A Go Go"&lt;br /&gt;kMDItemAttributeChangeDate     = 2005-11-26 22:00:00 -0500&lt;br /&gt;kMDItemAudioBitRate            = 128&lt;br /&gt;kMDItemAudioChannelCount       = 2&lt;br /&gt;kMDItemAudioSampleRate         = 44100&lt;br /&gt;kMDItemAuthors                 = ("John Scofield")&lt;br /&gt;kMDItemComment                 = "Created by Grip"&lt;br /&gt;kMDItemContentCreationDate     = 2003-10-28 20:34:30 -0500&lt;br /&gt;kMDItemContentModificationDate = 2003-10-28 20:34:35 -0500&lt;br /&gt;kMDItemContentType             = "public.mp3"&lt;br /&gt;kMDItemContentTypeTree         = (&lt;br /&gt;    "public.mp3", &lt;br /&gt;    "public.audio", &lt;br /&gt;    "public.audiovisual-content", &lt;br /&gt;    "public.data", &lt;br /&gt;    "public.item", &lt;br /&gt;    "public.content"&lt;br /&gt;)&lt;br /&gt;kMDItemDisplayName             = "02 Chank.mp3"&lt;br /&gt;kMDItemDurationSeconds         = 406&lt;br /&gt;kMDItemFSContentChangeDate     = 2003-10-28 20:34:35 -0500&lt;br /&gt;kMDItemFSCreationDate          = 2003-10-28 20:34:30 -0500&lt;br /&gt;kMDItemFSCreatorCode           = 0&lt;br /&gt;kMDItemFSFinderFlags           = 0&lt;br /&gt;kMDItemFSInvisible             = 0&lt;br /&gt;kMDItemFSIsExtensionHidden     = 0&lt;br /&gt;kMDItemFSLabel                 = 0&lt;br /&gt;kMDItemFSName                  = "02 Chank.mp3"&lt;br /&gt;kMDItemFSNodeCount             = 0&lt;br /&gt;kMDItemFSOwnerGroupID          = 20&lt;br /&gt;kMDItemFSOwnerUserID           = 501&lt;br /&gt;kMDItemFSSize                  = 6511095&lt;br /&gt;kMDItemFSTypeCode              = 0&lt;br /&gt;kMDItemID                      = 208222&lt;br /&gt;kMDItemKind                    = "MP3 Audio File"&lt;br /&gt;kMDItemLastUsedDate            = 2003-10-28 20:34:35 -0500&lt;br /&gt;kMDItemMediaTypes              = (Sound)&lt;br /&gt;kMDItemMusicalGenre            = "Jazz"&lt;br /&gt;kMDItemRecordingYear           = 1998&lt;br /&gt;kMDItemTitle                   = "Chank"&lt;br /&gt;kMDItemTotalBitRate            = 128&lt;br /&gt;kMDItemUsedDates               = (2003-10-28 20:34:35 -0500)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Looking at &lt;span style="font-style:italic;"&gt;kMDContentTypeTree&lt;/span&gt;, we can tell that &lt;span style="font-style:italic;"&gt;public.mp3&lt;/span&gt; falls under &lt;span style="font-style:italic;"&gt;public.audio&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;We could have also searched by &lt;span style="font-style:italic;"&gt;kMDItemMediaTypes&lt;/span&gt;, or &lt;span style="font-style:italic;"&gt;kMDItemKind&lt;/span&gt;, or even a '*mp3' pattern in kMDItemDisplayName.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 6. Finding Other Content&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You can find images by querying for files with kMDContentTypeTree set to &lt;span style="font-style:italic;"&gt;public.image&lt;/span&gt;.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentTypeTree == 'public.image'"&lt;/pre&gt;&lt;br /&gt;How about we refine that to only images within our iPhoto library.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind -onlyin ~/Pictures "kMDItemContentTypeTree == 'public.image'"&lt;/pre&gt;&lt;br /&gt;Much Better.&lt;br /&gt;&lt;br /&gt;Looking for Word documents?&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentType == 'com.microsoft.word.doc'"&lt;/pre&gt;&lt;br /&gt;Or maybe just PDFs?&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentType == 'com.adobe.pdf'"&lt;/pre&gt;&lt;br /&gt;Or Both?&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentType == 'com.microsoft.word.doc' || kMDItemContentType == 'com.adobe.pdf'"&lt;/pre&gt;&lt;br /&gt;Lets stick to plain-text.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentTypeTree == 'public.text"&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 7. Looking for Source Code&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Finding all Ruby scripts.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentType == 'public.ruby-script'"&lt;/pre&gt;&lt;br /&gt;Finding all kinds of scripts (Python, Bash, Ruby etc.)&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentTypeTree == 'public.shell-script'"&lt;/pre&gt;&lt;br /&gt;Finding everything except Python scripts.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentTypeTree == 'public.shell-script' &amp;&amp; kMDItemContentType != 'public.python-script'"&lt;/pre&gt;&lt;br /&gt;Finding Source Code (not scripts).&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ mdfind "kMDItemContentTypeTree == 'public.source-code"&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 8. Using "kind" Keywords (Added 24/Mar/06)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Commandline Spotlight also supports the "kind:" keyword. This is simpler than filtering with kMDItemContentType.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:/ mohit$ mdfind "kind:pdf Calculus"&lt;br /&gt;/Users/mohit/Documents/Elementary Calculus.pdf&lt;br /&gt;/Users/mohit/.Trash/marktoberdorf.pdf&lt;br /&gt;/Users/mohit/.Trash/FoundInfsmlCalc.pdf&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Spotlight "kind" Keyword list.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;Applications  kind:application, kind:applications, kind:app&lt;br /&gt;Contacts      kind:contact, kind:contacts&lt;br /&gt;Folders       kind:folder, kind:folders&lt;br /&gt;Email         kind:email, kind:emails, kind:mail message, kind:mail messages&lt;br /&gt;iCal Events   kind:event, kind:events&lt;br /&gt;iCal To Dos   kind:todo, kind:todos, kind:to do, kind:to dos&lt;br /&gt;Images        kind:image, kind:images&lt;br /&gt;Movies        kind:movie, kind:movies&lt;br /&gt;Music         kind:music&lt;br /&gt;Audio         kind:audio&lt;br /&gt;PDF           kind:pdf, kind:pdfs&lt;br /&gt;Preferences   kind:system preferences, kind:preferences&lt;br /&gt;Bookmarks     kind:bookmark, kind:bookmarks&lt;br /&gt;Fonts         kind:font, kind:fonts&lt;br /&gt;Presentations kind:presentations, kind:presentation&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example 9. Using "date" Keywords (Added 24/Mar/06)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Files can also be filtered based on date related information.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;evil:/ mohit$ mdfind "kind:pdf date:this week"&lt;br /&gt;/Users/mohit/Desktop/chapter_1a.pdf&lt;br /&gt;/Users/mohit/Documents/Elementary Calculus.pdf&lt;br /&gt;/Users/mohit/Desktop/13.pdf&lt;br /&gt;/Users/mohit/Desktop/Internet_map_labels.pdf&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The date ranges that can be specified are: &lt;ul&gt; &lt;li&gt; date:this month&lt;/li&gt;&lt;li&gt; date:this week&lt;/li&gt;&lt;li&gt; date:this year&lt;/li&gt;&lt;li&gt; date:today&lt;/li&gt;&lt;li&gt; date:yesterday&lt;/li&gt;&lt;li&gt; date:tomorrow&lt;/li&gt;&lt;li&gt; date:next month&lt;/li&gt;&lt;li&gt; date:next week&lt;/li&gt;&lt;li&gt; date:next year&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Note that the future ranges (tomorrow, next week, etc.) are for Calendar appointments.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Finally&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;As you can see, Spotlight is great for commandline junkies too. It is a fast, flexible alternative to the UNIX &lt;span style="font-style:italic;"&gt;find&lt;/span&gt; command, and in many respects, more powerful than &lt;span style="font-style:italic;"&gt;find&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;But it is by no means a replacement. There are some things that Spotlight's &lt;span style="font-style:italic;"&gt;mdfind&lt;/span&gt; just cannot do. UNIX &lt;span style="font-style:italic;"&gt;find&lt;/span&gt; has a much richer set of options, and when it comes to digging deep into the system, there is no alternative.&lt;br /&gt;&lt;br /&gt;For most purposes though, Spotlight works very well. Rewriting your shell scripts to use &lt;span style="font-style:italic;"&gt;mdfind&lt;/span&gt; instead of &lt;span style="font-style:italic;"&gt;find&lt;/span&gt;, will make them far more responsive (and far less portable). So here's another case where OS X's UNIX underpinnings have made for a useful tool that is usable, both from the GUI and from the Commandline.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114303054204797200?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114303054204797200/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/03/using-spotlight-from-os-x-commandline.html#comment-form' title='17 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114303054204797200'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114303054204797200'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/03/using-spotlight-from-os-x-commandline.html' title='Using Spotlight from the OS X Commandline'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>17</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114217845282679525</id><published>2006-03-12T09:04:00.001-05:00</published><updated>2010-04-13T08:42:54.868-04:00</updated><title type='text'>Troubleshooting UNIX Systems with Lsof</title><content type='html'>One of the least-talked-about tools in a UNIX sysadmin's toolkit is &lt;span style="font-style:italic;"&gt;lsof&lt;/span&gt;. Lsof lists information about files opened by processes. But that's really an understatement. &lt;br /&gt;&lt;br /&gt;Most people forget that, in UNIX, (almost) everything is a file. The OS makes hardware available to applications by way of files in &lt;span style="font-style:italic;"&gt;/dev&lt;/span&gt;. Kernel, system, memory, device etc. information in made available inside files in &lt;span style="font-style:italic;"&gt;/proc&lt;/span&gt;. TCP/UDP sockets are sometimes represented internally as files. Even directories are really just files containing other filenames.&lt;br /&gt;&lt;br /&gt;Lsof works by examining kernel data-structures and provides a variety of information related to files, pipes, sockets and more.&lt;br /&gt;&lt;br /&gt;Lsof is installed by default on most Linux distributions, BSD distributions and OS X. Binary packages for Solaris, AIX, HP-UX, *cough*SCO OpenServer*cough* and many other UNIXes (Unices?) are available on the web.&lt;br /&gt;&lt;br /&gt;So, just how useful is &lt;span style="font-style:italic;"&gt;lsof&lt;/span&gt;?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Deciphering its Output&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Switch to root, and type &lt;span style="font-style:italic;"&gt;lsof&lt;/span&gt; on the commandline.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;linux# lsof&lt;br /&gt;COMMAND     PID     USER   FD      TYPE     DEVICE     SIZE       NODE NAME&lt;br /&gt;init          1     root  cwd       DIR       3,65     4096          2 /&lt;br /&gt;init          1     root  rtd       DIR       3,65     4096          2 /&lt;br /&gt;init          1     root  txt       REG       3,65    29556     172317 /sbin/init&lt;br /&gt;init          1     root  mem       REG       3,65  1166880      93908 /lib/libc-2.3.5.so&lt;br /&gt;init          1     root  mem       REG       3,65   103053      93909 /lib/ld-2.3.5.so&lt;br /&gt;init          1     root   10u     FIFO       3,65               48438 /dev/initctl&lt;br /&gt;ksoftirqd     2     root  cwd       DIR       3,65     4096          2 /&lt;br /&gt;ksoftirqd     2     root  rtd       DIR       3,65     4096          2 /&lt;br /&gt;ksoftirqd     2     root  txt   unknown                                /proc/2/exe&lt;br /&gt;events/0      3     root  cwd       DIR       3,65     4096          2 /&lt;br /&gt;events/0      3     root  rtd       DIR       3,65     4096          2 /&lt;br /&gt;events/0      3     root  txt   unknown                                /proc/3/exe&lt;br /&gt;&lt;br /&gt;...SNIP...&lt;br /&gt;&lt;br /&gt;syslog-ng  6529     root  txt       REG       3,69   114132      84690 /usr/sbin/syslog-ng&lt;br /&gt;syslog-ng  6529     root  mem       REG       3,65  1166880      93908 /lib/libc-2.3.5.so&lt;br /&gt;syslog-ng  6529     root  mem       REG       3,65    64568      93943 /lib/libresolv-2.3.5.so&lt;br /&gt;syslog-ng  6529     root  mem       REG       3,65    75176      93924 /lib/libnsl-2.3.5.so&lt;br /&gt;syslog-ng  6529     root  mem       REG       3,65   103053      93909 /lib/ld-2.3.5.so&lt;br /&gt;syslog-ng  6529     root    0u      CHR        1,3               47320 /dev/null&lt;br /&gt;syslog-ng  6529     root    1u      CHR        1,3               47320 /dev/null&lt;br /&gt;syslog-ng  6529     root    2u      CHR        1,3               47320 /dev/null&lt;br /&gt;syslog-ng  6529     root    3u     unix 0xdea00e00              672127 /dev/log&lt;br /&gt;&lt;br /&gt;...SNIP...&lt;br /&gt;&lt;br /&gt;asterisk   7001     root   10u     IPv4       6015                 TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk   7001     root   11r     FIFO       3,70                 306 /var/run/asterisk/autod&lt;br /&gt;ial.ctl&lt;br /&gt;asterisk   7001     root   12u     IPv4       6834                 UDP *:5060 &lt;br /&gt;asterisk   7001     root   13r     FIFO        0,5                6019 pipe&lt;br /&gt;asterisk   7001     root   14u     IPv4       6016                 TCP localhost:5038-&gt;localho&lt;br /&gt;st:32768 (ESTABLISHED)&lt;br /&gt;asterisk   7001     root   15u     IPv4       6835                 UDP *:2727 &lt;br /&gt;asterisk   7001     root   16u     IPv4       6861                 UDP *:4569 &lt;br /&gt;asterisk   7001     root   17u      REG       3,70        0     593222 /var/lib/asterisk/astdb&lt;br /&gt;asterisk   7001     root   18r     FIFO        0,5                6883 pipe&lt;br /&gt;asterisk   7001     root   19u      REG       3,70    39402      32066 /var/tmp/iaxy.bin-19098&lt;br /&gt;89093 (deleted)&lt;br /&gt;asterisk   7001     root   20w     FIFO        0,5                6883 pipe&lt;br /&gt;&lt;br /&gt;...LOTS MORE SNIPPED...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;What you will be presented with is a very long list of open files, which you might want to pipe through your favourite pager. &lt;br /&gt;&lt;br /&gt;By default (on Linux), lsof displays the following information about each open file:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;COMMAND&lt;/span&gt;: The name of the UNIX command associated with the process. &lt;/li&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;PID&lt;/span&gt;: The Process ID.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;USER&lt;/span&gt;: The user ID or login name of the user to whom the process belongs.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;FD&lt;/span&gt;: The file descriptor number of the file or a code representing more information about the structure. See manual page for details. &lt;/li&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;TYPE&lt;/span&gt;: The type of the node associated with the file. E.g. REG signifies a regular file, IPv4 or IPv6 signifies an IP socket, DIR a directory, "unix" a UNIX domain socket, etc.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;DEVICE&lt;/span&gt;: Usually contains major and minor device numbers for the files, or addresses/references for other structures.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;SIZE&lt;/span&gt;: The size of the file or the file offset, in bytes. (If available.) In the case of files that don't have true sizes (eg., sockets, pipes), lsof displays the size of the content their kernel buffer descriptors.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;NODE&lt;/span&gt;: Node number / inode / Internet protocol type (TCP) etc.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt; &lt;span style="font-style:italic;"&gt;NAME&lt;/span&gt;: The name of the file / mount point / device / Internet address / etc.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;For a comprehensive description of these fields, refer the &lt;span style="font-style:italic;"&gt;lsof&lt;/span&gt; manual page.&lt;br /&gt;&lt;br /&gt;Since lsof works by examining kernel memory, you will need root access to be able to fully utilize it. A non-root user will not have access to information that belongs to other users.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Common Usage&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Lsof is usually run with one or more of the following options:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-style:italic;"&gt;/path/to/file&lt;/span&gt;: List processes, owners and open file descriptors that are currently using the specified file.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;-i [46][protocol][@hostname|hostaddr][:service|port]&lt;/span&gt;: List Internet files / sockets.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;-u name&lt;/span&gt;: List files owned by user.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;-p pid&lt;/span&gt;: List files open by specified process.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;-t&lt;/span&gt;: Terse output. No headers, only PIDs. Useful within scripts.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;-n&lt;/span&gt;: Disable resolving of network names.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;-N&lt;/span&gt;: List NFS files&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;These options are ORed by default.&lt;br /&gt;&lt;br /&gt;Display all internet files OR files opened by user "foobar". &lt;pre class="prettyprint lang-js"&gt;# lsof -u foobar -i&lt;/pre&gt;&lt;br /&gt;To display all internet files that are opened by foobar, you need to apply the AND (-a) condition between the switches. &lt;pre class="prettyprint lang-js"&gt;# lsof -u foobar -a -i&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The following recipes demonstrate how &lt;span style="font-style:italic;"&gt;lsof&lt;/span&gt; can be used to troubleshoot real-world problems.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #1: Finding Port Hogs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Your web-server is refusing to come up because port 80 is in use by another process. How do you track down the offending process?&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;&lt;br /&gt;# lsof -i&lt;br /&gt;&lt;br /&gt;... SNIP ...&lt;br /&gt;&lt;br /&gt;asterisk   7554     root   16u  IPv4   6861       UDP *:4569 &lt;br /&gt;postmaste  7688 postgres    5u  IPv4   5955       UDP localhost:32768-&gt;localhost:32768 &lt;br /&gt;postmaste  7689 postgres    5u  IPv4   5955       UDP localhost:32768-&gt;localhost:32768 &lt;br /&gt;sshd      27038     root    3u  IPv4 677971       TCP reddwarf:ssh-&gt;CPE.xxxx.com:61702 (ESTABLISHED)&lt;br /&gt;sshd      27043    mohit    3u  IPv4 677971       TCP reddwarf:ssh-&gt;CPE.xxxx.com:61702 (ESTABLISHED)&lt;br /&gt;&lt;br /&gt;... SNIP ...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Nice. A list of open Internet sockets, along with the processes, addresses and owners. Also note that (similar to netstat), the TCP states are displayed. Above, we can see two established ssh sessions in progress.&lt;br /&gt;&lt;br /&gt;Let's add a port filter and find exactly what we're looking for.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;&lt;br /&gt;# lsof -i TCP:80&lt;br /&gt;COMMAND   PID     USER   FD   TYPE DEVICE SIZE NODE NAME&lt;br /&gt;lighttpd 7356 lighttpd    3u  IPv4   6409       TCP *:http (LISTEN)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Okay, so lighttpd is the reason why Apache won't run. That's probably a good thing.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #2: Finding Processes Within a Given Port Range&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You need to find a range of free ports for your new multimedia application.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;&lt;br /&gt;# lsof -i TCP:5000-5200&lt;br /&gt;COMMAND   PID USER   FD   TYPE DEVICE SIZE NODE NAME&lt;br /&gt;asterisk 7001 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7001 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;asterisk 7002 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7002 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;asterisk 7039 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7039 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;asterisk 7040 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7040 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;asterisk 7041 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7041 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;asterisk 7042 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7042 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;asterisk 7044 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7044 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;perl     7046 root    3u  IPv4   6054       TCP *:5100 (LISTEN)&lt;br /&gt;perl     7046 root    4u  IPv4   6055       TCP *:5101 (LISTEN)&lt;br /&gt;perl     7046 root    6u  IPv4   6056       TCP localhost:32768-&gt;localhost:5038 (ESTABLISHED)&lt;br /&gt;asterisk 7073 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7073 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;asterisk 7504 root   10u  IPv4   6015       TCP localhost:5038 (LISTEN)&lt;br /&gt;asterisk 7504 root   14u  IPv4   6016       TCP localhost:5038-&gt;localhost:32768 (ESTABLISHED)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #3: Listing User Files&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What files do users "foobar" and "apache" have open? &lt;pre class="prettyprint lang-js"&gt;# lsof -u foobar,apache&lt;/pre&gt;&lt;br /&gt;List UDP ports in use by user "mohit". &lt;pre class="prettyprint lang-js"&gt;# lsof -i UDP -a -u mohit&lt;/pre&gt;&lt;br /&gt;Who's responding to "who"? &lt;pre class="prettyprint lang-js"&gt;# lsof -i UDP:who&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #4: Unmounting a Disk or Filesystem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Sometimes you need to track down the user or process that's blocking you from unmounting a disk.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;# umount /opt&lt;br /&gt;umount: /opt: device is busy&lt;br /&gt;umount: /opt: device is busy&lt;br /&gt;# mount | grep "/opt"&lt;br /&gt;/dev/hdb9 on /opt type ext3 (rw,noatime)&lt;br /&gt;# lsof /dev/hdb9&lt;br /&gt;COMMAND    PID     USER   FD   TYPE DEVICE    SIZE    NODE NAME&lt;br /&gt;perl      7046     root    2w   REG   3,73     111 1376386 /opt/local/paynacea/var/state/callmanager.pid.err&lt;br /&gt;perl      7046     root    5w   REG   3,73    6783 1376385 /opt/local/paynacea/var/log/callmanager.log&lt;br /&gt;# kill 7046&lt;br /&gt;# umount /opt&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Or the simpler:&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;# kill `lsof -t /opt`&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #5: Finding Device Hogs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Who's using the audio manager? &lt;pre class="prettyprint lang-js"&gt;# lsof /dev/audio&lt;/pre&gt;&lt;br /&gt;Why can't I start my alternate logger?&lt;pre class="prettyprint lang-js"&gt;# lsof /dev/log&lt;/pre&gt;&lt;br /&gt;Why doesn't my CD eject?&lt;pre class="prettyprint lang-js"&gt;# lsof /dev/cdrom&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #6: Using Exclusions&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The '^' (negated) modifier can prefix the User or Process ID parameters to exclude them from the resulting list.  Since they represent exclusions, they are applied without ORing or ANDing and take effect before any other selection criteria are applied.&lt;br /&gt;&lt;br /&gt;List all Internet files/sockets open by non-root users. &lt;pre class="prettyprint lang-js"&gt;# lsof -i -u^root&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #7: Recursing Directories&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The '+D' option causes lsof to search for open files within a specified directory, recursing down to its complete depth.&lt;br /&gt;&lt;br /&gt;List all processes that have files open in &lt;span style="font-style:italic;"&gt;/tmp&lt;/span&gt;. &lt;pre class="prettyprint lang-js"&gt;# lsof +D /tmp&lt;/pre&gt;&lt;br /&gt;The '+d' option does the same thing, but does _not_ descend the directory tree.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #8: Matching by Process Name&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;List all files open by processes beginning with the letters &lt;span style="font-style:italic;"&gt;mpg&lt;/span&gt;. &lt;pre class="prettyprint lang-js"&gt;# lsof -c mpg&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Using a regular-expression. &lt;pre class="prettyprint lang-js"&gt;# lsof -c '/post.*er/'&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #9: Examining Suspicious Processes&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Lsof can be used along with strace to examine and monitor the operation of viruses, worms or spyware.&lt;br /&gt;&lt;br /&gt;What files are opened by PID 14554? &lt;pre class="prettyprint lang-js"&gt;# lsof -p 14554&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Who's looking at the password file? &lt;pre class="prettyprint lang-js"&gt;# lsof /etc/passwd&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #10: Repeat Mode&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The -r switch puts lsof in repeat mode. It delays every 15 seconds (unless specified), and displays another listing.&lt;br /&gt;&lt;br /&gt;Watching a user's open files every 5 seconds: &lt;pre class="prettyprint lang-js"&gt; # lsof -u badcop -r5 &lt;/pre&gt;&lt;br /&gt;Monitoring the password file: &lt;pre class="prettyprint lang-js"&gt;# lsof /etc/passwd -r 2&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Recipe #11: Finding Deleted Open Files&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;&lt;br /&gt;This recipe was added on 26/Mar/06 after an anonymous poster left a comment regarding deleted files.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;One of the most annoying problems is a file-system quickly running out of space, without a hint of what file is responsible for it. This happens when a file (usually a log-file), gets deleted while it's still being written to. When you delete an open file, the kernel unlinks the file from the directory, but cannot remove the inode, since it's still open.&lt;br /&gt;&lt;br /&gt;This causes the file to continue to grow, with no trace of its existance anywhere. Well... almost anywhere.&lt;br /&gt;&lt;br /&gt;Lsof provides the &lt;span style="font-style:italic;"&gt;+L&lt;/span&gt; parameter to list the number of link counts an open file has. When followed by a number, &lt;span style="font-style:italic;"&gt;lsof&lt;/span&gt; only displays files with link counts less thatn the specified number.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;mohit@reddwarf ~ $ lsof +L3&lt;br /&gt;COMMAND   PID  USER   FD   TYPE DEVICE    SIZE NLINK      NODE NAME&lt;br /&gt;sshd    11540 mohit  mem    REG   3,69  303448     1     85869 /usr/sbin/sshd&lt;br /&gt;sshd    11540 mohit  mem    REG   3,65   35404     1     94075 /lib/libnss_nis-2.3.5.so&lt;br /&gt;sshd    11540 mohit  mem    REG   3,65   30928     1     94086 /lib/libnss_compat-2.3.5.so&lt;br /&gt;sshd    11540 mohit  mem    REG   3,65   35236     1     93958 /lib/libnss_files-2.3.5.so&lt;br /&gt;sshd    11540 mohit  mem    REG   3,65   28444     1     94094 /lib/libcrack.so.2.8.0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A deleted file has zero links. So the following command displays deleted-but-open files on a system.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ lsof +L1&lt;/pre&gt;&lt;br /&gt;Display a list of deleted-but-open files within a specific filesystem.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ lsof +aL1 /tmp&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Finally&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We barely scratched the surface with the above recipes, but as you can see, &lt;span style="font-style:italic;"&gt;lsof&lt;/span&gt; is a powerful troubleshooting tool. I'd be interested in learning what other users do with lsof. Toy with it, tinker with it, use it and let me know how it has helped you.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114217845282679525?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114217845282679525/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/03/troubleshooting-unix-systems-with-lsof.html#comment-form' title='18 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114217845282679525'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114217845282679525'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/03/troubleshooting-unix-systems-with-lsof.html' title='Troubleshooting UNIX Systems with Lsof'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>18</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114191439506928225</id><published>2006-03-09T08:29:00.001-05:00</published><updated>2010-04-13T08:43:36.373-04:00</updated><title type='text'>Tailing Multiple Log Files with Twisted</title><content type='html'>In my last entry, I described a mechanism to follow a growing log file using the Twisted framework. Now, what if you wanted to follow multiple log files simultaneously?&lt;br /&gt;&lt;br /&gt;You could try something like this:&lt;br /&gt;&lt;pre class="prettyprint lang-py"&gt;#!/usr/bin/env python&lt;br /&gt;&lt;br /&gt;from twisted.internet import reactor&lt;br /&gt;from Support.followtail import FollowTail&lt;br /&gt;&lt;br /&gt;def lineReceived_file1( line ):&lt;br /&gt;  print "FILE1: " + line&lt;br /&gt;&lt;br /&gt;def lineReceived_file2( line ):&lt;br /&gt;  print "FILE2: " + line&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;  tailer1 = FollowTail( "/home/mohit/var/log/access-log" )&lt;br /&gt;  tailer2 = FollowTail( "/home/mohit/var/log/error-log" )&lt;br /&gt;&lt;br /&gt;  tailer1.lineReceived = lineReceived_file1&lt;br /&gt;  tailer2.lineReceived = lineReceived_file2&lt;br /&gt;&lt;br /&gt;  tailer1.start()&lt;br /&gt;  tailer2.start()&lt;br /&gt;&lt;br /&gt;  reactor.run()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Sure, that works fine. But there's a scaling issue above. What if you wanted to follow an unknown number of files, and track which file each line came from? Since a function callback is required for each file, it's quite difficult to use FollowTail for more "dynamic" applications.&lt;br /&gt;&lt;br /&gt;Here's a simple solution: Subclass FollowTail, and override lineReceived to have it supply the filename along with the line. &lt;br /&gt;&lt;pre class="prettyprint lang-py"&gt;#!/usr/bin/env python&lt;br /&gt;&lt;br /&gt;from twisted.internet import reactor&lt;br /&gt;from Support.followtail import FollowTail&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;class MultiFollowTail( FollowTail ):&lt;br /&gt;  def lineReceived( self, line ):&lt;br /&gt;    self.onFileLine( self.filename, line )&lt;br /&gt;&lt;br /&gt;  def onFileLine( self, filename, line ):&lt;br /&gt;    pass&lt;br /&gt;&lt;br /&gt;def lineReceived( filename, line ):&lt;br /&gt;  print filename + ": " + line&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;  for arg in sys.argv[1:]:&lt;br /&gt;    print "Monitoring: " + arg&lt;br /&gt;    tailer = MultiFollowTail( arg )&lt;br /&gt;    tailer.onFileLine = lineReceived&lt;br /&gt;    tailer.start()&lt;br /&gt;&lt;br /&gt;  if len( sys.argv ) &gt; 1:  &lt;br /&gt;    reactor.run()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Above, we created a new MultiFollowTail to do just that. Since the filename is included with each callback, we only need a single function to monitor multiple files.&lt;br /&gt;&lt;br /&gt;And there you have it, a simple easy-to-use mechanism to monitor multiple logfiles with Python.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114191439506928225?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114191439506928225/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/03/tailing-multiple-log-files-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114191439506928225'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114191439506928225'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/03/tailing-multiple-log-files-with.html' title='Tailing Multiple Log Files with Twisted'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114174446778037940</id><published>2006-03-07T09:21:00.001-05:00</published><updated>2010-04-13T08:44:12.939-04:00</updated><title type='text'>Following a Log File with Twisted</title><content type='html'>Another POE component that I dearly miss in Twisted is POE::Wheel::FollowTail. FollowTail lets you monitor a growing log file, spewing events when new lines are received. It also gracefully handles log-rotated files by reopening it when it resets.&lt;br /&gt;&lt;br /&gt;After some searching, I found a post in the Kragen-hacks mailing list that does something similar to the POE wheel.&lt;br /&gt;&lt;br /&gt;I took that code, added line-buffering, wrapped it in a class, and added events for errors and file resets.&lt;br /&gt;&lt;br /&gt;Here's the result:&lt;br /&gt;&lt;pre class="prettyprint lang-py"&gt;# Twisted FollowTail&lt;br /&gt;# Mohit Muthanna &lt;http://www.muthanna.com&gt;&lt;br /&gt;#&lt;br /&gt;# A Twisted version of POE::Wheel::FollowTail. Adapted from&lt;br /&gt;# a post by Kragen Sitaker on the Kragen-hacks mailing list.&lt;br /&gt;#&lt;br /&gt;# http://lists.canonical.org/pipermail/kragen-hacks/2005-June/000413.html&lt;br /&gt;&lt;br /&gt;from twisted.internet import reactor&lt;br /&gt;from twisted.protocols import basic&lt;br /&gt;import os, stat&lt;br /&gt;&lt;br /&gt;class FollowTail:&lt;br /&gt;  from os import linesep as newline&lt;br /&gt;  __line_buffer = ""&lt;br /&gt;&lt;br /&gt;  def __init__( self, filename = None, seekend = True, delay = 1 ):&lt;br /&gt;    self.filename = filename&lt;br /&gt;    self.delay = delay&lt;br /&gt;    self.seekend = seekend&lt;br /&gt;    self.keeprunning = False&lt;br /&gt;&lt;br /&gt;  def fileIdentity( self, struct_stat ):&lt;br /&gt;    return struct_stat[stat.ST_DEV], struct_stat[stat.ST_INO]&lt;br /&gt;&lt;br /&gt;  def start( self ):&lt;br /&gt;    self.keeprunning = True&lt;br /&gt;    self.followTail()&lt;br /&gt;&lt;br /&gt;  def stop( self ):&lt;br /&gt;    self.keeprunning = False&lt;br /&gt;&lt;br /&gt;  def followTail( self, fileobj = None, fstat = None ):&lt;br /&gt;    if fileobj is None:&lt;br /&gt;      fileobj = open( self.filename )&lt;br /&gt;      if self.seekend: fileobj.seek( 0, 2 )&lt;br /&gt;&lt;br /&gt;    line = fileobj.read()&lt;br /&gt;&lt;br /&gt;    if line: self.dataReceived( line )&lt;br /&gt;&lt;br /&gt;    if fstat is None: fstat = os.fstat( fileobj.fileno() )&lt;br /&gt;&lt;br /&gt;    try: stat = os.stat( self.filename )&lt;br /&gt;    except: stat = fstat&lt;br /&gt;&lt;br /&gt;    if self.fileIdentity( stat ) != self.fileIdentity( fstat ):&lt;br /&gt;      fileobj = open( self.filename )&lt;br /&gt;      fstat = os.fstat( fileobj.fileno() )&lt;br /&gt;      self.fileReset()&lt;br /&gt;&lt;br /&gt;    &lt;br /&gt;    if self.keeprunning: &lt;br /&gt;      reactor.callLater( self.delay, lambda: self.followTail( fileobj, fstat ) )&lt;br /&gt;  &lt;br /&gt;  def dataReceived( self, data ):&lt;br /&gt;    # Fill buffer&lt;br /&gt;    self.__line_buffer += data&lt;br /&gt;    &lt;br /&gt;    # Split lines&lt;br /&gt;    lines = self.__line_buffer.splitlines()&lt;br /&gt;    &lt;br /&gt;    if not data.endswith( self.newline ):&lt;br /&gt;      self.__line_buffer = lines.pop()&lt;br /&gt;    else:&lt;br /&gt;      self.__line_buffer = ""&lt;br /&gt;    &lt;br /&gt;    for line in lines:&lt;br /&gt;      self.lineReceived( line )&lt;br /&gt;  &lt;br /&gt;  def lineReceived( self, line ):&lt;br /&gt;    """Override This"""&lt;br /&gt;  &lt;br /&gt;  def fileReset( self ):&lt;br /&gt;    """Override This"""&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Usage is quite straightforward. The FollowTail constructor has three parameters:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;filename&lt;/span&gt;: Full path to file.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;seekend&lt;/span&gt;: If set to "False", starts from  beginning of file. Default "True".&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;span style="font-style:italic;"&gt;delay&lt;/span&gt;: How often (in seconds) the file should be polled. Default 1 second.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;When a new line is received, FollowTail calls the method "lineReceived" and supplies the line string. If the file has been rotated, it calls "fileReset". Both methods can be overridden in your program.&lt;br /&gt;&lt;br /&gt;Here's some sample code that uses this class.&lt;br /&gt;&lt;pre class="prettyprint lang-py"&gt;#!/usr/bin/env python&lt;br /&gt;&lt;br /&gt;from twisted.internet import reactor&lt;br /&gt;from followtail import FollowTail&lt;br /&gt;&lt;br /&gt;def onLine( line ):&lt;br /&gt;  print "Line: " + line&lt;br /&gt;&lt;br /&gt;def onReset():&lt;br /&gt;  print "File Rotated."&lt;br /&gt;&lt;br /&gt;filename = "/home/mohit/var/log/access-log"&lt;br /&gt;tailer = FollowTail( filename )&lt;br /&gt;tailer.lineReceived = onLine&lt;br /&gt;tailer.resetFile = onReset&lt;br /&gt;tailer.start()&lt;br /&gt;&lt;br /&gt;reactor.run()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Download: &lt;a href="http://www.muthanna.com/downloads/followtail.py"&gt;followtail.py&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114174446778037940?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114174446778037940/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/03/following-log-file-with-twisted.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114174446778037940'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114174446778037940'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/03/following-log-file-with-twisted.html' title='Following a Log File with Twisted'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114142044627883988</id><published>2006-03-03T15:21:00.001-05:00</published><updated>2010-04-13T08:45:38.044-04:00</updated><title type='text'>Line Filtering With Twisted</title><content type='html'>Unlike &lt;a href="http://poe.perl.org"&gt;POE&lt;/a&gt;, the Twisted framework does not allow for generic stream filters.&lt;br /&gt;&lt;br /&gt;This means that in order to pre- or post-process certain data-streams, one cannot reuse  processing code used by other classes. I stumbled across this while trying to parse the output of a running process.&lt;br /&gt;&lt;br /&gt;Take the following code for example. It listens on a socket, and when a connection is received, it spawns a process and redirects its output to the socket.&lt;br /&gt;&lt;pre class="prettyprint lang-py"&gt;# test1.py&lt;br /&gt;from twisted.internet import protocol, reactor&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;class TestProcessProtocol( protocol.ProcessProtocol ):&lt;br /&gt;  def outReceived( self, data ):&lt;br /&gt;    self.lineReceived( data )&lt;br /&gt;      &lt;br /&gt;  def lineReceived( self, line ):&lt;br /&gt;    """Override This"""&lt;br /&gt;&lt;br /&gt;class AdminServerProtocol( protocol.Protocol ):&lt;br /&gt;  def connectionMade( self ):&lt;br /&gt;    p = TestProcessProtocol()&lt;br /&gt;    p.lineReceived = self.lineReceived&lt;br /&gt;    p.outConnectionLost = self.transport.loseConnection&lt;br /&gt;    reactor.spawnProcess( p, "./sample_module.py", ["sample_module.py"] )&lt;br /&gt;&lt;br /&gt;  def lineReceived( self, line ):&lt;br /&gt;    self.transport.write( "GOT LINE: " + line )&lt;br /&gt;&lt;br /&gt;f = protocol.Factory()&lt;br /&gt;f.protocol = AdminServerProtocol&lt;br /&gt;&lt;br /&gt;reactor.listenTCP( 1079, f )&lt;br /&gt;reactor.run()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here's the spawned program.&lt;br /&gt;&lt;pre class="prettyprint lang-py"&gt;# sample_module.py&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;for y in range(5):&lt;br /&gt;  print "line 1"&lt;br /&gt;  print "line 2"&lt;br /&gt;  print "line 3"&lt;br /&gt;  print "line 4"&lt;br /&gt;&lt;br /&gt;  # Flush STDOUT. Added for clarity.&lt;br /&gt;  sys.stdout.flush()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let's see what happens when I run &lt;span style="font-style:italic;"&gt;test1.py&lt;/span&gt; and connect to it.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ telnet localhost 1079&lt;br /&gt;Trying 127.0.0.1...&lt;br /&gt;Connected to localhost.&lt;br /&gt;Escape character is '^]'.&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;line 2&lt;br /&gt;line 3&lt;br /&gt;line 4&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;line 2&lt;br /&gt;line 3&lt;br /&gt;line 4&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;line 2&lt;br /&gt;line 3&lt;br /&gt;line 4&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;line 2&lt;br /&gt;line 3&lt;br /&gt;line 4&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;line 2&lt;br /&gt;line 3&lt;br /&gt;line 4&lt;br /&gt;Connection closed by foreign host.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hmmm... not quite what we expected. It would be nice to receive the data line-by-line  instead of the whole buffer when it's flushed. Now, we could modify the spawned process to flush at every line, but sometimes we don't have that luxury. Also, if the process was rapidly spewing out data, or very long lines, we could end-up with partial (incomplete) lines.&lt;br /&gt;&lt;br /&gt;Unlike POE, we can't just apply a filter to the stream and let loose. And unfortunately, &lt;span style="font-style:italic;"&gt;protocol.LineReceiver&lt;/span&gt; can't help use because it only works with sockets. &lt;br /&gt;&lt;br /&gt;The solution would be to subclass &lt;span style="font-style:italic;"&gt;ProcessProtocol&lt;/span&gt;, and use that as our parent class.&lt;br /&gt;&lt;pre class="prettyprint lang-py"&gt;from twisted.internet import protocol, reactor&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;class LineProcessProtocol( protocol.ProcessProtocol ):&lt;br /&gt;  from os import linesep as newline&lt;br /&gt;  __out_line_buffer = ""&lt;br /&gt;&lt;br /&gt;  def outReceived( self, data ):&lt;br /&gt;    # Fill buffer&lt;br /&gt;    self.__out_line_buffer += data&lt;br /&gt;&lt;br /&gt;    # Split lines&lt;br /&gt;    lines = self.__out_line_buffer.splitlines()&lt;br /&gt;&lt;br /&gt;    if not data.endswith( self.newline ):&lt;br /&gt;      self.__out_line_buffer = lines.pop()&lt;br /&gt;    else:&lt;br /&gt;      self.__out_line_buffer = ""&lt;br /&gt;&lt;br /&gt;    for line in lines:&lt;br /&gt;      self.lineReceived( line )&lt;br /&gt;&lt;br /&gt;  def lineReceived( self, line ):&lt;br /&gt;    """Override This"""&lt;br /&gt;&lt;br /&gt;  def writeLine( self, data ):&lt;br /&gt;    self.transport.write( data + self.newline )&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;class TestProcessProtocol( LineProcessProtocol ):&lt;br /&gt;  pass&lt;br /&gt;&lt;br /&gt;class AdminServerProtocol( protocol.Protocol ):&lt;br /&gt;  def connectionMade( self ):&lt;br /&gt;    p = TestProcessProtocol()&lt;br /&gt;    p.lineReceived = self.lineReceived&lt;br /&gt;    p.outConnectionLost = self.transport.loseConnection&lt;br /&gt;    reactor.spawnProcess( p, "./sample_module.py", ["sample_module.py"] )&lt;br /&gt;&lt;br /&gt;  def lineReceived( self, line ):&lt;br /&gt;    self.transport.write( "GOT LINE: " + line + "\n")&lt;br /&gt;&lt;br /&gt;f = protocol.Factory()&lt;br /&gt;f.protocol = AdminServerProtocol&lt;br /&gt;&lt;br /&gt;reactor.listenTCP( 1079, f )&lt;br /&gt;reactor.run()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Above, we inheret &lt;span style="font-style:italic;"&gt;LineProcessProtocol&lt;/span&gt; from &lt;span style="font-style:italic;"&gt;ProcessProtocol&lt;/span&gt;, and create a string-buffer. Everytime data is received, we fill the string buffer and split up the lines. We then feed any &lt;span style="font-weight:bold;"&gt;complete&lt;/span&gt; lines to &lt;span style="font-style:italic;"&gt;lineReceived&lt;/span&gt;, while leaving only incomplete lines in the string buffer.&lt;br /&gt;&lt;br /&gt;Let's see what we get.&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;$ telnet localhost 1079&lt;br /&gt;Trying 127.0.0.1...&lt;br /&gt;Connected to localhost.&lt;br /&gt;Escape character is '^]'.&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;GOT LINE: line 2&lt;br /&gt;GOT LINE: line 3&lt;br /&gt;GOT LINE: line 4&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;GOT LINE: line 2&lt;br /&gt;GOT LINE: line 3&lt;br /&gt;GOT LINE: line 4&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;GOT LINE: line 2&lt;br /&gt;GOT LINE: line 3&lt;br /&gt;GOT LINE: line 4&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;GOT LINE: line 2&lt;br /&gt;GOT LINE: line 3&lt;br /&gt;GOT LINE: line 4&lt;br /&gt;GOT LINE: line 1&lt;br /&gt;GOT LINE: line 2&lt;br /&gt;GOT LINE: line 3&lt;br /&gt;GOT LINE: line 4&lt;br /&gt;Connection closed by foreign host.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Perfect! We can now use our LineProcessProtocol instead of the stock ProcessProtocol to parse process output.&lt;br /&gt;&lt;br /&gt;Now the question is, how easy is it to use Twisted's component architecture to create an interface (ILineReceiver), and adapters for the various classes?&lt;br /&gt;&lt;br /&gt;Or better still, how easy would it be to implement a generic stream framework within Twisted, making all this unnecessary, while still enjoying some POE goodness?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114142044627883988?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114142044627883988/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/03/line-filtering-with-twisted.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114142044627883988'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114142044627883988'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/03/line-filtering-with-twisted.html' title='Line Filtering With Twisted'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114132774997774952</id><published>2006-03-02T13:52:00.001-05:00</published><updated>2010-04-13T08:48:43.413-04:00</updated><title type='text'>Gentoo Emerge Issues</title><content type='html'>Last night I was unfortunate enough to break one of my staging servers during an &lt;span style="font-style:italic;"&gt;emerge world&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Access Violations&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;First was the dreaded "ACCESS VIOLATION SUMMARY" error. Portage builds packages inside a virtual sandbox, and uses it to ensure that ebuilds don't do anything bad.&lt;br /&gt;&lt;br /&gt;That said, sometimes not-fully-tested ebuilds get into the portage tree and make life a little more complicated.&lt;br /&gt;&lt;br /&gt;The right thing to do would be to file a bug-report, with details related to you environment, packages, etc. etc. But then, some of us just want to get things done a little quicker.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;&lt;br /&gt;$ FEATURES="-sandbox" emerge application&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The above command temporarily disables the sandbox during the emerge. Do NOT do this with &lt;span style="font-style:italic;"&gt;emerge world&lt;/span&gt;, as at that point, you'll just be testing fate.&lt;br /&gt;&lt;br /&gt;What you need to do is, first proceed with a normal emerge. Go through the logs and find out why exactly the access-violations occured (chmod, unlink etc.) If you're comfortable with what you see then disable the sandbox for &lt;span style="font-style:italic;"&gt;the broken application only&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Masking Packages&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Portage also tried to upgrade postgres from v7 to v8. No no. This won't do.&lt;br /&gt;&lt;br /&gt;This means that I would need v8 bindings for ruby, perl and python. Not to mention framework support for things like rails and twisted.&lt;br /&gt;&lt;br /&gt;Not going there.&lt;br /&gt;&lt;br /&gt;The solution to this is really quite simple. Add the following line to /etc/portage/package.mask.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-js"&gt;&lt;br /&gt;&gt;dev-db/postgresql-8.0.0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;No DevFS&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As close to the metal as Gentoo is, sometimes it does try and get ahead of itself. Like automatic /dev management detection. Within /etc/conf.d/rc, there's a new option called RC_DEVICES. It's set to "auto". This means that it tries to automatically detect if you're using devfs, udev or static device links.&lt;br /&gt;&lt;br /&gt;My kernel was compled with devfs support, but I wasn't using it. Gentoo detected it and assumed that devfs was my preference. Ugh. &lt;br /&gt;&lt;br /&gt;Now I had a system that wouldn't boot. You can imagine what it was like trying to figure this out.&lt;br /&gt;&lt;br /&gt;Anyhow, to get out of this mess you need to rebuild the kernel without DevFS support.&lt;br /&gt;&lt;br /&gt;Bummer.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Lesson&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Do _NOT_ use "emerge world". Instead, use "emerge -pv world", find the packages that need to be upgraded, and emerge them individually.&lt;br /&gt;&lt;br /&gt;Always use "-pv" to verify what portage is going to do before the real emerge.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114132774997774952?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114132774997774952/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/03/gentoo-emerge-issues.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114132774997774952'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114132774997774952'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/03/gentoo-emerge-issues.html' title='Gentoo Emerge Issues'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114114560244190926</id><published>2006-02-28T10:36:00.000-05:00</published><updated>2006-11-11T08:25:44.316-05:00</updated><title type='text'>Working at Google</title><content type='html'>So yesterday I accepted the job offer from Google. I will be working with their Site Reliability Engineering team in Manhattan, NY. &lt;br /&gt;&lt;br /&gt;I know, I know, I should've picked California. &lt;br /&gt;&lt;br /&gt;Anyhow, I first got an e-mail from Google last September about working there. I never really wanted to leave Toronto, but the opportunity sounded intriguing. I expressed my interest and the recruiter began to explain the screening and interview process to me. &lt;br /&gt;&lt;br /&gt;I then went through three technical phone-interviews, each about 45-minutes long. They covered everything from algorithms to networks to operating systems. I had to write programs in various languages &lt;span style="font-style:italic;"&gt;over the phone.&lt;/span&gt; That's right, "for open-parenthesis x equals 1 semicolon x less-than er no greater-than er yeah... blah blah".&lt;br /&gt;&lt;br /&gt;Ofcourse, since I'm in Canada, I had to do extra phone interviews just so that they could be sure about flying me down for the &lt;span style="font-style:italic;"&gt;real&lt;/span&gt; interview (or atleast that's what they told me).&lt;br /&gt;&lt;br /&gt;There I was at the GooglePlex in Mountain View, CA. Six back-to-back interviews scheduled. All of which were very technical, and very detailed. We discussed search algorithms, bandwidth throttling, optimization, bottleneck-finding, and wrote lots of quasi-code.&lt;br /&gt;&lt;br /&gt;It was as brutal as it was fun. By the end of it, I was exhausted. I initially wanted to tour and take pics of the campus (no pics allowed inside); but by the end of it, I just wanted to go home.&lt;br /&gt;&lt;br /&gt;Anyhow, a few weeks later, they told me that I got the job. Unfortunately, at the time I wasn't able to legally work in the U.S. without an H1-B. I had to wait until I got my Canadian citizenship, before I could start.&lt;br /&gt;&lt;br /&gt;Now I'm a Citizen, got my passport; and am starting with Google on the 27th of March.&lt;br /&gt;&lt;br /&gt;To anyone else who's interviewing at Google: don't sweat it. There's a lot of posts on the web where people talk about Google interviews being outrageously difficult, and something only Ph.D.s can get through; but that's just blown way out of proportion. &lt;br /&gt;&lt;br /&gt;They're not easy, but if you know your stuff, and enjoy what you do, you'll do good.&lt;br /&gt;&lt;br /&gt;So... I'm off by the end of March; I've got lots of stuff for sale; and after five years of being a Canadian resident, am once again going to be an American resident.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114114560244190926?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114114560244190926/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/02/working-at-google.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114114560244190926'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114114560244190926'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/02/working-at-google.html' title='Working at Google'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114087794249299290</id><published>2006-02-25T09:32:00.000-05:00</published><updated>2006-08-16T07:24:56.186-04:00</updated><title type='text'>Finding New Music with Google Sets</title><content type='html'>So here's something cool. &lt;a href="http://labs.google.com"&gt;Google Labs&lt;/a&gt; has this online set completion tool called &lt;a href="http://labs.google.com/sets"&gt;Google Sets.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Basically, you give it a list of items, and it finds you other similar items. For example, sending it "Beef" and "Pork", brings up "Poultry", "Chicken", "Seafood", "Lamb" etc.&lt;br /&gt;&lt;br /&gt;It appears that they harness their massive search history database, and use some kind of Bayesian or LSI classification system to produce results. But then again, it's Google, and likely way more complicated than that.&lt;br /&gt;&lt;br /&gt;Since I'm a big jazz fan, and jazz is a hugely unpopular genre among the masses, finding new artists is not very easy.&lt;br /&gt;&lt;br /&gt;So let's see what happens when I plug in the names of two popular jazz guitarists, "Pat Metheney" and "John Scofield".&lt;br /&gt;&lt;br /&gt;*type*type*click*&lt;br /&gt;&lt;br /&gt;Bill Frissel, Charlie Haden, Lyle Mays, Dave Holland, and many more.&lt;br /&gt;&lt;br /&gt;Okay, so they're not all guitarists, but they're jazz; and I did find some new interesting names.&lt;br /&gt;&lt;br /&gt;I wonder what happens if I add a few more names to the initial list: "Scott Henderson" and "Wayne Krantz".&lt;br /&gt;&lt;br /&gt;*type*type*click*&lt;br /&gt;&lt;br /&gt;Awesome! Mike Stern, Al DiMeola, Larry Coryell, &lt;br /&gt;John McLaughlin, Allan Holdsworth, Robben Ford, Pat Martino, Larry Carlton, and on and on and on.&lt;br /&gt;&lt;br /&gt;Just what I need. Some of them were even in the Fusion sub-genre of Jazz, like the names in the original list (well, except Metheney).&lt;br /&gt;&lt;br /&gt;BTW, you'll yield much better results by enclosing multi-word search queries (like ours above) within double-quotes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114087794249299290?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114087794249299290/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/02/finding-new-music-with-google-sets.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114087794249299290'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114087794249299290'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/02/finding-new-music-with-google-sets.html' title='Finding New Music with Google Sets'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-114009674064877659</id><published>2006-02-16T07:59:00.004-05:00</published><updated>2010-04-13T08:54:44.723-04:00</updated><title type='text'>4 Steps to Telephone Authentication</title><content type='html'>This article explains how you can use the telephone to authenticate users, using the &lt;a href="http://www.teleauth.com"&gt;TeleAuth&lt;/a&gt; web-service.&lt;br /&gt;&lt;br /&gt;TeleAuth provides an API that allows you to call a user and request a numeric secret &lt;span style="font-style:italic;"&gt;in real-time&lt;/span&gt;. It can be used within web-based applications, console apps, or for network services.&lt;br /&gt;&lt;br /&gt;It can also be used with online shopping sites to do things like collecting credit-card information, or delivering pass-codes.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S8RpDB7jBII/AAAAAAAABgo/NjpZTaUzHjc/s1600/screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 320px;" src="http://2.bp.blogspot.com/_Zfnd5XZP80Y/S8RpDB7jBII/AAAAAAAABgo/NjpZTaUzHjc/s400/screenshot.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5459604149058602114" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Step 1. Sign Up&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Visit &lt;a href="http://www.teleauth.com"&gt;http://www.teleauth.com&lt;/a&gt; and sign up for a beta account.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Step 2. Create an API Key&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Log into your account, and click the "Add New Key" link. A new API key will be generated for you to use in your SOAP or XML-RPC requests.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Step 3. Test It&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Expand the "Test Call" tab, and select an API Key. Then, enter a North American phone number, and select a prompt. The prompts are what the receiver hears when he/she receives a call (e.g., "Enter PIN code", or "Enter your credit-card number").&lt;br /&gt;&lt;br /&gt;Click "Place Call" to initiate the phone call. The call status is updated in real-time on the page.&lt;br /&gt;&lt;br /&gt;The phone should receive a call in a matter of seconds. Answer it, and enter some digits on the dial-pad. As soon as you hit the "#" key, you should see the result on the page.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Step 3. Try Some Code&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here's a simple Ruby program that calls a phone number and requests a PIN code:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-rb"&gt;#!/usr/bin/env ruby&lt;br /&gt;&lt;br /&gt;require 'soap/wsdlDriver'&lt;br /&gt;require 'cgi'&lt;br /&gt;&lt;br /&gt;WSDL_URL = "http://teleauth.com/phone/service.wsdl"&lt;br /&gt;soap_client = SOAP::WSDLDriverFactory.new(WSDL_URL).create_rpc_driver&lt;br /&gt;&lt;br /&gt;# Log SOAP request and response&lt;br /&gt;soap_client.wiredump_file_base = "soap-log.txt"&lt;br /&gt;&lt;br /&gt;# Place the phone call&lt;br /&gt;response = soap_client.getSecret( "SKJDekqQ4wUBiJEGFpkgA8Ph0bkkAXb",&lt;br /&gt;    "15556673323", "pin")&lt;br /&gt;&lt;br /&gt;# Display reponse&lt;br /&gt;puts response.result, " / ", response.secret, " / ", response.message&lt;/pre&gt;&lt;br /&gt;Above, we use the SOAP WSDL-schema from &lt;a href="http://teleauth.com/phone/service.wsdl"&gt;http://teleauth.com/phone/service.wsdl&lt;/a&gt;, and call the "GetSecret" method. The parameters we provide are: the API key, a phone number, and a voice-prompt.&lt;br /&gt;&lt;br /&gt;The return values are:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;result&lt;/span&gt;: The result code. Usually "OK", or "ERROR".&lt;br /&gt;&lt;span style="font-style:italic;"&gt;secret&lt;/span&gt;: The digits dialed on the key-pad (PIN code, credit-card number).&lt;br /&gt;&lt;span style="font-style:italic;"&gt;message&lt;/span&gt;: An error message, if the result was "ERROR".&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Step 4. Use It&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And there we have it. TeleAuth supports both SOAP and XML-RPC. Use it with Rails. Use it with Java. Use it with Z80 Assembler if you wish.&lt;br /&gt;&lt;br /&gt;The service is currently in beta, and is currently looking for testers.&lt;br /&gt;&lt;br /&gt;Enjoy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-114009674064877659?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/114009674064877659/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/02/4-steps-to-telephone-authentication.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114009674064877659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/114009674064877659'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/02/4-steps-to-telephone-authentication.html' title='4 Steps to Telephone Authentication'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_Zfnd5XZP80Y/S8RpDB7jBII/AAAAAAAABgo/NjpZTaUzHjc/s72-c/screenshot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19544619.post-113664301889715047</id><published>2006-01-07T09:10:00.000-05:00</published><updated>2006-03-27T15:42:01.920-05:00</updated><title type='text'>Useful OS X Shortcuts</title><content type='html'>When you spend 99% of your time on the terminal, it's nice not to have to use the mouse... _at all_. And this is quite close.&lt;br /&gt;&lt;br /&gt;OS X has a feature called "Full Keyboard Access", that needs to be enabled before the following will work.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Ctrl-F1&lt;/span&gt; Enable/Disable Full Keyboard Access&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Ctrl-F2&lt;/span&gt; Access Menubar&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Ctrl-F2&lt;/span&gt; Access Dock&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Ctrl-F7&lt;/span&gt; Navigate controls in current dialog&lt;br /&gt;&lt;br /&gt;More &lt;span style="font-style:italic;"&gt;full-access&lt;/span&gt; shortcuts at the &lt;a href="http://docs.info.apple.com/article.html?artnum=61466"&gt;Apple Support Page&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19544619-113664301889715047?l=0xfe.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://0xfe.blogspot.com/feeds/113664301889715047/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://0xfe.blogspot.com/2006/01/useful-os-x-shortcuts.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/113664301889715047'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19544619/posts/default/113664301889715047'/><link rel='alternate' type='text/html' href='http://0xfe.blogspot.com/2006/01/useful-os-x-shortcuts.html' title='Useful OS X Shortcuts'/><author><name>0xfe</name><uri>http://www.blogger.com/profile/11179501091623983192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry></feed>
