Skip to content

Instantly share code, notes, and snippets.

@danigb
Created March 21, 2016 13:38
Show Gist options
  • Save danigb/e7dd8e1d4f1ba93f83df to your computer and use it in GitHub Desktop.
Save danigb/e7dd8e1d4f1ba93f83df to your computer and use it in GitHub Desktop.
A javascript (incomplete) implementation of the paper "Lisp as second language" (http://www.mcg.uva.nl/papers/lisp2nd/functional.pdf)
/* global describe it */
var assert = require('assert')
var equal = assert.deepEqual
var M = require('./musical')
var note = M.note
var phrase = M.phrase
var chord = M.chord
var seq = M.sequential
var par = M.parallel
var arrayMax = function (arr) { return Math.max.apply(null, arr) }
var arrayAdd = function (arr) { return arr.reduce(function (a, b) { return a + b }) }
describe('Musical structures', function () {
it('creates a note', function () {
equal(note(2, 72, 0.5), [ 'note', 2, 72, 0.5 ])
})
it('create sequences', function () {
equal(seq(note(1, 60, 1), note(2, 62, 1)),
[ 'sequential', [ 'note', 1, 60, 1 ], [ 'note', 2, 62, 1 ] ])
})
it('create parallels', function () {
equal(par(note(1, 60, 1), note(1, 65, 0.5)),
[ 'parallel', [ 'note', 1, 60, 1 ], [ 'note', 1, 65, 0.5 ] ])
})
it('type of structure', function () {
equal(M.typeOf(seq(note(), note())), 'sequential')
})
it('elements of structure', function () {
equal(M.elementsOf(seq(note(1), note(2))),
[ [ 'note', 1 ], [ 'note', 2 ] ])
})
it('can create musical phrases sequences', function () {
equal(phrase('60 61 62 63', [0.5, 0.75]), [ 'sequential',
[ 'note', 0.5, '60', 1 ],
[ 'note', 0.75, '61', 1 ],
[ 'note', 0.5, '62', 1 ],
[ 'note', 0.75, '63', 1 ] ])
})
it('can create chords', function () {
equal(chord('60 65 70', 1, '0.5 0.2'), [ 'parallel',
[ 'note', 1, 60, 0.5 ],
[ 'note', 1, 65, 0.2 ],
[ 'note', 1, 70, 0.5 ] ])
})
})
describe('transformations', function () {
it('transpose notes', function () {
var transposeOct = M.transformation(M.sequential,
M.parallel, M.transposer(10))
equal(transposeOct(note(1, 60, 1)), [ 'note', 1, 70, 1 ])
equal(transposeOct(phrase('60 61 62', 1)),
[ 'sequential',
[ 'note', 1, 70, 1 ],
[ 'note', 1, 71, 1 ],
[ 'note', 1, 72, 1 ] ])
equal(transposeOct(chord('60 65 70', 1)),
[ 'parallel',
[ 'note', 1, 70, 1 ],
[ 'note', 1, 75, 1 ],
[ 'note', 1, 80, 1 ] ])
})
it('get durations', function () {
var duration = M.transformation(arrayAdd, arrayMax, M.note.duration)
var s = seq(chord('60 64 70', 3), phrase('50 51 52', 1))
equal(duration(s), 6)
})
it('longest note duration', function () {
var longestNoteDuration = M.transformation(arrayMax, arrayMax, M.note.duration)
equal(longestNoteDuration(seq(note(1), note(2),
par(note(7), note(2)), note(4))), 7)
})
it('number of notes', function () {
var numberOfNotes = M.transformation(arrayAdd, arrayAdd, function () { return 1 })
equal(numberOfNotes(seq(note(1), par(note(2), note(1)), note(1))), 4)
})
})
'use strict'
// ******** UTILITIES ******** //
var slice = Array.prototype.slice
var isArray = Array.isArray
// convert anything to an array
// it its a string, split it by spaces
function toArray (obj) {
if (Array.isArray(obj)) return obj
else if (typeof obj === 'string') return obj.split(/\s+/)
else return [ obj ]
}
// A typed list builder
function builderFor (name) {
return function (p) {
if (arguments.length === 1 && isArray(p)) return [name].concat(p)
else return [name].concat(slice.call(arguments))
}
}
// Helper functions to create sequences of notes
function notes (builder) {
return function (pitches, durations, velocities) {
var d = toArray(durations || 1)
var v = toArray(velocities || 1)
return builder(toArray(pitches).map(function (p, i) {
return note(+d[i % d.length], +p, +v[i % v.length])
}))
}
}
// ******** API ******** //
function typeOf (obj) { return isArray(obj) ? obj[0] : null }
function elementsOf (obj) { return obj.slice(1) }
var note = builderFor('note')
note.duration = function (n) { return n[1] }
note.pitch = function (n) { return n[2] }
note.velocity = function (n) { return n[3] }
var sequential = builderFor('sequential')
var parallel = builderFor('parallel')
function transposer (interval) {
return function (n) { return note(n[1], n[2] + interval, n[3]) }
}
var phrase = notes(sequential)
var chord = notes(parallel)
function transformation (seqTransform, parTransform, noteTransform) {
var transform = function (obj) {
switch (typeOf(obj)) {
case 'note':
return noteTransform(obj)
case 'sequential':
return seqTransform(elementsOf(obj).map(transform))
case 'parallel':
return parTransform(elementsOf(obj).map(transform))
default:
return obj
}
}
return transform
}
module.exports = {
typeOf: typeOf,
elementsOf: elementsOf,
note: note,
sequential: sequential,
parallel: parallel,
phrase: phrase,
chord: chord,
transposer: transposer,
transformation: transformation
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment