Created
March 21, 2016 13:38
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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) | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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