Skip to content

Instantly share code, notes, and snippets.

@mattly
Created December 19, 2009 08:20
Show Gist options
  • Select an option

  • Save mattly/260006 to your computer and use it in GitHub Desktop.

Select an option

Save mattly/260006 to your computer and use it in GitHub Desktop.
class MelodyBase
include Alexandria
design :hooks do
hash :predictive
# this indicates there are map.js and reduce.js functions in
# designs/hooks/views/predictive that are designed to be
# called from a group reduce, the results of which look like a
# ruby hash full of keys/values.
# predictive is a map/reduce view that looks at a "hook" document,
# or a 1/2/4/8 bar melody and iterates across it
# using a markov chain type of function. it uses collation to narrow specificity.
# for details on the theory, read up on jchris's markov chains with mapreduce:
# http://jchrisa.net/drl/_design/sofa/_show/post/markov_chains_using_couchdb_s_g
# for a melody that looks like: C E G F E <rest> D A it would emit:
# | key | value
# | ["C", "E", "G", "F", "E"] | 1
# | ["D", "A"] | 1
# | ["E", null, "D", "A"] | 1
# | ["E", "G", "F", "E", null] | 1
# | ["F", "E", null, "D", "A"] | 1
# | ["G", "F", "E", null, "D"] | 1
# you'll notice that for any given note, it will emit the next four notes
# alternately, we could emit intervals, but that makes explaining how
# the library works a lot trickier
# and the reduce is simply a sum
end
end
# so let's insert a few melodies at the "nes" database in the local couch,
# remembering how the indexes are constructed above:
# C E G F E <rest> D A
# C Db F Ab F E E B
# C F D A G F A <rest>
#
# ok, so now we want to say, hey, I've got an 'F', what comes next?
@db = MelodyBase.new('nes')
f = @db.hooks.predictive(:range => ["F"])
# translates into :startkey => ["F", nil], :endkey => ["F", {}], :group => 2
# it infers the group number from the length of the range and the fact that
# this is a 'hash' group reduce view.
# returns:
# { "A" => 1, "Ab" => 1, "D" => 1, "E" => 2 }
# the query handler knows about the parent range and automatically removes
# it from the keys presented from the results returned
# so we infer that there's a 20% chance each of the next note being an A,
# Ab or D, and a 40% chance of it being an E.
# let's pretend for the sake of interestingness our markov chain chooses the E
f.range.push("E").get
# appends "E" to our range, so we're now querying for
# :startkey => ["F", "E", nil], :endkey => ["F", "E", {}], :group => 3
# returns:
# { nil => 1, "E" => 1 }
# neat!
# let's say we're operating in the context of a step sequencer that doesn't
# like rests, and whose scale rejects the repeated "E" value:
f.range.pop
# do our random thing again, this time choosing "A". our range is now ["F", "A"]
f.range.push("A").get
# { nil => 1 }
# d'oh!, we don't like rests, we're too house for that
f.range.shift
# the range is now ["A"]
f.get
# { "G" => 1, nil => 1 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment