August 2008

Pics from the Office

by Joey deVilla on August 15, 2008

I was inspired by yesterday’s posting of the office space used by people developing the Boeing 787 Dreamliner to post photos of b5media’s tech team office. This is the old b5media office; the rest of the team — operations, content, marketing and advertising — are in an office across the hall.

The space is looking pretty zen right now, but I kind of like it. We’ll fill it up eventually.

You can click any of the thumbnails below to see the full-size photo:

{ 0 comments }

Enumerating Enumerable: Enumerable#first

by Joey deVilla on August 15, 2008

Enumerating Enumerable

Welcome to another installment of Enumerating Enumerable, my series of articles in I attempt to do a better job of documenting Ruby’s Enumerable module than Ruby-Doc.org. In this installment, I cover the first method.

In case you missed any of the previous articles, they’re listed and linked below:

  1. all?
  2. any?
  3. collect / map
  4. count
  5. cycle
  6. detect / find
  7. drop
  8. drop_while
  9. each_cons
  10. each_slice
  11. each_with_index
  12. entries / to_a
  13. find_all / select
  14. find_index

Enumerable#first Quick Summary

Graphic representing the "first" method in Ruby's "Enumerable" module

In the simplest possible terms What are the first n items in the collection?
Ruby version 1.8 and 1.9
Expects An optional integer n that specifies the first n items of the collection to return. If this integer is not given, n is 1 by default.
Returns If first is applied to a collection containing m elements:

  • The first item in the collection, if m > 0 and no argument n is provided.
  • An array containing the first n items in the collection, if m > 0 and an argument n is provided.
  • nil if the collection is empty and no argument n is provided.
  • The empty array [] if the collection is empty and an argument n is provided.
RubyDoc.org’s entry Enumerable#first

Enumerable#first and Arrays

When used on an array without an argument, first returns the first item in the array:

posts = ["First post!", "Second post!", "Third post!"]
=> ["First post!", "Second post!", "Third post!"]

# What's the first item in posts?
posts.first
=> "First post!"

# Here's the equivalent using array notation:
posts[0]
=> "First post!"

When used on an array with an integer argument n, first returns an array containing the first n items in the original array:

# What are the first 2 items in posts?
posts.first 2
=> ["First post!", "Second post!"]

# Note that when you provide an argument of 1,
# the result is still an array -- with just one element.
# If you want a scalar, don't use an argument.
posts.first 1
=> ["First post!"]

# Here's the equivalent using array slice notation:
posts[0..1]
=> ["First post!", "Second post!"]

posts[0...2]
=> ["First post!", "Second post!"]

When used on an empty array, first returns:

  • nil if no argument n is provided
  • The empty array, [], if an argument n is provided

[].first
=> nil

[].first 2
=> []

Enumerable#first and Hashes

In Ruby 1.8 and previous versions, hash order is seemingly arbitrary. Starting with Ruby 1.9, hashes retain the order in which they were defined, which makes the first method a little more applicable.

When used on a hash without an argument, first returns the first item in the hash as a two-element array, with the key as the first element and the corresponding value as the second element.

# Let's see what stages of partying our friends are in
party_stages = {"Alice" => :party_mineral,
                "Bob"   => :party_animal,
                "Carol" => :party_reptile,
                "Dave"  => :party_vegetable}
=> {"Alice"=>:party_mineral, "Bob"=>:party_animal, "Carol"=>:party_reptile, "Dave"=>:party_vegetable}

# Who's the first partier and what state is s/he in?
party_stages.first
=> ["Alice", :party_mineral]

When used on a hash with an integer argument n, first returns an array containing the first n items in the hash, with each item represented as a two-element array:

party_stages.first 2
=> [["Alice", :party_mineral], ["Bob", :party_animal]]

# Note that when you provide an argument of 1,
# the result is still an array -- with just one element.
# If you want a scalar, don't use an argument.
party_stages.first 1
=> [["Alice", :party_mineral]]

When used on an empty hash, first returns:

  • nil if no argument n is provided
  • The empty array, [], if an argument n is provided

{}.first
=> nil

{}.first 2
=> []

{ 0 comments }

Now THIS is a Cool Office!

by Joey deVilla on August 14, 2008

I used to envy the office space that local Ruby heroes Unspace have (they’re just down the street from b5), but now I envy the office space used by the people working on the Boeing 787 Dreamliner:

Boeing 787 under construction
Click to see a larger version.
Photo courtesy of Miss Fipi Lele.

{ 6 comments }

Enumerating Enumerable: Enumerable#find_index

by Joey deVilla on August 14, 2008

Enumerating Enumerable

Once again, it’s Enumerating Enumerable, my series of articles in which I attempt to outdo Ruby-Doc.org’s documentation of Ruby’s Enumerable module. In this article, I cover the find_index method, which was introduced in Ruby 1.9.

In case you missed any of the previous articles, they’re listed and linked below:

  1. all?
  2. any?
  3. collect / map
  4. count
  5. cycle
  6. detect / find
  7. drop
  8. drop_while
  9. each_cons
  10. each_slice
  11. each_with_index
  12. entries / to_a
  13. find_all / select

Enumerable#find_index Quick Summary

Graphic representation of the "find_index" method in Ruby's "Enumerable" module

In the simplest possible terms What’s the index of the first item in the collection that meets the given criteria?
Ruby version 1.9
Expects A block containing the criteria.
Returns
  • The index of the item in the collection that matches the criteria, if there is one.
  • nil, if no item in the collection matches the crtieria.
RubyDoc.org’s entry Enumerable#find_index

Enumerable#find_index and Arrays

When used on an array, find_index passes each item in the array to the given block and either:

  • Stops when the current item causes the block to return a value that evaluates to true (that is, anything that isn’t false or nil) and returns the index of that item, or
  • Returns nil if there is no item in the array that causes the block to return a value that evaluates to true.

Some examples:

# How about an array of the name of the first cosmonauts and astronauts,
# listed in the chronological order of the missions?
mission_leaders = ["Gagarin", "Shepard", "Grissom", "Titov", "Glenn", "Carpenter",
"Nikolayev", "Popovich"]
=> ["Gagarin", "Shepard", "Grissom", "Titov", "Glenn", "Carpenter", "Nikolayev",
"Popovich"]

# Yuri Gagarin was the first in space
mission_leaders.find_index{|leader| leader == "Gagarin"}
=> 0

# John Glenn was the fifth
mission_leaders.find_index{|leader| leader == "Glenn"}
=> 4

# And James Tiberius Kirk is not listed in the array
kirk_present = mission_leaders.find_index{|leader| leader == "Kirk"}
=> nil

Enumerable#find_index and Hashes

When used on a hash, find_index passes each key/value pair in the hash to the block, which you can “catch” as either:

  1. A two-element array, with the key as element 0 and its corresponding value as element 1, or
  2. Two separate items, with the key as the first item and its corresponding value as the second item.

As with arrays, find_index:

  • Stops when the current item causes the block to return a value that evaluates to true (that is, anything that isn’t false or nil) and returns the index of that item, or
  • Returns nil if there is no item in the array that causes the block to return a value that evaluates to true.

Some examples:

require 'date'
=> true

# These are the names of the first manned spaceships and their launch dates
launch_dates = {"Kedr"              => Date.new(1961, 4, 12),
                "Freedom 7"         => Date.new(1961, 5, 5),
                "Liberty Bell 7"    => Date.new(1961, 7, 21),
                "Orel"              => Date.new(1961, 8, 6),
                "Friendship 7"      => Date.new(1962, 2, 20),
                "Aurora 7"          => Date.new(1962, 5, 24),
                "Sokol"             => Date.new(1962, 8, 11),
                "Berkut"            => Date.new(1962, 8, 12)}
=> {"Kedr"=>#<Date: 4874803/2,0,2299161>, "Freedom 7"=>#<Date: 4874849/2,0,2299161>,
"Liberty Bell 7"=>#<Date: 4875003/2,0,2299161>, "Orel"=>#<Date: 4875035/2,0,2299161>,
"Friendship 7"=>#<Date: 4875431/2,0,2299161>, "Aurora 7"=>#<Date: 4875617/2,0,2299161>,
"Sokol"=>#<Date: 4875775/2,0,2299161>, "Berkut"=>#<Date: 4875777/2,0,2299161>}

# Where in the list is John Glenn's ship, the Friendship 7?
launch_dates.find_index{|ship, date| ship == "Friendship 7"}
=> 4

# Where in the list is the first mission launched in August 1962?
launch_dates.find_index{|ship, date| date.year == 1962 && date.month == 8}
=> 6

# The same thing, expressed a little differently
launch_dates.find_index{|launch| launch[1].year == 1962 && launch[1].month == 8}
=> 6

Using find_index as a Membership Test

Although Enumerable has a method for checking whether an item is a member of a collection (the include? method and its synonym, member?), find_index is a more powerful membership test for two reasons:

  1. include?/member? only check membership by using the == operator, while find_index lets you define a block to set up all sorts of tests. include?/member? asks “Is there an object X in the collection equal to my object Y?” while find_index can be used to ask “Is there an object X in the collection that matches these criteria?”
  2. include?/member? returns true if there is an object X in the collection that is equal to the given object Y. find_index goes one step further: not only can it be used to report the equivalent of true if there is an object X in the collection that is equal to the given object Y, it also reports its location in the collection.

A quick example of this use in action:

# Once again, the mission leaders
mission_leaders = ["Gagarin", "Shepard", "Grissom", "Titov", "Glenn", "Carpenter",
"Nikolayev", "Popovich"]
=> ["Gagarin", "Shepard", "Grissom", "Titov", "Glenn", "Carpenter", "Nikolayev",
 "Popovich"]

# Yuri Gagarin is in the list
gagarin_in_list = mission_leaders.find_index {|leader| leader == "Gagarin"}
=> 0

# Captain James T. Kirk is not
kirk_in_list = mission_leaders.find_index {|leader| leader == "Kirk"}
=> nil

# gagarin_in_list is 0, which as a non-false and non-nil value evaluates as true.
# We can use it as both a membership test *and* as his location in the list.
p "Gagarin's there. He's number #{gagarin_in_list + 1} in the list." if gagarin_in_list
"Gagarin's there. He's number 1 in the list."
=> "Gagarin's there. He's number 1 in the list."

# kirk_in_list is nil, which is one of Ruby's two "false" values.
# Let's use it with the "something OR something else" idiom that
# many Ruby programmers like.
kirk_in_list || (p "You only *think* he wasn't there.")
"You only *think* he wasn't there."
=> "You only *think* he wasn't there."

Parts that Haven’t Been Implemented Yet

Ruby-Doc.org’s documentation is generated from the comments in the C implementation of Ruby. It mentions a way of calling find_index that is just like calling include?/member?:

# What the docs say (does not work yet!)
["Alice", "Bob", "Carol"].find_index("Bob")
=> 1

# What happens with the current version of Ruby 1.9
["Alice", "Bob", "Carol"].find_index("Bob")
ArgumentError: wrong number of arguments(1 for 0)
...


Ruby 1.9 is considered to be a work in progress, so I suppose it’ll get implemented in a later release.

{ 0 comments }

XBox on a REALLY Big Screen

by Joey deVilla on August 13, 2008

"Grand Theft Auto" on a movie screen

Here’s something for gamers who want to go big: the Cineplex chain of movie theatres in Canada is renting out downtime at 29 of its locations to people who want to play XBox 360 games on their giants screens. CDN$179 (US$169) gets you and 11 of your friends 2 hours’ worth of big screen time.

Here’s an excerpt from the CBC article:

Theatres will generally have about 12 to 24 hours of available downtime a week, mostly in the morning, she said. Many theatres are in “full grind” right now showing summer movies, but they should slow down and have more available time once school begins in September.

Theatres may also stay open late into the evening to accommodate groups, at the discretion of each manager.

“If they wanted to book a four-hour window, we could certainly go later in the evening,” [Pat Marshall, Cineplex's vice-president of communications] said. “If the theatre manager has the staffing, they could go till two in the morning.”

The wife is a big Rock Band aficionado. Maybe I could book something for her birthday…

{ 2 comments }

Nine Startup Diseases and How to Cure Them

by Joey deVilla on August 13, 2008

"Game Over" screen from the '80s arcade game "Battlezone"

Maybe I’m a glutton for punishment: my current job as Tech Project Manager at b5media marks the fourth startup for which I’ve worked; if you count Mackerel Interactive Multimedia — whose story, Burying the Fish, was written by Cory Doctorow for Wired but never published — I’ve worked at five. I like the “feel” of working at a startup, and now that I’ve got experience and real-world and blog-based reputations to back me up, startups are willing to pay me not only to be part of their team but to also be the “adult supervision”. At the ripe old age of 40, I’m an elder statesman in these parts (and playing an old man’s instrument only adds to that image).

That’s why I read SitePoint’s article Nine Deadly Startup Diseases—and How to Cure Them with a sense of deja vu, going through each item in their list of mistakes and saying “yup, did that one…did that one too…”

Put together, the startups for which I worked had all but one of the diseases listed in the article except for “Marketing Blind Spot”. For some reason, there was always a marketer in our midst, drumming it into our heads that marketing was necessary.

I’ve taken their list of startup diseases and cures and summarized it in the table below. For full explanations behind each disease and cure, be sure to read the article.

Startup Disease Cure
Imaginary User Syndrome: Your product isn’t targeted at anyone in particular. Establish a small, defined set of users who could benefit from your product and tailor it to them.
Frenetic Distraction Pox: Wasting time on non-essential tasks that don’t bring the business closer to break-even or profit. Focus!
Wrong Hire Infection: You’ve hired people who can’t perform or who underperform. “The smart, brave solution in those cases is amputation. Let them go gently if you want, but let them go.”
Implicit Promise Fever: You’re assuming that there are certain promises made between you and your co-founders, but you haven’t discussed them directly or put them in writing. “Have those discussions. Write the results down.”
Stealth Product Delusion: You’re waiting way too long to show your product to users while honing it to perfection (or as close to perfect as you can get). Get people to look at it! They’ll have some criticism, but that feedback is going to be very valuable.
Wrong Platform Fracture: The platform on which you’re developing (language, framework, technology) keeps getting in the way of development. Maybe you think you’ve gone too far to turn around and switch platforms. Switch platforms! ““We’ve walked this far already” isn’t a good enough reason to continue heading in that direction. Chances are, you’re much, much further from the completion of your product than you think.”
Other Interest Disorder: Other interests are pulling at you; you’re either saying “but I’m still working on my startup” and “I’ll get back to my startup soon” or working on several startups at once. Pick the project you want to work on, and break cleanly from the others.
Perfection Hallucination: You’re spending a large amount of time getting your prodcut to the point where it’s perfect, especially close to the end of the product development cycle. “Users are more forgiving of progress in the wrong direction than of a lack of progress. What you’ve built will never be perfect, but if it’s close enough your users will tell you how to improve it…Release early, release often.”
Marketing Blind Spot: You’re not doing any marketing. Do some marketing! “Marketing doesn’t have to cost much, but if you don’t do enough of it, you’re setting yourself up for failure.”

{ 0 comments }

“why the lucky stiff” on Why You Should Create

by Joey deVilla on August 8, 2008

Why\'s photo-illustration of his book, \"why\'s (Poignant) Guide to Ruby\"
why’s (Poignant) Guide to Ruby, the most whimsical programming book ever written.

Here’s a great quote from the enigmatic programmer known only as “why the lucky stiff” on why you should create:

when you don’t create things, you become defined by your tastes rather than ability. your tastes only narrow & exclude people. so create.

Very true, especially since there are whole industries and professions that specialize in manipulating your tastes in order to get you to line other people’s pockets. Well put, why!

{ 2 comments }

An Illustrated Guide to the Kaminsky DNS Vulnerability

by Joey deVilla on August 8, 2008

Diagram of Dan Kaminsky\'s explanation of how DNS can be \"poisoned\"

Steve Friedl has a number of excellent technical explanations on his site, and his latest one, An Illustrated Guide to the Kaminsky DNS Vulnerability, is a masterpiece that does a fine job of explaining the DNS vulnerability that Dan Kaminsky found.

{ 0 comments }

Copy and Paste

by Joey deVilla on August 8, 2008

In the very unlikely event that you forgot what the keyboard shortcuts were…

Two wonen: one wearing a \"Copy (control + C)\" T-shirt, the other wearing a \"Paste (control + V)\" T-shirt.

{ 4 comments }

Linux Bloat

by Joey deVilla on August 8, 2008

Slide: Linux Symposium T-shirt sizes in 1999 (mostly medium) and 2008 (mostly XL, followed by large and XXL)

It could be that programmers are getting larger, but it also could be that Linux Symposium is using American Apparel shirts. They’re supposedly not made in sweatshops and are made from really soft cotton, but they’re about a size smaller than the corresponding Hanes Beefy-T’s.

{ 2 comments }

b5media is Looking for Contract PHP/MySQL Developers

by Joey deVilla on August 7, 2008

PHP, MySQL and WordPress developers wanted at b5media

Attention Toronto-area developers! If you’ve got good PHP/MySQL chops (having a little experience building WordPress templates and plugins would be a bonus, but not absolutely necessary), perhaps you’d like to do a little contract work for my company, b5media. Our regular programming staff has its hands full with our core projects, so I’m using my authority as the company’s Nerd Wrangler (a.k.a. Technical Project Manager) to cast a net for local developers who’d like to work on a project or two.

I can’t go into details here on the blog — here’s what I can tell you:

  • We’re a network of about 300 or so blogs and our revenue is based on advertising, which in turn is based on our readership.
  • Our blogs are based on WordPress. Mark Jaquith, one of our programmers, is a WordPress core developer. As you may have already reasoned out, our stuff is based on MySQL and PHP.
  • I have a couple of projects:
    • A WordPress-based promo site using a customized template and featuring some custom widgets and plugins, and
    • An ecommerce site that makes it easy for people to purchase ads on our blogs

If you’re picked to do the job, you’ll be working from a pretty complete spec written by someone who understands both the business and technical sides of the job: me! You’ll also be reporting to me, and I’m told that I’m a pretty good guy to work with.

There’s something to be said for face-to-face meetings and being able to walk up to the project lead and ask questions, so we’d rather hire a developer in town.

Interested? Drop me a line at my work email address — joey@b5media.com — and I’ll be happy to tell you more.

{ 1 comment }

Internet Memes Timeline

by Joey deVilla on August 7, 2008

Internet Meme Timeline

Feeling nostalgic for “Ate My Balls”, “I Kiss You” or “All Your Base are Belong to Us”? The Internet Memes Timeline’s got the cure for that.

{ 1 comment }

Code Swarm: A Visual History of Python Commits

by Joey deVilla on August 7, 2008

The Code_Swarm video is an interesting visualization of the evolution of the work done on the Python programming language and the people involved, tracing its evolution from late 1990 to mid-2005.


code_swarm – Python from Michael Ogawa on Vimeo.

The intro for the video says:

In 1991, Guido van Rossum released his Python scripting language to the public.

This video will take you through the history of the project, compressed into a fraction of the time.

You will see the names of developers fade in and out of prominence, while the files they work on swirl around them.

Red files are core code. Blue files are documents. Yellow files are modules. The histogram on the bottom tracks the size and time of commits. When a person makes a commit, their name stands out. The files they commit also stand out. Files grow in size every time they are committed. Files and people gradually fade when there is no activity.

[Thanks to The War on Folly.]

{ 0 comments }

Enumerating Enumerable

We’re at lucky number 13 — the thirteenth article in the Enumerating Enumerable series, which covers the methods of Ruby’s Enumerable module. I started this series after being disappointed with the documentation at Ruby-Doc.org.

In this article, I’m covering the find_all — a.k.a. select — method.

In case you missed any of the previous articles, they’re listed and linked below:

  1. all?
  2. any?
  3. collect / map
  4. count
  5. cycle
  6. detect / find
  7. drop
  8. drop_while
  9. each_cons
  10. each_slice
  11. each_with_index
  12. entries / to_a

Enumerable#find_all / Enumerable#select Quick Summary

Graphic representation of the \"find_all\" / \"select\" method in Ruby\'s \"Enumerable\" module

In the simplest possible terms Which items in the collection meet the given criteria?
Ruby version 1.8 and 1.9
Expects A block containing the criteria.
Returns An array containing the items in the collection that meet the given criteria. If no items in the collection meet the given criteria, this is an empty array.
RubyDoc.org’s entry Enumerable#find_all / Enumerable#select

Enumerable#find_all / Enumerable#select and Arrays

When used on an array, find_all and its synonym select passes each item from the array to the block, returning an array containing only those elements in the original array for which the block returns a value that doesn’t evaluate to false.

If no item in the array causes the block to return a value that doesn’t evaluate to false, find_all/select returns an empty array.

In the examples that follow (which are based on my examples for the detect/find method), I’ll be using the find_all method. select does exactly the same thing; it’s just that I prefer find_all.

# Once again,  I shall establish my "old fart" credentials
classic_rock_bands = ["AC/DC", "Black Sabbath", "Queen", \
"Ted Nugent and the Amboy Dukes", "Scorpions", "Van Halen"]
=> ["AC/DC", "Black Sabbath", "Queen", "Ted Nugent and the Amboy Dukes",
"Scorpions", "Van Halen"]

# Of the bands in the array, which ones have
# a name longer than 8 characters?
classic_rock_bands.find_all {|band| band.length > 8}
=> ["Black Sabbath", "Ted Nugent and the Amboy Dukes", "Scorpions", "Van Halen"]

# Which ones have a name exactly 5 characters long?
classic_rock_bands.find_all {|band| band.length == 5}
=> ["AC/DC", "Queen"]

# If no band in the array meets the criteria,
# find_all returns an empty array.
# Which ones have names shorter than 5 characters?
classic_rock_bands.find_all {|band| band.length < 5}
=> []

Enumerable#find_all / Enumerable#select and Hashes

When used on a hash, find_all/select passes each key/value pair in the hash to the block, which you can “catch” as either:

  1. A two-element array, with the key as element 0 and its corresponding value as element 1, or
  2. Two separate items, with the key as the first item and its corresponding value as the second item.

As with arrays, find_all/select passes each item from the hash to the block, returning an array containing only those items in the original array for which the block returns a value that doesn’t evaluate to false.

Note that each item in the result array is a two-element array corresponding to an item from the original hash. In this array, element 0 contains the key and element 1 contains the corresponding value.

If no item in the hash causes the block to return a value that doesn’t evaluate to false, find_all/select returns an empty array.

years_founded = {"AC/DC" => 1973, \
                 "Black Sabbath" => 1968, \
                 "Queen" => 1970, \
                 "Ted Nugent and the Amboy Dukes" => 1967, \
                 "Scorpions" => 1965, \
                 "Van Halen" => 1972}
=> {"AC/DC"=>1973, "Black Sabbath"=>1968, "Queen"=>1970,
"Ted Nugent and the Amboy Dukes"=>1967, "Scorpions"=>1965, "Van Halen"=>1972}

# Ruby 1.9 preserves hash order so that hashes keep the order in which
# you defined them, while Ruby 1.8 puts them in some mysterious order.
# All these examples are in Ruby 1.9

# Which bands were founded in 1970 or later?
years_founded.find_all {|band| band[1] >= 1970}
=> [["AC/DC", 1973], ["Queen", 1970], ["Van Halen", 1972]]

# Here's another way of phrasing it:
years_founded.find_all {|band, year_founded| year_founded >= 1970}
=> [["AC/DC", 1973], ["Queen", 1970], ["Van Halen", 1972]]

# Which bands were founded after 1980?
years_founded.find_all {|band, year_founded| year_founded > 1980}
=> []

{ 0 comments }

Sayonara, Cassettes

by Joey deVilla on August 6, 2008

Pictured below is the death spasm of a recording format: the compact cassette (a.k.a. “cassette tape”), sitting on the bargain shelf at a drugstore somewhere in the U.S., its price reduced so that it’s one of the cheapest items in the store. Even gum is probably more expensive:

Maxell cassette tapes on sale
Photo courtesy of Miss Fipi Lele.

Here’s another death spasm: an invitation for a farewell party for the cassette held by the book publishing company Hachette, pictured below. Audiobooks were the cassette’s last domain, but in the age of the iPod and phones that double as MP3 players, they had become obsolete:

Hachette\'s invitation to a farewell party for the compact cassette
Image from the New York Times.
Click it to see the source story.


In the first half of the 1980s, the music formats available to a teenager were vinyl records and cassette tape (formally known in the industry as “compact cassette”). CDs hit the market in late 1982, but the first pressings were mostly of classical music and cost anywhere from $20 – $35, well out of the reach of most teenagers (remember, these are 1980s dollars!).

Vinyl was far cheaper: if you were smart and shopped downtown (as opposed to the record stores in the malls, where the prices were $2 – $5 higher), domestic albums sold for about $8 – $12 and imports, special releases and double albums went for about $12 – $18. They didn’t have the signal-to-noise ratio that CDs had, but on a good turntable on a half-decent sound system, you got better sound than a lot of downsampled MP3s playing on the budget speakers that came free with your computer.

Diagram showing the internals of a compact cassette
The internals of a compact cassette.

At the bottom of the hi-fi spectrum is the compact cassette. A clunky storage medium, it was often “hissy”, with a signal-to-noise ratio equivalent to listening to a jazz band in a small club while sitting near the air conditioner. The tape was prone to stretching from the stresses involved in both normal playback and more so with fast-forwarding and rewinding, especially in the case of the C120 (120 minute) cassette, whose tape had to be made thinner so that its reel would still fit inside the shell. Finally, there was its mechanical nature: it had actual moving parts whose quality would have a direct impact on your sound. A cheap shell, a wobbly reel, a misaligned guide roller or any combination thereof could make it sound worse.

In spite of all these disadvantages, it became an incredibly popular format. Cassettes were portable and handled jostling well, which made them perfect for car audio and the Walkman. They also represented the first time that most people could create what we now take for granted in the age of digital audio: the customized playlist in the form of the mix tape. If you were dating in the ’80s, making a mix tape was an important courtship ritual:

\"Breakup Girl\" on mix tapes
From the Breakup Girl comic, Mixed Messages.
Click the image to read the full comic.

Mix tapes didn’t make everyone happy: the record companies became quite concerned about people passing around copies of their music or making copies of their music for their car or Walkman and put up some campaigns to stop home taping, including the infamous “Home Taping is Killing Music” promotion:

Home taping is killing music
History proved this was a lie.

By the way, it turned out to be a lie, as the music industry boomed as home taping blossomed, and home taping for personal use is not illegal; it’s fair use.

For a while, sales of albums in cassette form surpassed those on vinyl or CD. The lesson to be learned from this is the same one that the MP3 format taught us: in spite of what the audiophiles will tell you, versatility and convenience trumps sound quality.

In the days before MP3s and MySpace, before CD-burning was available to the masses, the cassette was the only economical way for a small band to get their music into their audience’s hands. A number of bands got their start this way; one famous local example was the Barenaked Ladies’ “Yellow Tape”, pictured below, which many fans say featured better performances than those on the CD that followed after they got the record deal:

The Barenaked Ladies\' \"Yellow Tape\"
Now a collector’s item.

Finally there’s a way I used cassettes that you may have never encountered: as a storage medium for computer data. Back in the late 70s and early 80s, before 5 1/4″ floppy drives became cheap and ubiquitous, it was the preferred way to store your home computer’s programs and data. Even the original IBM PC used them:

Commodore \"datasette\" cassette recorder and \"computer\" cassettes
Slow but reliable: cassette tapes as computer data storage medium. Some synthesizers of the era also used cassette tape for data storage.

(Somewhere in my parents’ basement sits a pile of cassettes holding my high school programming assignments written on Waterloo Structured BASIC for the Commodore PET. I’m curious to see what the programs I wrote back then look like.)


I don’t miss the cassette: I rather like a world where my music is in digital form and moves frictionlessly from my iPod to my computer to my USB key and across the net (and sometimes onto my camera chip when there’s no other place to store it). I haven’t owned a cassette player in about 8 years — come to think of it, I don’t even own a stand-alone CD Player anymore. Still, I feel I should pay tribute to that clunky mechanical piece of tech that served me so well in my youth.

Maybe I’ll pick up that USB key that comes in mix tape-inspired packaging:

\"Mix Tape\" USB key

{ 3 comments }