diff --git a/src/jumski/midi_dataset_toolkit/core.clj b/src/jumski/midi_dataset_toolkit/core.clj index 39acb5c..1886e7f 100644 --- a/src/jumski/midi_dataset_toolkit/core.clj +++ b/src/jumski/midi_dataset_toolkit/core.clj @@ -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)))) diff --git a/src/jumski/midi_dataset_toolkit/toolkit.clj b/src/jumski/midi_dataset_toolkit/toolkit.clj index 5f020cd..15933af 100644 --- a/src/jumski/midi_dataset_toolkit/toolkit.clj +++ b/src/jumski/midi_dataset_toolkit/toolkit.clj @@ -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" @@ -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")))