Skip to content

Commit

Permalink
Merge pull request #1 from jumski/refactor
Browse files Browse the repository at this point in the history
Refactor code to be more idiomatic and use less privates
  • Loading branch information
jumski authored Mar 2, 2020
2 parents 3790695 + 9b5a12a commit 85eaf02
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/jumski/midi_dataset_toolkit/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
(if (empty? args)
(println "Please provide path to midi file or files!")
(doseq [path args
:let [steps-string (toolkit/midi-file-to-steps-string path)]]
:let [steps-string (toolkit/midi-file->steps-stream path)]]
(println steps-string))))
82 changes: 24 additions & 58 deletions src/jumski/midi_dataset_toolkit/toolkit.clj
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,6 @@
(and (= :note-on (:command event))
(not (nil? (:note event)))))

(defn- simplify-event
"Returns map that contains only :timestamp and :note"
[event]
(select-keys event [:timestamp :note]))

(defn- read-note-ons-from-file
"Returns a list of events, flattened from all tracks in midi path from path"
[path]
(->> (midifile/midi-file path)
(:tracks)
(map :events)
(flatten)
(filter note-on?)
(map simplify-event)))

(defn- group-by-timestamp
"Returns map of timestamp to list of events at given timestamp"
[events]
(->> (group-by :timestamp events)))

(defn- sort-numerically-by-first
"Returns list sorted by first elements of colls, numerically, lower first"
[colls]
(sort #(< (first %1) (first %2)) colls))

(defn- extract-timestamp-and-note
"Returns list of lists of notes"
[time-events]
(for [[timestamp events] time-events]
[timestamp (map :note events)]))

(defn- convert-to-steps
"Returns list of lists, each sublist containing :note values for all events
occuring at the same :timestamp. Outer list is sorted by :timestamp,
lower first."
[note-on-events]
(->> note-on-events
(group-by-timestamp)
(extract-timestamp-and-note)
(sort-numerically-by-first)
(map #(nth % 1))))

(defn- notes-to-bitmask
"Returns 128-chars long string of 0s and 1s, representing all possible notes
that should play at given step"
Expand All @@ -66,23 +24,31 @@
str-bitmask (map bool-to-str (reverse bitmask))]
(clojure.string/join str-bitmask)))

(defn- notes-to-bitstring-steps
"Returns list of bitstrings, each representing one step with all notes,
where 1s correspond to note being played at this step and 0s to notes being off.
Bitstring is encoded in little-endian order (least significant bit is on the right)."
[notes]
(->> notes
(convert-to-steps)
(map notes-to-bitmask)
(map bitmask-to-bitstring)))
(defn- events->steps-stream
"Converts list of events to multiline string of steps
Takes list of hashes (from `overtone.midi.file`) from `:events` for some `:track`
and converts it to multiline string of 0s and 1s. Each line is 128 chars long
and represents a binary number encoded in little endian, where 1s are notes
that are playing for given step."
[events]
(let [note-ons (filter note-on? events)
times-and-notes (map (juxt :timestamp :note) note-ons)
times-to-events (group-by first times-and-notes)
times-to-notes (for [[k v] times-to-events] [k (vec (map last v))])]
(->> (sort-by first times-to-notes)
(map last)
(map notes-to-bitmask)
(map bitmask-to-bitstring)
(clojure.string/join "\n"))))

;;; Public functions

(defn midi-file-to-steps-string
"Returns string representing steps composed of bitstrings based on notes
read from midi file at path."
(defn midi-file->steps-stream
"Loads midi file and outputs one steps-stream concatenating step-streams for each track."
[path]
(->> (read-note-ons-from-file path)
(notes-to-bitstring-steps)
(clojure.string/join "\n")))

(->> (midifile/midi-file path)
(:tracks)
(map :events)
(map events->steps-stream)
(filter (complement empty?))
(clojure.string/join "\n")))

0 comments on commit 85eaf02

Please sign in to comment.