Cogs and Levers A blog full of technical stuff

Bridging Science and Art with Ruby

Introduction

Having a love for music and technology at the same time can be a dangerous business. You can really fool yourself into thinking that you can boil art down into algebraic or procedural recipes that you can just turn the handle on. The frustration sets in when it’s just not that black-and-white. In this post, I’ve put some Ruby code together that takes in an array of musical intervals and attempts to give that array of intervals a name.

In music, this is better known as a chord or arpeggio.

Assumptions

As above, I want an array of intervals in so this is going to take shape in the form of an integer array. These integers will be the individual distances (in semi-tones) from the root which will start at 0. For reference, here’s a chart that I used during development.

First Octave Second Octave Interval Names
0 12 Root/Unison
1 13 Minor Second/Flat Nine
2 14 Major Second/Nine
3 15 Minor Third
4 16 Major Third
5 17 Perfect Fourth/Eleventh
6 18 Tritone/Sharp Eleven
7 19 Perfect Fifth
8 20 Minor Sixth/Flat Thirteen
9 21 Major Sixth/Thirteenth
10 22 Minor Seventh
11 23 Major Seventh

</div><div>
Intervals that are emphasised, didn’t really factor into the overall solution as they add nothing to the chord’s quality or extension. Well, this may not be entirely true, some jazz-heads have probably already got their cross-hairs locked onto me, ready to flame away.

Gathering facts

First of all, we need the array of intervals into our class. We’ll always assume that there is a root and if there isn’t one, we’ll pop one into the array for good measure.

class Chord

   def initialize(intervals)
      if not intervals.include?(0)
         intervals = [0] + intervals
      end

      @intervals = intervals
   end
   
end

We’re now managing an array of intervals. What we need to do is ask questions of that array so we can gather information about our chord. The following methods ask the important questions in the first octave.

def minor?
   @intervals.include?(3)
end

def major?
   @intervals.include?(4)
end

def has_fourth?
   @intervals.include?(5)
end

def has_tritone?
   @intervals.include?(6)
end

def has_augmented_fifth?
   @intervals.include?(8)
end

def has_sixth?
   @intervals.include?(9)
end

def dominant?
   @intervals.include?(10)
end

def has_major_seven?
   @intervals.include?(11)
end

With just this base information we can find a lot out about the chord, but it’s not nearly enough. We need to be sure. The best part about putting these basic building blocks in place is that our methods are going to read a little more human from now on. Here are some more fact finders.

def diminished?
   self.minor? and self.has_tritone?
end

def augmented?
   self.major? and self.has_augmented_fifth?
end

def has_third?
   self.minor? or self.major?
end

def suspended?
   not self.has_third? and (self.has_second? or self.has_fourth?)
end

def has_seventh?
   self.dominant? or self.has_major_seven?
end

Finally we have a couple more tests that we need to conduct on the array in the upper octave, otherwise none of the jazz-guys are going to get their chords! These again will form syntactic sugar for the main feature, to_s.

def has_minor_ninth?
   @intervals.include?(13)
end

def has_ninth?
   @intervals.include?(14)
end

def has_augmented_ninth?
   @intervals.include?(15)
end

def has_eleventh?
   @intervals.include?(17)
end

def has_sharp_eleventh?
   @intervals.include?(18)
end

def has_minor_thirteenth?
   @intervals.include?(20)
end

def has_thirteenth?
   @intervals.include?(21)
end

Piecing it together

It’s good that we know so much about our chord. It means that we can ask questions and make decisions based on the outcomes of these questions. Upon reflection, I did have another idea on assembling those fact-finding methods into a more flexible but highly-unhuman set of methods that when you asked about a particular interval, you’d get back either :flat, :natural or :sharp. Perhaps I’ll try it in another revision; I digress. All the facts are in front of us, let’s construct a string from these facts. Now here’s where the awkward bridge between science and art starts to burn a little, then a lot. Questions must not only be asked of the array, but they have to be asked in the right order otherwise you’ll get a different result.</div><div>
</div><div>That’s naming for you though. It doesn’t read pretty, but here it is.

def to_s

   quality = "Major " if self.major?
   quality = "Minor " if self.minor?
   quality = "Augmented " if self.augmented? and not self.has_seventh?
   quality = "Diminished " if self.diminished? and not self.has_seventh?

   extensions = ""

   if not self.suspended?

      if (self.major? and self.has_major_seven?) or
         (self.minor? and self.dominant?)
         extensions = "Seventh "
      else
         if self.dominant?
            quality = "Seventh "
         end
      end

   else
      quality = "Suspended "
      extensions = "Second " if self.has_second?
      extensions = "Fourth " if self.has_fourth?
   end

   if self.has_sixth?
      if self.has_tritone?
         quality = "Diminished "
         extensions = "Seventh "
      else
         extensions += "Sixth "
      end
   end

   if not self.diminished? or self.has_seventh?
      extensions += "Flat Five " if self.has_tritone?
   end

   if not self.augmented? or self.has_seventh?
      extensions += "Sharp Five " if self.has_augmented_fifth?
   end

   if self.has_seventh?
      extensions += "Flat Nine " if self.has_minor_ninth?

      if self.has_ninth?
         if self.dominant?
            quality = ""
            extensions = "Ninth "
         else
            extensions += "Nine"
         end
      end

      if self.has_eleventh?
         if self.dominant?
            quality = ""
            extensions = "Eleventh "
         else
            extensions += "Eleven"
         end
      end

      if self.has_thirteenth?
         if self.dominant?
            quality = ""
            extensions = "Thirteenth "
         else
            extensions += "Thirteen"
         end
      end

      extensions += "Sharp Nine " if self.has_augmented_ninth?
      extensions += "Sharp Eleven " if self.has_sharp_eleventh?
   end

   extensions = extensions.strip()
   chord_name = quality.strip()
   chord_name += " " if chord_name.length > 0 and extensions.length > 0
   chord_name += extensions if extensions.length > 0

   chord_name
end

Woosh. That was a lot of if-treeing. There has to be a better way of doing this, but by my calculations using this code I’ve covered the following use-cases off:

  • Major
  • Minor
  • Diminished
  • Diminished Seventh
  • Augmented
  • Seventh
  • Minor Seventh
  • Major Seventh
  • Suspended Second
  • Suspended Fourth
  • Seventh Flat 5
  • Seventh Sharp 5
  • Major Seventh Flat 5
  • Major Seventh Sharp 5
  • Minor Seventh Flat 5
  • Minor Seventh Sharp 5
  • Ninth
  • Eleventh
  • Thirteenth

There’s a few chords there, but there are just so many more and this code may work for them. Those other cases just haven’t made it into my unit tests yet.

Plans

I really want to make this part of a bigger application that I’m writing at the moment. I was motivated to write a unit like this because I didn’t want chord definitions just sitting in a database, I wanted the application to do the think work. Possibilities from here also reach into inversions and slash chords. It would be easy to permute the list of intervals and enumerate all of the inversion scenarios.

Anyway, until next time!

Clojure and Leiningen quickstart

Introduction

Clojure is the modern LISP. Clojure is an elegant, dynamic programming language that runs a-top the JVM. In today’s post, I’ll show you how to get started with Clojure & Leiningen.

Getting installed

I’ve written this article from the perspective of a Debian user. Translating these steps into your own native environments should be as easy as re-structuring the installation steps to target your package manager of choice. Installing Clojure & Leiningen was a simple as this:

$ sudo apt-get install clojure leiningen

You’re done. You should now have Clojure and Leiningen at your disposal.

Initial steps

You’ll want to kick the tires on this puppy, so from your bash prompt fire up the REPL environment and try a few commands:

$ clojure
Clojure 1.2.1
user=> (+ 2 4)
6
user=> (print "Clojure is installed!")
Clojure is installed!nil
user=> (if (= 1 1) (print "Yes, 1 does equal 1") (print "Mathematics just stopped working"))
Yes, 1 does equal 1nil

Alright! Enough of this already. Let’s generate a project. Leiningen is a painless way to get started on your Clojure project. All you have to do, is issue the following commands and you’ve got a project ready to go:

$ lein new projname

Where projname is the name of your project. For this test, I just called mine “myproj”. If you have a look inside the directory that Leiningen has just generated for you, you’ll see the following sub-directories:

lib         - holds your programs dependencies
project.clj - a clojure file describing your project 
README      - duh! 
src         - your source files 
test        - any tests for your application

This is a pretty neat-and-tidy layout, ready for you to start coding.

Bulding and running and cleaning, oh my!

Leiningen also makes it very easy to build, run and cleanup your project. Here’s how. From within your project directory:

# Build your project
$ lein compile

# Clean any built files
$ lein clean

# Run your project
$ lein run

Too easy.

List comprehension in Haskell

Introduction

Generating and operating on lists of data in languages is an essential tool when doing even the most basic of processing. List comprehension is just this process. It’s prevalent in most of today’s languages and today I want to post about this process in Haskell.

A numeric example

Working with numeric data is probably the easiest of examples to help you grasp this concept. Let’s start by using a list comprehension to generate a list of numbers 1-through-5.

[x | x <- [1..5]]

This will return a list looking like [1, 2, 3, 4, 5]. Analysing what we’ve just written here, we can see that what we want to return is on the left hand side of the pipe | symbol, how we want to generate each value sits on the right hand side of the pipe symbol. This expression is wrapped in square braces because we want a list! We can change this ever so slightly to only give us back odd numbers like so:

[[x | x <- [1..5], odd x]

You can see that we’ve just concatenated another piece of criteria to the right hand side of the expression specifying that we only want odd numbers. We then end up with a list looking like [1, 3, 5]. These are still very simple examples, but we can do some very powerful things with these expressions. Take the following for example.

[[x * y | x <- [1..5], y <- [1..5]]

Looking at the left-hand side you can see that we want the multiple of x and y. On the right-hand side you can see that both x and y iterate 1-through-5, so we end up with the following:

[1,2,3,4,5,2,4,6,8,10,3,6,9,12,15,4,8,12,16,20,5,10,15,20,25]

Now we’re getting somewhere.

Folding in Haskell

Introduction

In this post I would like to present some basic concepts in folding. This really will be over in a flash, so don’t blink - it’s easy.

tl;dr

The Haskell functions foldl and foldr allow you to “fold” functions between values.

Diagram it for me!

If you read up on these functions in the documentation, you’ll see mention of “reducing values in a list” and such. All this really means, is that you’re going to iterate through a list apply a function at every stop and finish up with a “reduced” answer. Here, take a look at this. I have an array spanning 1 through 5.

[1, 2, 3, 4, 5]

I want to add all of the values together, so I use foldl to move through the list applying the + operator.

foldl (+) 0 [1,2,3,4,5]

This command in english says, apply the + operator between each element in the list (moving left to right) with an initial value of 0. Or:

0 + 1 + 2 + 3 + 4 + 5 = 15

Bring it back the other way!

Folding right is interesting. No so much for the example that we have above, as addition moving left or right is at identity with each other. I’ve prepared a more interesting example for moving to the right. Take a look at the following and the results:

foldl (-) 10 [10, 20, 30]
-50

foldr (-) 10 [10, 20, 30]
10

Wow, that’s quite the difference. When folding right, the reduction occurs in reverse (values right to left) and then it’s applied to the initial value.

(foldl)
10 - 10 - 20 - 30     = -50 

(foldr)
10 - (20 - (30 - 10)) = 10

So, there we are folding to the right! That’s (very basic) folding for you anyway. An interesting follow-up to this article is the function foldl'. By nature foldl that we’ve just discussed is lazy, meaning it will build the list of computations to execute using the source array and only execute those computations once the array is depleted (internally). It’s been shown that this model of execution can cause stack overflow errors for larger lists because of this deferred execution model. foldl' solves this by not deferring execution. So, the overall result will be the same it’s just that foldl' won’t be lazy in getting the answer back to you.

Function currying in Haskell

I think it’s important to follow up my previous post on anonymous functions with a post on currying. One of the more difficult concepts to think about (only because Haskell does a great job of separating you from this) is that every function only has 1 argument.

Take this basic greeting example which expects the name of someone who is doing the greeting and the name of someone who is being greeted:

let sayHello toWho fromWho = fromWho ++ " says Hello to " ++ toWho

This is a pretty trivial example. Give it two names and the computer will appear to play nice between these two people:

*Main> sayHello "John" "Peter"
"Peter says Hello to John"

We like John so much, that we’re going to make a new function using this existing one.

let sayHelloToJohn = sayHello "John"

So now, we can get anyone to play nice with John:

*Main> sayHelloToJohn "Peter"
"Peter says Hello to John"
*Main> sayHelloToJohn "Joe"
"Joe says Hello to John"
*Main> sayHelloToJohn "Jane"
"Jane says Hello to John"

Great! We’ve just made a partially applied function. When you don’t specify enough parameters to a function, you’re actually returned a function (or, partially applied function) that you can continue to use. Breaking down how this works, when Jane is saying hello to John she is actually doing so by doing this:

(sayHello "John") "Jane"

This should at least explain my outlandish claims above of functions only having one argument, anyway. You’ve just witnessed function currying in motion. These principles are also directly applicable on infix functions as well, they just need a little extra help to be told so. Take this for example:

double :: (Floating a) => a -> a
double = (*2)

Ignoring the function definition, you can see that all you need to do for infix functions is to surround then with parenthesis. You need to supply the value that makes it a partial application of course!