From 8a2edb7aab50d8b443ac5df4fc7ec208ebaff112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Thu, 1 May 2025 11:45:00 -0600 Subject: [PATCH 01/54] [cs] fix analysis and quick-cs-subsets functions Inversions of the intervals were not being accounted. --- src/erv/constant_structures/brute_force.cljc | 41 +++++++++++++------- src/erv/constant_structures/core.cljc | 27 +++++++++---- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/erv/constant_structures/brute_force.cljc b/src/erv/constant_structures/brute_force.cljc index 45e457b..a197a7b 100644 --- a/src/erv/constant_structures/brute_force.cljc +++ b/src/erv/constant_structures/brute_force.cljc @@ -2,7 +2,7 @@ "Find constant structures by brute force" (:require [clojure.math.combinatorics :refer [combinations]] - [erv.constant-structures.core :refer [analyze maybe-round]] + [erv.constant-structures.core :refer [analyze maybe-rationalize]] [erv.utils.conversions :as conv] [erv.utils.core :refer [interval]] [erv.utils.ratios :refer [ratios->scale]] @@ -92,7 +92,7 @@ (let [[deg1 deg2] (sort deg-pair) a (:bounded-ratio (nth scale deg1)) b (:bounded-ratio (nth scale deg2))] - (maybe-round (interval a b)))) + (maybe-rationalize (interval a b) 10))) (def ^:private memoized-deg-combinations (memoize (comp #(map sort %) @@ -102,22 +102,35 @@ (def ^:private conj-set (fnil conj #{})) +(defn- invert-interval [interval period] + (* period (/ 1 interval))) + (defn- quick-check-cs? [deg-combinations scale] - (reduce - (fn [data deg-pair] - (let [[deg1 deg2] deg-pair - steps (- deg2 deg1) - interval (get-interval scale deg-pair) - updated-data (update data interval conj-set steps) - interval-steps (updated-data interval)] - (if (> (count interval-steps) 1) - (reduced {}) - updated-data))) - {} - deg-combinations)) + (let [period (-> scale first :bounding-period) + size (count scale)] + (reduce + (fn [data deg-pair] + (let [[deg1 deg2] deg-pair + steps (- deg2 deg1) + interval (get-interval scale deg-pair) + inversion-steps (- size steps) + inversion (invert-interval interval period) + updated-data (-> data + (update interval conj-set steps) + (update inversion conj-set inversion-steps)) + interval-steps (updated-data interval)] + (when (> inversion period) + (timbre/error "Error in calculation, `inversion` cannot be larger than period")) + (if (> (count interval-steps) 1) + (reduced {}) + updated-data))) + {} + deg-combinations))) (defn quick-cs-subsets + "Calculate all CS subset of the given sizes. + FIXME only really works for JI scales." [cs-sizes scale] (eduction (mapcat #(combinations (range (count scale)) %)) diff --git a/src/erv/constant_structures/core.cljc b/src/erv/constant_structures/core.cljc index 540548b..1d72438 100644 --- a/src/erv/constant_structures/core.cljc +++ b/src/erv/constant_structures/core.cljc @@ -13,22 +13,33 @@ (round2 6 n)) :cljs (round2 6 n))) +(defn maybe-rationalize + [n decimals] + #?(:clj (if (rational? n) + n + (rationalize (round2 decimals n))) + ;; TODO implement + :cljs (round2 6 n))) + (defn get-intervals - [note-pair] + [scale-size note-pair] (->> note-pair ((fn [[a b]] - {(maybe-round (interval (:bounded-ratio a) - (:bounded-ratio b))) - {:steps #{(- (:index b) - (:index a))} - :intervals [[a b]]}})))) + (let [period (:bounding-period a) + intvl (maybe-round (interval (:bounded-ratio a) + (:bounded-ratio b))) + inversion (* period (/ 1 intvl))] + {intvl {:steps #{(- (:index b) (:index a))} + :intervals [[a b]]} + inversion {:steps #{(- scale-size (- (:index b) (:index a)))} + :intervals [[b a]]}}))))) (defn analyze [scale] (let [interval-data (->> (combo/combinations (map-indexed #(assoc %2 :index %1) scale) 2) - (map get-intervals) + (map (partial get-intervals (count scale))) (apply merge-with (partial merge-with concat)) (map (fn [[interval data]] {interval (update data :steps (partial into #{}))})) @@ -64,7 +75,7 @@ (:scale (cps/make 2 [11 13 5 7])))) (:constant-structure? (analyze - (:scale (cps/make 2 [1 3 5 7 9])))) + (:scale (cps/make 2 [1 3 5 7])))) (:constant-structure? (analyze (:scale (edo/from-pattern [2 2 1 2 2 2 1])))) #_(:non-cs-intervals (analyze From ffed952394e916afb70e31ec153317628f31e2a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 2 May 2025 10:46:17 -0600 Subject: [PATCH 02/54] Export new functions --- src/js/export_fn.cljs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/export_fn.cljs b/src/js/export_fn.cljs index 6799dba..58d36a9 100644 --- a/src/js/export_fn.cljs +++ b/src/js/export_fn.cljs @@ -3,6 +3,7 @@ [erv.cps.core :as cps] [erv.edo.core :as edo] [erv.mos.mos :as mos] + [erv.scale.core :as scale] [erv.utils.conversions :as conv] [erv.utils.core :as utils])) @@ -11,4 +12,6 @@ :mos {:make (comp clj->js mos/make)} :edo {:fromPattern (comp clj->js edo/from-pattern)} :utils {:rotate (comp clj->js utils/rotate) - :ratioToCents (comp clj->js conv/ratio->cents)}})) + :ratioToCents (comp clj->js conv/ratio->cents) + :freqToMidi (comp clj->js conv/cps->midi)} + :scale {:degToFreq (comp clj->js scale/deg->freq)}})) From 115da79a70c4e283d89614c72354ddeb7bf87367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 2 May 2025 12:18:06 -0600 Subject: [PATCH 03/54] [npm] Update JS erv exports --- package.json | 5 ++++- src/js/export_fn.cljs | 11 +++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6cdeb09..3ffc612 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@diegovdc/erv", - "version": "0.0.8", + "version": "0.0.14", "description": "Calculations and other algorithmic treatments of some of [Erv Wilson's](http://anaphoria.com/wilson.html) music scale theories.", "scripts": { "release:lib": "shadow-cljs release lib; sed -i '' 's/global/globalThis/g' dist/lib.js", @@ -8,6 +8,9 @@ "prepublishOnly": "npm run release:lib" }, "main": "dist/lib.js", + "files": [ + "dist/" + ], "keywords": [ "microtonality", "music theory" diff --git a/src/js/export_fn.cljs b/src/js/export_fn.cljs index 58d36a9..125577c 100644 --- a/src/js/export_fn.cljs +++ b/src/js/export_fn.cljs @@ -5,6 +5,7 @@ [erv.mos.mos :as mos] [erv.scale.core :as scale] [erv.utils.conversions :as conv] + [erv.utils.ratios :as ratios] [erv.utils.core :as utils])) (defn generate-exports [] @@ -13,5 +14,11 @@ :edo {:fromPattern (comp clj->js edo/from-pattern)} :utils {:rotate (comp clj->js utils/rotate) :ratioToCents (comp clj->js conv/ratio->cents) - :freqToMidi (comp clj->js conv/cps->midi)} - :scale {:degToFreq (comp clj->js scale/deg->freq)}})) + :centsToRatio (comp clj->js conv/cents->ratio) + :freqToMidi (comp clj->js conv/cps->midi) + :ratiosToScale (comp clj->js + (fn [period ratios] + (ratios/ratios->scale period (js->clj ratios))))} + :scale {:degToFreq (comp clj->js (fn [scale root degree] + (let [scale* (js->clj scale {:keywordize-keys true})] + (scale/deg->freq scale* root degree))))}})) From aeb4d8d34fddd7fc72281d8dc9d3b278599eff11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Mon, 12 May 2025 20:05:54 -0600 Subject: [PATCH 04/54] Add comment --- src/erv/constant_structures/brute_force.cljc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/erv/constant_structures/brute_force.cljc b/src/erv/constant_structures/brute_force.cljc index a197a7b..7ea3eb8 100644 --- a/src/erv/constant_structures/brute_force.cljc +++ b/src/erv/constant_structures/brute_force.cljc @@ -141,7 +141,9 @@ (when (seq (quick-check-cs? deg-pairs subscale)) (map #(nth scale %) degs-combination))))) cs-sizes)) - +(comment + (require '[clojure.math.combinatorics :as combo]) + (combo/count-combinations (range 31) 22)) (defn take-quick-cs-subsets "Takes n-items after dropping n-items. Takes an `eduction` as returned by `quick-cs-subsets`. NOTE this code is a general solution, but has specific names for documentation purposes. From b88f8c85050331dcaed03e92c81dbe5f268d5b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 13 May 2025 12:38:29 -0600 Subject: [PATCH 05/54] [scale.utils] add diamond function --- src/erv/utils/scale.clj | 18 +++++++++++++++--- test/erv/utils/scale_test.clj | 36 ++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/erv/utils/scale.clj b/src/erv/utils/scale.clj index 74beafa..cc19a4c 100644 --- a/src/erv/utils/scale.clj +++ b/src/erv/utils/scale.clj @@ -103,15 +103,15 @@ rotation))) (defn cross-set - [period & ratios] - (let [scale (->> ratios + [period & ratio-vecs] + (let [scale (->> ratio-vecs (apply combo/cartesian-product) (map #(apply * %)) flatten (ratios->scale period) dedupe-scale)] {:meta {:scale :cross-set - :sets ratios + :sets ratio-vecs :size (count scale) :period period} :scale scale})) @@ -150,3 +150,15 @@ (reduce (fn [acc n] (conj acc (+ n (or (last acc) 0)))) [0]) (drop-last (if remove-octave? 1 0))))) + +(defn diamond + [period & factors] + (let [scale (->> (combo/cartesian-product factors factors) + (mapv (fn [[a b]] (/ a b))) + (ratios->scale period) + dedupe-scale)] + {:meta {:scale :diamond + :factors factors + :size (count scale) + :period period} + :scale scale})) diff --git a/test/erv/utils/scale_test.clj b/test/erv/utils/scale_test.clj index d77074b..52ad1b9 100644 --- a/test/erv/utils/scale_test.clj +++ b/test/erv/utils/scale_test.clj @@ -3,7 +3,7 @@ [clojure.test :refer [deftest is testing]] [erv.edo.core :as edo] [erv.utils.ratios :refer [ratios->scale]] - [erv.utils.scale :refer [cross-set dedupe-scale degree-stack + [erv.utils.scale :refer [cross-set dedupe-scale degree-stack diamond find-subset-degrees get-degrees rotate-scale scale->stacked-subscale scale-intervals scale-steps->degrees tritriadic]])) @@ -207,3 +207,37 @@ (deftest scale-steps->degrees-test (is (= [0 2 4 5 7 9 11 12] (scale-steps->degrees [2 2 1 2 2 2 1] false)))) + +(deftest diamond-test + (is (= {:meta {:scale :diamond + :factors [1 3 5 7 9] + :period 2 + :size 19} + :scale + [{:bounded-ratio 1 :bounding-period 2 :ratio 1} + {:bounded-ratio 10/9 :bounding-period 2 :ratio 10/9} + {:bounded-ratio 9/8 :bounding-period 2 :ratio 9/8} + {:bounded-ratio 8/7 :bounding-period 2 :ratio 8/7} + {:bounded-ratio 7/6 :bounding-period 2 :ratio 7/6} + {:bounded-ratio 6/5 :bounding-period 2 :ratio 6/5} + {:bounded-ratio 5/4 :bounding-period 2 :ratio 5/4} + {:bounded-ratio 9/7 :bounding-period 2 :ratio 9/7} + {:bounded-ratio 4/3 :bounding-period 2 :ratio 4/3} + {:bounded-ratio 7/5 :bounding-period 2 :ratio 7/5} + {:bounded-ratio 10/7 :bounding-period 2 :ratio 10/7} + {:bounded-ratio 3/2 :bounding-period 2 :ratio 3/2} + {:bounded-ratio 14/9 :bounding-period 2 :ratio 14/9} + {:bounded-ratio 8/5 :bounding-period 2 :ratio 8/5} + {:bounded-ratio 5/3 :bounding-period 2 :ratio 5/3} + {:bounded-ratio 12/7 :bounding-period 2 :ratio 12/7} + {:bounded-ratio 7/4 :bounding-period 2 :ratio 7/4} + {:bounded-ratio 16/9 :bounding-period 2 :ratio 16/9} + {:bounded-ratio 9/5 :bounding-period 2 :ratio 9/5}]} + (diamond 2 1 3 5 7 9))) + + (testing "A `diamond` is a special case of `cross-set`" + (is (= + (map :bounded-ratio (:scale (diamond 2 1 3 5 7 9))) + (map :bounded-ratio (:scale (cross-set 2 + [1 3 5 7 9] + (map #(/ 1 %) [1 3 5 7 9])))))))) From 2c20ff99e755b005681aebc84dea78c1ff106b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Sun, 10 Aug 2025 19:53:06 -0600 Subject: [PATCH 06/54] Formatting --- src/erv/constant_structures/graphics.cljc | 1 - src/erv/cps/cycles.cljc | 13 +++--------- src/erv/cps/cycles/v2.cljc | 17 +++++++-------- src/erv/cps/similarity.clj | 12 ++++------- src/erv/cps/utils.cljc | 25 ++++++++++------------- src/erv/edo/core.cljc | 9 ++++---- src/erv/meru/scratch/beatings.cljc | 20 ++++++++---------- src/erv/scratch.clj | 1 - test/erv/scale/core_test.clj | 2 -- 9 files changed, 40 insertions(+), 60 deletions(-) diff --git a/src/erv/constant_structures/graphics.cljc b/src/erv/constant_structures/graphics.cljc index 6711336..b9f483a 100644 --- a/src/erv/constant_structures/graphics.cljc +++ b/src/erv/constant_structures/graphics.cljc @@ -83,7 +83,6 @@ (q/arc 0 0 (+ i* (- radius 100)) (+ i* (- radius 100)) start end)))))) ;; Draw a circle at x y with the correct diameter - (defn make-state [scale added-notes] (let [scale+added-notes* (scale+added-notes scale added-notes) diff --git a/src/erv/cps/cycles.cljc b/src/erv/cps/cycles.cljc index 9a66274..956ec57 100644 --- a/src/erv/cps/cycles.cljc +++ b/src/erv/cps/cycles.cljc @@ -2,8 +2,6 @@ (:require [clojure.set :as set] [erv.utils.core :as utils])) - - (defn get-next-nodes [graph cycle] (->> (graph (-> cycle :seq first)) (reduce @@ -33,13 +31,11 @@ :status :open}))) ()))) - (defn update-finder-state [previous-state new-interation-result] (let [{:keys [open closed]} (group-by :status new-interation-result)] {:open open :closed (into (:closed previous-state) closed)})) - (defn init-state [graph] {:open (mapv (fn [node] {:seq (list node) @@ -73,9 +69,8 @@ (if (-> acc :set (contains? cycle*)) acc {:set (apply conj (:set acc) (utils/get-all-rotations cycle*)) - :cycles (conj (:cycles acc) cycle*)} - )) - ) + :cycles (conj (:cycles acc) cycle*)}))) + {:set #{} :cycles []} cycles))))) (comment @@ -152,6 +147,4 @@ {:seq (#{7 5} #{1 3} #{7 1}), :set #{#{7 1} #{7 5} #{1 3}}, :status :open} {:seq (#{7 3} #{1 3} #{7 1}), :set #{#{7 1} #{7 3} #{1 3}}, :status :open} {:seq (#{1 5} #{1 3} #{7 1}), :set #{#{7 1} #{1 5} #{1 3}}, :status :open} - {:seq (#{1 3} #{7 1}), :set #{#{7 1} #{1 3}}, :status :closed})) - - ) + {:seq (#{1 3} #{7 1}), :set #{#{7 1} #{1 3}}, :status :closed}))) diff --git a/src/erv/cps/cycles/v2.cljc b/src/erv/cps/cycles/v2.cljc index db5c13a..3bba02d 100644 --- a/src/erv/cps/cycles/v2.cljc +++ b/src/erv/cps/cycles/v2.cljc @@ -170,12 +170,13 @@ (str/join " - "))) (str/join "\n") (spit "eikosany-harmonic-triad-cycles-of-A.B.C.txt"))) +(comment -(->> hexany - :meta - :cps/factors - (map-indexed - (fn - [i fac] - {fac (str (char (+ 65 i)))})) - (into {})) + (->> hexany + :meta + :cps/factors + (map-indexed + (fn + [i fac] + {fac (str (char (+ 65 i)))})) + (into {}))) diff --git a/src/erv/cps/similarity.clj b/src/erv/cps/similarity.clj index 86a469f..4489e8c 100644 --- a/src/erv/cps/similarity.clj +++ b/src/erv/cps/similarity.clj @@ -8,8 +8,6 @@ [erv.utils.core :as utils] [clojure.string :as str])) - - (defn twelvulate [scale] (map #(-> % (/ 100) float (Math/round) (* 100)) scale)) @@ -42,7 +40,6 @@ 813.6862861351651 933.1290943962624)) - (defn +cents [cps] (assoc cps :cents (->> cps :scale @@ -83,11 +80,10 @@ %1 %2))))) (+euclidean-distance {:cents '(0 204 316 519 702 1018)}) -(sort (map #(-> % (- 182) (mod 1200)) '(0 182 386 498 701 884) )) +(sort (map #(-> % (- 182) (mod 1200)) '(0 182 386 498 701 884))) (defn +gens [factors cps] (assoc cps :factors factors)) - (comment (require '[clojure.math.combinatorics :as combo] ;; '[clojure.data.csv :as csv] @@ -122,9 +118,9 @@ (with-open [writer (io/writer "3oo7-similarity-to-12edo-scales-up-to-23.csv")] (csv/write-csv writer (->> #_cps-sorted-by-euclidean-distance - #_cps-sorted-by-euclidean-distance-up-to-53 - cps-sorted-by-euclidean-distance-up-to-23 - (mapv (juxt :factors :mode :cents :closest-12-edo :euclidean-distance )) + #_cps-sorted-by-euclidean-distance-up-to-53 + cps-sorted-by-euclidean-distance-up-to-23 + (mapv (juxt :factors :mode :cents :closest-12-edo :euclidean-distance)) (mapv (fn [data] (mapv #(cond (= java.lang.Long (type %)) % diff --git a/src/erv/cps/utils.cljc b/src/erv/cps/utils.cljc index 58552f1..b05977c 100644 --- a/src/erv/cps/utils.cljc +++ b/src/erv/cps/utils.cljc @@ -52,8 +52,7 @@ {}))) (comment - (make-degree->note (erv.cps.core/make 2 [1 3 5 7])) - ) + (make-degree->note (erv.cps.core/make 2 [1 3 5 7]))) (defn make-set->degrees-map [{:keys [scale] :as _cps}] @@ -68,7 +67,6 @@ (reduce (fn [m [deg set]] (update m deg (fnil conj #{}) set)) {}))) - (defn- set-d1-intersection? [set1 set2] (= 1 (count (set/difference set1 set2)))) @@ -86,7 +84,7 @@ (map #(set (conj % degree-set))) (filter #(->> (combo/combinations % 2) (map (partial apply set-d1-intersection?)) - (every? true?)) ) + (every? true?))) set))) (comment @@ -94,7 +92,6 @@ 4 0))) - (defn harmonic-set-degrees "Returns a list of harmonic sets (as degrees) for a specific degree of a cps A harmonic set is a set where all notes are connected with any other by all of its factors except one." @@ -104,16 +101,16 @@ (sort (map (comp #(into [] %) sort (partial map set->degrees)) - sets))))= + sets)))) = (comment (= - [#{#{#{7 5} #{3 5} #{7 3}} - #{#{7 5} #{3 5} #{1 5}} - #{#{7 1} #{7 5} #{7 3}} - #{#{7 1} #{7 5} #{1 5}}} + [#{#{#{7 5} #{3 5} #{7 3}} + #{#{7 5} #{3 5} #{1 5}} + #{#{7 1} #{7 5} #{7 3}} + #{#{7 1} #{7 5} #{1 5}}} '((0 1 4) (0 1 5) (0 2 4) (0 2 5))] - (let [cps (erv.cps.core/make 2 [1 3 5 7]) - set-size 3] - [(harmonic-sets cps set-size 0) - (harmonic-set-degrees cps set-size 0)]))) + (let [cps (erv.cps.core/make 2 [1 3 5 7]) + set-size 3] + [(harmonic-sets cps set-size 0) + (harmonic-set-degrees cps set-size 0)]))) diff --git a/src/erv/edo/core.cljc b/src/erv/edo/core.cljc index 6e4c373..b5856df 100644 --- a/src/erv/edo/core.cljc +++ b/src/erv/edo/core.cljc @@ -24,11 +24,11 @@ (def submosi (make-all-submos (mos 6) 5)) (do) (def submos) (-> submosi #_(->> (filter :true-submos?)) - #_ #_ (nth 0) :submos - #_ #_ (nth 1) :mos) + #_#_(nth 0) :submos + #_#_(nth 1) :mos) (demo! (:scale (from-pattern submos)) :note-dur 200 :direction :down) (demo! (:scale (from-pattern [3,5,2,5,3,5,4])) :note-dur 200 :direction :down) - (demo! (:scale (from-pattern[6, 3, 4, 3, 7, 4, 3, 1] 2)) :note-dur 200 :direction :up )) + (demo! (:scale (from-pattern [6, 3, 4, 3, 7, 4, 3, 1] 2)) :note-dur 200 :direction :up)) (defn from-pattern "For use with `mos` patterns or other custom intervalic patterns, i.e. [3 2 3 2 2]" @@ -47,5 +47,4 @@ :bounding-period period}) degrees)}))) - -(from-pattern [ 2, 2, 5, 2, 5, 2, 5, 2, 2, 5, 2, 5, 2, 5, 2, 5]) +(from-pattern [2, 2, 5, 2, 5, 2, 5, 2, 2, 5, 2, 5, 2, 5, 2, 5]) diff --git a/src/erv/meru/scratch/beatings.cljc b/src/erv/meru/scratch/beatings.cljc index b303771..be01e4b 100644 --- a/src/erv/meru/scratch/beatings.cljc +++ b/src/erv/meru/scratch/beatings.cljc @@ -12,13 +12,13 @@ [root ratios] (->> ratios (mapcat - (fn [degree ratio] - (map (fn [i] {:degree degree - :ratio ratio - :partial i - :partial-ratio (* i ratio)}) - (range 1 9))) - (range)) + (fn [degree ratio] + (map (fn [i] {:degree degree + :ratio ratio + :partial i + :partial-ratio (* i ratio)}) + (range 1 9))) + (range)) #_sort (#(combo/combinations % 2)) (remove (fn [[x1 x2]] (= (:ratio x1) (:ratio x2)))) @@ -86,7 +86,7 @@ 7/4 57/32 465/256])) - + (->> metameantone-beatings #_(map :diff-c4) #_(remove zero?) @@ -100,9 +100,7 @@ #_(dedupe) #_(map #(/ % (* 1/16 1/64)))) - (->> metaslendro-beatings #_(map (juxt :diff :diff-c4)) #_(dedupe) - (filter #(->> % :pair (map :ratio) set ((fn [%] (% 1))))) - ) + (filter #(->> % :pair (map :ratio) set ((fn [%] (% 1)))))) diff --git a/src/erv/scratch.clj b/src/erv/scratch.clj index 65f8c4c..776f0b8 100644 --- a/src/erv/scratch.clj +++ b/src/erv/scratch.clj @@ -18,7 +18,6 @@ (if (> n target) n (recur (+ period n))))) ;; inversions of a dekany - (defn simplify-inversion [inversion] (let [common-factors (map (comp frequencies prime-factors) inversion) common-denominator (->> common-factors diff --git a/test/erv/scale/core_test.clj b/test/erv/scale/core_test.clj index 66c41f0..de87142 100755 --- a/test/erv/scale/core_test.clj +++ b/test/erv/scale/core_test.clj @@ -16,7 +16,6 @@ (cps/bound-ratio 2) (cps/maps->data :bounded-ratio))) - (deftest cps-scale-fulfills-the-scale-spec (is (true? (s/valid? :erv.scale.core/scale (hexany :scale))))) @@ -69,7 +68,6 @@ (= [1 3 6 10] (mapv i->d4 [1 2 3 4])))))) - (deftest demo-scale*-test (testing "single period, default base-freq @ 440hz" (is (= '(1925/4 1155/2 2475/4 1485/2 825N 3465/4 1925/2 3465/4 825N 1485/2 2475/4 1155/2 1925/4) From 0a6261a7f343a949e5b611493b979732e53ceea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Sun, 10 Aug 2025 19:53:18 -0600 Subject: [PATCH 07/54] [deps] update core.async --- deps.edn | 1 + 1 file changed, 1 insertion(+) diff --git a/deps.edn b/deps.edn index 3a3bc78..20d22d7 100755 --- a/deps.edn +++ b/deps.edn @@ -1,6 +1,7 @@ {:paths ["src" "test" "dev"] :deps {org.clojure/clojure {:mvn/version "1.11.1"} org.clojure/clojurescript {:mvn/version "1.11.60"} + org.clojure/core.async {:mvn/version "1.3.618"} com.google.javascript/closure-compiler-unshaded {:mvn/version "v20221102"} org.clojure/math.combinatorics {:mvn/version "0.3.0"} com.taoensso/timbre {:mvn/version "4.10.0"} From 89c6a01e1cd8d8f141801985afd123b960c49a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Sun, 10 Aug 2025 19:53:36 -0600 Subject: [PATCH 08/54] [deps] add codox --- deps.edn | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deps.edn b/deps.edn index 20d22d7..2f55b1f 100755 --- a/deps.edn +++ b/deps.edn @@ -14,4 +14,8 @@ :extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}} :main-opts ["-m" "cognitect.test-runner"] - :exec-fn cognitect.test-runner.api/test}}} + :exec-fn cognitect.test-runner.api/test} + :codox {:extra-deps {codox/codox {:mvn/version "0.10.8"}} + :exec-fn codox.main/generate-docs + :codox {:output-path "codox"} + :exec-args {:source-paths ["src"]}}}} From 5dae55ac7c8e7c8f1a63369d19bf3351d12943a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Sun, 10 Aug 2025 19:53:57 -0600 Subject: [PATCH 09/54] [meru] WIP meru-diagonals --- src/erv/math/pascals_triangle.clj | 12 -------- src/erv/math/pascals_triangle.cljc | 17 +++++++---- src/erv/meru/diagonals.clj | 38 +++++++++++++++++++++++++ test/erv/math/pascals_triangle_test.clj | 18 ++++++++++++ 4 files changed, 67 insertions(+), 18 deletions(-) delete mode 100644 src/erv/math/pascals_triangle.clj create mode 100644 src/erv/meru/diagonals.clj create mode 100644 test/erv/math/pascals_triangle_test.clj diff --git a/src/erv/math/pascals_triangle.clj b/src/erv/math/pascals_triangle.clj deleted file mode 100644 index 99de89a..0000000 --- a/src/erv/math/pascals_triangle.clj +++ /dev/null @@ -1,12 +0,0 @@ -(ns erv.math.pascals-triangle) - -(defn make [size] - (reduce (fn [acc _] - (conj acc - (concat [1] - (mapv #(apply + %) (partition 2 1 (last acc))) - [1]))) - [[]] - (range size))) - -(defn row [n] (last (make n))) diff --git a/src/erv/math/pascals_triangle.cljc b/src/erv/math/pascals_triangle.cljc index 99de89a..7363eda 100644 --- a/src/erv/math/pascals_triangle.cljc +++ b/src/erv/math/pascals_triangle.cljc @@ -1,12 +1,17 @@ (ns erv.math.pascals-triangle) -(defn make [size] +(defn make + [size] (reduce (fn [acc _] - (conj acc - (concat [1] - (mapv #(apply + %) (partition 2 1 (last acc))) - [1]))) - [[]] + (->> (concat [1] + (mapv #(apply + %) (partition 2 1 (last acc))) + [1]) + (into []) + (conj acc))) + [[1]] (range size))) +(comment + (make 10)) + (defn row [n] (last (make n))) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj new file mode 100644 index 0000000..ce5d01c --- /dev/null +++ b/src/erv/meru/diagonals.clj @@ -0,0 +1,38 @@ +(ns erv.meru.diagonals + "Based on: https://www.anaphoria.com/meru.pdf" + (:require + [erv.math.pascals-triangle :as pascals-triangle])) + +(do + ;; TODO page 3 can't be completely generated at the moment. + ;; If x in the diagonal is > 1 then there will be some cells that will never be touched what Erv seems to do is to also start diagonals from there, in the order of the row. The zeros that he adds correspond to missing/placeholder values when the row size is < x. + (defn make + ([diagonal] (make (pascals-triangle/make 30) diagonal)) + ([triangle diagonal] + (let [vec-x (first diagonal) + vec-y (second diagonal) + diagonals (loop [initial-row 0 + coord [0 0] + diagonal [] + diagonals []] + (let [[y* x*] coord + val (-> triangle + (nth y* nil) + (nth x* nil))] + (cond + val (recur + initial-row + [(+ y* vec-y) (+ x* vec-x)] + (conj diagonal val) + diagonals) + (and (not val) + (nth triangle (inc initial-row) nil)) (recur + (inc initial-row) + [(inc initial-row) 0] + [] + (conj diagonals diagonal)) + :else (conj diagonals diagonal))))] + (mapv (partial apply +) diagonals))))) + +(comment + (make [2 -1])) diff --git a/test/erv/math/pascals_triangle_test.clj b/test/erv/math/pascals_triangle_test.clj new file mode 100644 index 0000000..c14053b --- /dev/null +++ b/test/erv/math/pascals_triangle_test.clj @@ -0,0 +1,18 @@ +(ns erv.math.pascals-triangle-test + (:require + [clojure.test :refer [deftest is]] + [erv.math.pascals-triangle :as subject])) + +(deftest make-test + (is (= [[1] + [1 1] + [1 2 1] + [1 3 3 1] + [1 4 6 4 1] + [1 5 10 10 5 1] + [1 6 15 20 15 6 1] + [1 7 21 35 35 21 7 1] + [1 8 28 56 70 56 28 8 1] + [1 9 36 84 126 126 84 36 9 1] + [1 10 45 120 210 252 210 120 45 10 1]] + (subject/make 10)))) From 82878c74fef6beb24cea2a1653c20aadc31616d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Thu, 14 Aug 2025 23:14:51 -0600 Subject: [PATCH 10/54] [meru] WIP diagonals (impl v2. which works as expected) --- src/erv/math/pascals_triangle.cljc | 28 +++++- src/erv/meru/diagonals.clj | 149 ++++++++++++++++++++++++----- 2 files changed, 151 insertions(+), 26 deletions(-) diff --git a/src/erv/math/pascals_triangle.cljc b/src/erv/math/pascals_triangle.cljc index 7363eda..d1f034a 100644 --- a/src/erv/math/pascals_triangle.cljc +++ b/src/erv/math/pascals_triangle.cljc @@ -4,14 +4,38 @@ [size] (reduce (fn [acc _] (->> (concat [1] - (mapv #(apply + %) (partition 2 1 (last acc))) + (mapv #(apply + %) + (into [] (partition 2 1 (last acc)))) [1]) + (map #?(:clj bigint :cljs js/BigInt)) (into []) (conj acc))) [[1]] (range size))) (comment - (make 10)) + (make 100)) (defn row [n] (last (make n))) + +(defn factorial [x] + (apply * (map #?(:clj bigint :cljs js/BigInt) + (range 1 (inc x))))) + +(defn f + "Calculates a point the pascal's triangle based on an `x,y` coordinate. + Taken from Thomas M. Green's Recurrent Sequences and Pascal's Triangle (referred in meruone.pdf)." + [x y] + (/ (factorial (+ x y)) + (* (factorial x) (factorial y)))) + +;; TODO create a Pascal's Triangle implementation that can be seeded, and that returns something that has an interface like `f` above. + +(defn pascal-coordinates + [size] + (->> (range size) + (mapv + (fn [size*] + (->> (range 0 (inc size*)) + (map + (fn [i] [(- size* i) i]))))))) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index ce5d01c..3e376ac 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -3,36 +3,137 @@ (:require [erv.math.pascals-triangle :as pascals-triangle])) +(defn- diagonals-x-roots + "Calculates the row indexes where a diagonal should start so that every member of any row will be part of a diagnonal." + [diagonal-vector] + (let [[vec-x _vec-y] diagonal-vector] + (range 0 vec-x 1))) + +(diagonals-x-roots [3 1]) + (do ;; TODO page 3 can't be completely generated at the moment. ;; If x in the diagonal is > 1 then there will be some cells that will never be touched what Erv seems to do is to also start diagonals from there, in the order of the row. The zeros that he adds correspond to missing/placeholder values when the row size is < x. + (defn make - ([diagonal] (make (pascals-triangle/make 30) diagonal)) - ([triangle diagonal] - (let [vec-x (first diagonal) - vec-y (second diagonal) - diagonals (loop [initial-row 0 - coord [0 0] + "NOTE: The `diagonal-vector` is a trigonometric vector with x,y coordinates. " + ([slope] (make (pascals-triangle/make 30) slope)) + ([triangle slope] + (let [[vec-x vec-y] slope + [x-root & x-roots*] (diagonals-x-roots slope) + diagonals (loop [y-root 0 + [x* y*] [0 x-root] diagonal [] - diagonals []] - (let [[y* x*] coord - val (-> triangle - (nth y* nil) - (nth x* nil))] + diagonals [] + remaining-roots x-roots*] + (let [val (-> triangle (nth y* nil) (nth x* nil))] (cond - val (recur - initial-row - [(+ y* vec-y) (+ x* vec-x)] - (conj diagonal val) - diagonals) + ;; continue with the diagonal + val + (recur y-root + [(+ x* vec-x) (- y* vec-y)] + (conj diagonal val) + diagonals + remaining-roots) + + ;; move to next-x-root (and (not val) - (nth triangle (inc initial-row) nil)) (recur - (inc initial-row) - [(inc initial-row) 0] - [] - (conj diagonals diagonal)) + (seq remaining-roots)) + (let [[next-x-root & remaining-x-roots*] remaining-roots] + (recur y-root + [next-x-root y-root] + [] + (conj diagonals diagonal) + remaining-x-roots*)) + + ;; go to next row + (and (not val) (nth triangle (inc y-root) nil)) + (recur (inc y-root) + [0 (inc y-root)] + [] + (conj diagonals diagonal) + x-roots*) :else (conj diagonals diagonal))))] - (mapv (partial apply +) diagonals))))) + (mapv (partial apply +) diagonals)))) + + (comment) + ;; pg 13, the order of numbers here does not correspond to the Erv's, his ordering is related to the recurrent sequence formula. + (make [2 3])) + +;;;;;;;;;;;;;;;;;;;;;;;; +;; V2 +;; This one really works! +;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- slope->n-increment ;; TODO rename + [{:keys [x _y] :as _slope}] + (/ 1 x)) + +(defn- sum-diagonal + "`inital-val` {:value 0 :slope slope :coords []}" + [initial-val diagonal] + (reduce (fn [acc {:keys [coord]}] + (let [{:keys [x y]} coord + pascal-num (pascals-triangle/f x y)] + (-> acc + (update :coords conj coord) + (update :value + pascal-num)))) + initial-val + diagonal)) + +(defn- diagonal-sums + [diagonals slopes] + (mapv (fn [slope] + (let [initial-val {:value 0 :slope slope :coords []} + diagonal (get diagonals slope)] + (if diagonal + (sum-diagonal initial-val diagonal) + initial-val))) + slopes)) + +(defn- safe-division + ([a b] (safe-division 0 a b)) + ([default-val a b] + (if (zero? a) default-val (double (/ b a))))) + +(defn- convergence-analysis + [diagonals-series] + (->> diagonals-series + (partition 2 1) + ((fn [parts] + (reduce (fn [{:keys [last-10 convergence-index series] :as acc} [a b]] + (let [ratio (safe-division nil (:value a) (:value b))] + (if (and (= 10 (count last-10)) + (apply = last-10)) + (reduced (-> acc + (update :convergence-index - 10) + (assoc :reached-convergence? true))) + (-> acc + (assoc + :series (conj series (assoc b :ratio-vs-previous ratio)) + :last-10 (take 10 (conj last-10 ratio)) + :convergence-index (inc convergence-index)))))) + {:convergence-index -1 + :last-10 () + :series [(first parts)]} + parts))) + (#(dissoc % :last-10)))) + +(do + ;; TODO: maybe make it dynamic so it creates as many rows as necessary instead of having a hardcoded value of 100 + (defn diagonal-sums-data + "Figure out `n` for every point for the linear formula: y = (slope-y/slope-x)*x + n, by iterating over the pascal-triangle as a vector of coordinates." + ([slope] (diagonal-sums-data 100 slope)) + ([triangle-rows-size slope] + (let [n (fn [x y] (+ y (* x (/ (:y slope) (:x slope))))) + triangle-coords (apply concat (pascals-triangle/pascal-coordinates triangle-rows-size)) + coord->slope (mapv (fn [[x y]] {:coord {:x x :y y} :slope (n x y)}) triangle-coords) + diagonals (group-by :slope coord->slope) + last-n (->> diagonals vec (sort-by first) last first) + n-increment (slope->n-increment slope) + slopes (range 0 (+ last-n n-increment) n-increment) + ;; TODO: allow passing in a custom pascal-triangle + diagonal-sums* (diagonal-sums diagonals slopes)] + (convergence-analysis diagonal-sums*)))) -(comment - (make [2 -1])) + (diagonal-sums-data 100 {:x 4 :y 3})) From 6ccd23c283f4cb5232cc6f6941e7480939e4d776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 19 Aug 2025 16:42:54 -0600 Subject: [PATCH 11/54] [meru] diagonals small refactoring --- src/erv/math/pascals_triangle.cljc | 37 +++++++++++++++++---------- src/erv/meru/diagonals.clj | 41 ++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/erv/math/pascals_triangle.cljc b/src/erv/math/pascals_triangle.cljc index d1f034a..e40d958 100644 --- a/src/erv/math/pascals_triangle.cljc +++ b/src/erv/math/pascals_triangle.cljc @@ -1,17 +1,18 @@ (ns erv.math.pascals-triangle) (defn make - [size] - (reduce (fn [acc _] - (->> (concat [1] - (mapv #(apply + %) - (into [] (partition 2 1 (last acc)))) - [1]) - (map #?(:clj bigint :cljs js/BigInt)) - (into []) - (conj acc))) - [[1]] - (range size))) + ([size] (make 1 1 size)) + ([seed-l seed-r size] + (reduce (fn [acc _] + (->> (concat [seed-l] + (mapv #(apply + %) + (into [] (partition 2 1 (last acc)))) + [seed-r]) + (map #?(:clj bigint :cljs js/BigInt)) + (into []) + (conj acc))) + [[seed-r]] ;; seed-r is privileged so that it works well for calculating the meru diagonals (as shown in merutwo.pdf) + (range size)))) (comment (make 100)) @@ -22,10 +23,10 @@ (apply * (map #?(:clj bigint :cljs js/BigInt) (range 1 (inc x))))) -(defn f +(defn default-coord-map "Calculates a point the pascal's triangle based on an `x,y` coordinate. Taken from Thomas M. Green's Recurrent Sequences and Pascal's Triangle (referred in meruone.pdf)." - [x y] + [[x y]] (/ (factorial (+ x y)) (* (factorial x) (factorial y)))) @@ -39,3 +40,13 @@ (->> (range 0 (inc size*)) (map (fn [i] [(- size* i) i]))))))) + +(do + (defn make-coord-map + "Returns a `hash-map` that maps between a pascal coordinate (a [pos-int? pos-int?] vector) and the corresponding pascal-number. Works the same as `default-coord-map` (except for the row `size` constraint) but works for custom seeded pascal triangles. " + [seed-l seed-r size] + (->> (map vector + (apply concat (pascal-coordinates size)) + (apply concat (make seed-l seed-r size))) + (into {}))) + (make-coord-map 1 2 3)) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index 3e376ac..aa5b623 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -62,7 +62,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;; ;; V2 -;; This one really works! +;; This one really works!... but some diagonals are truncated (missing points) :( ;;;;;;;;;;;;;;;;;;;;;;;; (defn- slope->n-increment ;; TODO rename @@ -74,7 +74,7 @@ [initial-val diagonal] (reduce (fn [acc {:keys [coord]}] (let [{:keys [x y]} coord - pascal-num (pascals-triangle/f x y)] + pascal-num (pascals-triangle/default-coord-map [x y])] (-> acc (update :coords conj coord) (update :value + pascal-num)))) @@ -115,25 +115,38 @@ :convergence-index (inc convergence-index)))))) {:convergence-index -1 :last-10 () - :series [(first parts)]} + :series [(first (first parts))]} parts))) (#(dissoc % :last-10)))) +(defn make-slope-n->coords + [size slope] + (let [triangle-coords (apply concat (pascals-triangle/pascal-coordinates size)) + n (fn [x y] (+ y (* x (/ (:y slope) (:x slope))))) ;; y = (slope-y/slope-x)*x + n + ;; Figure out `n` for every point for the linear formula: y = (slope-y/slope-x)*x + n, by iterating over the pascal-triangle as a vector of coordinates. + ] + (->> triangle-coords + (mapv (fn [[x y]] {:coord {:x x :y y} :slope (n x y)})) + (group-by :slope)))) +(make-slope-n->coords 10 {:x 1 :y 1}) (do - ;; TODO: maybe make it dynamic so it creates as many rows as necessary instead of having a hardcoded value of 100 (defn diagonal-sums-data - "Figure out `n` for every point for the linear formula: y = (slope-y/slope-x)*x + n, by iterating over the pascal-triangle as a vector of coordinates." + ;; TODO: maybe make it dynamic so it creates as many rows as necessary instead of having a hardcoded value of 100 ([slope] (diagonal-sums-data 100 slope)) - ([triangle-rows-size slope] - (let [n (fn [x y] (+ y (* x (/ (:y slope) (:x slope))))) - triangle-coords (apply concat (pascals-triangle/pascal-coordinates triangle-rows-size)) - coord->slope (mapv (fn [[x y]] {:coord {:x x :y y} :slope (n x y)}) triangle-coords) - diagonals (group-by :slope coord->slope) - last-n (->> diagonals vec (sort-by first) last first) + ([size slope] + (let [slope-n->coords (make-slope-n->coords size slope) + last-n (->> slope-n->coords vec (sort-by first) last first) n-increment (slope->n-increment slope) + _ (println "n-increment" n-increment) slopes (range 0 (+ last-n n-increment) n-increment) ;; TODO: allow passing in a custom pascal-triangle - diagonal-sums* (diagonal-sums diagonals slopes)] - (convergence-analysis diagonal-sums*)))) + diagonal-sums* (diagonal-sums slope-n->coords slopes)] + diagonal-sums* + #_(take size) (convergence-analysis diagonal-sums*)))) - (diagonal-sums-data 100 {:x 4 :y 3})) + (->> (diagonal-sums-data 10 {:x 4 :y 3}) + :series + (map (juxt :value :slope :coords)))) +;; if x < y then coords move by -x + y +;; if x < y then coords move by +x - y +;; slope = y From 4994dfe5856c6e10646ac72f6012fda0e779e8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 19 Aug 2025 23:07:11 -0600 Subject: [PATCH 12/54] [meru] WIP v3 on-demand full diagonals --- src/erv/meru/diagonals.clj | 45 ++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index aa5b623..bef4721 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -1,6 +1,7 @@ (ns erv.meru.diagonals "Based on: https://www.anaphoria.com/meru.pdf" (:require + [clojure.math :refer [ceil]] [erv.math.pascals-triangle :as pascals-triangle])) (defn- diagonals-x-roots @@ -144,9 +145,45 @@ diagonal-sums* #_(take size) (convergence-analysis diagonal-sums*)))) - (->> (diagonal-sums-data 10 {:x 4 :y 3}) + (->> (diagonal-sums-data 10 {:x 1 :y 2}) :series (map (juxt :value :slope :coords)))) -;; if x < y then coords move by -x + y -;; if x < y then coords move by +x - y -;; slope = y + +;; Problem: +;; Some diagonals are incomplete +;; +;; Ideal solution: +;; Diagonals should be created on demand +;; +;; ;; Sub-problem: +;; ;; It seems impossible to know the order of diagonals +;;;;; But is it really impossible? Perhaps the distance of the slopes can be know... it seems like it... If so, then this would be great. +;; +;; Alternate solution: +;; The incomplete diagonals should either be +;;;; A. Completed - using slope to fully trace their path +;;;; B. Filtered out - removed (by checking missing points in their path) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; v3 generate complete diagonals on demand +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn intish? [n] (= n (int n))) + +;; WIP generate diagonals +(let [i 4 ;; diagonal index + slope {:x 1 :y 2} + n-inc-size 1 + get-n (fn [x y] (+ y (* x (/ (:y slope) (:x slope))))) ;; y = (slope-y/slope-x)*x + n + + ;; x = (slope-x/slope-y) * (y - n) + get-x (fn [y n] (* -1 (/ (:x slope) (:y slope)) (- y n))) + get-y (fn [x n] (+ n (* x -1 (/ (:y slope) (:x slope))))) + n (* i n-inc-size) + x-at-y0 (get-x 0 n) + x-range (range (-> x-at-y0 int inc))] + (keep (fn [x] (let [y (get-y x n)] + (when (intish? y) {:x x :y y}))) + x-range)) + +;; TODO pascal triangle that generates rows on demand? The idea is to gradually generate diagonals up to either a given number or a convergence pred From d631d1e549bd9c04fa343a3241c3d7d942c9fb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 19 Aug 2025 23:14:00 -0600 Subject: [PATCH 13/54] [meru] document solution v3 --- src/erv/meru/diagonals.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index bef4721..4e26ea8 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -171,6 +171,7 @@ (defn intish? [n] (= n (int n))) ;; WIP generate diagonals +"Given the linear formula `y = (slope-y/slope-x)*x + n`, the algo first calculates the crossing at `x` (when `y` is 0). That gives the range of `x` integer points to check. Given that range use the line formula to find all `y` points that are also integers. When both c and y are integers the coordinate belongs to the pascal diagonal. `i` is the diagonal index and `n-inc-size` is the space between each diagonal." (let [i 4 ;; diagonal index slope {:x 1 :y 2} n-inc-size 1 From 0380811ed218294ab207195e12534b85a4623e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 20 Aug 2025 12:15:57 -0600 Subject: [PATCH 14/54] [meru] v3 working --- src/erv/meru/diagonals.clj | 64 +++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index 4e26ea8..554812c 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -112,9 +112,11 @@ (-> acc (assoc :series (conj series (assoc b :ratio-vs-previous ratio)) + :convergence-ratio ratio :last-10 (take 10 (conj last-10 ratio)) :convergence-index (inc convergence-index)))))) - {:convergence-index -1 + {:convergence-ratio nil + :convergence-index -1 :last-10 () :series [(first (first parts))]} parts))) @@ -170,21 +172,47 @@ (defn intish? [n] (= n (int n))) -;; WIP generate diagonals -"Given the linear formula `y = (slope-y/slope-x)*x + n`, the algo first calculates the crossing at `x` (when `y` is 0). That gives the range of `x` integer points to check. Given that range use the line formula to find all `y` points that are also integers. When both c and y are integers the coordinate belongs to the pascal diagonal. `i` is the diagonal index and `n-inc-size` is the space between each diagonal." -(let [i 4 ;; diagonal index - slope {:x 1 :y 2} - n-inc-size 1 - get-n (fn [x y] (+ y (* x (/ (:y slope) (:x slope))))) ;; y = (slope-y/slope-x)*x + n - - ;; x = (slope-x/slope-y) * (y - n) - get-x (fn [y n] (* -1 (/ (:x slope) (:y slope)) (- y n))) - get-y (fn [x n] (+ n (* x -1 (/ (:y slope) (:x slope))))) - n (* i n-inc-size) - x-at-y0 (get-x 0 n) - x-range (range (-> x-at-y0 int inc))] - (keep (fn [x] (let [y (get-y x n)] - (when (intish? y) {:x x :y y}))) - x-range)) - +(defn get-x + "x = (slope-x/slope-y) * (y - n) + NOTE: Multiplied by -1 because the line is assumed to be descending." + [y n slope] + (* -1 (/ (:x slope) (:y slope)) (- y n))) + +(defn get-y + "y = (slope-y/slope-x)*x + n + NOTE: Multiplied by -1 because the line is assumed to be descending." + [x n slope] + (+ n (* x -1 (/ (:y slope) (:x slope))))) + +(defn make-diagonal + "Given the linear formula `y = (slope-y/slope-x)*x + n`, the algorithm + first calculates the crossing at `x` (when `y` is 0). This gives the + range of `x` integer points to check. Given that range use the line + formula to find all `y` points that are also integers. + When both `x` and `y` are integers the coordinate belongs to the pascal diagonal. + `n-inc-size` is the space between each diagonal, and the `diagonal-index` serves to calcualte the resulting diagonal given the `n-inc-size`." + [slope n-inc-size diagonal-index] + (let [n (* diagonal-index n-inc-size) + x-at-y0 (get-x 0 n slope) + x-range (range (-> x-at-y0 int inc))] + (keep (fn [x] (let [y (get-y x n slope)] + (when (intish? y) {:x x :y y}))) + x-range))) + +(make-diagonal {:x 1 :y 2} 1 4) ;; TODO pascal triangle that generates rows on demand? The idea is to gradually generate diagonals up to either a given number or a convergence pred + +(do + (defn diagonals + [size slope pascal-coord->number] + (->> (range size) + (map #(make-diagonal slope (slope->n-increment slope) %)) + (map (fn [coords] + {:value (->> coords + (map (fn [{:keys [x y]}] + (pascal-coord->number [x y]))) + (apply +)) + :coords (vec coords)})) + convergence-analysis)) + + (diagonals 200 {:x 3 :y 5} pascals-triangle/default-coord-map)) From cf799e219549d03ccbff3446ecc7a951737d345d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 20 Aug 2025 12:19:29 -0600 Subject: [PATCH 15/54] [meru] cleanup diagonals file --- src/erv/meru/diagonals.clj | 141 ++----------------------------------- 1 file changed, 5 insertions(+), 136 deletions(-) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index 554812c..69d23a4 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -1,97 +1,15 @@ (ns erv.meru.diagonals "Based on: https://www.anaphoria.com/meru.pdf" - (:require - [clojure.math :refer [ceil]] - [erv.math.pascals-triangle :as pascals-triangle])) - -(defn- diagonals-x-roots - "Calculates the row indexes where a diagonal should start so that every member of any row will be part of a diagnonal." - [diagonal-vector] - (let [[vec-x _vec-y] diagonal-vector] - (range 0 vec-x 1))) - -(diagonals-x-roots [3 1]) - -(do - ;; TODO page 3 can't be completely generated at the moment. - ;; If x in the diagonal is > 1 then there will be some cells that will never be touched what Erv seems to do is to also start diagonals from there, in the order of the row. The zeros that he adds correspond to missing/placeholder values when the row size is < x. - - (defn make - "NOTE: The `diagonal-vector` is a trigonometric vector with x,y coordinates. " - ([slope] (make (pascals-triangle/make 30) slope)) - ([triangle slope] - (let [[vec-x vec-y] slope - [x-root & x-roots*] (diagonals-x-roots slope) - diagonals (loop [y-root 0 - [x* y*] [0 x-root] - diagonal [] - diagonals [] - remaining-roots x-roots*] - (let [val (-> triangle (nth y* nil) (nth x* nil))] - (cond - ;; continue with the diagonal - val - (recur y-root - [(+ x* vec-x) (- y* vec-y)] - (conj diagonal val) - diagonals - remaining-roots) - - ;; move to next-x-root - (and (not val) - (seq remaining-roots)) - (let [[next-x-root & remaining-x-roots*] remaining-roots] - (recur y-root - [next-x-root y-root] - [] - (conj diagonals diagonal) - remaining-x-roots*)) - - ;; go to next row - (and (not val) (nth triangle (inc y-root) nil)) - (recur (inc y-root) - [0 (inc y-root)] - [] - (conj diagonals diagonal) - x-roots*) - :else (conj diagonals diagonal))))] - (mapv (partial apply +) diagonals)))) - - (comment) - ;; pg 13, the order of numbers here does not correspond to the Erv's, his ordering is related to the recurrent sequence formula. - (make [2 3])) + (:require [erv.math.pascals-triangle :as pascals-triangle])) ;;;;;;;;;;;;;;;;;;;;;;;; -;; V2 -;; This one really works!... but some diagonals are truncated (missing points) :( -;;;;;;;;;;;;;;;;;;;;;;;; +;; V3 +;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- slope->n-increment ;; TODO rename [{:keys [x _y] :as _slope}] (/ 1 x)) -(defn- sum-diagonal - "`inital-val` {:value 0 :slope slope :coords []}" - [initial-val diagonal] - (reduce (fn [acc {:keys [coord]}] - (let [{:keys [x y]} coord - pascal-num (pascals-triangle/default-coord-map [x y])] - (-> acc - (update :coords conj coord) - (update :value + pascal-num)))) - initial-val - diagonal)) - -(defn- diagonal-sums - [diagonals slopes] - (mapv (fn [slope] - (let [initial-val {:value 0 :slope slope :coords []} - diagonal (get diagonals slope)] - (if diagonal - (sum-diagonal initial-val diagonal) - initial-val))) - slopes)) - (defn- safe-division ([a b] (safe-division 0 a b)) ([default-val a b] @@ -122,54 +40,6 @@ parts))) (#(dissoc % :last-10)))) -(defn make-slope-n->coords - [size slope] - (let [triangle-coords (apply concat (pascals-triangle/pascal-coordinates size)) - n (fn [x y] (+ y (* x (/ (:y slope) (:x slope))))) ;; y = (slope-y/slope-x)*x + n - ;; Figure out `n` for every point for the linear formula: y = (slope-y/slope-x)*x + n, by iterating over the pascal-triangle as a vector of coordinates. - ] - (->> triangle-coords - (mapv (fn [[x y]] {:coord {:x x :y y} :slope (n x y)})) - (group-by :slope)))) -(make-slope-n->coords 10 {:x 1 :y 1}) -(do - (defn diagonal-sums-data - ;; TODO: maybe make it dynamic so it creates as many rows as necessary instead of having a hardcoded value of 100 - ([slope] (diagonal-sums-data 100 slope)) - ([size slope] - (let [slope-n->coords (make-slope-n->coords size slope) - last-n (->> slope-n->coords vec (sort-by first) last first) - n-increment (slope->n-increment slope) - _ (println "n-increment" n-increment) - slopes (range 0 (+ last-n n-increment) n-increment) - ;; TODO: allow passing in a custom pascal-triangle - diagonal-sums* (diagonal-sums slope-n->coords slopes)] - diagonal-sums* - #_(take size) (convergence-analysis diagonal-sums*)))) - - (->> (diagonal-sums-data 10 {:x 1 :y 2}) - :series - (map (juxt :value :slope :coords)))) - -;; Problem: -;; Some diagonals are incomplete -;; -;; Ideal solution: -;; Diagonals should be created on demand -;; -;; ;; Sub-problem: -;; ;; It seems impossible to know the order of diagonals -;;;;; But is it really impossible? Perhaps the distance of the slopes can be know... it seems like it... If so, then this would be great. -;; -;; Alternate solution: -;; The incomplete diagonals should either be -;;;; A. Completed - using slope to fully trace their path -;;;; B. Filtered out - removed (by checking missing points in their path) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; v3 generate complete diagonals on demand -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (defn intish? [n] (= n (int n))) (defn get-x @@ -199,8 +69,7 @@ (when (intish? y) {:x x :y y}))) x-range))) -(make-diagonal {:x 1 :y 2} 1 4) -;; TODO pascal triangle that generates rows on demand? The idea is to gradually generate diagonals up to either a given number or a convergence pred +#_(make-diagonal {:x 1 :y 2} 1 4) (do (defn diagonals @@ -215,4 +84,4 @@ :coords (vec coords)})) convergence-analysis)) - (diagonals 200 {:x 3 :y 5} pascals-triangle/default-coord-map)) + (diagonals 300 {:x 3 :y 5} pascals-triangle/default-coord-map)) From 10d3a6d6fd5fb1077a3a6afc7cc888b309f1fd69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 26 Aug 2025 16:59:31 -0600 Subject: [PATCH 16/54] [meru] add convergence precision functionality --- src/erv/meru/diagonals.clj | 94 ++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index 69d23a4..6a22b02 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -1,6 +1,9 @@ (ns erv.meru.diagonals "Based on: https://www.anaphoria.com/meru.pdf" - (:require [erv.math.pascals-triangle :as pascals-triangle])) + (:require + [erv.math.pascals-triangle :as pascals-triangle] + [erv.utils.core :refer [round2]] + [taoensso.timbre :as timbre])) ;;;;;;;;;;;;;;;;;;;;;;;; ;; V3 @@ -15,30 +18,47 @@ ([default-val a b] (if (zero? a) default-val (double (/ b a))))) +(defn- default-convergence?-fn + [last-10-ratios] + (and (= 10 (count last-10-ratios)) + (apply = last-10-ratios))) + +(defn- decimal-places-convergence?-fn + "Evaluate convergence according to a given number of decimal places in the provided ratios." + [decimal-places last-10-ratios] + (->> last-10-ratios + (map #(if (nil? %) nil (round2 decimal-places %))) + default-convergence?-fn)) + +(decimal-places-convergence?-fn 2 [1.111234 + 1.111235]) + (defn- convergence-analysis - [diagonals-series] - (->> diagonals-series - (partition 2 1) - ((fn [parts] - (reduce (fn [{:keys [last-10 convergence-index series] :as acc} [a b]] - (let [ratio (safe-division nil (:value a) (:value b))] - (if (and (= 10 (count last-10)) - (apply = last-10)) - (reduced (-> acc - (update :convergence-index - 10) - (assoc :reached-convergence? true))) - (-> acc - (assoc - :series (conj series (assoc b :ratio-vs-previous ratio)) - :convergence-ratio ratio - :last-10 (take 10 (conj last-10 ratio)) - :convergence-index (inc convergence-index)))))) - {:convergence-ratio nil - :convergence-index -1 - :last-10 () - :series [(first (first parts))]} - parts))) - (#(dissoc % :last-10)))) + ([diagonals-series] (convergence-analysis default-convergence?-fn diagonals-series)) + ([convergence?-fn diagonals-series] + (->> diagonals-series + (partition 2 1) + ((fn [parts] + (reduce (fn [{:keys [last-10 convergence-index series-data] :as acc} [a b]] + (let [ratio (safe-division nil (:value a) (:value b))] + (if (convergence?-fn last-10) + (reduced (-> acc + (update :convergence-index - 10) + (assoc :reached-convergence? true))) + (-> acc + (assoc + :series-data (conj series-data (assoc b :ratio-vs-previous ratio)) + :convergence-ratio ratio + :last-10 (take 10 (conj last-10 ratio)) + :convergence-index (inc convergence-index)))))) + {:convergence-ratio nil + :convergence-index -1 + :last-10 () + :series-data [(first (first parts))] + :reached-convergence? false} + parts))) + (#(dissoc % :last-10)) + (#(assoc % :series (map :value (:series-data %))))))) (defn intish? [n] (= n (int n))) @@ -71,9 +91,21 @@ #_(make-diagonal {:x 1 :y 2} 1 4) -(do - (defn diagonals - [size slope pascal-coord->number] +(defn diagonals + [{:keys [size slope pascal-coord->number convergence?-fn convergence-precision]}] + (when (and convergence-precision convergence?-fn) + (timbre/warn "Both `convergence?-fn` and `convergence-precision` have been provided. The latter is going to be ignored.")) + (let [convergence?-fn (cond + convergence?-fn convergence?-fn + convergence-precision (partial decimal-places-convergence?-fn convergence-precision) + :else default-convergence?-fn) + update-convergence-data (fn [data] + (assoc data + :convergence-precision convergence-precision + :convergence-ratio-with-precision (if convergence-precision + (round2 convergence-precision + (:convergence-ratio data)) + (:convergence-ratio data))))] (->> (range size) (map #(make-diagonal slope (slope->n-increment slope) %)) (map (fn [coords] @@ -82,6 +114,10 @@ (pascal-coord->number [x y]))) (apply +)) :coords (vec coords)})) - convergence-analysis)) + (convergence-analysis convergence?-fn) + update-convergence-data))) - (diagonals 300 {:x 3 :y 5} pascals-triangle/default-coord-map)) +(diagonals {:size 100 + :slope {:x 3 :y 5} + :convergence-precision 3 + :pascal-coord->number pascals-triangle/default-coord-map}) From dffc9e0996e7684b3660b3978e88c2ba55658529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 26 Aug 2025 16:59:38 -0600 Subject: [PATCH 17/54] [meru] add first diagonals test --- src/erv/math/pascals_triangle.cljc | 21 +++++++-------- src/erv/meru/diagonals.clj | 26 +++++++++++++++++- src/erv/mos/v3/core.clj | 3 +-- test/erv/meru/diagonals_test.clj | 43 ++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 test/erv/meru/diagonals_test.clj diff --git a/src/erv/math/pascals_triangle.cljc b/src/erv/math/pascals_triangle.cljc index e40d958..1492a58 100644 --- a/src/erv/math/pascals_triangle.cljc +++ b/src/erv/math/pascals_triangle.cljc @@ -30,9 +30,7 @@ (/ (factorial (+ x y)) (* (factorial x) (factorial y)))) -;; TODO create a Pascal's Triangle implementation that can be seeded, and that returns something that has an interface like `f` above. - -(defn pascal-coordinates +(defn- pascal-coordinates [size] (->> (range size) (mapv @@ -41,12 +39,11 @@ (map (fn [i] [(- size* i) i]))))))) -(do - (defn make-coord-map - "Returns a `hash-map` that maps between a pascal coordinate (a [pos-int? pos-int?] vector) and the corresponding pascal-number. Works the same as `default-coord-map` (except for the row `size` constraint) but works for custom seeded pascal triangles. " - [seed-l seed-r size] - (->> (map vector - (apply concat (pascal-coordinates size)) - (apply concat (make seed-l seed-r size))) - (into {}))) - (make-coord-map 1 2 3)) +(defn make-coord-map + "Returns a `hash-map` that maps between a pascal coordinate (a [pos-int? pos-int?] vector) and the corresponding pascal-number. Works the same as `default-coord-map` (except for the row `size` constraint) but works for custom seeded pascal triangles. " + [seed-l seed-r size] + (with-meta (->> (map vector + (apply concat (pascal-coordinates size)) + (apply concat (make seed-l seed-r size))) + (into {})) + {:triangle-seed {:left seed-l :right seed-r}})) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index 6a22b02..3577bf4 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -2,6 +2,7 @@ "Based on: https://www.anaphoria.com/meru.pdf" (:require [erv.math.pascals-triangle :as pascals-triangle] + [erv.mos.v3.core :refer [gen->mos-ratios]] [erv.utils.core :refer [round2]] [taoensso.timbre :as timbre])) @@ -58,7 +59,7 @@ :reached-convergence? false} parts))) (#(dissoc % :last-10)) - (#(assoc % :series (map :value (:series-data %))))))) + (#(assoc % :series (mapv :value (:series-data %))))))) (defn intish? [n] (= n (int n))) @@ -91,6 +92,26 @@ #_(make-diagonal {:x 1 :y 2} 1 4) +(do + ;; TODO move somewhere else (utils or something, but input may be better in some other way). + (defn convergence-mos-data + [convergence-ratio] + (->> (gen->mos-ratios (rationalize convergence-ratio) 2 100) + (map :meta))) + + (defn convergence-mos-data-summary + [convergence-ratio] + (->> convergence-ratio + convergence-mos-data + (map (fn [meta] + (select-keys meta [:size + :mos/pattern.name + :mos/sL-ratio.float + :mos/s.cents + :mos/L.cents]))))) + + (convergence-mos-data-summary 1.618)) + (defn diagonals [{:keys [size slope pascal-coord->number convergence?-fn convergence-precision]}] (when (and convergence-precision convergence?-fn) @@ -101,6 +122,9 @@ :else default-convergence?-fn) update-convergence-data (fn [data] (assoc data + :triangle-seed (if (= pascal-coord->number pascals-triangle/default-coord-map) + {:left 1 :right 1} + (:triangle-seed (meta pascal-coord->number))) :convergence-precision convergence-precision :convergence-ratio-with-precision (if convergence-precision (round2 convergence-precision diff --git a/src/erv/mos/v3/core.clj b/src/erv/mos/v3/core.clj index 3036f63..09b9594 100644 --- a/src/erv/mos/v3/core.clj +++ b/src/erv/mos/v3/core.clj @@ -89,8 +89,7 @@ :meta) #_(comp (partial map :bounded-ratio) :scale) (gen->mos-ratios 11/8 - (rationalize (round2 4 (erv.utils.conversions/cents->ratio 400))) - 50)) + (rationalize (round2 4 (erv.utils.conversions/cents->ratio 400))) 50)) #_(map count (gen->mos 3/2 2 12))) (comment diff --git a/test/erv/meru/diagonals_test.clj b/test/erv/meru/diagonals_test.clj new file mode 100644 index 0000000..8869f54 --- /dev/null +++ b/test/erv/meru/diagonals_test.clj @@ -0,0 +1,43 @@ +(ns erv.meru.diagonals-test + (:require + [clojure.test :refer [deftest is testing]] + [erv.math.pascals-triangle :as pascals-triangle] + [erv.meru.diagonals :as subject])) + +(deftest diagonals-test + (testing "Has a `:series` key" + (is (= [1 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N] + (->> (subject/diagonals + {:size 12 + :slope {:x 1 :y 2} + :pascal-coord->number pascals-triangle/default-coord-map}) + :series)))) + (testing "Can work with a custom `pascal-coord->number function (or map)" + (is (= [1 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N] + (->> (subject/diagonals + {:size 12 + :slope {:x 1 :y 2} + :pascal-coord->number (pascals-triangle/make-coord-map 1 1 30)}) + :series)))) + (testing "Has a `:convergence-ratio` and a `:convergence-ratio-with-precision` key" + (is (= [1.617977528089888 1.618] + (->> (subject/diagonals + {:size 12 + :slope {:x 1 :y 2} + :convergence-precision 3 + :pascal-coord->number pascals-triangle/default-coord-map}) + ((juxt :convergence-ratio :convergence-ratio-with-precision)))))) + (testing "May have a `:triangle-seed` key, specially if `:pascal-coord->number` is `pascals-triangle/default-coord-map` or was created with `pascals-triangle/make-coord-map`." + (is (= {:left 1, :right 1} + (->> (subject/diagonals + {:size 12 + :slope {:x 1 :y 2} + :convergence-precision 3 + :pascal-coord->number pascals-triangle/default-coord-map}) + :triangle-seed) + (->> (subject/diagonals + {:size 12 + :slope {:x 1 :y 2} + :convergence-precision 3 + :pascal-coord->number (pascals-triangle/make-coord-map 1 1 30)}) + :triangle-seed))))) From 3a878cedbf0cd22b6b91a5a1cec53dd3eae533f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 10 Sep 2025 14:24:24 -0600 Subject: [PATCH 18/54] [meru] add malli and restructure meru.core --- deps.edn | 1 + src/erv/meru/core.clj | 85 ++----------------------- src/erv/meru/recurrent_series.cljc | 67 +++++++++++++++++++ src/erv/types.cljc | 60 +++++++++++++++++ test/erv/meru/core_test.clj | 2 +- test/erv/meru/recurrent_series_test.clj | 21 ++++++ 6 files changed, 155 insertions(+), 81 deletions(-) create mode 100644 src/erv/meru/recurrent_series.cljc create mode 100644 src/erv/types.cljc create mode 100644 test/erv/meru/recurrent_series_test.clj diff --git a/deps.edn b/deps.edn index 2f55b1f..f143949 100755 --- a/deps.edn +++ b/deps.edn @@ -6,6 +6,7 @@ org.clojure/math.combinatorics {:mvn/version "0.3.0"} com.taoensso/timbre {:mvn/version "4.10.0"} quil/quil {:mvn/version "4.0.0-SNAPSHOT"} + metosin/malli {:mvn/version "0.19.1"} com.gfredericks/exact {:mvn/version "0.1.11"} org.clojure/tools.namespace {:mvn/version "1.3.0"} table/table {:mvn/version "0.5.0"} diff --git a/src/erv/meru/core.clj b/src/erv/meru/core.clj index 61ed3cb..a3a7e69 100644 --- a/src/erv/meru/core.clj +++ b/src/erv/meru/core.clj @@ -1,88 +1,13 @@ (ns erv.meru.core (:require [clojure.math.combinatorics :as combo] [erv.cps.core :refer [within-bounding-period]] - [erv.constant-structures.graphics :as sketch])) + [erv.constant-structures.graphics :as sketch] + [erv.meru.recurrent-series] + [erv.meru.diagonals])) -(defn seq-ratios* [recurrent-seq] - (->> recurrent-seq - (partition 2 1) - (map (fn [[a b]] (/ b a))))) +(def recurrent-series #'erv.meru.recurrent-series/recurrent-series) -(defn seq-ratios [recurrent-seq] - (->> recurrent-seq - (partition 2 1) - (map (fn [[a b]] (double (/ b a)))))) - -(declare converges-at) -(defn converges-at [recurrent-seq & {:keys [ignore-first] - :or {ignore-first 0}}] - (->> recurrent-seq - (drop ignore-first) - seq-ratios - (partition 5 1) - (take-while (fn [ns] (apply not= ns))) - count - (+ ignore-first))) - -(def scale-formulas - {:fibonacci {:i1 1 :i2 2 :f +} - :meta-pelog {:i1 1 :i2 3 :f +} - :meta-slendro {:i1 2 :i2 3 :f +}}) - -(defn recurrent-series - "Creates a recurrent integer sequence and some data associated to it. - Config: - `:seed` A sequence of intergers to start the recurrent sequence. - `:formula` A keyword that should be contained in `scale-formulas`. It automatically provides the arguments below, so can be used in place of these. - In case no `:formula` is used: - `:i1` The lowest index in the formula. - If this is confusing, read below. - `:i2` The next index in the formula. - `:f` The function to apply to these indexes (probably always, it will be +) - - - For example on page 40 of https://anaphoria.com/merufour.pdf there is the Meta-Slendro formula: - Hn-3 + Hn-2 = Hn - TODO this may be mixed up... but the test function with the `:meta-slendro` `:formula` does works.... so review is needed - `:i1` corresponds to 3, taken from Hn-3 - `:i2` corresponds to 2, taken from Hn-2." - [{:keys [seed formula] :as config}] - (let [config* (get scale-formulas formula config) - {:keys [i1 i2 f] :or {f +}} config* - seed* (mapv bigint seed) - _ (when (> i2 (count seed)) - (throw (ex-info "The `seed` size must be equal or greater than `i1`" config*))) - _ (when (>= i1 i2) - (throw (ex-info "`i2` must be greater than `i1`" config*))) - series (loop [seq* seed* - a (first (take-last i1 seed)) - b (first (take-last i2 seed))] - (let [seq** (conj seq* (f a b)) - a* (first (take-last i1 seq**)) - b* (first (take-last i2 seq**))] - (if (apply = (seq-ratios (take-last 6 seq**))) - seq** - (recur seq** a* b*))))] - {:convergence-double (last (seq-ratios series)) - :convergence (last (seq-ratios* series)) - :converges-at (converges-at series) - :series series})) - -(comment - (recurrent-series #_{:seed [1 1 1] - :i1 2 - :i2 3 - :f (fn [a b] (+ a b))} - {:seed [1 1 1] - :formula :meta-slendro})) - -(defn within-period [period seq*] - (let [max* (apply max seq*) - min* (/ max* period)] - (map (fn [n] - (if (> min* n) - (* n (int (Math/ceil (/ min* n)))) - n)) - seq*))) +(def diagonals #'erv.meru.diagonals/diagonals) (comment (do diff --git a/src/erv/meru/recurrent_series.cljc b/src/erv/meru/recurrent_series.cljc new file mode 100644 index 0000000..2da3f7f --- /dev/null +++ b/src/erv/meru/recurrent_series.cljc @@ -0,0 +1,67 @@ +(ns erv.meru.recurrent-series) + +(defn seq-ratios* [recurrent-seq] + (->> recurrent-seq + (partition 2 1) + (map (fn [[a b]] (/ b a))))) + +(defn seq-ratios [recurrent-seq] + (->> recurrent-seq + (partition 2 1) + (map (fn [[a b]] (double (/ b a)))))) + +(declare converges-at) + +(defn converges-at + "Returns the index at which the recurrent-seq converges." + [recurrent-seq & {:keys [ignore-first] + :or {ignore-first 0}}] + (->> recurrent-seq + (drop ignore-first) + seq-ratios + (partition 5 1) + (take-while (fn [ns] (apply not= ns))) + count + (+ ignore-first))) + +(def scale-formulas + {:fibonacci {:i1 1 :i2 2 :f +} + :meta-pelog {:i1 1 :i2 3 :f +} + :meta-slendro {:i1 2 :i2 3 :f +}}) + +(defn recurrent-series + "Creates a recurrent integer sequence and some data associated to it. + Config: + `:seed` A sequence of intergers to start the recurrent sequence. + `:formula` A keyword that should be contained in `scale-formulas`. It automatically provides the arguments below, so can be used in place of these. + In case no `:formula` is used: + `:i1` The lowest index in the formula. - If this is confusing, read below. + `:i2` The next index in the formula. + `:f` The function to apply to these indexes (probably always, it will be +) + + + For example on page 40 of https://anaphoria.com/merufour.pdf there is the Meta-Slendro formula: + Hn-3 + Hn-2 = Hn + `:i1` corresponds to 2, taken from Hn-2 + `:i2` corresponds to 3, taken from Hn-3." + [{:keys [seed formula _i1 _i2 _f] :as config}] + (let [config* (get scale-formulas formula config) + {:keys [i1 i2 f] :or {f +}} config* + seed* (mapv #?(:clj bigint :cljs js/BigInt) seed) + _ (when (> i2 (count seed)) + (throw (ex-info "The `seed` size must be equal or greater than `i1`" config*))) + _ (when (>= i1 i2) + (throw (ex-info "`i2` must be greater than `i1`" config*))) + series (loop [seq* seed* + a (first (take-last i1 seed)) + b (first (take-last i2 seed))] + (let [seq** (conj seq* (f a b)) + a* (first (take-last i1 seq**)) + b* (first (take-last i2 seq**))] + (if (apply = (seq-ratios (take-last 6 seq**))) + seq** + (recur seq** a* b*))))] + {:convergence-double (last (seq-ratios series)) + :convergence (last (seq-ratios* series)) + :convergence-index (converges-at series) + :series series})) diff --git a/src/erv/types.cljc b/src/erv/types.cljc new file mode 100644 index 0000000..165789d --- /dev/null +++ b/src/erv/types.cljc @@ -0,0 +1,60 @@ +(ns erv.types + (:require [malli.core :as m] + [malli.util :as mu])) + +(def Intish + #_{:clj-kondo/ignore [:unresolved-symbol]} + [:or :int [:fn #(instance? clojure.lang.BigInt %)]]) + +(do + (def MeruBaseData + [:map + [:convergence-double double?] + [:convergence-index int?] + [:reached-convergence? :boolean] + [:series [:vector Intish]]]) + + (def MeruDiagonalsData + (mu/merge MeruBaseData + [:map + [:series-data [:vector + [:map + [:value Intish] + [:coords [:vector + [:map + [:x :int] + [:y Intish]]]] + [:ratio-vs-previous {:optional true} [:maybe :double]]]]] + [:triangle-seed [:map [:left :int] [:right :int]]] + [:convergence-precision :int] + [:convergence-ratio-with-precision :double]])) + + (m/explain MeruDiagonalsData + {:convergence-double 2.0, + :convergence-index 18, + :series-data + [{:value 1, :coords [{:x 0, :y 0N}]} + {:value 0, :coords [], :ratio-vs-previous 0.0} + {:value 0, :coords [], :ratio-vs-previous nil} + {:value 1N, :coords [{:x 0, :y 1N}], :ratio-vs-previous nil} + {:value 0, :coords [], :ratio-vs-previous 0.0} + {:value 1N, :coords [{:x 1, :y 0N}], :ratio-vs-previous nil} + {:value 1N, :coords [{:x 0, :y 2N}], :ratio-vs-previous 1.0} + {:value 0, :coords [], :ratio-vs-previous 0.0} + {:value 2N, :coords [{:x 1, :y 1N}], :ratio-vs-previous nil} + {:value 1N, :coords [{:x 0, :y 3N}], :ratio-vs-previous 0.5} + {:value 1N, :coords [{:x 2, :y 0N}], :ratio-vs-previous 1.0} + {:value 3N, :coords [{:x 1, :y 2N}], :ratio-vs-previous 3.0} + {:value 1N, :coords [{:x 0, :y 4N}], :ratio-vs-previous 0.3333333333333333} + {:value 3N, :coords [{:x 2, :y 1N}], :ratio-vs-previous 3.0} + {:value 4N, :coords [{:x 1, :y 3N}], :ratio-vs-previous 1.333333333333333} + {:value 2N, :coords [{:x 0, :y 5N} {:x 3, :y 0N}], :ratio-vs-previous 0.5} + {:value 6N, :coords [{:x 2, :y 2N}], :ratio-vs-previous 3.0} + {:value 5N, :coords [{:x 1, :y 4N}], :ratio-vs-previous 0.8333333333333333} + {:value 5N, :coords [{:x 0, :y 6N} {:x 3, :y 1N}], :ratio-vs-previous 1.0} + {:value 10N, :coords [{:x 2, :y 3N}], :ratio-vs-previous 2.0}], + :reached-convergence? false, + :series [1 0 0 1N 0 1N 1N 0 2N 1N 1N 3N 1N 3N 4N 2N 6N 5N 5N 10N], + :triangle-seed {:left 1, :right 1}, + :convergence-precision 3, + :convergence-ratio-with-precision 2.0})) diff --git a/test/erv/meru/core_test.clj b/test/erv/meru/core_test.clj index 6768a92..45e95d7 100644 --- a/test/erv/meru/core_test.clj +++ b/test/erv/meru/core_test.clj @@ -7,7 +7,7 @@ (let [meta-slendro-series {:convergence-double 1.324717957244746, :convergence 53406819691/40315615410, - :converges-at 84, + :convergence-index 84, :series [1 1 1 2 2 3 4 5 7 9 12 16 21 28 37 49 65 86 114 151 200 265 351 465 616 816 1081 1432 1897 2513 3329 4410 5842 7739 10252 13581 17991 23833 31572 41824 55405 73396 97229 128801 170625 226030 299426 396655 525456 696081 922111 1221537 1618192 2143648 2839729 3761840 4983377 6601569 8745217 11584946 15346786 20330163 26931732 35676949 47261895 62608681 82938844 109870576 145547525 192809420 255418101 338356945 448227521 593775046 786584466 1042002567 1380359512 1828587033 2422362079 3208946545 4250949112 5631308624 7459895657 9882257736 13091204281 17342153393 22973462017 30433357674 40315615410 53406819691]}] (testing "Can use a `:seed` and a `:formula`" (is (= meta-slendro-series diff --git a/test/erv/meru/recurrent_series_test.clj b/test/erv/meru/recurrent_series_test.clj new file mode 100644 index 0000000..e6dcb1f --- /dev/null +++ b/test/erv/meru/recurrent_series_test.clj @@ -0,0 +1,21 @@ +(ns erv.meru.recurrent-series-test + (:require + [clojure.test :refer [deftest is testing]] + [erv.meru.recurrent-series :as subject])) + +(deftest recurrent-series-test + (let [meta-slendro-series + {:convergence-double 1.324717957244746, + :convergence 53406819691/40315615410, + :convergence-index 84, + :series [1 1 1 2 2 3 4 5 7 9 12 16 21 28 37 49 65 86 114 151 200 265 351 465 616 816 1081 1432 1897 2513 3329 4410 5842 7739 10252 13581 17991 23833 31572 41824 55405 73396 97229 128801 170625 226030 299426 396655 525456 696081 922111 1221537 1618192 2143648 2839729 3761840 4983377 6601569 8745217 11584946 15346786 20330163 26931732 35676949 47261895 62608681 82938844 109870576 145547525 192809420 255418101 338356945 448227521 593775046 786584466 1042002567 1380359512 1828587033 2422362079 3208946545 4250949112 5631308624 7459895657 9882257736 13091204281 17342153393 22973462017 30433357674 40315615410 53406819691]}] + (testing "Can use a `:seed` and a `:formula`" + (is (= meta-slendro-series + (subject/recurrent-series {:seed [1 1 1] + :formula :meta-slendro})))) + (testing "Can use a `:seed` and a custom configuration of indexes and an operation for apply the values of the indexes." + (is (= meta-slendro-series + (subject/recurrent-series {:seed [1 1 1] + :i1 2 + :i2 3 + :f (fn [a b] (+ a b))})))))) From 3fd17726628bdf3f21ed820c0cf57a64d24b8ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 12 Sep 2025 21:10:06 -0600 Subject: [PATCH 19/54] [meru] wip clean up meru.core and add utils --- src/erv/meru/core.clj | 161 +++++++++++++++--------- src/erv/meru/diagonals.clj | 40 ++---- src/erv/meru/recurrent_series.cljc | 21 +++- src/erv/meru/utils.cljc | 10 ++ src/erv/types.cljc | 84 +++++-------- src/erv/utils/core.cljc | 24 ++++ src/erv/utils/scale.clj | 42 ++++++- test/erv/meru/core_test.clj | 39 +++--- test/erv/meru/diagonals_test.clj | 25 ++-- test/erv/meru/recurrent_series_test.clj | 43 ++++--- test/erv/utils/core_test.cljc | 7 +- 11 files changed, 308 insertions(+), 188 deletions(-) create mode 100644 src/erv/meru/utils.cljc diff --git a/src/erv/meru/core.clj b/src/erv/meru/core.clj index a3a7e69..8d4f0b0 100644 --- a/src/erv/meru/core.clj +++ b/src/erv/meru/core.clj @@ -1,65 +1,112 @@ (ns erv.meru.core - (:require [clojure.math.combinatorics :as combo] - [erv.cps.core :refer [within-bounding-period]] - [erv.constant-structures.graphics :as sketch] - [erv.meru.recurrent-series] - [erv.meru.diagonals])) + (:require + [clojure.math.combinatorics :as combo] + [erv.cps.core :refer [within-bounding-period]] + [erv.meru.diagonals] + [erv.meru.recurrent-series] + [erv.mos.v3.core :refer [gen->mos-ratios]] + [erv.utils.core :refer [lcm-of-list round2]])) (def recurrent-series #'erv.meru.recurrent-series/recurrent-series) (def diagonals #'erv.meru.diagonals/diagonals) +(diagonals {:size 20 + :slope {:x 1 :y 2}}) + +(defn convergence-mos-data + ([convergence-double] (convergence-mos-data {} convergence-double)) + ([{:keys [period max-size] + :or {period 2 max-size 100}} + convergence-double] + (->> (gen->mos-ratios (rationalize (round2 3 convergence-double)) period max-size) + (map :meta)))) + +(defn convergence-mos-data-summary + ([meru-diagonals-or-series-data] (convergence-mos-data-summary {} meru-diagonals-or-series-data)) + ([{:keys [_period _max-size] :as calc-config} + {:keys [convergence-double] :as _meru-diagonals-or-series-data}] + (->> convergence-double + (convergence-mos-data calc-config) + (map (fn [meta] + (select-keys meta [:size + :mos/pattern.name + :mos/sL-ratio.float + :mos/s.cents + :mos/L.cents])))))) + +(convergence-mos-data-summary (diagonals {:size 20 + + :slope {:x 1 :y 2}})) + +(do + + (defn proportional-chord? + [& ratios] + (let [ratio-analysis (decompose-ratio ratios) + lcm (lcm-of-list (mapv :denom ratio-analysis))] + (->> ratio-analysis + (mapv (fn [{:keys [denom numer]}] + (* numer (/ lcm denom)))) + sort + (partition 2 1) + (mapv (fn [[a b]] (- b a))) + (apply =)))) + + (proportional-chord? 1 3/2 5/4)) + (comment (do - (def test1 - (let [seed [1 1 1] - period 2] - (->> (recurrent-series (mapv bigint seed) - :i1 3 - :i2 2 - :f (fn [a b] (+ a b))) - (partition 9 1) - (map (fn [seq*] - (let [seq** (sort (set (map (partial within-bounding-period period) - seq*))) - indexed-seq (->> seq** - (map-indexed (fn [i x] {x i})) - (apply merge)) - min* (/ (apply max seq**) 2)] - (->> seq** - (#(combo/combinations % 3)) - (reduce (fn [acc ns] - (let [diffs (->> ns - sort - (partition 2 1) - (map (fn [[a b]] (- b a))))] - (if (= 1 (count (set diffs))) - (update acc :proportional-triads - conj {:ratios ns - :degrees (->> (map indexed-seq ns)) - :diff (first diffs)}) - acc))) - {:meta {:scale :meru - :period period - :seed seed - :size (count seq**)} - :scale (map (fn [r] - {:ratio r - :bounded-ratio (/ r min*) - :bounding-period 2}) - seq**)}) - (#(assoc-in % [:meta :total-triads] (count (:proportional-triads %)))) - (#(assoc-in % [:meta :proportional-triads] (:proportional-triads %))) - (#(dissoc % :proportional-triads)))))) - (remove (comp empty? :proportional-triads :meta))))) + #_(def test1 + (let [seed [1 1 1] + period 2] + (->> (recurrent-series {:seed (mapv bigint seed) + :i1 2 + :i2 3 + :f (fn [a b] (+ a b))}) + (partition 9 1) + (map (fn [seq*] + (let [seq** (sort (set (map (partial within-bounding-period period) + seq*))) + indexed-seq (->> seq** + (map-indexed (fn [i x] {x i})) + (apply merge)) + min* (/ (apply max seq**) 2)] + (->> seq** + (#(combo/combinations % 3)) + (reduce (fn [acc ns] + (let [diffs (->> ns + sort + (partition 2 1) + (map (fn [[a b]] (- b a))))] + (if (= 1 (count (set diffs))) + (update acc :proportional-triads + conj {:ratios ns + :degrees (->> (map indexed-seq ns)) + :diff (first diffs)}) + acc))) + {:meta {:scale :meru + :period period + :seed seed + :size (count seq**)} + :scale (map (fn [r] + {:ratio r + :bounded-ratio (/ r min*) + :bounding-period 2}) + seq**)}) + #_(#(assoc-in % [:meta :total-triads] (count (:proportional-triads %)))) + #_(#(assoc-in % [:meta :proportional-triads] (:proportional-triads %))) + #_(#(dissoc % :proportional-triads)))))) + #_(remove (comp empty? :proportional-triads :meta))))) (def test1 (let [seed [1 1] period 2] - (->> (recurrent-series (mapv bigint seed) - :i1 1 - :i2 2 - ;; :f (fn [a b] (+ a b)) + (->> (recurrent-series {:seed (mapv bigint [1 1]) + :i1 1 + :i2 2} + ;; :f (fn [a b] (+ a b)) ) + :series (partition 21 1) (map (fn [seq*] (let [seq** (sort (set (map (partial within-bounding-period period) @@ -90,15 +137,15 @@ :bounded-ratio (/ r min*) :bounding-period 2}) seq**)}) - (#(assoc-in % [:meta :total-triads] (count (:proportional-triads %)))) - (#(assoc-in % [:meta :proportional-triads] (:proportional-triads %))) - (#(dissoc % :proportional-triads)))))) - (remove (comp empty? :proportional-triads :meta))))) + #_(#(assoc-in % [:meta :total-triads] (count (:proportional-triads %)))) + #_(#(assoc-in % [:meta :proportional-triads] (:proportional-triads %))) + #_(#(dissoc % :proportional-triads)))))) + #_(remove (comp empty? :proportional-triads :meta))))) (->> test1 - (sort-by (comp :size :meta) >) - first - :scale + #_#_#_(sort-by (comp :size :meta) >) + first + :scale #_(map (comp (juxt :size :total-triads) :meta))))) diff --git a/src/erv/meru/diagonals.clj b/src/erv/meru/diagonals.clj index 3577bf4..8281c14 100644 --- a/src/erv/meru/diagonals.clj +++ b/src/erv/meru/diagonals.clj @@ -2,6 +2,7 @@ "Based on: https://www.anaphoria.com/meru.pdf" (:require [erv.math.pascals-triangle :as pascals-triangle] + [erv.meru.utils :refer [get-convergence-double-with-precision]] [erv.mos.v3.core :refer [gen->mos-ratios]] [erv.utils.core :refer [round2]] [taoensso.timbre :as timbre])) @@ -49,10 +50,10 @@ (-> acc (assoc :series-data (conj series-data (assoc b :ratio-vs-previous ratio)) - :convergence-ratio ratio + :convergence-double ratio :last-10 (take 10 (conj last-10 ratio)) :convergence-index (inc convergence-index)))))) - {:convergence-ratio nil + {:convergence-double nil :convergence-index -1 :last-10 () :series-data [(first (first parts))] @@ -92,28 +93,9 @@ #_(make-diagonal {:x 1 :y 2} 1 4) -(do - ;; TODO move somewhere else (utils or something, but input may be better in some other way). - (defn convergence-mos-data - [convergence-ratio] - (->> (gen->mos-ratios (rationalize convergence-ratio) 2 100) - (map :meta))) - - (defn convergence-mos-data-summary - [convergence-ratio] - (->> convergence-ratio - convergence-mos-data - (map (fn [meta] - (select-keys meta [:size - :mos/pattern.name - :mos/sL-ratio.float - :mos/s.cents - :mos/L.cents]))))) - - (convergence-mos-data-summary 1.618)) - (defn diagonals - [{:keys [size slope pascal-coord->number convergence?-fn convergence-precision]}] + [{:keys [size slope pascal-coord->number convergence?-fn convergence-precision] + :or {pascal-coord->number pascals-triangle/default-coord-map}}] (when (and convergence-precision convergence?-fn) (timbre/warn "Both `convergence?-fn` and `convergence-precision` have been provided. The latter is going to be ignored.")) (let [convergence?-fn (cond @@ -126,10 +108,9 @@ {:left 1 :right 1} (:triangle-seed (meta pascal-coord->number))) :convergence-precision convergence-precision - :convergence-ratio-with-precision (if convergence-precision - (round2 convergence-precision - (:convergence-ratio data)) - (:convergence-ratio data))))] + :convergence-double-with-precision (get-convergence-double-with-precision + convergence-precision + (:convergence-double data))))] (->> (range size) (map #(make-diagonal slope (slope->n-increment slope) %)) (map (fn [coords] @@ -140,8 +121,3 @@ :coords (vec coords)})) (convergence-analysis convergence?-fn) update-convergence-data))) - -(diagonals {:size 100 - :slope {:x 3 :y 5} - :convergence-precision 3 - :pascal-coord->number pascals-triangle/default-coord-map}) diff --git a/src/erv/meru/recurrent_series.cljc b/src/erv/meru/recurrent_series.cljc index 2da3f7f..f761211 100644 --- a/src/erv/meru/recurrent_series.cljc +++ b/src/erv/meru/recurrent_series.cljc @@ -1,4 +1,6 @@ -(ns erv.meru.recurrent-series) +(ns erv.meru.recurrent-series + (:require + [erv.meru.utils :refer [get-convergence-double-with-precision]])) (defn seq-ratios* [recurrent-seq] (->> recurrent-seq @@ -44,8 +46,9 @@ Hn-3 + Hn-2 = Hn `:i1` corresponds to 2, taken from Hn-2 `:i2` corresponds to 3, taken from Hn-3." - [{:keys [seed formula _i1 _i2 _f] :as config}] - (let [config* (get scale-formulas formula config) + [{:keys [seed formula _i1 _i2 _f convergence-precision] :as config}] + (let [preset-config (get scale-formulas formula) + config* (or preset-config config) {:keys [i1 i2 f] :or {f +}} config* seed* (mapv #?(:clj bigint :cljs js/BigInt) seed) _ (when (> i2 (count seed)) @@ -60,8 +63,16 @@ b* (first (take-last i2 seq**))] (if (apply = (seq-ratios (take-last 6 seq**))) seq** - (recur seq** a* b*))))] - {:convergence-double (last (seq-ratios series)) + (recur seq** a* b*)))) + convergence-double (last (seq-ratios series))] + {:seed seed + :preset-formula formula + :convergence-precision convergence-precision + :convergence-double convergence-double + :convergence-double-with-precision (get-convergence-double-with-precision + convergence-precision + convergence-double) :convergence (last (seq-ratios* series)) :convergence-index (converges-at series) + :reached-convergence? true :series series})) diff --git a/src/erv/meru/utils.cljc b/src/erv/meru/utils.cljc new file mode 100644 index 0000000..6804405 --- /dev/null +++ b/src/erv/meru/utils.cljc @@ -0,0 +1,10 @@ +(ns erv.meru.utils + (:require + [erv.utils.core :refer [round2]])) + +(defn get-convergence-double-with-precision + [convergence-precision convergence-double] + (if convergence-precision + (round2 convergence-precision + convergence-double) + convergence-double)) diff --git a/src/erv/types.cljc b/src/erv/types.cljc index 165789d..df4f527 100644 --- a/src/erv/types.cljc +++ b/src/erv/types.cljc @@ -6,55 +6,39 @@ #_{:clj-kondo/ignore [:unresolved-symbol]} [:or :int [:fn #(instance? clojure.lang.BigInt %)]]) -(do - (def MeruBaseData - [:map - [:convergence-double double?] - [:convergence-index int?] - [:reached-convergence? :boolean] - [:series [:vector Intish]]]) - - (def MeruDiagonalsData - (mu/merge MeruBaseData - [:map - [:series-data [:vector - [:map - [:value Intish] - [:coords [:vector - [:map - [:x :int] - [:y Intish]]]] - [:ratio-vs-previous {:optional true} [:maybe :double]]]]] - [:triangle-seed [:map [:left :int] [:right :int]]] - [:convergence-precision :int] - [:convergence-ratio-with-precision :double]])) +(def MeruBaseData + [:map + [:convergence-double double?] + [:convergence-index int?] + [:reached-convergence? :boolean] + [:series [:vector Intish]] + [:convergence-precision [:or :int :nil]] + [:convergence-double-with-precision :double]]) +(def MeruRecurrentSeriesData + (mu/merge #'MeruBaseData + [:map + [:seed [:vector :int]]])) +(def MeruDiagonalsData + (mu/merge #'MeruBaseData + [:map + [:series-data [:vector + [:map + [:value #'Intish] + [:coords [:vector + [:map + [:x :int] + [:y #'Intish]]]] + [:ratio-vs-previous {:optional true} [:maybe :double]]]]] + [:triangle-seed [:map [:left :int] [:right :int]]] + [:convergence-precision [:or :int :nil]] + [:convergence-double-with-precision :double]])) +(comment (m/explain MeruDiagonalsData - {:convergence-double 2.0, - :convergence-index 18, - :series-data - [{:value 1, :coords [{:x 0, :y 0N}]} - {:value 0, :coords [], :ratio-vs-previous 0.0} - {:value 0, :coords [], :ratio-vs-previous nil} - {:value 1N, :coords [{:x 0, :y 1N}], :ratio-vs-previous nil} - {:value 0, :coords [], :ratio-vs-previous 0.0} - {:value 1N, :coords [{:x 1, :y 0N}], :ratio-vs-previous nil} - {:value 1N, :coords [{:x 0, :y 2N}], :ratio-vs-previous 1.0} - {:value 0, :coords [], :ratio-vs-previous 0.0} - {:value 2N, :coords [{:x 1, :y 1N}], :ratio-vs-previous nil} - {:value 1N, :coords [{:x 0, :y 3N}], :ratio-vs-previous 0.5} - {:value 1N, :coords [{:x 2, :y 0N}], :ratio-vs-previous 1.0} - {:value 3N, :coords [{:x 1, :y 2N}], :ratio-vs-previous 3.0} - {:value 1N, :coords [{:x 0, :y 4N}], :ratio-vs-previous 0.3333333333333333} - {:value 3N, :coords [{:x 2, :y 1N}], :ratio-vs-previous 3.0} - {:value 4N, :coords [{:x 1, :y 3N}], :ratio-vs-previous 1.333333333333333} - {:value 2N, :coords [{:x 0, :y 5N} {:x 3, :y 0N}], :ratio-vs-previous 0.5} - {:value 6N, :coords [{:x 2, :y 2N}], :ratio-vs-previous 3.0} - {:value 5N, :coords [{:x 1, :y 4N}], :ratio-vs-previous 0.8333333333333333} - {:value 5N, :coords [{:x 0, :y 6N} {:x 3, :y 1N}], :ratio-vs-previous 1.0} - {:value 10N, :coords [{:x 2, :y 3N}], :ratio-vs-previous 2.0}], - :reached-convergence? false, - :series [1 0 0 1N 0 1N 1N 0 2N 1N 1N 3N 1N 3N 4N 2N 6N 5N 5N 10N], - :triangle-seed {:left 1, :right 1}, - :convergence-precision 3, - :convergence-ratio-with-precision 2.0})) + (erv.meru.diagonals/diagonals + {:size 120 + :slope {:x 1 :y 2} + :pascal-coord->number erv.math.pascals-triangle/default-coord-map})) + (:errors (m/explain MeruRecurrentSeriesData + (erv.meru.recurrent-series/recurrent-series {:seed [1 1 1] + :formula :meta-slendro})))) diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index 59a95a8..a03ec36 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -120,3 +120,27 @@ "Find the greatest common divisor of a list of numbers" [nums] (reduce gcd nums)) + +(defn decompose-ratio + ([ratios] #?(:clj (mapv (fn [r] (try + {:numer (numerator r) :denom (denominator r)} + (catch Exception _ + {:numer r :denom 1}))) + ratios) + :cljs (mapv (fn [r] + ;; TODO `numer` is a float + {:numer r :denom 1}) + ratios)))) + +(defn make-map-by-key + "Given a vector of hash-maps with a specific `k`, return a map of `k`->hash-map. + The user is responsible for providing a unique `k`, otherwise data may be missing." + [k maps] + (reduce + (fn [acc m] + (assoc acc (k m) m)) + {} + maps)) + +;; TODO make test +(make-map-by-key :id [{:id 1} {:id 2}]) diff --git a/src/erv/utils/scale.clj b/src/erv/utils/scale.clj index cc19a4c..899c4ba 100644 --- a/src/erv/utils/scale.clj +++ b/src/erv/utils/scale.clj @@ -1,7 +1,8 @@ (ns erv.utils.scale (:require [clojure.math.combinatorics :as combo] - [erv.utils.core :refer [interval period-reduce rotate wrap-at]] + [erv.utils.core :refer [decompose-ratio interval lcm-of-list period-reduce + rotate wrap-at]] [erv.utils.ratios :refer [interval-seq->ratio-stack normalize-ratios ratios->scale ratios-intervals]])) @@ -143,7 +144,7 @@ (keep #(wrap-at % scale) degrees)) (defn scale-steps->degrees - "Convert a sequence of scale-steps defining a scale (e.g. [2 2 1 2 2 2 1] into a sequence of degrees" + "Convert a sequence of scale-steps defining a scale (e.g. [2 2 1 2 2 2 1]) into a sequence of degrees" ([scale-steps] (scale-steps->degrees scale-steps true)) ([scale-steps remove-octave?] (->> scale-steps @@ -162,3 +163,40 @@ :size (count scale) :period period} :scale scale})) + +;; TODO add tests +;; +;; +;; +;; +;; +(defn proportional-chord? + [ratios] + (let [ratio-analysis (decompose-ratio ratios) + lcm (lcm-of-list (mapv :denom ratio-analysis))] + (->> ratio-analysis + (mapv (fn [{:keys [denom numer]}] + (* numer (/ lcm denom)))) + sort + (partition 2 1) + (mapv (fn [[a b]] (- b a))) + (apply =)))) + +(proportional-chord? [1 3/2 5/4]) + +(defn +degree [scale] + (map-indexed (fn [i n] (assoc n :degree i)) scale)) + +(do + (defn proportional-chords + [chord-size scale] + (let [scale (+degree scale) + proportional-chords-by-notes (->> (combo/combinations scale chord-size) + (keep (fn [ns] + (when (->> ns (mapv :ratio) proportional-chord?) + ns))))] + {:by-notes proportional-chords-by-notes + :by-degrees (mapv (fn [pc] (mapv :degree pc)) + proportional-chords-by-notes)})) + + (proportional-chords 4 (ratios->scale [1 3 5 7 9]))) diff --git a/test/erv/meru/core_test.clj b/test/erv/meru/core_test.clj index 45e95d7..3661b84 100644 --- a/test/erv/meru/core_test.clj +++ b/test/erv/meru/core_test.clj @@ -1,21 +1,26 @@ (ns erv.meru.core-test (:require [clojure.test :refer [deftest is testing]] - [erv.meru.core :refer [recurrent-series]])) + [erv.meru.core :as subject])) -(deftest recurrent-series-test - (let [meta-slendro-series - {:convergence-double 1.324717957244746, - :convergence 53406819691/40315615410, - :convergence-index 84, - :series [1 1 1 2 2 3 4 5 7 9 12 16 21 28 37 49 65 86 114 151 200 265 351 465 616 816 1081 1432 1897 2513 3329 4410 5842 7739 10252 13581 17991 23833 31572 41824 55405 73396 97229 128801 170625 226030 299426 396655 525456 696081 922111 1221537 1618192 2143648 2839729 3761840 4983377 6601569 8745217 11584946 15346786 20330163 26931732 35676949 47261895 62608681 82938844 109870576 145547525 192809420 255418101 338356945 448227521 593775046 786584466 1042002567 1380359512 1828587033 2422362079 3208946545 4250949112 5631308624 7459895657 9882257736 13091204281 17342153393 22973462017 30433357674 40315615410 53406819691]}] - (testing "Can use a `:seed` and a `:formula`" - (is (= meta-slendro-series - (recurrent-series {:seed [1 1 1] - :formula :meta-slendro})))) - (testing "Can use a `:seed` and a custom configuration of indexes and an operation for apply the values of the indexes." - (is (= meta-slendro-series - (recurrent-series {:seed [1 1 1] - :i1 2 - :i2 3 - :f (fn [a b] (+ a b))})))))) +(deftest convergence-mos-data-summary-test + (is (= [{:size 2, + :mos/pattern.name "1s1L", + :mos/sL-ratio.float (float 2.2702353), + :mos/s.cents 366.9460706785318, + :mos/L.cents 833.0539293214687} + {:size 3, + :mos/pattern.name "2s1L", + :mos/sL-ratio.float (float 1.2702353), + :mos/s.cents 366.9460706785318, + :mos/L.cents 466.10785864293723} + {:size 4, + :mos/pattern.name "1s3L", + :mos/sL-ratio.float (float 3.7004786), + :mos/s.cents 99.16178796440605, + :mos/L.cents 366.9460706785318}] + (subject/convergence-mos-data-summary + {:max-size 4 + :period 2} + (subject/diagonals {:size 20 + :slope {:x 1 :y 2}}))))) diff --git a/test/erv/meru/diagonals_test.clj b/test/erv/meru/diagonals_test.clj index 8869f54..22df2ac 100644 --- a/test/erv/meru/diagonals_test.clj +++ b/test/erv/meru/diagonals_test.clj @@ -2,40 +2,49 @@ (:require [clojure.test :refer [deftest is testing]] [erv.math.pascals-triangle :as pascals-triangle] - [erv.meru.diagonals :as subject])) + [erv.meru.diagonals :as subject] + [erv.types :refer [MeruDiagonalsData]] + [malli.core :as m])) (deftest diagonals-test + (testing "The Malli type is up to date" + (let [data (subject/make + {:size 12 + :slope {:x 1 :y 2} + :pascal-coord->number pascals-triangle/default-coord-map})] + (is (m/validate MeruDiagonalsData data) + (m/explain MeruDiagonalsData data)))) (testing "Has a `:series` key" (is (= [1 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N] - (->> (subject/diagonals + (->> (subject/make {:size 12 :slope {:x 1 :y 2} :pascal-coord->number pascals-triangle/default-coord-map}) :series)))) (testing "Can work with a custom `pascal-coord->number function (or map)" (is (= [1 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N] - (->> (subject/diagonals + (->> (subject/make {:size 12 :slope {:x 1 :y 2} :pascal-coord->number (pascals-triangle/make-coord-map 1 1 30)}) :series)))) - (testing "Has a `:convergence-ratio` and a `:convergence-ratio-with-precision` key" + (testing "Has a `:convergence-double` and a `:convergence-double-with-precision` key" (is (= [1.617977528089888 1.618] - (->> (subject/diagonals + (->> (subject/make {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 :pascal-coord->number pascals-triangle/default-coord-map}) - ((juxt :convergence-ratio :convergence-ratio-with-precision)))))) + ((juxt :convergence-double :convergence-double-with-precision)))))) (testing "May have a `:triangle-seed` key, specially if `:pascal-coord->number` is `pascals-triangle/default-coord-map` or was created with `pascals-triangle/make-coord-map`." (is (= {:left 1, :right 1} - (->> (subject/diagonals + (->> (subject/make {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 :pascal-coord->number pascals-triangle/default-coord-map}) :triangle-seed) - (->> (subject/diagonals + (->> (subject/make {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 diff --git a/test/erv/meru/recurrent_series_test.clj b/test/erv/meru/recurrent_series_test.clj index e6dcb1f..a222a3e 100644 --- a/test/erv/meru/recurrent_series_test.clj +++ b/test/erv/meru/recurrent_series_test.clj @@ -1,21 +1,32 @@ (ns erv.meru.recurrent-series-test (:require [clojure.test :refer [deftest is testing]] - [erv.meru.recurrent-series :as subject])) + [erv.math.pascals-triangle :as pascals-triangle] + [erv.meru.recurrent-series :as subject] + [erv.types :refer [MeruRecurrentSeriesData]] + [malli.core :as m])) (deftest recurrent-series-test - (let [meta-slendro-series - {:convergence-double 1.324717957244746, - :convergence 53406819691/40315615410, - :convergence-index 84, - :series [1 1 1 2 2 3 4 5 7 9 12 16 21 28 37 49 65 86 114 151 200 265 351 465 616 816 1081 1432 1897 2513 3329 4410 5842 7739 10252 13581 17991 23833 31572 41824 55405 73396 97229 128801 170625 226030 299426 396655 525456 696081 922111 1221537 1618192 2143648 2839729 3761840 4983377 6601569 8745217 11584946 15346786 20330163 26931732 35676949 47261895 62608681 82938844 109870576 145547525 192809420 255418101 338356945 448227521 593775046 786584466 1042002567 1380359512 1828587033 2422362079 3208946545 4250949112 5631308624 7459895657 9882257736 13091204281 17342153393 22973462017 30433357674 40315615410 53406819691]}] - (testing "Can use a `:seed` and a `:formula`" - (is (= meta-slendro-series - (subject/recurrent-series {:seed [1 1 1] - :formula :meta-slendro})))) - (testing "Can use a `:seed` and a custom configuration of indexes and an operation for apply the values of the indexes." - (is (= meta-slendro-series - (subject/recurrent-series {:seed [1 1 1] - :i1 2 - :i2 3 - :f (fn [a b] (+ a b))})))))) + (testing "The Malli type is up to date" + (let [data (subject/recurrent-series + {:seed [1 1 1] + :formula :meta-slendro})] + (is (m/validate MeruRecurrentSeriesData data) + (m/explain MeruRecurrentSeriesData data)))) + (testing "Basic usage" + (let [meta-slendro-series {:convergence-double 1.324717957244746, + :convergence 53406819691/40315615410, + :convergence-index 84, + :series [1 1 1 2 2 3 4 5 7 9 12 16 21 28 37 49 65 86 114 151 200 265 351 465 616 816 1081 1432 1897 2513 3329 4410 5842 7739 10252 13581 17991 23833 31572 41824 55405 73396 97229 128801 170625 226030 299426 396655 525456 696081 922111 1221537 1618192 2143648 2839729 3761840 4983377 6601569 8745217 11584946 15346786 20330163 26931732 35676949 47261895 62608681 82938844 109870576 145547525 192809420 255418101 338356945 448227521 593775046 786584466 1042002567 1380359512 1828587033 2422362079 3208946545 4250949112 5631308624 7459895657 9882257736 13091204281 17342153393 22973462017 30433357674 40315615410 53406819691]}] + (testing "Can use a `:seed` and a `:formula`" + (is (= meta-slendro-series + (-> (subject/recurrent-series {:seed [1 1 1] + :formula :meta-slendro}) + (select-keys [:convergence-double :convergence :convergence-index :series]))))) + (testing "Can use a `:seed` and a custom configuration of indexes and an operation for apply the values of the indexes." + (is (= meta-slendro-series + (-> (subject/recurrent-series {:seed [1 1 1] + :i1 2 + :i2 3 + :f (fn [a b] (+ a b))}) + (select-keys [:convergence-double :convergence :convergence-index :series])))))))) diff --git a/test/erv/utils/core_test.cljc b/test/erv/utils/core_test.cljc index 7f591b8..2d125ea 100644 --- a/test/erv/utils/core_test.cljc +++ b/test/erv/utils/core_test.cljc @@ -1,7 +1,8 @@ (ns erv.utils.core-test (:require [clojure.test :refer [deftest is]] - [erv.utils.core :refer [pattern->degrees pick-degrees pick-pattern]])) + [erv.utils.core :refer [make-map-by-key pattern->degrees pick-degrees + pick-pattern]])) (deftest pattern->indexes-test (is (= [0 2 4 5 7 9 11] @@ -20,3 +21,7 @@ (pick-degrees (range 5) (range 10))))) + +(deftest make-map-by-key-test + (is (= {1 {:id 1}, 2 {:id 2}} + (make-map-by-key :id [{:id 1} {:id 2}])))) From 22841bd3b16c6a24c6b797d708650cdb3319800f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 12 Sep 2025 21:14:14 -0600 Subject: [PATCH 20/54] [beating-analyzer] wip --- src/erv/meru/scratch/beatings2.cljc | 174 ++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/erv/meru/scratch/beatings2.cljc diff --git a/src/erv/meru/scratch/beatings2.cljc b/src/erv/meru/scratch/beatings2.cljc new file mode 100644 index 0000000..04d87d4 --- /dev/null +++ b/src/erv/meru/scratch/beatings2.cljc @@ -0,0 +1,174 @@ +(ns erv.meru.scratch.beatings2 + (:require + [clojure.math.combinatorics :as combo] + [erv.utils.conversions :refer [cps->name*]] + [erv.utils.core :refer [make-map-by-key pow]])) + +(comment) +#_(def c (* 3 11 8)) + +(def c 256 #_(* 3 11 8)) + +(do + (defn get-beat-data + ([ratios] (get-beat-data (range 1 9) ratios)) + ([partials ratios] + (->> ratios + (mapcat + (fn [degree ratio] + (map (fn [i] {:degree degree + :ratio ratio + :partial i + :partial*ratio (* i ratio)}) + partials)) + (range)) + (#(combo/combinations % 2)) + (remove (fn [[x1 x2]] (= (:ratio x1) (:ratio x2)))) + (map (fn [pair] {:pair pair + :diff (abs (- (:partial*ratio (first pair)) + (:partial*ratio (second pair))))})) + (sort-by :diff) + #_(map (fn [pair] + (assoc pair + :diff-c4 (double (* root (:diff pair))) + :diff-c3 (double (/ (* root (:diff pair)) + 2)) + :diff-c2 (double (/ (* root (:diff pair)) + 4)) + :diff-c1 (double (/ (* root (:diff pair)) + 8)))))))) + + (def metameantone-beatings) + (get-beat-data [1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64]) + (get-beat-data [1 + 5/4 + 3/2 + 7/4])) + +(do + (defn- get-root-note-lowest-freq + [period freq] + (let [lowest-freq 20] + (loop + [freq freq] + (cond + (< freq lowest-freq) (recur (* freq period)) + (>= freq (* lowest-freq period)) (recur (/ freq period)) + (and (>= freq lowest-freq) (> (* lowest-freq period) freq)) freq)))) + (get-root-note-lowest-freq 2 20) + (get-root-note-lowest-freq 2 40) + (get-root-note-lowest-freq 2 16)) + +(do + (defn- get-freq-periods-range + [period initial-freq max-freq] + (->> (range) + (map #(* initial-freq (pow period %))) + (take-while #(<= % max-freq)))) + (get-freq-periods-range 2 1 4000)) + +(do + ;; TODO refactor to smaller functions and rename + (defn +beat-hz-by-period + "Using a root note, map the beat-data over the a range from 20 to 4000hz" + [period root ratios] + (let [max-freq 8000 + beat-data (get-beat-data ratios) + lowest-root (get-root-note-lowest-freq period root) + root-periods-freqs (get-freq-periods-range period lowest-root max-freq) + pair->beat-data (->> beat-data + (mapv + (fn [pair] + (->> root-periods-freqs + (mapv + (fn [period root-freq] + (let [k (keyword (str "diff-period-" period))] + (double (* root-freq (:diff pair))))) + (range)) + #_(into {}) + (assoc pair + :root-hz lowest-root + :beat-hz-by-period)))) + (group-by (comp set #(map :ratio %) :pair)) + (mapv (fn [[k v]] + [k (sort-by (juxt + (comp :partial first :pair) + (comp :partial second :pair)) + v)])) + (into {}) + #_(make-map-by-key (comp set #(map :ratio %) :pair))) + ratio-pairs (->> (keys pair->beat-data) + (sort-by (juxt first second)))] + (mapcat + (fn [period root] + (mapcat + (fn [pair] + (->> (pair->beat-data pair) + (keep (fn [beat-data] + (let [data ((juxt (comp :partial first :pair) + (comp :partial second :pair) + :beat-hz-by-period) + beat-data) + [pr1 pr2 beats-by-period] data + beats (nth beats-by-period period) + [r1 r2] (sort pair)] + (when (and (< beats 20) + (not (zero? beats))) + {:period period + :root-hz root + :root (cps->name* root) + :ratio-1 r1 + :ratio-2 r2 + :ratio-1-partial pr1 + :ratio-2-partial pr2 + :beat-freq beats})))))) + ratio-pairs)) + (range) + root-periods-freqs))) + (->> (+beat-hz-by-period 2 1 #_[1 5/4 3/2 7/4] + [1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64]))) + +(for [a [1 2 3 4] + b [1 2 3 4]] + [a b]) +(defn- ratio-pair->beat-data + [beat-data] + (make-map-by-key :pair beat-data)) + +#_(ratio-pair->beat-data (+beat-hz-by-period 2 256 (get-beat-data [1 5/4 3/2 7/4]))) From fd931ce3ae341d69b1f8a07c064b9bdade574f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Mon, 15 Sep 2025 13:17:38 -0600 Subject: [PATCH 21/54] [meru] add propotional-chords function --- src/erv/meru/core.clj | 25 +--------------- src/erv/types.cljc | 1 + src/erv/utils/core.cljc | 24 +++++++-------- src/erv/utils/scale.clj | 51 ++++++++++++++++++-------------- test/erv/meru/diagonals_test.clj | 12 ++++---- test/erv/utils/scale_test.clj | 18 +++++++++-- 6 files changed, 61 insertions(+), 70 deletions(-) diff --git a/src/erv/meru/core.clj b/src/erv/meru/core.clj index 8d4f0b0..5e0d8ad 100644 --- a/src/erv/meru/core.clj +++ b/src/erv/meru/core.clj @@ -5,15 +5,12 @@ [erv.meru.diagonals] [erv.meru.recurrent-series] [erv.mos.v3.core :refer [gen->mos-ratios]] - [erv.utils.core :refer [lcm-of-list round2]])) + [erv.utils.core :refer [round2]])) (def recurrent-series #'erv.meru.recurrent-series/recurrent-series) (def diagonals #'erv.meru.diagonals/diagonals) -(diagonals {:size 20 - :slope {:x 1 :y 2}}) - (defn convergence-mos-data ([convergence-double] (convergence-mos-data {} convergence-double)) ([{:keys [period max-size] @@ -35,26 +32,6 @@ :mos/s.cents :mos/L.cents])))))) -(convergence-mos-data-summary (diagonals {:size 20 - - :slope {:x 1 :y 2}})) - -(do - - (defn proportional-chord? - [& ratios] - (let [ratio-analysis (decompose-ratio ratios) - lcm (lcm-of-list (mapv :denom ratio-analysis))] - (->> ratio-analysis - (mapv (fn [{:keys [denom numer]}] - (* numer (/ lcm denom)))) - sort - (partition 2 1) - (mapv (fn [[a b]] (- b a))) - (apply =)))) - - (proportional-chord? 1 3/2 5/4)) - (comment (do #_(def test1 diff --git a/src/erv/types.cljc b/src/erv/types.cljc index df4f527..41e1301 100644 --- a/src/erv/types.cljc +++ b/src/erv/types.cljc @@ -19,6 +19,7 @@ (mu/merge #'MeruBaseData [:map [:seed [:vector :int]]])) + (def MeruDiagonalsData (mu/merge #'MeruBaseData [:map diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index a03ec36..5ccace6 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -122,25 +122,21 @@ (reduce gcd nums)) (defn decompose-ratio - ([ratios] #?(:clj (mapv (fn [r] (try - {:numer (numerator r) :denom (denominator r)} - (catch Exception _ - {:numer r :denom 1}))) - ratios) - :cljs (mapv (fn [r] - ;; TODO `numer` is a float - {:numer r :denom 1}) - ratios)))) + ([ratio] #?(:clj (try + {:numer (numerator ratio) :denom (denominator ratio)} + (catch Exception _ + {:numer ratio :denom 1})) + :cljs {:numer ratio :denom 1}))) + +(defn decompose-ratios + ([ratios] (mapv decompose-ratio ratios))) (defn make-map-by-key "Given a vector of hash-maps with a specific `k`, return a map of `k`->hash-map. The user is responsible for providing a unique `k`, otherwise data may be missing." - [k maps] + [key-fn maps] (reduce (fn [acc m] - (assoc acc (k m) m)) + (assoc acc (key-fn m) m)) {} maps)) - -;; TODO make test -(make-map-by-key :id [{:id 1} {:id 2}]) diff --git a/src/erv/utils/scale.clj b/src/erv/utils/scale.clj index 899c4ba..43464fb 100644 --- a/src/erv/utils/scale.clj +++ b/src/erv/utils/scale.clj @@ -1,11 +1,14 @@ (ns erv.utils.scale (:require [clojure.math.combinatorics :as combo] - [erv.utils.core :refer [decompose-ratio interval lcm-of-list period-reduce + [erv.utils.core :refer [decompose-ratios interval lcm-of-list period-reduce rotate wrap-at]] [erv.utils.ratios :refer [interval-seq->ratio-stack normalize-ratios ratios->scale ratios-intervals]])) +(defn +degree [scale] + (map-indexed (fn [i n] (assoc n :degree i)) scale)) + (defn degree-stack "Generate a stack ratios from a single (degree) generator" [{:keys [scale gen offset]}] @@ -170,9 +173,10 @@ ;; ;; ;; -(defn proportional-chord? +(defn proportional-difference + "Returns the difference between the ratios if the chord is proportional, otherwiser returns `nil`" [ratios] - (let [ratio-analysis (decompose-ratio ratios) + (let [ratio-analysis (decompose-ratios ratios) lcm (lcm-of-list (mapv :denom ratio-analysis))] (->> ratio-analysis (mapv (fn [{:keys [denom numer]}] @@ -180,23 +184,24 @@ sort (partition 2 1) (mapv (fn [[a b]] (- b a))) - (apply =)))) - -(proportional-chord? [1 3/2 5/4]) - -(defn +degree [scale] - (map-indexed (fn [i n] (assoc n :degree i)) scale)) - -(do - (defn proportional-chords - [chord-size scale] - (let [scale (+degree scale) - proportional-chords-by-notes (->> (combo/combinations scale chord-size) - (keep (fn [ns] - (when (->> ns (mapv :ratio) proportional-chord?) - ns))))] - {:by-notes proportional-chords-by-notes - :by-degrees (mapv (fn [pc] (mapv :degree pc)) - proportional-chords-by-notes)})) - - (proportional-chords 4 (ratios->scale [1 3 5 7 9]))) + (#(when (apply = %) (first %)))))) + +(defn proportional-chords + "Returns a map with keys `:by-notes` and `:by-degrees` with the notes or degrees that form proportional chords of a given size. + The map groups these notes or degrees by the difference in beats common to them." + [chord-size scale] + (let [scale (+degree scale) + proportional-chords-by-notes (->> (combo/combinations scale chord-size) + (keep (fn [ns] + (when-let [diff (->> ns (mapv :ratio) proportional-difference)] + [diff ns]))))] + {:by-notes (reduce + (fn [acc [diff ns]] + (update acc diff (fnil conj []) (mapv :ratio ns))) + {} + proportional-chords-by-notes) + :by-degrees (reduce + (fn [acc [diff ns]] + (update acc diff (fnil conj []) (mapv :degree ns))) + {} + proportional-chords-by-notes)})) diff --git a/test/erv/meru/diagonals_test.clj b/test/erv/meru/diagonals_test.clj index 22df2ac..91480da 100644 --- a/test/erv/meru/diagonals_test.clj +++ b/test/erv/meru/diagonals_test.clj @@ -8,7 +8,7 @@ (deftest diagonals-test (testing "The Malli type is up to date" - (let [data (subject/make + (let [data (subject/diagonal {:size 12 :slope {:x 1 :y 2} :pascal-coord->number pascals-triangle/default-coord-map})] @@ -16,21 +16,21 @@ (m/explain MeruDiagonalsData data)))) (testing "Has a `:series` key" (is (= [1 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N] - (->> (subject/make + (->> (subject/diagonal {:size 12 :slope {:x 1 :y 2} :pascal-coord->number pascals-triangle/default-coord-map}) :series)))) (testing "Can work with a custom `pascal-coord->number function (or map)" (is (= [1 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N] - (->> (subject/make + (->> (subject/diagonal {:size 12 :slope {:x 1 :y 2} :pascal-coord->number (pascals-triangle/make-coord-map 1 1 30)}) :series)))) (testing "Has a `:convergence-double` and a `:convergence-double-with-precision` key" (is (= [1.617977528089888 1.618] - (->> (subject/make + (->> (subject/diagonal {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 @@ -38,13 +38,13 @@ ((juxt :convergence-double :convergence-double-with-precision)))))) (testing "May have a `:triangle-seed` key, specially if `:pascal-coord->number` is `pascals-triangle/default-coord-map` or was created with `pascals-triangle/make-coord-map`." (is (= {:left 1, :right 1} - (->> (subject/make + (->> (subject/diagonal {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 :pascal-coord->number pascals-triangle/default-coord-map}) :triangle-seed) - (->> (subject/make + (->> (subject/diagonal {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 diff --git a/test/erv/utils/scale_test.clj b/test/erv/utils/scale_test.clj index 52ad1b9..3d5883b 100644 --- a/test/erv/utils/scale_test.clj +++ b/test/erv/utils/scale_test.clj @@ -4,9 +4,10 @@ [erv.edo.core :as edo] [erv.utils.ratios :refer [ratios->scale]] [erv.utils.scale :refer [cross-set dedupe-scale degree-stack diamond - find-subset-degrees get-degrees rotate-scale - scale->stacked-subscale scale-intervals - scale-steps->degrees tritriadic]])) + find-subset-degrees get-degrees + proportional-chords proportional-difference + rotate-scale scale->stacked-subscale + scale-intervals scale-steps->degrees tritriadic]])) (deftest degree-stack-test (is (= [0 4 8] @@ -241,3 +242,14 @@ (map :bounded-ratio (:scale (cross-set 2 [1 3 5 7 9] (map #(/ 1 %) [1 3 5 7 9])))))))) + +(deftest proportional-difference-test + (is (= 1 + (proportional-difference [1 5/4 3/2]))) + (is (= nil + (proportional-difference [1 9/8 3/2])))) + +(deftest proportional-chords-test + (is (= {:by-notes {1N [[1 9/8 5/4] [1 5/4 3/2] [5/4 3/2 7/4]]}, + :by-degrees {1N [[0 1 2] [0 2 3] [2 3 4]]}} + (proportional-chords 3 (ratios->scale [1 3 5 7 9]))))) From 7494630f6e81be52d8129ed7016c01e0a92ed82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Mon, 15 Sep 2025 13:18:29 -0600 Subject: [PATCH 22/54] [scl] add kbm file generation --- src/erv/scale/scl.cljc | 66 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/erv/scale/scl.cljc b/src/erv/scale/scl.cljc index 4326b28..df7c974 100644 --- a/src/erv/scale/scl.cljc +++ b/src/erv/scale/scl.cljc @@ -133,3 +133,69 @@ (do (make-parents filepath) (spit filepath (:content (make-scl-file scale-data)))) :cljs (throw (js/Error. "Cannot spit file in JS, use make-scl-file instead")))) + +;;;;;;;;;;; +;; KBM Files +;;;;;;;;;;; + +(def kbm-template + "Template for a keyboard mapping" + "! KBM file for: %s; %s +! Size of map. The pattern repeats every so many keys: +%s +! First MIDI note number to retune: +0 +! Last MIDI note number to retune: +127 +! Middle note where the first entry of the mapping is mapped to: +%s +! Reference note for which frequency is given: +%s +! Frequency to tune the above note to +%s +! Scale degree to consider as formal octave (determines difference in pitch +! between adjacent mapping patterns): +%s +! Mapping. +! The numbers represent scale degrees mapped to keys. The first entry is for +! the given middle note, the next for subsequent higher keys. +! For an unmapped key, put in an \"x\". At the end, unmapped keys may be left out. +%s +! %s +") + +(defn make-kbm + [{:as _kbm-template-config + :keys [scale-data degrees middle-note middle-note-freq] + :or {middle-note-freq (conv/midi->cps 60) + middle-note 60}}] + (let [scale-description (get-description-data scale-data) + scale (:scale scale-data) + scale-size (count scale)] + (format kbm-template + (:name scale-description "unknown.scl") + (:description scale-description "") + (count degrees) + middle-note ;; middle note + middle-note ;; reference note + middle-note-freq ;; frequency + scale-size + (str/join "\n" degrees) + made-with))) + +(comment + (def scale-data (erv.cps.core/make 2 [1 3 5 7])) + + (println (make-kbm {:scale-data scale-data + :degrees [1 "x" "x" 3 "x"]})) + + (spit-kbm {:scale-data scale-data + :degrees [1 3]})) + +(defn ^:export spit-kbm + #_{:clj-kondo/ignore [:unused-binding]} + [{:keys [filepath] :as kbm-template-config}] + #?(:clj + (do (make-parents filepath) + (spit filepath (make-kbm kbm-template-config))) + :cljs (throw (js/Error. "Cannot spit file in JS, use make-kbm instead")))) From 31655128b5421c82431f519f99fdc2a95ad4636f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Mon, 15 Sep 2025 13:51:57 -0600 Subject: [PATCH 23/54] [scl] kbm remove comments option --- src/erv/scale/scl.cljc | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/erv/scale/scl.cljc b/src/erv/scale/scl.cljc index df7c974..5e56a36 100644 --- a/src/erv/scale/scl.cljc +++ b/src/erv/scale/scl.cljc @@ -166,22 +166,28 @@ (defn make-kbm [{:as _kbm-template-config - :keys [scale-data degrees middle-note middle-note-freq] + :keys [scale-data degrees middle-note middle-note-freq comments?] :or {middle-note-freq (conv/midi->cps 60) - middle-note 60}}] + middle-note 60 + comments? true}}] (let [scale-description (get-description-data scale-data) scale (:scale scale-data) scale-size (count scale)] - (format kbm-template - (:name scale-description "unknown.scl") - (:description scale-description "") - (count degrees) - middle-note ;; middle note - middle-note ;; reference note - middle-note-freq ;; frequency - scale-size - (str/join "\n" degrees) - made-with))) + (cond-> (format kbm-template + (:name scale-description "unknown.scl") + (:description scale-description "") + (count degrees) + middle-note ;; middle note + middle-note ;; reference note + middle-note-freq ;; frequency + scale-size + (str/join "\n" degrees) + made-with) + (not comments?) ((fn [kbm] + (let [lines (str/split-lines kbm)] + (->> lines + (remove #(str/starts-with? % "!")) + (str/join "\n")))))))) (comment (def scale-data (erv.cps.core/make 2 [1 3 5 7])) From 21ee04b82db63a330a117f42a5a4485bfefa969a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 10 Oct 2025 17:03:57 -0600 Subject: [PATCH 24/54] [scl] improve printing of degrees --- src/erv/scale/scl.cljc | 51 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/erv/scale/scl.cljc b/src/erv/scale/scl.cljc index 5e56a36..a37db89 100644 --- a/src/erv/scale/scl.cljc +++ b/src/erv/scale/scl.cljc @@ -172,7 +172,11 @@ comments? true}}] (let [scale-description (get-description-data scale-data) scale (:scale scale-data) - scale-size (count scale)] + scale-size (count scale) + degrees* (->> degrees + (map #(mod % scale-size)) + sort + (str/join "\n"))] (cond-> (format kbm-template (:name scale-description "unknown.scl") (:description scale-description "") @@ -181,7 +185,7 @@ middle-note ;; reference note middle-note-freq ;; frequency scale-size - (str/join "\n" degrees) + degrees* made-with) (not comments?) ((fn [kbm] (let [lines (str/split-lines kbm)] @@ -190,10 +194,51 @@ (str/join "\n")))))))) (comment + (require '[erv.utils.ratios :refer [ratios->scale]]) (def scale-data (erv.cps.core/make 2 [1 3 5 7])) (println (make-kbm {:scale-data scale-data - :degrees [1 "x" "x" 3 "x"]})) + :comments? false + :degrees [0 6 12 16 22 28 32]})) + + (println (make-kbm {:scale-data {:scale (ratios->scale [4181/4096 + 2178309/2097152 + 17/16 + 17711/16384 + 9227465/8388608 + 9/8 + 75025/65536 + 39088169/33554432 + 305/256 + 317811/262144 + 165580141/134217728 + 323/256 + 1346269/1048576 + 21/16 + 5473/4096 + 5702887/4194304 + 89/64 + 1449/1024 + 24157817/16777216 + 377/256 + 98209/65536 + 102334155/67108864 + 1597/1024 + 104005/65536 + 13/8 + 6765/4096 + 1762289/1048576 + 55/32 + 28657/16384 + 933147/524288 + 233/128 + 121393/65536 + 31622993/16777216 + 987/512 + 514229/262144 + 2/1])} + :comments? false + :degrees [0 6 12 16 22 28 32]})) (spit-kbm {:scale-data scale-data :degrees [1 3]})) From 021d79e883c2096706424badb7e016fff05f6fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 10 Oct 2025 17:04:58 -0600 Subject: [PATCH 25/54] [beats] wip beating analyzer --- src/erv/meru/scratch/beatings2.cljc | 98 ++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 22 deletions(-) diff --git a/src/erv/meru/scratch/beatings2.cljc b/src/erv/meru/scratch/beatings2.cljc index 04d87d4..01d2139 100644 --- a/src/erv/meru/scratch/beatings2.cljc +++ b/src/erv/meru/scratch/beatings2.cljc @@ -2,7 +2,7 @@ (:require [clojure.math.combinatorics :as combo] [erv.utils.conversions :refer [cps->name*]] - [erv.utils.core :refer [make-map-by-key pow]])) + [erv.utils.core :refer [decompose-ratio make-map-by-key pow prime-factors]])) (comment) #_(def c (* 3 11 8)) @@ -101,7 +101,7 @@ (mapv (fn [period root-freq] (let [k (keyword (str "diff-period-" period))] - (double (* root-freq (:diff pair))))) + #_(double) (* root-freq (:diff pair)))) (range)) #_(into {}) (assoc pair @@ -143,26 +143,80 @@ ratio-pairs)) (range) root-periods-freqs))) - (->> (+beat-hz-by-period 2 1 #_[1 5/4 3/2 7/4] - [1 - 67/64 - 279/256 - 9/8 - 75/64 - 39/32 - 5/4 - 167/128 - 87/64 - 45/32 - 187/128 - 3/2 - 25/16 - 417/256 - 27/16 - 7/4 - 233/128 - 15/8 - 125/64]))) + (def beat-data + (->> (+beat-hz-by-period 2 1 #_[1 5/4 3/2 7/4] + #_[1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64] + [4181/4096 + 2178309/2097152 + 17/16 + 17711/16384 + 9227465/8388608 + 9/8 + 75025/65536 + 39088169/33554432 + 305/256 + 317811/262144 + 165580141/134217728 + 323/256 + 1346269/1048576 + 21/16 + 5473/4096 + 5702887/4194304 + 89/64 + 1449/1024 + 24157817/16777216 + 377/256 + 98209/65536 + 102334155/67108864 + 1597/1024 + 104005/65536 + 13/8 + 6765/4096 + 1762289/1048576 + 55/32 + 28657/16384 + 933147/524288 + 233/128 + 121393/65536 + 31622993/16777216 + 987/512 + 514229/262144 + 2/1]) + reverse + (map :beat-freq) + frequencies + (sort-by second) + reverse))) +(comment + (->> beat-data + (map (fn [[beats total]] + {:beat-hz (float beats) :factors (factorize beats) :instances total})) + (filter #(> (:instances %) 2)) + (sort-by :beat-hz))) +(do + (defn factorize + [n] + (-> n decompose-ratio :numer prime-factors)) + + (factorize 102334155/67108864)) (for [a [1 2 3 4] b [1 2 3 4]] From 1e1df7c5b66264dfe9055a14625ad8e13a28df22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 14 Oct 2025 16:40:28 -0600 Subject: [PATCH 26/54] [beats] move +degrees to more shareable ns --- deps.edn | 3 +- src/erv/cps/core.cljc | 10 +++-- src/erv/scale/scl.cljc | 5 ++- src/erv/types.cljc | 49 +++++++++++++++++++++++++ src/erv/utils/core.cljc | 17 +++++++++ src/erv/utils/impl.cljc | 7 ++++ src/erv/utils/ratios.cljc | 14 ++++++- src/erv/utils/{scale.clj => scale.cljc} | 4 +- 8 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 src/erv/utils/impl.cljc rename src/erv/utils/{scale.clj => scale.cljc} (99%) diff --git a/deps.edn b/deps.edn index f143949..95764f5 100755 --- a/deps.edn +++ b/deps.edn @@ -10,7 +10,8 @@ com.gfredericks/exact {:mvn/version "0.1.11"} org.clojure/tools.namespace {:mvn/version "1.3.0"} table/table {:mvn/version "0.5.0"} - org.clojure/data.json {:mvn/version "2.4.0"}} + org.clojure/data.json {:mvn/version "2.4.0"} + hiccup/hiccup {:mvn/version "2.0.0"}} :aliases {:test {:extra-paths ["test"] :extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}} diff --git a/src/erv/cps/core.cljc b/src/erv/cps/core.cljc index 2e70219..251f6f5 100755 --- a/src/erv/cps/core.cljc +++ b/src/erv/cps/core.cljc @@ -1,14 +1,15 @@ (ns erv.cps.core ;; TODO use https://github.com/Engelberg/ubergraph for the graphs (:require + #? (:cljs [goog.string :as gstr]) + #? (:cljs [goog.string.format]) [clojure.math.combinatorics :as combo] [clojure.set :as set] [clojure.spec.alpha :as s] [clojure.string :as str] [clojure.walk :as walk] - [erv.utils.core :refer [interval validate]] - #? (:cljs [goog.string :as gstr]) - #? (:cljs [goog.string.format]))) + [erv.utils.core :refer [validate]] + [erv.utils.scale :refer [+degree]])) #?(:cljs (def format gstr/format)) @@ -333,7 +334,8 @@ set->maps (bound-ratio period norm-fac) (maps->data :bounded-ratio) - (+meta size factors norm-fac))) + (+meta size factors norm-fac) + (#(update % :scale +degree)))) (defn +subcps [cps-data set-size factors-size] (let [{:keys [cps/size cps/factors period cps/normalized-by]} (:meta cps-data)] diff --git a/src/erv/scale/scl.cljc b/src/erv/scale/scl.cljc index a37db89..b207d96 100644 --- a/src/erv/scale/scl.cljc +++ b/src/erv/scale/scl.cljc @@ -194,8 +194,9 @@ (str/join "\n")))))))) (comment - (require '[erv.utils.ratios :refer [ratios->scale]]) - (def scale-data (erv.cps.core/make 2 [1 3 5 7])) + (require '[erv.utils.ratios :refer [ratios->scale]] + '[erv.cps.core :as cps]) + (def scale-data (cps/make 2 [1 3 5 7])) (println (make-kbm {:scale-data scale-data :comments? false diff --git a/src/erv/types.cljc b/src/erv/types.cljc index 41e1301..d883157 100644 --- a/src/erv/types.cljc +++ b/src/erv/types.cljc @@ -34,6 +34,7 @@ [:triangle-seed [:map [:left :int] [:right :int]]] [:convergence-precision [:or :int :nil]] [:convergence-double-with-precision :double]])) + (comment (m/explain MeruDiagonalsData (erv.meru.diagonals/diagonals @@ -43,3 +44,51 @@ (:errors (m/explain MeruRecurrentSeriesData (erv.meru.recurrent-series/recurrent-series {:seed [1 1 1] :formula :meta-slendro})))) + +(def Ratio + #?(:clj [:or :int ratio?])) + +(comment + (m/explain Ratio 3/2) + (println (keys (m/default-schemas)))) + +(defn make-scale-meta + [fields] + (into [:map + [:period number?] + [:size :int]] + fields)) + +(def CPSMeta (make-scale-meta + [[:scale [:enum :cps]] + [:cps/size :int] + [:cps/factors [:vector :int]] + [:cps/normalized-by :int] + [:cps/type :string]])) + +(def Note + [:map + [:ratio Ratio] + [:bounded-ratio Ratio] + [:bounding-period number?] + [:degree :int]]) + +(def ScaleData + [:map + [:meta (make-scale-meta [])] + [:scale [:sequential Note]]]) + +;; You can validate your data like this: + +(comment + (require '[erv.cps.core :as cps]) + (def scale-data (cps/make 2 [1 3 5 7])) + (-> scale-data :scale) + (m/explain ScaleData (-> scale-data + (update :scale #(into [] %)))) + (m/explain Note {:set #{7 5}, + :archi-set #{:c :d}, + :ratio 35, + :bounded-ratio 35/32, + :bounding-period 2, + :degree 0})) diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index 5ccace6..fb6989e 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -131,6 +131,23 @@ (defn decompose-ratios ([ratios] (mapv decompose-ratio ratios))) +(defn factorize-ratio + [n] + (-> n decompose-ratio + (update :numer prime-factors) + (update :denom prime-factors))) +(do + (defn factors->hiccup + "Outputs hiccup with factors in power notation" + [factors] + (if-not (seq factors) + [:span 1] + (->> (frequencies factors) + (sort-by first) + (map (fn [[factor power]] [:span factor [:sup power]]))))) + (factors->hiccup [3 3 7 5]) + (factors->hiccup [])) + (defn make-map-by-key "Given a vector of hash-maps with a specific `k`, return a map of `k`->hash-map. The user is responsible for providing a unique `k`, otherwise data may be missing." diff --git a/src/erv/utils/impl.cljc b/src/erv/utils/impl.cljc new file mode 100644 index 0000000..17eac06 --- /dev/null +++ b/src/erv/utils/impl.cljc @@ -0,0 +1,7 @@ +(ns erv.utils.impl + "Contains implementations or functions that are shared among different files.") + +(defn +degree + "Adds degrees to a scale" + [scale] + (map-indexed (fn [i n] (assoc n :degree i)) scale)) diff --git a/src/erv/utils/ratios.cljc b/src/erv/utils/ratios.cljc index 6167fb8..da03b57 100644 --- a/src/erv/utils/ratios.cljc +++ b/src/erv/utils/ratios.cljc @@ -7,7 +7,8 @@ [com.gfredericks.exact :as e] [erv.utils.conversions :as conv] [erv.utils.core :refer [gcd-of-list interval period-reduce prime-factors - round2]])] + round2]] + [erv.utils.impl :as impl])] :cljs [(:require [clojure.string :as str] @@ -142,7 +143,16 @@ {:ratio ratio :bounded-ratio ratio :bounding-period period}))) - (sort-by :bounded-ratio)))) + (sort-by :bounded-ratio) + impl/+degree))) + +(defn ratios->scale-data + ([ratios] (ratios->scale-data 2 ratios)) + ([period ratios] + (let [scale (ratios->scale period ratios)] + {:meta {:period period + :size (count scale)} + :scale scale}))) (defn ratios-intervals "Get the intervals between the ratios in the sequence. diff --git a/src/erv/utils/scale.clj b/src/erv/utils/scale.cljc similarity index 99% rename from src/erv/utils/scale.clj rename to src/erv/utils/scale.cljc index 43464fb..31839f9 100644 --- a/src/erv/utils/scale.clj +++ b/src/erv/utils/scale.cljc @@ -3,11 +3,11 @@ [clojure.math.combinatorics :as combo] [erv.utils.core :refer [decompose-ratios interval lcm-of-list period-reduce rotate wrap-at]] + [erv.utils.impl :as impl] [erv.utils.ratios :refer [interval-seq->ratio-stack normalize-ratios ratios->scale ratios-intervals]])) -(defn +degree [scale] - (map-indexed (fn [i n] (assoc n :degree i)) scale)) +(def +degree #'impl/+degree) (defn degree-stack "Generate a stack ratios from a single (degree) generator" From 7cc8be828452ae5ca55a76e2e20df303af788611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 14 Oct 2025 16:40:55 -0600 Subject: [PATCH 27/54] [beats] hiccup table --- src/erv/meru/scratch/beatings2.cljc | 347 ++++++++++++++++++++-------- 1 file changed, 246 insertions(+), 101 deletions(-) diff --git a/src/erv/meru/scratch/beatings2.cljc b/src/erv/meru/scratch/beatings2.cljc index 01d2139..5e6551a 100644 --- a/src/erv/meru/scratch/beatings2.cljc +++ b/src/erv/meru/scratch/beatings2.cljc @@ -1,17 +1,23 @@ (ns erv.meru.scratch.beatings2 (:require [clojure.math.combinatorics :as combo] + [clojure.string :as str] [erv.utils.conversions :refer [cps->name*]] - [erv.utils.core :refer [decompose-ratio make-map-by-key pow prime-factors]])) + [erv.utils.core :refer [decompose-ratio factorize-ratio factors->hiccup + make-map-by-key pow prime-factors]] + [erv.utils.ratios :refer [ratios->scale-data]] + [taoensso.timbre :as timbre] + #?(:clj [hiccup2.core :as h]))) -(comment) +(comment + (def a 1)) #_(def c (* 3 11 8)) (def c 256 #_(* 3 11 8)) (do - (defn get-beat-data - ([ratios] (get-beat-data (range 1 9) ratios)) + (defn get-beat-data-by-pairs + ([ratios] (get-beat-data-by-pairs (range 1 9) ratios)) ([partials ratios] (->> ratios (mapcat @@ -39,29 +45,29 @@ 8)))))))) (def metameantone-beatings) - (get-beat-data [1 - 67/64 - 279/256 - 9/8 - 75/64 - 39/32 - 5/4 - 167/128 - 87/64 - 45/32 - 187/128 - 3/2 - 25/16 - 417/256 - 27/16 - 7/4 - 233/128 - 15/8 - 125/64]) - (get-beat-data [1 - 5/4 - 3/2 - 7/4])) + (get-beat-data-by-pairs [1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64]) + (get-beat-data-by-pairs [1 + 5/4 + 3/2 + 7/4])) (do (defn- get-root-note-lowest-freq @@ -87,11 +93,11 @@ (do ;; TODO refactor to smaller functions and rename - (defn +beat-hz-by-period + (defn get-beat-data "Using a root note, map the beat-data over the a range from 20 to 4000hz" - [period root ratios] + [period root partials ratios] (let [max-freq 8000 - beat-data (get-beat-data ratios) + beat-data (get-beat-data-by-pairs partials ratios) lowest-root (get-root-note-lowest-freq period root) root-periods-freqs (get-freq-periods-range period lowest-root max-freq) pair->beat-data (->> beat-data @@ -123,11 +129,11 @@ (fn [pair] (->> (pair->beat-data pair) (keep (fn [beat-data] - (let [data ((juxt (comp :partial first :pair) - (comp :partial second :pair) + (let [data ((juxt (comp (juxt :partial :degree) first :pair) + (comp (juxt :partial :degree) second :pair) :beat-hz-by-period) beat-data) - [pr1 pr2 beats-by-period] data + [[pr1 deg1] [pr2 deg2] beats-by-period] data beats (nth beats-by-period period) [r1 r2] (sort pair)] (when (and (< beats 20) @@ -135,88 +141,227 @@ {:period period :root-hz root :root (cps->name* root) + :degree-1 deg1 + :degree-2 deg2 :ratio-1 r1 :ratio-2 r2 :ratio-1-partial pr1 :ratio-2-partial pr2 - :beat-freq beats})))))) + :beat-freq.ratio beats + :beat-freq.hz (float beats) + :beat-freq.factors (factorize-ratio beats)})))))) ratio-pairs)) (range) root-periods-freqs))) - (def beat-data - (->> (+beat-hz-by-period 2 1 #_[1 5/4 3/2 7/4] - #_[1 - 67/64 - 279/256 - 9/8 - 75/64 - 39/32 - 5/4 - 167/128 - 87/64 - 45/32 - 187/128 - 3/2 - 25/16 - 417/256 - 27/16 - 7/4 - 233/128 - 15/8 - 125/64] - [4181/4096 - 2178309/2097152 - 17/16 - 17711/16384 - 9227465/8388608 - 9/8 - 75025/65536 - 39088169/33554432 - 305/256 - 317811/262144 - 165580141/134217728 - 323/256 - 1346269/1048576 - 21/16 - 5473/4096 - 5702887/4194304 - 89/64 - 1449/1024 - 24157817/16777216 - 377/256 - 98209/65536 - 102334155/67108864 - 1597/1024 - 104005/65536 - 13/8 - 6765/4096 - 1762289/1048576 - 55/32 - 28657/16384 - 933147/524288 - 233/128 - 121393/65536 - 31622993/16777216 - 987/512 - 514229/262144 - 2/1]) - reverse - (map :beat-freq) - frequencies + (defn +beat-data + [root-freq partials scale-data] + (let [beat-data (get-beat-data (-> scale-data :meta :period) + root-freq + partials + (map :bounded-ratio (:scale scale-data))) + root (-> beat-data first :root-hz) + root-kw (keyword (str root "hz"))] + (when (not= root-freq root) + (timbre/info "root-freq normalized to:" root)) + (timbre/info (format "Access beat data like: (-> scale-data :beat-data %s)" root-kw)) + (assoc-in scale-data + [:beat-data root-kw] + beat-data))) + (+beat-data + 1 + (range 1 6) + (ratios->scale-data [1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64]))) +(->> (get-beat-data 2 1 #_[1 5/4 3/2 7/4] + (range 1 6) + [1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64] + #_[4181/4096 + 2178309/2097152 + 17/16 + 17711/16384 + 9227465/8388608 + 9/8 + 75025/65536 + 39088169/33554432 + 305/256 + 317811/262144 + 165580141/134217728 + 323/256 + 1346269/1048576 + 21/16 + 5473/4096 + 5702887/4194304 + 89/64 + 1449/1024 + 24157817/16777216 + 377/256 + 98209/65536 + 102334155/67108864 + 1597/1024 + 104005/65536 + 13/8 + 6765/4096 + 1762289/1048576 + 55/32 + 28657/16384 + 933147/524288 + 233/128 + 121393/65536 + 31622993/16777216 + 987/512 + 514229/262144 + 2/1]) + reverse + #_(map :beat-freq) + #_(map #(update % :beat-freq float)) + #_#_#_frequencies (sort-by second) - reverse))) -(comment - (->> beat-data - (map (fn [[beats total]] + reverse) +(let [scale-data + (+beat-data + 1 + (range 1 6) + (ratios->scale-data [1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64]))] + (defn beat-factors-hiccup + [{:keys [numer denom]}] + [:span (factors->hiccup numer) "/" (factors->hiccup denom)]) + + (defn hiccup-table [beat-data] + (let [data-by-deg-pairs (group-by (comp (juxt :degree-1 :degree-2 :period)) beat-data) + beat-freqs (->> beat-data + (group-by #(select-keys % [:beat-freq.hz :beat-freq.factors])) + (map (fn [[m vs]] (assoc m :instances (count vs)))) + (sort-by :beat-freq.hz)) + rows (->> data-by-deg-pairs + (sort-by (comp (juxt #(nth % 2 nil) first second) first)) + (map + (fn [[deg-pair pair-beat-data]] + (println (:root-hz (first pair-beat-data))) + (let [data-by-hz (group-by :beat-freq.hz pair-beat-data) + beating-harmonics-at-beat-hz-index (map + (fn [{hz :beat-freq.hz}] + [:td + {:style {:white-space "nowrap"}} + (->> hz + data-by-hz + (map (comp #(str/join "," %) (juxt :ratio-1-partial :ratio-2-partial))) + (str/join " "))]) + beat-freqs)] + (into [:tr + [:td (nth deg-pair 2) "@" (:root-hz (first pair-beat-data)) "hz"] + [:td (str/join "," (take 2 deg-pair))]] + beating-harmonics-at-beat-hz-index))))) + thead [:thead (into [:tr + [:th "Period"] + [:th "Degrees"]] + (map (fn [bf] + [:th (:beat-freq.hz bf) "hz " "(" (:instances bf) ")" + [:br] + (beat-factors-hiccup (:beat-freq.factors bf))]) + beat-freqs))]] + [:table thead [:tbody rows]] + #_beat-freqs + #_data-by-deg-pairs) + #_(h/html [:p])) + (hiccup-table (:32hz (:beat-data scale-data))) + + (defn html-template [body] + [:html [:head [:style + " +table, th, td { + border: 1px solid black; + border-collapse: collapse; +} + +thead th { + position: sticky; /* Makes the header cells sticky */ + top: 0; + background: #fff; /* Prevents content from showing through */ + z-index: 1; /* Ensures the header stays above the scrolling rows */ +} + +th { +min-width: 70px; +padding: 0 4px; +} +"]] [:body body]]) + #?(:clj + (defn spit-html-table + [out-path beat-data] + (let [hiccup-data (-> beat-data + hiccup-table + html-template)] + (->> hiccup-data + h/html + str + (spit out-path))))) + (spit-html-table "resources/meta-meantone-beats.html" (:32hz (:beat-data scale-data)))) +(comment) +(->> beat-data + hiccup-table + #_(map (fn [[beats total]] {:beat-hz (float beats) :factors (factorize beats) :instances total})) - (filter #(> (:instances %) 2)) - (sort-by :beat-hz))) + #_(filter #(> (:instances %) 2)) + #_(sort-by :beat-hz)) (do (defn factorize [n] (-> n decompose-ratio :numer prime-factors)) - (factorize 102334155/67108864)) + (factorize-ratio 102334155/67108864)) (for [a [1 2 3 4] b [1 2 3 4]] From 8342bbd0efda1b935e6d89ebdae6bd1c233e4341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 14 Oct 2025 22:48:31 -0600 Subject: [PATCH 28/54] [js-ratios+beats] remove rationals outside comments --- src/erv/meru/scratch/beatings2.cljc | 514 ++++++++++++++-------------- 1 file changed, 249 insertions(+), 265 deletions(-) diff --git a/src/erv/meru/scratch/beatings2.cljc b/src/erv/meru/scratch/beatings2.cljc index 5e6551a..38d03da 100644 --- a/src/erv/meru/scratch/beatings2.cljc +++ b/src/erv/meru/scratch/beatings2.cljc @@ -15,34 +15,35 @@ (def c 256 #_(* 3 11 8)) -(do - (defn get-beat-data-by-pairs - ([ratios] (get-beat-data-by-pairs (range 1 9) ratios)) - ([partials ratios] - (->> ratios - (mapcat - (fn [degree ratio] - (map (fn [i] {:degree degree - :ratio ratio - :partial i - :partial*ratio (* i ratio)}) - partials)) - (range)) - (#(combo/combinations % 2)) - (remove (fn [[x1 x2]] (= (:ratio x1) (:ratio x2)))) - (map (fn [pair] {:pair pair - :diff (abs (- (:partial*ratio (first pair)) - (:partial*ratio (second pair))))})) - (sort-by :diff) - #_(map (fn [pair] - (assoc pair - :diff-c4 (double (* root (:diff pair))) - :diff-c3 (double (/ (* root (:diff pair)) - 2)) - :diff-c2 (double (/ (* root (:diff pair)) - 4)) - :diff-c1 (double (/ (* root (:diff pair)) - 8)))))))) +(defn get-beat-data-by-pairs + ([ratios] (get-beat-data-by-pairs (range 1 9) ratios)) + ([partials ratios] + (->> ratios + (mapcat + (fn [degree ratio] + (map (fn [i] {:degree degree + :ratio ratio + :partial i + :partial*ratio (* i ratio)}) + partials)) + (range)) + (#(combo/combinations % 2)) + (remove (fn [[x1 x2]] (= (:ratio x1) (:ratio x2)))) + (map (fn [pair] {:pair pair + :diff (abs (- (:partial*ratio (first pair)) + (:partial*ratio (second pair))))})) + (sort-by :diff) + #_(map (fn [pair] + (assoc pair + :diff-c4 (double (* root (:diff pair))) + :diff-c3 (double (/ (* root (:diff pair)) + 2)) + :diff-c2 (double (/ (* root (:diff pair)) + 4)) + :diff-c1 (double (/ (* root (:diff pair)) + 8)))))))) + +(comment (def metameantone-beatings) (get-beat-data-by-pairs [1 @@ -91,82 +92,85 @@ (take-while #(<= % max-freq)))) (get-freq-periods-range 2 1 4000)) -(do - ;; TODO refactor to smaller functions and rename - (defn get-beat-data - "Using a root note, map the beat-data over the a range from 20 to 4000hz" - [period root partials ratios] - (let [max-freq 8000 - beat-data (get-beat-data-by-pairs partials ratios) - lowest-root (get-root-note-lowest-freq period root) - root-periods-freqs (get-freq-periods-range period lowest-root max-freq) - pair->beat-data (->> beat-data - (mapv - (fn [pair] - (->> root-periods-freqs - (mapv - (fn [period root-freq] - (let [k (keyword (str "diff-period-" period))] - #_(double) (* root-freq (:diff pair)))) - (range)) - #_(into {}) - (assoc pair - :root-hz lowest-root - :beat-hz-by-period)))) - (group-by (comp set #(map :ratio %) :pair)) - (mapv (fn [[k v]] - [k (sort-by (juxt - (comp :partial first :pair) - (comp :partial second :pair)) - v)])) - (into {}) - #_(make-map-by-key (comp set #(map :ratio %) :pair))) - ratio-pairs (->> (keys pair->beat-data) - (sort-by (juxt first second)))] - (mapcat - (fn [period root] - (mapcat - (fn [pair] - (->> (pair->beat-data pair) - (keep (fn [beat-data] - (let [data ((juxt (comp (juxt :partial :degree) first :pair) - (comp (juxt :partial :degree) second :pair) - :beat-hz-by-period) - beat-data) - [[pr1 deg1] [pr2 deg2] beats-by-period] data - beats (nth beats-by-period period) - [r1 r2] (sort pair)] - (when (and (< beats 20) - (not (zero? beats))) - {:period period - :root-hz root - :root (cps->name* root) - :degree-1 deg1 - :degree-2 deg2 - :ratio-1 r1 - :ratio-2 r2 - :ratio-1-partial pr1 - :ratio-2-partial pr2 - :beat-freq.ratio beats - :beat-freq.hz (float beats) - :beat-freq.factors (factorize-ratio beats)})))))) - ratio-pairs)) - (range) - root-periods-freqs))) - (defn +beat-data - [root-freq partials scale-data] - (let [beat-data (get-beat-data (-> scale-data :meta :period) - root-freq - partials - (map :bounded-ratio (:scale scale-data))) - root (-> beat-data first :root-hz) - root-kw (keyword (str root "hz"))] - (when (not= root-freq root) - (timbre/info "root-freq normalized to:" root)) - (timbre/info (format "Access beat data like: (-> scale-data :beat-data %s)" root-kw)) - (assoc-in scale-data - [:beat-data root-kw] - beat-data))) +;; TODO refactor to smaller functions and rename + +(defn get-beat-data + "Using a root note, map the beat-data over the a range from 20 to 4000hz" + [period root partials ratios] + (let [max-freq 8000 + beat-data (get-beat-data-by-pairs partials ratios) + lowest-root (get-root-note-lowest-freq period root) + root-periods-freqs (get-freq-periods-range period lowest-root max-freq) + pair->beat-data (->> beat-data + (mapv + (fn [pair] + (->> root-periods-freqs + (mapv + (fn [period root-freq] + (let [k (keyword (str "diff-period-" period))] + #_(double) (* root-freq (:diff pair)))) + (range)) + #_(into {}) + (assoc pair + :root-hz lowest-root + :beat-hz-by-period)))) + (group-by (comp set #(map :ratio %) :pair)) + (mapv (fn [[k v]] + [k (sort-by (juxt + (comp :partial first :pair) + (comp :partial second :pair)) + v)])) + (into {}) + #_(make-map-by-key (comp set #(map :ratio %) :pair))) + ratio-pairs (->> (keys pair->beat-data) + (sort-by (juxt first second)))] + (mapcat + (fn [period root] + (mapcat + (fn [pair] + (->> (pair->beat-data pair) + (keep (fn [beat-data] + (let [data ((juxt (comp (juxt :partial :degree) first :pair) + (comp (juxt :partial :degree) second :pair) + :beat-hz-by-period) + beat-data) + [[pr1 deg1] [pr2 deg2] beats-by-period] data + beats (nth beats-by-period period) + [r1 r2] (sort pair)] + (when (and (< beats 20) + (not (zero? beats))) + {:period period + :root-hz root + :root (cps->name* root) + :degree-1 deg1 + :degree-2 deg2 + :ratio-1 r1 + :ratio-2 r2 + :ratio-1-partial pr1 + :ratio-2-partial pr2 + :beat-freq.ratio beats + :beat-freq.hz (float beats) + :beat-freq.factors (factorize-ratio beats)})))))) + ratio-pairs)) + (range) + root-periods-freqs))) + +(defn +beat-data + [root-freq partials scale-data] + (let [beat-data (get-beat-data (-> scale-data :meta :period) + root-freq + partials + (map :bounded-ratio (:scale scale-data))) + root (-> beat-data first :root-hz) + root-kw (keyword (str root "hz"))] + (when (not= root-freq root) + (timbre/info "root-freq normalized to:" root)) + #?(:clj + (timbre/info (format "Access beat data like: (-> scale-data :beat-data %s)" root-kw))) + (assoc-in scale-data + [:beat-data root-kw] + beat-data))) +(comment (+beat-data 1 (range 1 6) @@ -188,139 +192,140 @@ 7/4 233/128 15/8 - 125/64]))) -(->> (get-beat-data 2 1 #_[1 5/4 3/2 7/4] - (range 1 6) - [1 - 67/64 - 279/256 - 9/8 - 75/64 - 39/32 - 5/4 - 167/128 - 87/64 - 45/32 - 187/128 - 3/2 - 25/16 - 417/256 - 27/16 - 7/4 - 233/128 - 15/8 - 125/64] - #_[4181/4096 - 2178309/2097152 - 17/16 - 17711/16384 - 9227465/8388608 + 125/64])) + (->> (get-beat-data 2 1 #_[1 5/4 3/2 7/4] + (range 1 6) + [1 + 67/64 + 279/256 9/8 - 75025/65536 - 39088169/33554432 - 305/256 - 317811/262144 - 165580141/134217728 - 323/256 - 1346269/1048576 - 21/16 - 5473/4096 - 5702887/4194304 - 89/64 - 1449/1024 - 24157817/16777216 - 377/256 - 98209/65536 - 102334155/67108864 - 1597/1024 - 104005/65536 - 13/8 - 6765/4096 - 1762289/1048576 - 55/32 - 28657/16384 - 933147/524288 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 233/128 - 121393/65536 - 31622993/16777216 - 987/512 - 514229/262144 - 2/1]) - reverse - #_(map :beat-freq) - #_(map #(update % :beat-freq float)) - #_#_#_frequencies - (sort-by second) - reverse) -(let [scale-data - (+beat-data - 1 - (range 1 6) - (ratios->scale-data [1 - 67/64 - 279/256 - 9/8 - 75/64 - 39/32 - 5/4 - 167/128 - 87/64 - 45/32 - 187/128 - 3/2 - 25/16 - 417/256 - 27/16 - 7/4 - 233/128 - 15/8 - 125/64]))] - (defn beat-factors-hiccup - [{:keys [numer denom]}] - [:span (factors->hiccup numer) "/" (factors->hiccup denom)]) + 15/8 + 125/64] + #_[4181/4096 + 2178309/2097152 + 17/16 + 17711/16384 + 9227465/8388608 + 9/8 + 75025/65536 + 39088169/33554432 + 305/256 + 317811/262144 + 165580141/134217728 + 323/256 + 1346269/1048576 + 21/16 + 5473/4096 + 5702887/4194304 + 89/64 + 1449/1024 + 24157817/16777216 + 377/256 + 98209/65536 + 102334155/67108864 + 1597/1024 + 104005/65536 + 13/8 + 6765/4096 + 1762289/1048576 + 55/32 + 28657/16384 + 933147/524288 + 233/128 + 121393/65536 + 31622993/16777216 + 987/512 + 514229/262144 + 2/1]) + reverse + #_(map :beat-freq) + #_(map #(update % :beat-freq float)) + #_#_#_frequencies + (sort-by second) + reverse) + (def scale-data (+beat-data + 1 + (range 1 6) + (ratios->scale-data [1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64])))) +(defn beat-factors-hiccup + [{:keys [numer denom]}] + [:span (factors->hiccup numer) "/" (factors->hiccup denom)]) - (defn hiccup-table [beat-data] - (let [data-by-deg-pairs (group-by (comp (juxt :degree-1 :degree-2 :period)) beat-data) - beat-freqs (->> beat-data - (group-by #(select-keys % [:beat-freq.hz :beat-freq.factors])) - (map (fn [[m vs]] (assoc m :instances (count vs)))) - (sort-by :beat-freq.hz)) - rows (->> data-by-deg-pairs - (sort-by (comp (juxt #(nth % 2 nil) first second) first)) - (map - (fn [[deg-pair pair-beat-data]] - (println (:root-hz (first pair-beat-data))) - (let [data-by-hz (group-by :beat-freq.hz pair-beat-data) - beating-harmonics-at-beat-hz-index (map - (fn [{hz :beat-freq.hz}] - [:td - {:style {:white-space "nowrap"}} - (->> hz - data-by-hz - (map (comp #(str/join "," %) (juxt :ratio-1-partial :ratio-2-partial))) - (str/join " "))]) - beat-freqs)] - (into [:tr - [:td (nth deg-pair 2) "@" (:root-hz (first pair-beat-data)) "hz"] - [:td (str/join "," (take 2 deg-pair))]] - beating-harmonics-at-beat-hz-index))))) - thead [:thead (into [:tr - [:th "Period"] - [:th "Degrees"]] - (map (fn [bf] - [:th (:beat-freq.hz bf) "hz " "(" (:instances bf) ")" - [:br] - (beat-factors-hiccup (:beat-freq.factors bf))]) - beat-freqs))]] - [:table thead [:tbody rows]] - #_beat-freqs - #_data-by-deg-pairs) - #_(h/html [:p])) - (hiccup-table (:32hz (:beat-data scale-data))) +(defn hiccup-table [beat-data] + (let [data-by-deg-pairs (group-by (comp (juxt :degree-1 :degree-2 :period)) beat-data) + beat-freqs (->> beat-data + (group-by #(select-keys % [:beat-freq.hz :beat-freq.factors])) + (map (fn [[m vs]] (assoc m :instances (count vs)))) + (sort-by :beat-freq.hz)) + rows (->> data-by-deg-pairs + (sort-by (comp (juxt #(nth % 2 nil) first second) first)) + (map + (fn [[deg-pair pair-beat-data]] + (println (:root-hz (first pair-beat-data))) + (let [data-by-hz (group-by :beat-freq.hz pair-beat-data) + beating-harmonics-at-beat-hz-index (map + (fn [{hz :beat-freq.hz}] + [:td + {:style {:white-space "nowrap"}} + (->> hz + data-by-hz + (map (comp #(str/join "," %) (juxt :ratio-1-partial :ratio-2-partial))) + (str/join " "))]) + beat-freqs)] + (into [:tr + [:td (nth deg-pair 2) "@" (:root-hz (first pair-beat-data)) "hz"] + [:td (str/join "," (take 2 deg-pair))]] + beating-harmonics-at-beat-hz-index))))) + thead [:thead (into [:tr + [:th "Period"] + [:th "Degrees"]] + (map (fn [bf] + [:th (:beat-freq.hz bf) "hz " "(" (:instances bf) ")" + [:br] + (beat-factors-hiccup (:beat-freq.factors bf))]) + beat-freqs))]] + [:table thead [:tbody rows]] + #_beat-freqs + #_data-by-deg-pairs) + #_(h/html [:p])) - (defn html-template [body] - [:html [:head [:style - " +(comment + (hiccup-table (:32hz (:beat-data scale-data)))) + +(defn html-template [body] + [:html [:head [:style + " table, th, td { border: 1px solid black; border-collapse: collapse; @@ -338,36 +343,15 @@ min-width: 70px; padding: 0 4px; } "]] [:body body]]) - #?(:clj - (defn spit-html-table - [out-path beat-data] - (let [hiccup-data (-> beat-data - hiccup-table - html-template)] - (->> hiccup-data - h/html - str - (spit out-path))))) +#?(:clj + (defn spit-html-table + [out-path beat-data] + (let [hiccup-data (-> beat-data + hiccup-table + html-template)] + (->> hiccup-data + h/html + str + (spit out-path))))) +(comment (spit-html-table "resources/meta-meantone-beats.html" (:32hz (:beat-data scale-data)))) -(comment) -(->> beat-data - hiccup-table - #_(map (fn [[beats total]] - {:beat-hz (float beats) :factors (factorize beats) :instances total})) - #_(filter #(> (:instances %) 2)) - #_(sort-by :beat-hz)) -(do - (defn factorize - [n] - (-> n decompose-ratio :numer prime-factors)) - - (factorize-ratio 102334155/67108864)) - -(for [a [1 2 3 4] - b [1 2 3 4]] - [a b]) -(defn- ratio-pair->beat-data - [beat-data] - (make-map-by-key :pair beat-data)) - -#_(ratio-pair->beat-data (+beat-hz-by-period 2 256 (get-beat-data [1 5/4 3/2 7/4]))) From 90f0695cd46c067363dd069e35f07e0f8b01f7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 14 Oct 2025 22:49:15 -0600 Subject: [PATCH 29/54] [js-ratios] parse ratio strings --- src/erv/utils/parse.cljc | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/erv/utils/parse.cljc diff --git a/src/erv/utils/parse.cljc b/src/erv/utils/parse.cljc new file mode 100644 index 0000000..8f8134d --- /dev/null +++ b/src/erv/utils/parse.cljc @@ -0,0 +1,47 @@ +(ns erv.utils.parse + "Parse numbers into ratios" + (:require + [clojure.string :as str] + [com.gfredericks.exact :as e])) + +(defn- parseable-ratio? + [s] + (boolean (re-matches #"^-?\d+(/-?\d+)?$" s))) + +(do + (defn parse-ratio + [ratio-str] + (when-not (parseable-ratio? ratio-str) + (throw (ex-info "Cannot parse string into a ratio or an integer" + {:ratio-str ratio-str}))) + (let [[numer denom] + (->> (str/split ratio-str #"/") + (map e/string->integer))] + (e// numer (or denom e/ONE)))) + + #_(parse-ratio "3/2")) + +(defn parse-scale + "Parses a scale of ratio-strings" + [scale-str] + (->> (str/split scale-str #"[,|\s]") + (remove empty?) + (map parse-ratio))) + +#_(parse-scale "1 3/2\n 8/7") + +(defn print-ratio + [exact-int-or-ratio] + (cond + (e/ratio? exact-int-or-ratio) (str + (e/numerator exact-int-or-ratio) + "/" + (e/denominator exact-int-or-ratio)) + (e/integer? exact-int-or-ratio) (str + (e/integer->string exact-int-or-ratio) + "/" + 1) + :else (throw (ex-info "Don't know how to parse ratio" + {:input exact-int-or-ratio})))) + +#_(map print-ratio (parse-scale "1 3/2\n 8/7")) From 086fca36e389fb3c7f10fc42430a9de4706fdea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 14 Oct 2025 22:53:52 -0600 Subject: [PATCH 30/54] [js-ratios] fix missing require --- src/erv/utils/ratios.cljc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/erv/utils/ratios.cljc b/src/erv/utils/ratios.cljc index da03b57..4962d5d 100644 --- a/src/erv/utils/ratios.cljc +++ b/src/erv/utils/ratios.cljc @@ -14,7 +14,8 @@ [clojure.string :as str] [com.gfredericks.exact :as e] [erv.utils.conversions :as conv] - [erv.utils.core :refer [interval period-reduce round2 prime-factors]])])) + [erv.utils.core :refer [interval period-reduce round2 prime-factors]] + [erv.utils.impl :as impl])])) (defn ratio-proximity-list "Make a list of `ratios` that approximate a `target-ratio` in a list of `target-ratios`" From cab3acf2035ed9e643d9bed9f29816636315f9c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 00:13:31 -0600 Subject: [PATCH 31/54] fix clj tests --- src/erv/utils/ratios.cljc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/erv/utils/ratios.cljc b/src/erv/utils/ratios.cljc index 4962d5d..440cf4c 100644 --- a/src/erv/utils/ratios.cljc +++ b/src/erv/utils/ratios.cljc @@ -145,7 +145,8 @@ :bounded-ratio ratio :bounding-period period}))) (sort-by :bounded-ratio) - impl/+degree))) + ;; impl/+degree + ))) (defn ratios->scale-data ([ratios] (ratios->scale-data 2 ratios)) From 681c0c32047391ff4fed5baa3374994c3d5894a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 00:15:09 -0600 Subject: [PATCH 32/54] [js-ratios] add exact to utils.core and utils.ratios ns And add cljs tests --- shadow-cljs.edn | 11 ++++++-- src/erv/utils/core.cljc | 28 +++++++++++-------- src/erv/utils/parse.cljc | 25 ++++++++--------- src/erv/utils/ratios.cljc | 6 ++-- .../erv/lattice/{v2_test.cljc => v2_test.clj} | 0 test/erv/meru/core_test.clj | 2 +- test/erv/utils/ratios_test.cljs | 14 ++++++++++ 7 files changed, 55 insertions(+), 31 deletions(-) rename test/erv/lattice/{v2_test.cljc => v2_test.clj} (100%) create mode 100644 test/erv/utils/ratios_test.cljs diff --git a/shadow-cljs.edn b/shadow-cljs.edn index b432a13..b01a15b 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -1,4 +1,4 @@ -{:source-paths ["src"] +{:source-paths ["src" "test"] :dependencies [[binaryage/devtools "0.9.7"] [cider/cider-nrepl "0.25.3"] [com.taoensso/timbre "5.1.0"] @@ -7,8 +7,13 @@ [com.gfredericks/exact "0.1.11"] [org.clojure/math.combinatorics "0.1.6"] [com.taoensso/timbre "4.10.0"]] - :dev-http {5678 "build/browser"} + :dev-http {5678 "build/browser" + 3001 "out/test"} :builds ; https://shadow-cljs.github.io/docs/UsersGuide.html#target-node-script {:lib {:target :node-library :output-to "dist/lib.js" - :exports-fn js.export-fn/generate-exports}}} + :exports-fn js.export-fn/generate-exports} + :browser-test {:target :browser-test + :test-dir "out/test"} + :test {:target :node-test + :output-to "out/node-tests.js"}}} diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index fb6989e..6e376fc 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -1,7 +1,11 @@ (ns erv.utils.core + #?(:cljs (:refer-clojure :exclude [> >= < <= = + - * / -compare compare numerator denominator integer? + mod rem quot even? odd?])) (:require + [clojure.core :as core] [clojure.set :as set] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + #?(:cljs [com.gfredericks.exact :as e :refer [> < = + - * / rem mod]]))) (defn validate [spec input] (or (s/valid? spec input) @@ -9,9 +13,8 @@ (defn wrap-at [i coll] (let [size (count coll) - i* (if (zero? size) 0 (mod i size))] + i* (if (zero? size) 0 (core/mod i size))] (nth coll i* nil))) - (defn round2 "Round a double to the given precision (number of significant digits)" [precision d] @@ -20,7 +23,7 @@ (defn rotate [xs n] (let [l (count xs) - off (mod (+ (mod n l) l) l)] + off (core/mod (core/+ (core/mod n l) l) l)] (concat (drop off xs) (take off xs)))) (defn get-all-rotations [pattern] @@ -55,12 +58,15 @@ (defn period-reduce ([ratio] (period-reduce 2 ratio)) ([period ratio] - (loop [ratio ratio] - (cond - (> period ratio 1) ratio - (or (= period ratio) (= 1 ratio)) 1 - (> ratio period) (recur (/ ratio period)) - (< ratio period) (recur (* ratio period)))))) + (let [one #?(:clj 1 :cljs (e/native->integer 1)) + period* #?(:clj period :cljs (e/native->integer period))] + (loop [ratio ratio] + (cond + (> period* ratio one) ratio + (or (= period* ratio) (= one ratio)) one + (> ratio period*) (recur (/ ratio period*)) + (< ratio period*) (recur (* ratio period*))))))) + (defn indexes-of [el coll] (keep-indexed #(when (= el %2) %1) coll)) @@ -75,7 +81,7 @@ (defn pattern->degrees [pattern] (->> pattern - (reduce (fn [acc el] (conj acc (+ el (last acc)))) + (reduce (fn [acc el] (conj acc (core/+ el (last acc)))) [0]) drop-last)) diff --git a/src/erv/utils/parse.cljc b/src/erv/utils/parse.cljc index 8f8134d..a89b7b7 100644 --- a/src/erv/utils/parse.cljc +++ b/src/erv/utils/parse.cljc @@ -1,5 +1,5 @@ (ns erv.utils.parse - "Parse numbers into ratios" + "Parse numbers into ratios using `gfredericks/exact`" (:require [clojure.string :as str] [com.gfredericks.exact :as e])) @@ -8,18 +8,15 @@ [s] (boolean (re-matches #"^-?\d+(/-?\d+)?$" s))) -(do - (defn parse-ratio - [ratio-str] - (when-not (parseable-ratio? ratio-str) - (throw (ex-info "Cannot parse string into a ratio or an integer" - {:ratio-str ratio-str}))) - (let [[numer denom] - (->> (str/split ratio-str #"/") - (map e/string->integer))] - (e// numer (or denom e/ONE)))) - - #_(parse-ratio "3/2")) +(defn parse-ratio + [ratio-str] + (when-not (parseable-ratio? ratio-str) + (throw (ex-info "Cannot parse string into a ratio or an integer" + {:ratio-str ratio-str}))) + (let [[numer denom] + (->> (str/split ratio-str #"/") + (map (comp e/string->integer)))] + (e// numer (or denom e/ONE)))) (defn parse-scale "Parses a scale of ratio-strings" @@ -30,7 +27,7 @@ #_(parse-scale "1 3/2\n 8/7") -(defn print-ratio +(defn exact->string [exact-int-or-ratio] (cond (e/ratio? exact-int-or-ratio) (str diff --git a/src/erv/utils/ratios.cljc b/src/erv/utils/ratios.cljc index 440cf4c..8c7c2fd 100644 --- a/src/erv/utils/ratios.cljc +++ b/src/erv/utils/ratios.cljc @@ -10,9 +10,11 @@ round2]] [erv.utils.impl :as impl])] :cljs - [(:require + [(:refer-clojure :exclude [> < + - * / - numerator denominator integer? + mod rem quot even? odd?]) + (:require [clojure.string :as str] - [com.gfredericks.exact :as e] + [com.gfredericks.exact :as e :refer [> < + - * / - mod]] [erv.utils.conversions :as conv] [erv.utils.core :refer [interval period-reduce round2 prime-factors]] [erv.utils.impl :as impl])])) diff --git a/test/erv/lattice/v2_test.cljc b/test/erv/lattice/v2_test.clj similarity index 100% rename from test/erv/lattice/v2_test.cljc rename to test/erv/lattice/v2_test.clj diff --git a/test/erv/meru/core_test.clj b/test/erv/meru/core_test.clj index 3661b84..7f4e0e2 100644 --- a/test/erv/meru/core_test.clj +++ b/test/erv/meru/core_test.clj @@ -8,7 +8,7 @@ :mos/pattern.name "1s1L", :mos/sL-ratio.float (float 2.2702353), :mos/s.cents 366.9460706785318, - :mos/L.cents 833.0539293214687} + :mos/L.cents 833.0539293214689} {:size 3, :mos/pattern.name "2s1L", :mos/sL-ratio.float (float 1.2702353), diff --git a/test/erv/utils/ratios_test.cljs b/test/erv/utils/ratios_test.cljs new file mode 100644 index 0000000..6353207 --- /dev/null +++ b/test/erv/utils/ratios_test.cljs @@ -0,0 +1,14 @@ +(ns erv.utils.ratios-test + (:require + [cljs.test :refer [deftest is]] + [erv.utils.parse :refer [exact->string parse-scale]] + [erv.utils.ratios :refer [ratios->scale]])) + +(deftest ratios->scale-test + (is (= [{:ratio "1/1", :bounded-ratio "1/1", :bounding-period 2} + {:ratio "5/4", :bounded-ratio "5/4", :bounding-period 2} + {:ratio "3/2", :bounded-ratio "3/2", :bounding-period 2}] + (->> (ratios->scale 2 (parse-scale "1 3 5/4")) + (map #(-> % + (update :ratio exact->string) + (update :bounded-ratio exact->string))))))) From a6bf6ff9867db38750d0788245a5214c58f7bf00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 00:26:13 -0600 Subject: [PATCH 33/54] [js-rationals] run js tests on ci --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++-- package.json | 3 ++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6a34d2..433cb9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: branches: [ main, dev ] jobs: - test: + clojure-test: runs-on: ubuntu-latest steps: @@ -35,5 +35,28 @@ jobs: restore-keys: | ${{ runner.os }}-gitlibs- - - name: Run tests + - name: Run Clojure tests run: clojure -M:test + + node-test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [lts/*, 22.x, 24.x] # LTS plus specific versions + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' # Enable dependency caching + + - name: Install dependencies + run: npm ci + + - name: Run npm tests + run: CI=false npm test diff --git a/package.json b/package.json index 3ffc612..ce6238b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "scripts": { "release:lib": "shadow-cljs release lib; sed -i '' 's/global/globalThis/g' dist/lib.js", "watch:lib": "shadow-cljs watch lib", - "prepublishOnly": "npm run release:lib" + "prepublishOnly": "npm run release:lib", + "test": "npx shadow-cljs compile test && node out/node-tests.js" }, "main": "dist/lib.js", "files": [ From 579aa3bc071a3be23b5dcda8fba550764d9cbaa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 00:30:37 -0600 Subject: [PATCH 34/54] fix broken diagonals test --- test/erv/meru/diagonals_test.clj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/erv/meru/diagonals_test.clj b/test/erv/meru/diagonals_test.clj index 91480da..8bc87e3 100644 --- a/test/erv/meru/diagonals_test.clj +++ b/test/erv/meru/diagonals_test.clj @@ -8,7 +8,7 @@ (deftest diagonals-test (testing "The Malli type is up to date" - (let [data (subject/diagonal + (let [data (subject/diagonals {:size 12 :slope {:x 1 :y 2} :pascal-coord->number pascals-triangle/default-coord-map})] @@ -16,21 +16,21 @@ (m/explain MeruDiagonalsData data)))) (testing "Has a `:series` key" (is (= [1 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N] - (->> (subject/diagonal + (->> (subject/diagonals {:size 12 :slope {:x 1 :y 2} :pascal-coord->number pascals-triangle/default-coord-map}) :series)))) (testing "Can work with a custom `pascal-coord->number function (or map)" (is (= [1 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N] - (->> (subject/diagonal + (->> (subject/diagonals {:size 12 :slope {:x 1 :y 2} :pascal-coord->number (pascals-triangle/make-coord-map 1 1 30)}) :series)))) (testing "Has a `:convergence-double` and a `:convergence-double-with-precision` key" (is (= [1.617977528089888 1.618] - (->> (subject/diagonal + (->> (subject/diagonals {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 @@ -38,13 +38,13 @@ ((juxt :convergence-double :convergence-double-with-precision)))))) (testing "May have a `:triangle-seed` key, specially if `:pascal-coord->number` is `pascals-triangle/default-coord-map` or was created with `pascals-triangle/make-coord-map`." (is (= {:left 1, :right 1} - (->> (subject/diagonal + (->> (subject/diagonals {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 :pascal-coord->number pascals-triangle/default-coord-map}) :triangle-seed) - (->> (subject/diagonal + (->> (subject/diagonals {:size 12 :slope {:x 1 :y 2} :convergence-precision 3 From 055cef1b5499136e71805ac59a599797d394ce71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 00:31:15 -0600 Subject: [PATCH 35/54] Only test node lts --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 433cb9e..a988c93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: strategy: matrix: - node-version: [lts/*, 22.x, 24.x] # LTS plus specific versions + node-version: [lts/*] # LTS plus specific versions steps: - name: Checkout code From 0e7a6ccf07a23cc63c5c159c7cad17095ddf3ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 01:14:34 -0600 Subject: [PATCH 36/54] [js-ratios] update shadow-cljs and use deps.edn deps Remove project.clj --- deps.edn | 18 +- package-lock.json | 2066 +++---------------------------- package.json | 2 +- project.clj | 15 - shadow-cljs.edn | 11 +- test/erv/utils/ratios_test.cljs | 1 + 6 files changed, 204 insertions(+), 1909 deletions(-) delete mode 100755 project.clj diff --git a/deps.edn b/deps.edn index 95764f5..8602adc 100755 --- a/deps.edn +++ b/deps.edn @@ -1,8 +1,6 @@ {:paths ["src" "test" "dev"] - :deps {org.clojure/clojure {:mvn/version "1.11.1"} - org.clojure/clojurescript {:mvn/version "1.11.60"} + :deps {org.clojure/clojure {:mvn/version "1.12.0"} org.clojure/core.async {:mvn/version "1.3.618"} - com.google.javascript/closure-compiler-unshaded {:mvn/version "v20221102"} org.clojure/math.combinatorics {:mvn/version "0.3.0"} com.taoensso/timbre {:mvn/version "4.10.0"} quil/quil {:mvn/version "4.0.0-SNAPSHOT"} @@ -12,7 +10,19 @@ table/table {:mvn/version "0.5.0"} org.clojure/data.json {:mvn/version "2.4.0"} hiccup/hiccup {:mvn/version "2.0.0"}} - :aliases {:test {:extra-paths ["test"] + :aliases {:cljs {:extra-deps + {thheller/shadow-cljs {:mvn/version "3.2.1"} + binaryage/devtools {:mvn/version "0.9.7"} + ;; the following block of dependencies copied from shadow-cljs own deps + org.clojure/clojurescript {:mvn/version "1.12.35" + :exclusions + [com.google.javascript/closure-compiler + org.clojure/google-closure-library + org.clojure/google-closure-library-third-party]} + com.google.javascript/closure-compiler {:mvn/version "v20250407"} + org.clojure/google-closure-library {:mvn/version "0.0-20250418-2ce9ab6d"} + org.clojure/google-closure-library-third-party {:mvn/version "0.0-20250418-2ce9ab6d"}}} + :test {:extra-paths ["test"] :extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}} :main-opts ["-m" "cognitect.test-runner"] diff --git a/package-lock.json b/package-lock.json index 0b0273b..72100cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,58 +1,15 @@ { - "name": "erv", - "version": "0.0.4", + "name": "@diegovdc/erv", + "version": "0.0.14", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "erv", - "version": "0.0.4", - "license": "GNU", + "name": "@diegovdc/erv", + "version": "0.0.14", + "license": "GNU GLP v3.0", "devDependencies": { - "shadow-cljs": "2.25.4" - } - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", - "dev": true - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.1" + "shadow-cljs": "3.2.1" } }, "node_modules/base64-js": { @@ -73,102 +30,13 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } + ], + "license": "MIT" }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/browserify-sign/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "funding": [ { @@ -183,1819 +51,257 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", - "dev": true - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, + ], + "license": "MIT", "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } + "license": "MIT" }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, + "license": "ISC", "engines": { - "node": "*" + "node": ">=16" } }, - "node_modules/des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" + "license": "MIT", + "engines": { + "node": ">= 0.6.0" } }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "node_modules/readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", "dev": true, "engines": { - "node": ">=0.4", - "npm": ">=1.2" + "node": ">= 0.8.0" } }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "node_modules/shadow-cljs": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-3.2.1.tgz", + "integrity": "sha512-xsTSHGUBGZqotbjdKTbKUuPaYoj41ozMPbylr0aQNHvpG+TEner7YTALPdthNGUsIseE+U7kNHV9HNTfRXc/Zw==", "dev": true, + "license": "ISC", "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" + "buffer": "^6.0.3", + "process": "^0.11.10", + "readline-sync": "^1.4.10", + "shadow-cljs-jar": "1.3.4", + "source-map-support": "^0.5.21", + "which": "^5.0.0", + "ws": "^8.18.1" + }, + "bin": { + "shadow-cljs": "cli/runner.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "node_modules/shadow-cljs-jar": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/shadow-cljs-jar/-/shadow-cljs-jar-1.3.4.tgz", + "integrity": "sha512-cZB2pzVXBnhpJ6PQdsjO+j/MksR28mv4QD/hP/2y1fsIa9Z9RutYgh3N34FZ8Ktl4puAXaIGlct+gMCJ5BmwmA==", "dev": true }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=0.8.x" + "node": ">=0.10.0" } }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "isexe": "^3.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "node": ">=10.0.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "peerDependenciesMeta": { + "bufferutil": { + "optional": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "utf-8-validate": { + "optional": true } - ] - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" } + } + }, + "dependencies": { + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/ieee754": { + "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } + "readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "shadow-cljs": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-3.2.1.tgz", + "integrity": "sha512-xsTSHGUBGZqotbjdKTbKUuPaYoj41ozMPbylr0aQNHvpG+TEner7YTALPdthNGUsIseE+U7kNHV9HNTfRXc/Zw==", "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" + "requires": { + "buffer": "^6.0.3", + "process": "^0.11.10", + "readline-sync": "^1.4.10", + "shadow-cljs-jar": "1.3.4", + "source-map-support": "^0.5.21", + "which": "^5.0.0", + "ws": "^8.18.1" } }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "shadow-cljs-jar": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/shadow-cljs-jar/-/shadow-cljs-jar-1.3.4.tgz", + "integrity": "sha512-cZB2pzVXBnhpJ6PQdsjO+j/MksR28mv4QD/hP/2y1fsIa9Z9RutYgh3N34FZ8Ktl4puAXaIGlct+gMCJ5BmwmA==", "dev": true }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "requires": { + "isexe": "^3.1.1" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "dev": true - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/readline-sync": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", - "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shadow-cljs": { - "version": "2.25.4", - "resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.25.4.tgz", - "integrity": "sha512-pxQok9zxKv8Ohp0bhJS7/lZq7Pe3P2NQ189FGGw3wcq/AgwHBH61ysFbGKZpQ5ZliYF8gJ4VwZM4Bqsbm7AqPw==", - "dev": true, - "dependencies": { - "node-libs-browser": "^2.2.1", - "readline-sync": "^1.4.7", - "shadow-cljs-jar": "1.3.4", - "source-map-support": "^0.4.15", - "which": "^1.3.1", - "ws": "^7.4.6" - }, - "bin": { - "shadow-cljs": "cli/runner.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/shadow-cljs-jar": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/shadow-cljs-jar/-/shadow-cljs-jar-1.3.4.tgz", - "integrity": "sha512-cZB2pzVXBnhpJ6PQdsjO+j/MksR28mv4QD/hP/2y1fsIa9Z9RutYgh3N34FZ8Ktl4puAXaIGlct+gMCJ5BmwmA==", - "dev": true - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "dependencies": { - "source-map": "^0.5.6" - } - }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", - "dev": true - }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", - "dev": true - }, - "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", - "dev": true, - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.11.0" - } - }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - } - }, - "dependencies": { - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", - "dev": true - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true - }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "dev": true - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readline-sync": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", - "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", - "dev": true - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shadow-cljs": { - "version": "2.25.4", - "resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.25.4.tgz", - "integrity": "sha512-pxQok9zxKv8Ohp0bhJS7/lZq7Pe3P2NQ189FGGw3wcq/AgwHBH61ysFbGKZpQ5ZliYF8gJ4VwZM4Bqsbm7AqPw==", - "dev": true, - "requires": { - "node-libs-browser": "^2.2.1", - "readline-sync": "^1.4.7", - "shadow-cljs-jar": "1.3.4", - "source-map-support": "^0.4.15", - "which": "^1.3.1", - "ws": "^7.4.6" - } - }, - "shadow-cljs-jar": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/shadow-cljs-jar/-/shadow-cljs-jar-1.3.4.tgz", - "integrity": "sha512-cZB2pzVXBnhpJ6PQdsjO+j/MksR28mv4QD/hP/2y1fsIa9Z9RutYgh3N34FZ8Ktl4puAXaIGlct+gMCJ5BmwmA==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", - "dev": true - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", - "dev": true - }, - "url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", - "dev": true, - "requires": { - "punycode": "^1.4.1", - "qs": "^6.11.0" - } - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "requires": {} - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true } } } diff --git a/package.json b/package.json index ce6238b..321c95a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "music theory" ], "devDependencies": { - "shadow-cljs": "2.25.4" + "shadow-cljs": "3.2.1" }, "license": "GNU GLP v3.0", "dependencies": {} diff --git a/project.clj b/project.clj deleted file mode 100755 index 7c40503..0000000 --- a/project.clj +++ /dev/null @@ -1,15 +0,0 @@ -(defproject erv "0.1.0-SNAPSHOT" - :description "A library to design microtonal scales with ideas mainly derived from Erv Wilson's work" - :url "https://github.com/diegovdc/erv" - :license {:name "GPL-3.0-or-later WITH Classpath-exception-2.0" - :url "https://www.gnu.org/licenses/gpl-3.0.en.html#license-text"} - :dependencies [[org.clojure/clojure "1.11.1"] - [org.clojure/math.combinatorics "0.3.0"] - [org.clojure/data.json "2.4.0"] - [com.gfredericks/exact "0.1.11"] - [com.taoensso/timbre "4.10.0"] - [table "0.5.0"] - [org.clojure/tools.namespace "1.3.0"] - [quil "4.0.0-SNAPSHOT"]] - :jvm-opts ["-Xmx8g"] - :repl-options {:init-ns user}) diff --git a/shadow-cljs.edn b/shadow-cljs.edn index b01a15b..13b35f6 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -1,12 +1,5 @@ -{:source-paths ["src" "test"] - :dependencies [[binaryage/devtools "0.9.7"] - [cider/cider-nrepl "0.25.3"] - [com.taoensso/timbre "5.1.0"] - [refactor-nrepl "2.5.0"] - [table/table "0.5.0"] - [com.gfredericks/exact "0.1.11"] - [org.clojure/math.combinatorics "0.1.6"] - [com.taoensso/timbre "4.10.0"]] +{;; :source-paths ["src" "test"] + :deps {:aliases [:cljs]} :dev-http {5678 "build/browser" 3001 "out/test"} :builds ; https://shadow-cljs.github.io/docs/UsersGuide.html#target-node-script diff --git a/test/erv/utils/ratios_test.cljs b/test/erv/utils/ratios_test.cljs index 6353207..cddf59c 100644 --- a/test/erv/utils/ratios_test.cljs +++ b/test/erv/utils/ratios_test.cljs @@ -12,3 +12,4 @@ (map #(-> % (update :ratio exact->string) (update :bounded-ratio exact->string))))))) + From 634a5e1eb909db498bd7e1e0c012173448bd2f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 01:24:42 -0600 Subject: [PATCH 37/54] update readme with dev instructions --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3ac1477..b2e2f6e 100755 --- a/README.md +++ b/README.md @@ -34,15 +34,15 @@ https://en.xen.wiki/w/MOS_scale (def generator 13) (def my-mos (mos/make period generator)) (submos/make-all-submos (nth my-mos 3) generator) ;; this will generate all secondary MOS and all possible "traverse" MOS - - - ``` ## Note -This library is a work in progress and mostly a workshop for myself, so the code is not polished as it should. If you are using this library, feel free to let make me aware of so that I can take more care of the code and the documentation. +This library is a work in progress and mostly a workshop for myself, so the code is not polished as it should. If you are using this library, feel free to let make me aware of it so that I can take more care of the code and the documentation. + +## Development +On `emacs` one can run `cider-jackin-clj&cljs` then select the `shadow-cljs` server and the `:browser-build`. ## License From a31506f623e7d0c8f1222a43b5487a443af53d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 01:32:29 -0600 Subject: [PATCH 38/54] rename file --- src/erv/utils/{parse.cljc => exact.cljc} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/erv/utils/{parse.cljc => exact.cljc} (98%) diff --git a/src/erv/utils/parse.cljc b/src/erv/utils/exact.cljc similarity index 98% rename from src/erv/utils/parse.cljc rename to src/erv/utils/exact.cljc index a89b7b7..a299531 100644 --- a/src/erv/utils/parse.cljc +++ b/src/erv/utils/exact.cljc @@ -1,4 +1,4 @@ -(ns erv.utils.parse +(ns erv.utils.exact "Parse numbers into ratios using `gfredericks/exact`" (:require [clojure.string :as str] From 83ca3a3a4ca74581945e2e8801dc8ceadaf79a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 12:40:16 -0600 Subject: [PATCH 39/54] [js-ratios] enable cps scales --- src/erv/cps/core.cljc | 70 +++--- src/erv/utils/exact.cljc | 12 + test/erv/cps/core_test.cljs | 401 ++++++++++++++++++++++++++++++++ test/erv/utils/ratios_test.cljs | 2 +- 4 files changed, 448 insertions(+), 37 deletions(-) create mode 100644 test/erv/cps/core_test.cljs diff --git a/src/erv/cps/core.cljc b/src/erv/cps/core.cljc index 251f6f5..62bcff7 100755 --- a/src/erv/cps/core.cljc +++ b/src/erv/cps/core.cljc @@ -1,14 +1,22 @@ (ns erv.cps.core ;; TODO use https://github.com/Engelberg/ubergraph for the graphs + #?(:cljs (:refer-clojure :exclude [> < >= <= = + - * / numerator denominator integer? + mod rem quot even? odd?])) + #_(:require + #?(:cljs [com.gfredericks.exact :as e :refer [* + - - /]]) + [clojure.string]) (:require #? (:cljs [goog.string :as gstr]) #? (:cljs [goog.string.format]) + #?(:cljs [com.gfredericks.exact :as e :refer [> < >= <= = + - * /]]) + #?(:cljs [erv.utils.exact :as exact.utils]) + [clojure.core :as core] [clojure.math.combinatorics :as combo] [clojure.set :as set] [clojure.spec.alpha :as s] [clojure.string :as str] [clojure.walk :as walk] - [erv.utils.core :refer [validate]] + [erv.utils.core :refer [validate period-reduce]] [erv.utils.scale :refer [+degree]])) #?(:cljs @@ -25,14 +33,14 @@ :graph :some-graph}}) (s/def ::cps (s/and (s/coll-of set? :distinct true) - #(->> % (map count) (apply =)))) + #(->> % (map count) (apply core/=)))) (s/def ::sub-cps-set (s/coll-of ::cps :distinct true)) (defn- num->kw [n] - (-> (char (+ 65 n)) str str/lower-case keyword)) + (-> (char (core/+ 65 n)) str str/lower-case keyword)) (defn ->cps [size factors] - (if (> size (count factors)) + (if (core/> size (count factors)) #{#{}} (with-meta (->> (combo/combinations (into [] factors) size) @@ -61,20 +69,7 @@ (map (fn [pair] {:set pair :archi-set (set (map archi-factors pair))}) cps-set))) -(defn within-bounding-period - "Transposes a ratio withing a bounding-period. - The octave is a `bounding-period` of 2,the tritave of 3, etc." - [bounding-period ratio] - {:pre [(> bounding-period 1)]} - (loop [r ratio] - (cond - (> r bounding-period) (recur (/ r bounding-period)) - (< r 1) (recur (* r bounding-period)) - (= bounding-period r) 1 - :else r))) - -#_(within-bounding-period 2 1/21) - +;; TODO: can this be replaced with the function at utils? (defn bound-ratio ;;; TODO, how to be able to specify multiple bounding-periods "Calculate all the ratios within the bounding-period (e.g. octave, tritave, etc.) @@ -83,20 +78,22 @@ i.e. for a cps with factors [1 3 5 7] you can use either 3, 5, or 7" ([bounding-period cps-map] (bound-ratio bounding-period 1 cps-map)) ([bounding-period normalization-generator cps-map] - (->> cps-map - (map (fn [node*] - (let [ratio (/ (apply * (node* :set)) normalization-generator)] - (if bounding-period;; TODO test `nil` `bounding-period` with sub-cps - (assoc node* - :ratio ratio - :bounded-ratio (within-bounding-period - bounding-period - ratio) - :bounding-period bounding-period) - (assoc node* - :ratio ratio - :bounded-ratio ratio - :bounding-period nil)))))))) + (let [norm-gen #?(:clj normalization-generator + :cljs (exact.utils/parse-ratio (str normalization-generator)))] + (->> cps-map + (map (fn [node*] + (let [set* #?(:clj (node* :set) + :cljs (map e/native->integer (node* :set))) + ratio (/ (apply * set*) norm-gen)] + (if bounding-period ;; TODO test `nil` `bounding-period` with sub-cps + (assoc node* + :ratio ratio + :bounded-ratio (period-reduce bounding-period ratio) + :bounding-period bounding-period) + (assoc node* + :ratio ratio + :bounded-ratio ratio + :bounding-period nil))))))))) (defn add-edge ;; TODO use https://github.com/Engelberg/ubergraph for the graphs @@ -115,8 +112,8 @@ (let [rest* (rest nodes) current-node (first nodes) edges (->> rest* - (filter #(= (dec (count (:set %))) (count (set/intersection (:set current-node) - (:set %))))) + (filter #(core/= (dec (count (:set %))) (count (set/intersection (:set current-node) + (:set %))))) (map #(conj [] current-node %)))] (recur rest* (reduce @@ -165,7 +162,7 @@ (defn find-subcps [cps-set-size factors sub-cps-set-size subcps-factors-size] (let [base-cps (->cps subcps-factors-size factors) - diff-set-size (Math/abs (- cps-set-size sub-cps-set-size)) + diff-set-size (Math/abs (core/- cps-set-size sub-cps-set-size)) gens-set (set factors) meta* {::type (str sub-cps-set-size ")" subcps-factors-size " of " cps-set-size ")" (count factors))}] (->> base-cps (map @@ -352,8 +349,9 @@ [cps-size cps-row] (->> (range 1 cps-row) (map (fn [row] - [row (range (max 1 (+ cps-size (- row cps-row))) + [row (range (max 1 (core/+ cps-size (core/- row cps-row))) (inc (min row cps-size)))])))) + (defn +all-subcps-row [cps-data [row cps-sizes]] (reduce (fn [cps-data* set-size] (+subcps cps-data* set-size row)) diff --git a/src/erv/utils/exact.cljc b/src/erv/utils/exact.cljc index a299531..b89dff1 100644 --- a/src/erv/utils/exact.cljc +++ b/src/erv/utils/exact.cljc @@ -2,6 +2,7 @@ "Parse numbers into ratios using `gfredericks/exact`" (:require [clojure.string :as str] + [clojure.walk :as walk] [com.gfredericks.exact :as e])) (defn- parseable-ratio? @@ -42,3 +43,14 @@ {:input exact-int-or-ratio})))) #_(map print-ratio (parse-scale "1 3/2\n 8/7")) + +(defn make-readable + "Takes a walkeable structure and converts all exact instances to a readable string" + [coll] + (walk/postwalk + (fn [x] + (if (or (e/integer? x) + (e/ratio? x)) + (exact->string x) + x)) + coll)) diff --git a/test/erv/cps/core_test.cljs b/test/erv/cps/core_test.cljs new file mode 100644 index 0000000..5fda4fb --- /dev/null +++ b/test/erv/cps/core_test.cljs @@ -0,0 +1,401 @@ +(ns erv.cps.core-test + (:require + [cljs.test :refer [deftest is]] + [erv.cps.core :as cps] + [erv.utils.exact :as exact.utils])) + +(deftest make-test + (is (= '{:meta + {:scale :cps, + :period 2, + :size 3, + :cps/size 2, + :cps/factors (1 3 5), + :cps/normalized-by 1, + :cps/type "2)3"}, + :scale + ({:set #{1 5}, + :archi-set #{:c :a}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2, + :degree 0} + {:set #{1 3}, + :archi-set #{:b :a}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2, + :degree 1} + {:set #{3 5}, + :archi-set #{:c :b}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2, + :degree 2}), + :nodes + ({:set #{1 3}, + :archi-set #{:b :a}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2} + {:set #{1 5}, + :archi-set #{:c :a}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2} + {:set #{3 5}, + :archi-set #{:c :b}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :graphs + {:full + {{:set #{1 3}, + :archi-set #{:b :a}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2} + #{{:set #{3 5}, + :archi-set #{:c :b}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2} + {:set #{1 5}, + :archi-set #{:c :a}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}}, + {:set #{1 5}, + :archi-set #{:c :a}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2} + #{{:set #{3 5}, + :archi-set #{:c :b}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2} + {:set #{1 3}, + :archi-set #{:b :a}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2}}, + {:set #{3 5}, + :archi-set #{:c :b}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2} + #{{:set #{1 3}, + :archi-set #{:b :a}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2} + {:set #{1 5}, + :archi-set #{:c :a}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}}}, + :simple + {#{1 3} #{#{3 5} #{1 5}}, #{1 5} #{#{3 5} #{1 3}}, #{3 5} #{#{1 5} #{1 3}}}}, + :subcps + {"1)1 of 2)3 3.5" + {:meta + {:scale :cps, + :period 2, + :size 1, + :cps/size 2, + :cps/factors (3 5), + :cps/normalized-by 1, + :cps/type "1)1 of 2)3"}, + :scale + ({:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :nodes + ({:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :graphs {:full {}, :simple {}}}, + "1)1 of 2)3 1.5" + {:meta + {:scale :cps, + :period 2, + :size 1, + :cps/size 2, + :cps/factors (1 5), + :cps/normalized-by 1, + :cps/type "1)1 of 2)3"}, + :scale + ({:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}), + :nodes + ({:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}), + :graphs {:full {}, :simple {}}}, + "2)2 of 2)3 1.3" + {:meta + {:scale :cps, + :period 2, + :size 1, + :cps/size 2, + :cps/factors (1 3), + :cps/normalized-by 1, + :cps/type "2)2 of 2)3"}, + :scale + ({:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2}), + :nodes + ({:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2}), + :graphs {:full {}, :simple {}}}, + "1)2 of 2)3 5-1.3" + {:meta + {:scale :cps, + :period 2, + :size 2, + :cps/size 2, + :cps/factors (1 3 5), + :cps/normalized-by 1, + :cps/type "1)2 of 2)3"}, + :scale + ({:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2} + {:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :nodes + ({:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2} + {:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :graphs + {:full + {{:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2} + #{{:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}}, + {:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2} + #{{:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}}}, + :simple {#{1 5} #{#{3 5}}, #{3 5} #{#{1 5}}}}}, + "1)2 of 2)3 3-1.5" + {:meta + {:scale :cps, + :period 2, + :size 2, + :cps/size 2, + :cps/factors (1 3 5), + :cps/normalized-by 1, + :cps/type "1)2 of 2)3"}, + :scale + ({:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2} + {:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :nodes + ({:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2} + {:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :graphs + {:full + {{:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2} + #{{:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}}, + {:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2} + #{{:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2}}}, + :simple {#{1 3} #{#{3 5}}, #{3 5} #{#{1 3}}}}}, + "2)2 of 2)3 1.5" + {:meta + {:scale :cps, + :period 2, + :size 1, + :cps/size 2, + :cps/factors (1 5), + :cps/normalized-by 1, + :cps/type "2)2 of 2)3"}, + :scale + ({:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}), + :nodes + ({:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}), + :graphs {:full {}, :simple {}}}, + "1)2 of 2)3 1-3.5" + {:meta + {:scale :cps, + :period 2, + :size 2, + :cps/size 2, + :cps/factors (1 3 5), + :cps/normalized-by 1, + :cps/type "1)2 of 2)3"}, + :scale + ({:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2} + {:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2}), + :nodes + ({:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2} + {:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}), + :graphs + {:full + {{:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2} + #{{:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2}}, + {:set #{1 5}, + :archi-set #{nil}, + :ratio "5/1", + :bounded-ratio "5/4", + :bounding-period 2} + #{{:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2}}}, + :simple {#{1 3} #{#{1 5}}, #{1 5} #{#{1 3}}}}}, + "2)2 of 2)3 3.5" + {:meta + {:scale :cps, + :period 2, + :size 1, + :cps/size 2, + :cps/factors (3 5), + :cps/normalized-by 1, + :cps/type "2)2 of 2)3"}, + :scale + ({:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :nodes + ({:set #{3 5}, + :archi-set #{nil}, + :ratio "15/1", + :bounded-ratio "15/8", + :bounding-period 2}), + :graphs {:full {}, :simple {}}}, + "1)1 of 2)3 1.3" + {:meta + {:scale :cps, + :period 2, + :size 1, + :cps/size 2, + :cps/factors (1 3), + :cps/normalized-by 1, + :cps/type "1)1 of 2)3"}, + :scale + ({:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2}), + :nodes + ({:set #{1 3}, + :archi-set #{nil}, + :ratio "3/1", + :bounded-ratio "3/2", + :bounding-period 2}), + :graphs {:full {}, :simple {}}}}} + (->> (cps/make 2 [1 3 5]) + (cps/+all-subcps) + exact.utils/make-readable)))) + +(->> (cps/make 2 [1 3 5]) + (cps/+all-subcps) + exact.utils/make-readable) diff --git a/test/erv/utils/ratios_test.cljs b/test/erv/utils/ratios_test.cljs index cddf59c..19d9c6a 100644 --- a/test/erv/utils/ratios_test.cljs +++ b/test/erv/utils/ratios_test.cljs @@ -1,7 +1,7 @@ (ns erv.utils.ratios-test (:require [cljs.test :refer [deftest is]] - [erv.utils.parse :refer [exact->string parse-scale]] + [erv.utils.exact :refer [exact->string parse-scale]] [erv.utils.ratios :refer [ratios->scale]])) (deftest ratios->scale-test From b0b3c9f3a57aff22e647429db18cea311f7ed251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 18:18:27 -0600 Subject: [PATCH 40/54] Add tests for utils.ratios --- README.md | 3 + src/erv/utils/conversions.cljc | 7 +- src/erv/utils/core.cljc | 43 +++++++---- src/erv/utils/exact.cljc | 59 +++++++++++---- src/erv/utils/impl.cljc | 8 +- src/erv/utils/ratios.cljc | 107 +++++++++++++-------------- src/erv/utils/scale.cljc | 7 +- test/erv/cps/core_test.cljs | 102 ++++++++++++------------- test/erv/utils/conversions_test.clj | 8 ++ test/erv/utils/conversions_test.cljs | 9 +++ test/erv/utils/ratios_test.clj | 2 +- test/erv/utils/ratios_test.cljs | 69 +++++++++++++++-- 12 files changed, 278 insertions(+), 146 deletions(-) create mode 100644 test/erv/utils/conversions_test.clj create mode 100644 test/erv/utils/conversions_test.cljs diff --git a/README.md b/README.md index b2e2f6e..90e2a00 100755 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ https://en.xen.wiki/w/MOS_scale This library is a work in progress and mostly a workshop for myself, so the code is not polished as it should. If you are using this library, feel free to let make me aware of it so that I can take more care of the code and the documentation. +## To Do +- [ ] In JS, by using exact, operations involving floating point or double instances may be broken as they expect the source to be strings containing parseable integers or rationals. Floating point support needs to be tested and supported. + ## Development On `emacs` one can run `cider-jackin-clj&cljs` then select the `shadow-cljs` server and the `:browser-build`. diff --git a/src/erv/utils/conversions.cljc b/src/erv/utils/conversions.cljc index 16e19b8..d9463c6 100755 --- a/src/erv/utils/conversions.cljc +++ b/src/erv/utils/conversions.cljc @@ -1,8 +1,11 @@ (ns erv.utils.conversions - (:require [erv.utils.core :refer [round2]])) + (:require + [clojure.math :refer [log]] + [erv.utils.core :refer [round2]] + [erv.utils.exact :as exact.utils])) (defn ratio->cents [ratio] - (-> (Math/log ratio) (/ (Math/log 2)) (* 1200))) + (-> (log (exact.utils/->native ratio)) (/ (log 2)) (* 1200))) (defn cents->ratio [cents] (-> (/ cents 1200) (* (Math/log 2)) Math/exp)) diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index 6e376fc..39d2ab7 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -1,11 +1,12 @@ (ns erv.utils.core #?(:cljs (:refer-clojure :exclude [> >= < <= = + - * / -compare compare numerator denominator integer? - mod rem quot even? odd?])) + mod rem quot even? odd? pos? zero? inc])) (:require + #?(:cljs [com.gfredericks.exact :as e :refer [* / < = > mod rem zero? inc]]) [clojure.core :as core] [clojure.set :as set] [clojure.spec.alpha :as s] - #?(:cljs [com.gfredericks.exact :as e :refer [> < = + - * / rem mod]]))) + [erv.utils.exact :as exact.utils])) (defn validate [spec input] (or (s/valid? spec input) @@ -13,8 +14,9 @@ (defn wrap-at [i coll] (let [size (count coll) - i* (if (zero? size) 0 (core/mod i size))] + i* (if (core/zero? size) 0 (core/mod i size))] (nth coll i* nil))) + (defn round2 "Round a double to the given precision (number of significant digits)" [precision d] @@ -34,14 +36,18 @@ (filter #(= 0 (rem n %)) (range 2 n))) (defn prime-factors [n] - (loop [n n divisor 2 factors []] - (if (< n 2) - factors - (if (zero? (rem n divisor)) - (recur (/ n divisor) divisor (conj factors divisor)) - (recur n (inc divisor) factors))))) + (let [_2 (exact.utils/->exact 2)] + (loop [n n + divisor _2 + factors []] + (if (< n _2) + factors + (if (zero? (rem n divisor)) + (recur (/ n divisor) divisor (conj factors divisor)) + (recur n (inc divisor) factors)))))) + (comment - (prime-factors 2)) + (prime-factors (exact.utils/->exact 1))) (defn coprime? [& ns] (->> ns (map (comp set prime-factors)) (apply set/intersection) empty?)) @@ -67,7 +73,6 @@ (> ratio period*) (recur (/ ratio period*)) (< ratio period*) (recur (* ratio period*))))))) - (defn indexes-of [el coll] (keep-indexed #(when (= el %2) %1) coll)) (defn ^:export pow [n power] @@ -102,19 +107,31 @@ indexes))) ;; TODO add tests -(defn gcd +(defn gcd* "Greatest common divisor" [a b] (if (zero? b) a (recur b (mod a b)))) +(defn gcd + "Greatest common divisor" + [a b] + (gcd* (exact.utils/->exact a) + (exact.utils/->exact b))) + ;; TODO add tests -(defn lcm +(defn lcm* "Least common multiple" [a b] (/ (* a b) (gcd a b))) +(defn lcm + "Least common multiple" + [a b] + (lcm* (exact.utils/->exact a) + (exact.utils/->exact b))) + ;; TODO add tests (defn lcm-of-list "Find the least common multiple of a list of numbers" diff --git a/src/erv/utils/exact.cljc b/src/erv/utils/exact.cljc index b89dff1..665988b 100644 --- a/src/erv/utils/exact.cljc +++ b/src/erv/utils/exact.cljc @@ -1,5 +1,5 @@ (ns erv.utils.exact - "Parse numbers into ratios using `gfredericks/exact`" + "Parse numbers into ratios using `gfredericks/exact`. Also provides helpers for working around `exact` based numbers." (:require [clojure.string :as str] [clojure.walk :as walk] @@ -7,7 +7,8 @@ (defn- parseable-ratio? [s] - (boolean (re-matches #"^-?\d+(/-?\d+)?$" s))) + (boolean (when (string? s) + (re-matches #"^-?\d+(/-?\d+)?$" s)))) (defn parse-ratio [ratio-str] @@ -19,10 +20,46 @@ (map (comp e/string->integer)))] (e// numer (or denom e/ONE)))) -(defn parse-scale - "Parses a scale of ratio-strings" - [scale-str] - (->> (str/split scale-str #"[,|\s]") +(defn ->exact + "If on a `cljs` environment: + Turn `x` into an `exact` integer or ratio. If the value is already and instance of those, + return the value as is." + [x] + #?(:clj x + :cljs (cond + (e/integer? x) x + (e/ratio? x) x + (int? x) (e/native->integer x) + (parseable-ratio? x) (parse-ratio x) + :else (throw (ex-info "Don't know how to turn value into `exact` instance" + {:value x}))))) + +(defn exact-ratio->number + [eratio] + (/ (-> eratio + e/numerator + e/integer->native) + (-> eratio + e/denominator + e/integer->native))) + +#_(exact-ratio->number (e// (e/native->integer 2) + (e/native->integer 3))) +#_(number? (e// (e/native->integer 2) + (e/native->integer 3))) +(defn ->native + [x] + (cond + (number? x) x + (e/integer? x) (e/integer->native x) + (e/ratio? x) (exact-ratio->number x) + :else (throw (ex-info "Don't know how to turn value into number" + {:value x})))) + +(defn parse-ratios + "Parses a string of ratios separated by `,` or whitespaces" + [ratios-str] + (->> (str/split ratios-str #"[,|\s]") (remove empty?) (map parse-ratio))) @@ -35,12 +72,9 @@ (e/numerator exact-int-or-ratio) "/" (e/denominator exact-int-or-ratio)) - (e/integer? exact-int-or-ratio) (str - (e/integer->string exact-int-or-ratio) - "/" - 1) + (e/integer? exact-int-or-ratio) (e/integer->string exact-int-or-ratio) :else (throw (ex-info "Don't know how to parse ratio" - {:input exact-int-or-ratio})))) + {:value exact-int-or-ratio})))) #_(map print-ratio (parse-scale "1 3/2\n 8/7")) @@ -49,8 +83,7 @@ [coll] (walk/postwalk (fn [x] - (if (or (e/integer? x) - (e/ratio? x)) + (if (or (e/integer? x) (e/ratio? x)) (exact->string x) x)) coll)) diff --git a/src/erv/utils/impl.cljc b/src/erv/utils/impl.cljc index 17eac06..95a349a 100644 --- a/src/erv/utils/impl.cljc +++ b/src/erv/utils/impl.cljc @@ -1,5 +1,11 @@ (ns erv.utils.impl - "Contains implementations or functions that are shared among different files.") + "Contains implementations or functions that are shared among different files." + + #? (:cljs (:require [goog.string :as gstr] + [goog.string.format]))) + +#?(:cljs + (def format gstr/format)) (defn +degree "Adds degrees to a scale" diff --git a/src/erv/utils/ratios.cljc b/src/erv/utils/ratios.cljc index 8c7c2fd..10ed0da 100644 --- a/src/erv/utils/ratios.cljc +++ b/src/erv/utils/ratios.cljc @@ -1,23 +1,27 @@ (ns erv.utils.ratios + ;; TODO: improve namespace definition #?@ (:clj [(:require + [clojure.core :as core] [clojure.edn :as edn] [clojure.string :as str] [com.gfredericks.exact :as e] [erv.utils.conversions :as conv] [erv.utils.core :refer [gcd-of-list interval period-reduce prime-factors round2]] - [erv.utils.impl :as impl])] + [erv.utils.exact :as exact.utils])] :cljs [(:refer-clojure :exclude [> < + - * / - numerator denominator integer? - mod rem quot even? odd?]) + mod rem quot even? odd? min]) (:require + [clojure.core :as core] [clojure.string :as str] - [com.gfredericks.exact :as e :refer [> < + - * / - mod]] + [erv.utils.exact :as exact.utils] + [com.gfredericks.exact :as e :refer [> < + - * / - mod min numerator denominator]] [erv.utils.conversions :as conv] - [erv.utils.core :refer [interval period-reduce round2 prime-factors]] - [erv.utils.impl :as impl])])) + [erv.utils.core :refer [gcd-of-list interval period-reduce round2 prime-factors]] + [erv.utils.impl :as impl :refer [format]])])) (defn ratio-proximity-list "Make a list of `ratios` that approximate a `target-ratio` in a list of `target-ratios`" @@ -79,53 +83,46 @@ (defn ratio-string->ratio [ratio-string] - (let [[numer denom] (-> ratio-string - (str/split #"/"))] + (let [[numer denom] (-> ratio-string (str/split #"/"))] #?(:clj (/ (edn/read-string numer) (edn/read-string denom)) :cljs (e// (e/string->integer numer) (e/string->integer denom))))) -(do - #?(:clj (defn analyze-ratio +;; TODO: simplify definition +#?(:clj (defn analyze-ratio + [ratio] + (let [numerator* (if (integer? ratio) + ;; in case it's big int,or something like 1N + (int ratio) + (numerator ratio)) + denominator* (if (integer? ratio) + 1 + (denominator ratio))] + {:numerator numerator* + :denominator denominator* + :numer-factors (prime-factors numerator*) + :denom-factors (prime-factors denominator*)})) + :cljs (defn analyze-ratio [ratio] - (let [numerator* (if (integer? ratio) - ;; in case it's big int,or something like 1N - (int ratio) - (numerator ratio)) - denominator* (if (integer? ratio) - 1 - (denominator ratio))] + (let [numerator* (if-not (e/ratio? ratio) ;when receiving something like 1/1 the above cond will not return a ratio type + ratio + (e/numerator ratio)) + denominator* (if-not (e/ratio? ratio) + e/ONE + (e/denominator ratio))] + {:numerator numerator* :denominator denominator* :numer-factors (prime-factors numerator*) - :denom-factors (prime-factors denominator*)})) - :cljs (defn analyze-ratio - [ratio] - (let [ratio* (cond - #?(:clj (ratio? ratio) :cljs false) ratio - (float? ratio) (float->ratio ratio) - (and (string? ratio) (str/includes? ratio "/")) (ratio-string->ratio ratio) - (and (string? ratio) (seq ratio)) (e/string->integer ratio) - :else (throw (ex-info (str "Don't know how to convert ratio, received " ratio) {:ratio ratio}))) - numerator* (if-not (e/ratio? ratio*) ;when receiving something like 1/1 the above cond will not return a ratio type - (e/integer->native ratio*) - (e/integer->native (e/numerator ratio*))) - denominator* (if-not (e/ratio? ratio*) - 1 - (e/integer->native (e/denominator ratio*)))] - {:numerator numerator* - :denominator denominator* - :numer-factors (prime-factors numerator*) - :denom-factors (prime-factors denominator*)})))) + :denom-factors (prime-factors denominator*)}))) (defn ratio->factor-string [ratio] (->> ratio analyze-ratio - ((juxt (comp #(str/join "." (if (seq %) % [1])) :numer-factors) - (comp #(str/join "." (if (seq %) % [1])) :denom-factors))) - #?(:clj (apply format "%s/%s") - :cljs ((fn [n d] (str n "/" d)))))) + ((juxt (comp #(str/join "." (if (seq %) (map exact.utils/->native %) [1])) :numer-factors) + (comp #(str/join "." (if (seq %) (map exact.utils/->native %) [1])) :denom-factors))) + (apply format "%s/%s"))) (defn seq-interval-analysis [ratios] @@ -147,7 +144,7 @@ :bounded-ratio ratio :bounding-period period}))) (sort-by :bounded-ratio) - ;; impl/+degree + ;; impl/+degree ;; TODO: should this be used here? ))) (defn ratios->scale-data @@ -167,39 +164,39 @@ (map #(apply interval %)))) (defn interval-seq->ratio-stack - [interval-seq size] - (loop [ratios [1] + [size interval-seq] + (loop [ratios [(exact.utils/->exact 1)] index 0] (if (= size (count ratios)) ratios (recur (conj ratios (* (last ratios) - (nth interval-seq (mod index (count interval-seq))))) + (nth interval-seq (core/mod index (count interval-seq))))) (inc index))))) (defn normalize-ratios "Will return a vector of ratios sorted and normalized so that the smallest one is 1/1." - ([ratios] (normalize-ratios nil ratios) - (let [min* (apply min ratios)] - (->> ratios sort (map #(/ % min*))))) + ([ratios] (normalize-ratios nil ratios)) ([period ratios] - (let [min* (apply min ratios) + (let [period (when period (exact.utils/->exact period)) + ratios (mapv exact.utils/->exact ratios) + min* (apply min ratios) ratios* (->> ratios sort (map #(/ % min*)))] (if period (map #(period-reduce period %) ratios*) ratios*)))) +(normalize-ratios [1 3 4]) + (defn ratios->harmonic-series [ratios] - #?(:clj - (let [denominators (map (fn [r] (if (int? r) r (denominator r))) ratios) - anti-denom (apply * denominators) - harmonics (map #(* anti-denom %) ratios) - gcd (gcd-of-list harmonics)] - (map #(/ % gcd) harmonics)) - :cljs (throw (js/Error (str "ratios->harmonic-series not implemented, cannot process:" ratios))))) + (let [denominators (map (fn [r] (if (int? r) r (denominator r))) ratios) + anti-denom (apply * denominators) + harmonics (map #(* anti-denom %) ratios) + gcd (gcd-of-list harmonics)] + (map #(/ % gcd) harmonics))) (defn gen-chain "Create a chain of ratios starting from 1" [length generator] (->> (range length) - (map (fn [i] (apply * (repeat i generator)))))) + (map (fn [i] (apply core/* (repeat i generator)))))) diff --git a/src/erv/utils/scale.cljc b/src/erv/utils/scale.cljc index 31839f9..3e0620d 100644 --- a/src/erv/utils/scale.cljc +++ b/src/erv/utils/scale.cljc @@ -55,7 +55,8 @@ :scale (ratios->scale period (map #(* (last triad-ratios) %) (interval-seq->ratio-stack - (ratios-intervals triad-ratios) 7)))}))) + 7 + (ratios-intervals triad-ratios))))}))) (defn scale->stacked-subscale "Make a scale from a stack of generator steps from a parent scale. @@ -68,8 +69,8 @@ :offset offset} degree-stack scale-intervals - (interval-seq->ratio-stack size) - (->> (ratios->scale period)) + (->> (interval-seq->ratio-stack size) + (ratios->scale period)) distinct)] {:meta {:scale :stacked-subscale :intervals (scale-intervals scale) diff --git a/test/erv/cps/core_test.cljs b/test/erv/cps/core_test.cljs index 5fda4fb..22fbb76 100644 --- a/test/erv/cps/core_test.cljs +++ b/test/erv/cps/core_test.cljs @@ -16,83 +16,83 @@ :scale ({:set #{1 5}, :archi-set #{:c :a}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2, :degree 0} {:set #{1 3}, :archi-set #{:b :a}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2, :degree 1} {:set #{3 5}, :archi-set #{:c :b}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2, :degree 2}), :nodes ({:set #{1 3}, :archi-set #{:b :a}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2} {:set #{1 5}, :archi-set #{:c :a}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2} {:set #{3 5}, :archi-set #{:c :b}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :graphs {:full {{:set #{1 3}, :archi-set #{:b :a}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2} #{{:set #{3 5}, :archi-set #{:c :b}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2} {:set #{1 5}, :archi-set #{:c :a}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}}, {:set #{1 5}, :archi-set #{:c :a}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2} #{{:set #{3 5}, :archi-set #{:c :b}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2} {:set #{1 3}, :archi-set #{:b :a}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2}}, {:set #{3 5}, :archi-set #{:c :b}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2} #{{:set #{1 3}, :archi-set #{:b :a}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2} {:set #{1 5}, :archi-set #{:c :a}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}}}, :simple @@ -110,13 +110,13 @@ :scale ({:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :nodes ({:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :graphs {:full {}, :simple {}}}, @@ -132,13 +132,13 @@ :scale ({:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}), :nodes ({:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}), :graphs {:full {}, :simple {}}}, @@ -154,13 +154,13 @@ :scale ({:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2}), :nodes ({:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2}), :graphs {:full {}, :simple {}}}, @@ -176,45 +176,45 @@ :scale ({:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2} {:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :nodes ({:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2} {:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :graphs {:full {{:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2} #{{:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}}, {:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2} #{{:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}}}, :simple {#{1 5} #{#{3 5}}, #{3 5} #{#{1 5}}}}}, @@ -230,45 +230,45 @@ :scale ({:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2} {:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :nodes ({:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2} {:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :graphs {:full {{:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2} #{{:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}}, {:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2} #{{:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2}}}, :simple {#{1 3} #{#{3 5}}, #{3 5} #{#{1 3}}}}}, @@ -284,13 +284,13 @@ :scale ({:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}), :nodes ({:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}), :graphs {:full {}, :simple {}}}, @@ -306,45 +306,45 @@ :scale ({:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2} {:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2}), :nodes ({:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2} {:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}), :graphs {:full {{:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2} #{{:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2}}, {:set #{1 5}, :archi-set #{nil}, - :ratio "5/1", + :ratio "5", :bounded-ratio "5/4", :bounding-period 2} #{{:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2}}}, :simple {#{1 3} #{#{1 5}}, #{1 5} #{#{1 3}}}}}, @@ -360,13 +360,13 @@ :scale ({:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :nodes ({:set #{3 5}, :archi-set #{nil}, - :ratio "15/1", + :ratio "15", :bounded-ratio "15/8", :bounding-period 2}), :graphs {:full {}, :simple {}}}, @@ -382,13 +382,13 @@ :scale ({:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2}), :nodes ({:set #{1 3}, :archi-set #{nil}, - :ratio "3/1", + :ratio "3", :bounded-ratio "3/2", :bounding-period 2}), :graphs {:full {}, :simple {}}}}} diff --git a/test/erv/utils/conversions_test.clj b/test/erv/utils/conversions_test.clj new file mode 100644 index 0000000..5e86744 --- /dev/null +++ b/test/erv/utils/conversions_test.clj @@ -0,0 +1,8 @@ +(ns erv.utils.conversions-test + (:require + [clojure.test :refer [deftest is]] + [erv.utils.conversions :as subject])) + +(deftest ratio->cents-test + (is (= 701.9550008653874 + (subject/ratio->cents 3/2)))) diff --git a/test/erv/utils/conversions_test.cljs b/test/erv/utils/conversions_test.cljs new file mode 100644 index 0000000..161ac98 --- /dev/null +++ b/test/erv/utils/conversions_test.cljs @@ -0,0 +1,9 @@ +(ns erv.utils.conversions-test + (:require + [clojure.test :refer [deftest is]] + [erv.utils.conversions :as subject] + [erv.utils.exact :as exact.utils])) + +(deftest ratio->cents-test + (is (= 701.9550008653874 + (subject/ratio->cents (exact.utils/parse-ratio "3/2"))))) diff --git a/test/erv/utils/ratios_test.clj b/test/erv/utils/ratios_test.clj index 13b5d4f..642b0c4 100644 --- a/test/erv/utils/ratios_test.clj +++ b/test/erv/utils/ratios_test.clj @@ -18,7 +18,7 @@ (deftest interval-seq->ratio-stack-test (is (= [1 3/2 2N 3N 4N 6N 8N] - (interval-seq->ratio-stack [3/2 4/3] 7)))) + (interval-seq->ratio-stack 7 [3/2 4/3])))) (deftest normalize-ratios-test (is (= [1 7/6 3/2] diff --git a/test/erv/utils/ratios_test.cljs b/test/erv/utils/ratios_test.cljs index 19d9c6a..72826f2 100644 --- a/test/erv/utils/ratios_test.cljs +++ b/test/erv/utils/ratios_test.cljs @@ -1,15 +1,70 @@ (ns erv.utils.ratios-test (:require [cljs.test :refer [deftest is]] - [erv.utils.exact :refer [exact->string parse-scale]] - [erv.utils.ratios :refer [ratios->scale]])) + [erv.utils.exact :as exact.utils] + [erv.utils.ratios :refer [gen-chain interval-seq->ratio-stack + normalize-ratios ratios->harmonic-series + ratios->scale ratios-intervals + seq-interval-analysis]])) (deftest ratios->scale-test - (is (= [{:ratio "1/1", :bounded-ratio "1/1", :bounding-period 2} + (is (= [{:ratio "1", :bounded-ratio "1", :bounding-period 2} {:ratio "5/4", :bounded-ratio "5/4", :bounding-period 2} {:ratio "3/2", :bounded-ratio "3/2", :bounding-period 2}] - (->> (ratios->scale 2 (parse-scale "1 3 5/4")) - (map #(-> % - (update :ratio exact->string) - (update :bounded-ratio exact->string))))))) + (->> "1 3 5/4" + exact.utils/parse-ratios + (ratios->scale 2) + exact.utils/make-readable)))) +(deftest ratios-intervals-test + (is (= ["5/4" "6/5"] + (->> "1 5/4 3/2" + exact.utils/parse-ratios + ratios-intervals + exact.utils/make-readable)))) + +(deftest interval-seq->ratio-stack-test + (is (= ["1" "3/2" "2" "3" "4" "6" "8"] + (->> "3/2 4/3" + exact.utils/parse-ratios + (interval-seq->ratio-stack 7) + exact.utils/make-readable)))) + +(deftest normalize-ratios-test + (is (= ["1" "7/6" "3/2"] + (->> (normalize-ratios [6 7 9]) + exact.utils/make-readable)))) + +(deftest ratios->harmonic-series-test + (is (= ["6" "7" "9"] + (->> "1 7/6 3/2" + exact.utils/parse-ratios + ratios->harmonic-series + exact.utils/make-readable)))) + +(deftest seq-interval-analysis-test + (is (= {:rooted-seq + [["1" "1/1" 0] + ["14/11" "2.7/11" 417.50796410436817] + ["3/2" "3/2" 701.9550008653874] + ["21/11" "3.7/11" 1119.4629649697556] + ["2" "2/1" 1200]] + :pairs + [[["4/3" "56/33"] ["14/11" "2.7/11" 417.50796410436817]] + [["56/33" "2"] ["33/28" "3.11/2.2.7" 284.44703676101926]] + [["2" "28/11"] ["14/11" "2.7/11" 417.50796410436817]] + [["28/11" "8/3"] ["22/21" "2.11/3.7" 80.53703503024445]]] + :ratio-factorization + [["4/3" "2.2/3"] + ["56/33" "2.2.2.7/3.11"] + ["2" "2/1"] + ["28/11" "2.2.7/11"] + ["8/3" "2.2.2/3"]]} + (->> "4/3 56/33 2 28/11 8/3" + exact.utils/parse-ratios + seq-interval-analysis + exact.utils/make-readable)))) + +(deftest gen-chain-test + (is (= [1 3 9 27 81] + (gen-chain 5 3)))) From 33a008b6bb1d77e4f88691d7db83b16cedc03a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 19:24:53 -0600 Subject: [PATCH 41/54] Add tests for utils.scale --- src/erv/utils/core.cljc | 20 +-- src/erv/utils/ratios.cljc | 10 +- src/erv/utils/scale.cljc | 28 ++-- test/erv/utils/ratios_test.cljs | 6 +- test/erv/utils/scale_test.clj | 5 +- test/erv/utils/scale_test.cljs | 218 ++++++++++++++++++++++++++++++++ 6 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 test/erv/utils/scale_test.cljs diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index 39d2ab7..d848386 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -2,7 +2,7 @@ #?(:cljs (:refer-clojure :exclude [> >= < <= = + - * / -compare compare numerator denominator integer? mod rem quot even? odd? pos? zero? inc])) (:require - #?(:cljs [com.gfredericks.exact :as e :refer [* / < = > mod rem zero? inc]]) + #?(:cljs [com.gfredericks.exact :as e :refer [* / < = > mod rem zero? inc numerator denominator]]) [clojure.core :as core] [clojure.set :as set] [clojure.spec.alpha :as s] @@ -64,12 +64,12 @@ (defn period-reduce ([ratio] (period-reduce 2 ratio)) ([period ratio] - (let [one #?(:clj 1 :cljs (e/native->integer 1)) - period* #?(:clj period :cljs (e/native->integer period))] + (let [_1 (exact.utils/->exact 1) + period* (exact.utils/->exact period)] (loop [ratio ratio] (cond - (> period* ratio one) ratio - (or (= period* ratio) (= one ratio)) one + (> period* ratio _1) ratio + (or (= period* ratio) (= _1 ratio)) _1 (> ratio period*) (recur (/ ratio period*)) (< ratio period*) (recur (* ratio period*))))))) @@ -145,11 +145,11 @@ (reduce gcd nums)) (defn decompose-ratio - ([ratio] #?(:clj (try - {:numer (numerator ratio) :denom (denominator ratio)} - (catch Exception _ - {:numer ratio :denom 1})) - :cljs {:numer ratio :denom 1}))) + ([ratio] + (try + {:numer (numerator ratio) :denom (denominator ratio)} + (catch #?(:clj Exception :cljs js/Error) _ + {:numer ratio :denom (exact.utils/->exact 1)})))) (defn decompose-ratios ([ratios] (mapv decompose-ratio ratios))) diff --git a/src/erv/utils/ratios.cljc b/src/erv/utils/ratios.cljc index 10ed0da..84226b2 100644 --- a/src/erv/utils/ratios.cljc +++ b/src/erv/utils/ratios.cljc @@ -138,11 +138,11 @@ ([ratios] (ratios->scale 2 ratios)) ([period ratios] (->> ratios - (map (fn [r] - (let [ratio (period-reduce period r)] - {:ratio ratio - :bounded-ratio ratio - :bounding-period period}))) + (mapv (fn [r] + (let [ratio (period-reduce period r)] + {:ratio ratio + :bounded-ratio ratio + :bounding-period (exact.utils/->exact period)}))) (sort-by :bounded-ratio) ;; impl/+degree ;; TODO: should this be used here? ))) diff --git a/src/erv/utils/scale.cljc b/src/erv/utils/scale.cljc index 3e0620d..fae81bc 100644 --- a/src/erv/utils/scale.cljc +++ b/src/erv/utils/scale.cljc @@ -1,8 +1,13 @@ (ns erv.utils.scale + #?(:cljs (:refer-clojure :exclude [+ - * / - numerator denominator integer? + mod rem quot even? odd?])) (:require + #?(:cljs [com.gfredericks.exact :as e :refer [* + - - /]]) + [clojure.core :as core] [clojure.math.combinatorics :as combo] [erv.utils.core :refer [decompose-ratios interval lcm-of-list period-reduce rotate wrap-at]] + [erv.utils.exact :as exact.utils] [erv.utils.impl :as impl] [erv.utils.ratios :refer [interval-seq->ratio-stack normalize-ratios ratios->scale ratios-intervals]])) @@ -18,7 +23,7 @@ ratio-subset #{} offset offset gen-index 0] - (let [i (mod offset (count scale)) + (let [i (core/mod offset (count scale)) new-note (assoc (nth scale i) :gen/index gen-index) ratio (:bounded-ratio new-note)] @@ -26,7 +31,7 @@ subset (recur (conj subset new-note) (conj ratio-subset ratio) - (+ offset gen) + (core/+ offset gen) (inc gen-index))))))) (defn scale-intervals @@ -47,7 +52,7 @@ (defn tritriadic "Make a scale from stacking a triad three times. https://en.xen.wiki/w/Tritriadic_scale" - ([triad-ratios] (tritriadic 2 triad-ratios)) + ([triad-ratios] (tritriadic (exact.utils/->exact 2) triad-ratios)) ([period triad-ratios] (let [triad-ratios (normalize-ratios period triad-ratios)] {:meta {:scale :tritriadic @@ -73,6 +78,7 @@ (ratios->scale period)) distinct)] {:meta {:scale :stacked-subscale + :period period :intervals (scale-intervals scale) :parent-scale scale :gen gen @@ -110,10 +116,11 @@ (defn cross-set [period & ratio-vecs] (let [scale (->> ratio-vecs + #?(:cljs (map (partial map exact.utils/->exact))) (apply combo/cartesian-product) (map #(apply * %)) flatten - (ratios->scale period) + (ratios->scale (exact.utils/->exact period)) dedupe-scale)] {:meta {:scale :cross-set :sets ratio-vecs @@ -134,12 +141,12 @@ scale) total-subset (count subset-set) total-subscale (count subscale)] - (when (<= (- total-subset total-subscale) - max-missing-notes) + (when (core/<= (core/- total-subset total-subscale) + max-missing-notes) (let [degrees (map :rotated-scale/original-degree subscale) matched-ratios (map :matched-ratio subscale)] {:degrees degrees - :matched (/ total-subscale total-subset) + :matched (core// total-subscale total-subset) :subscale/matched-ratios matched-ratios})))) scale-rotations))) @@ -152,13 +159,14 @@ ([scale-steps] (scale-steps->degrees scale-steps true)) ([scale-steps remove-octave?] (->> scale-steps - (reduce (fn [acc n] (conj acc (+ n (or (last acc) 0)))) + (reduce (fn [acc n] (conj acc (core/+ n (or (last acc) 0)))) [0]) (drop-last (if remove-octave? 1 0))))) (defn diamond - [period & factors] - (let [scale (->> (combo/cartesian-product factors factors) + [period factors] + (let [factors (map exact.utils/->exact factors) + scale (->> (combo/cartesian-product factors factors) (mapv (fn [[a b]] (/ a b))) (ratios->scale period) dedupe-scale)] diff --git a/test/erv/utils/ratios_test.cljs b/test/erv/utils/ratios_test.cljs index 72826f2..eb46b6c 100644 --- a/test/erv/utils/ratios_test.cljs +++ b/test/erv/utils/ratios_test.cljs @@ -8,9 +8,9 @@ seq-interval-analysis]])) (deftest ratios->scale-test - (is (= [{:ratio "1", :bounded-ratio "1", :bounding-period 2} - {:ratio "5/4", :bounded-ratio "5/4", :bounding-period 2} - {:ratio "3/2", :bounded-ratio "3/2", :bounding-period 2}] + (is (= [{:ratio "1", :bounded-ratio "1", :bounding-period "2"} + {:ratio "5/4", :bounded-ratio "5/4", :bounding-period "2"} + {:ratio "3/2", :bounded-ratio "3/2", :bounding-period "2"}] (->> "1 3 5/4" exact.utils/parse-ratios (ratios->scale 2) diff --git a/test/erv/utils/scale_test.clj b/test/erv/utils/scale_test.clj index 3d5883b..89f14bd 100644 --- a/test/erv/utils/scale_test.clj +++ b/test/erv/utils/scale_test.clj @@ -53,6 +53,7 @@ (deftest scale->stacked-subscale-test (is (= {:meta {:scale :stacked-subscale + :period 4 :intervals [3/2 4/3 3/2 4/3], :parent-scale [{:ratio 1, :bounded-ratio 1, :bounding-period 4} @@ -234,11 +235,11 @@ {:bounded-ratio 7/4 :bounding-period 2 :ratio 7/4} {:bounded-ratio 16/9 :bounding-period 2 :ratio 16/9} {:bounded-ratio 9/5 :bounding-period 2 :ratio 9/5}]} - (diamond 2 1 3 5 7 9))) + (diamond 2 [1 3 5 7 9]))) (testing "A `diamond` is a special case of `cross-set`" (is (= - (map :bounded-ratio (:scale (diamond 2 1 3 5 7 9))) + (map :bounded-ratio (:scale (diamond 2 [1 3 5 7 9]))) (map :bounded-ratio (:scale (cross-set 2 [1 3 5 7 9] (map #(/ 1 %) [1 3 5 7 9])))))))) diff --git a/test/erv/utils/scale_test.cljs b/test/erv/utils/scale_test.cljs new file mode 100644 index 0000000..cbefa89 --- /dev/null +++ b/test/erv/utils/scale_test.cljs @@ -0,0 +1,218 @@ +(ns erv.utils.scale-test + (:require + [clojure.test :refer [deftest is testing]] + [com.gfredericks.exact :as e] + [erv.edo.core :as edo] + [erv.utils.exact :as exact.utils] + [erv.utils.ratios :refer [ratios->scale]] + [erv.utils.scale :refer [cross-set dedupe-scale degree-stack diamond + find-subset-degrees get-degrees + proportional-chords proportional-difference + rotate-scale scale->stacked-subscale + scale-intervals scale-steps->degrees tritriadic]])) + +(deftest degree-stack-test + (is (= [0 4 8] + (map :edo/degree + (degree-stack {:scale (:scale (edo/from-pattern (repeat 12 1))) + :gen 4 + :offset 0})))) + (testing "Will make an ordered list with the degrees from the stack" + (is (= [0 7 2 9 4 11 6 1 8 3 10 5] + (map :edo/degree + (degree-stack {:scale (:scale (edo/from-pattern (repeat 12 1))) + :gen 7 + :offset 0})))))) + +(deftest scale-intervals-test + (is (= ["5/4" "6/5" "4/3"] + (->> "1 5/4 3/2" + exact.utils/parse-ratios + ratios->scale + scale-intervals + exact.utils/make-readable)))) + +(deftest tritriadic-test + (testing "Can make a tritriadic scale (Tritriad69)" + (is (= {:meta {:scale :tritriadic + :triad-ratios ["1" "7/6" "3/2"]} + :scale [{:ratio "9/8" + :bounded-ratio "9/8" + :bounding-period "2"} + {:ratio "81/64" + :bounded-ratio "81/64" + :bounding-period "2"} + {:ratio "21/16" + :bounded-ratio "21/16" + :bounding-period "2"} + {:ratio "3/2" + :bounded-ratio "3/2" + :bounding-period "2"} + {:ratio "27/16" + :bounded-ratio "27/16" + :bounding-period "2"} + {:ratio "7/4" + :bounded-ratio "7/4" + :bounding-period "2"} + {:ratio "63/32" + :bounded-ratio "63/32" + :bounding-period "2"}]} + (->> "1 7/6 3/2" + exact.utils/parse-ratios + tritriadic + exact.utils/make-readable))))) + +(deftest scale->stacked-subscale-test + (is (= {:meta {:scale :stacked-subscale + :period 4 + :intervals ["3/2" "4/3" "3/2" "4/3"] + :parent-scale [{:ratio "1" + :bounded-ratio "1" + :bounding-period "4"} + {:ratio "3/2" + :bounded-ratio "3/2" + :bounding-period "4"} + {:ratio "2" + :bounded-ratio "2" + :bounding-period "4"} + {:ratio "3" + :bounded-ratio "3" + :bounding-period "4"}] + :gen 2 + :starting-offset 0} + :scale [{:ratio "1" + :bounded-ratio "1" + :bounding-period "4"} + {:ratio "3/2" + :bounded-ratio "3/2" + :bounding-period "4"} + {:ratio "2" + :bounded-ratio "2" + :bounding-period "4"} + {:ratio "3" + :bounded-ratio "3" + :bounding-period "4"}]} + (-> (scale->stacked-subscale {:scale (ratios->scale (exact.utils/parse-ratios "1 5/4 3/2 15/8")) + :gen 2 + :offset 0 + :size 40 + :period 4}) + exact.utils/make-readable)))) + +(deftest dedupe-scale-test + (is (= [{:bounded-ratio 1} + {:bounded-ratio "5/4"} + {:bounded-ratio "3/2"}] + (dedupe-scale [{:bounded-ratio 1} + {:bounded-ratio "5/4"} + {:bounded-ratio "3/2"} + {:bounded-ratio "3/2"}])))) + +(deftest rotate-scale-test + (testing "Rotates the scale and leaves a trace of the original degree and ratio" + (is (= [{:ratio "1", + :bounded-ratio "1", + :bounding-period "2", + :rotated-scale/original-degree 1, + :rotated-scale/original-ratio "5/4"} + {:ratio "6/5", + :bounded-ratio "6/5", + :bounding-period "2", + :rotated-scale/original-degree 2, + :rotated-scale/original-ratio "3/2"} + {:ratio "8/5", + :bounded-ratio "8/5", + :bounding-period "2", + :rotated-scale/original-degree 0, + :rotated-scale/original-ratio "1"}] + (->> "1 5/4 3/2" + exact.utils/parse-ratios + ratios->scale + (rotate-scale 1) + exact.utils/make-readable))))) + +(deftest cross-set-test + (is (= {:meta + {:scale :cross-set, + :sets [[1 3 9] ["1" "5" "25"] ["1" "7" "49"]], + :size 27, + :period 2}, + :scale + [{:ratio "1", :bounded-ratio "1", :bounding-period "2"} + {:ratio "525/512", :bounded-ratio "525/512", :bounding-period "2"} + {:ratio "2205/2048", :bounded-ratio "2205/2048", :bounding-period "2"} + {:ratio "35/32", :bounded-ratio "35/32", :bounding-period "2"} + {:ratio "9/8", :bounded-ratio "9/8", :bounding-period "2"} + {:ratio "147/128", :bounded-ratio "147/128", :bounding-period "2"} + {:ratio "75/64", :bounded-ratio "75/64", :bounding-period "2"} + {:ratio "1225/1024", :bounded-ratio "1225/1024", :bounding-period "2"} + {:ratio "315/256", :bounded-ratio "315/256", :bounding-period "2"} + {:ratio "5/4", :bounded-ratio "5/4", :bounding-period "2"} + {:ratio "21/16", :bounded-ratio "21/16", :bounding-period "2"} + {:ratio "11025/8192", :bounded-ratio "11025/8192", :bounding-period "2"} + {:ratio "175/128", :bounded-ratio "175/128", :bounding-period "2"} + {:ratio "45/32", :bounded-ratio "45/32", :bounding-period "2"} + {:ratio "735/512", :bounded-ratio "735/512", :bounding-period "2"} + {:ratio "3/2", :bounded-ratio "3/2", :bounding-period "2"} + {:ratio "49/32", :bounded-ratio "49/32", :bounding-period "2"} + {:ratio "1575/1024", :bounded-ratio "1575/1024", :bounding-period "2"} + {:ratio "25/16", :bounded-ratio "25/16", :bounding-period "2"} + {:ratio "105/64", :bounded-ratio "105/64", :bounding-period "2"} + {:ratio "441/256", :bounded-ratio "441/256", :bounding-period "2"} + {:ratio "7/4", :bounded-ratio "7/4", :bounding-period "2"} + {:ratio "225/128", :bounded-ratio "225/128", :bounding-period "2"} + {:ratio "3675/2048", :bounded-ratio "3675/2048", :bounding-period "2"} + {:ratio "15/8", :bounded-ratio "15/8", :bounding-period "2"} + {:ratio "245/128", :bounded-ratio "245/128", :bounding-period "2"} + {:ratio "63/32", :bounded-ratio "63/32", :bounding-period "2"}]} + (-> (cross-set 2 + [1 3 9] ;; suports ordinary vectors and vectors of `exact` numbers + (map exact.utils/->exact [1 5 25]) + (map exact.utils/->exact [1 7 49])) + exact.utils/make-readable)))) + +(deftest find-subset-degrees-test + (is (= '({:degrees (0 2 4), :matched 1, :subscale/matched-ratios ("1" "5/4" "3/2")} + {:degrees (3 5 0), :matched 1, :subscale/matched-ratios ("1" "5/4" "3/2")} + {:degrees (4 6 1), :matched 1, :subscale/matched-ratios ("1" "5/4" "3/2")}) + (-> (find-subset-degrees + {:scale (ratios->scale (exact.utils/parse-ratios "1 9/8 5/4 4/3 3/2 5/3 15/8")) + :subset-ratios (exact.utils/parse-ratios "1 5/4 3/2")}) + exact.utils/make-readable)))) + +(deftest get-degrees-test + (is (= '({:ratio "1", :bounded-ratio "1", :bounding-period "2"} + {:ratio "5/4", :bounded-ratio "5/4", :bounding-period "2"} + {:ratio "3/2", :bounded-ratio "3/2", :bounding-period "2"}) + (-> "1 9/8 5/4 4/3 3/2 5/3 15/8" + (exact.utils/parse-ratios) + (ratios->scale) + (get-degrees [0 2 4]) + exact.utils/make-readable)))) + +(deftest scale-steps->degrees-test + (is (= [0 2 4 5 7 9 11 12] + (scale-steps->degrees [2 2 1 2 2 2 1] false)))) + +(deftest diamond-test + (testing "A `diamond` is a special case of `cross-set`" + (is (= (map :bounded-ratio (:scale (diamond 2 [1 3 5 7 9]))) + (map :bounded-ratio (:scale (cross-set 2 + [1 3 5 7 9] + (map #(e// (exact.utils/->exact %)) + [1 3 5 7 9])))))))) + +(deftest proportional-difference-test + (is (= e/ONE + (proportional-difference (exact.utils/parse-ratios "1 5/4 3/2")))) + (is (= nil + (proportional-difference (exact.utils/parse-ratios "1 9/8 3/2"))))) + +(deftest proportional-chords-test + (is (= {:by-notes {"1" [["1" "9/8" "5/4"] ["1" "5/4" "3/2"] ["5/4" "3/2" "7/4"]]}, + :by-degrees {"1" [[0 1 2] [0 2 3] [2 3 4]]}} + (->> "1 3 5 7 9" + exact.utils/parse-ratios + ratios->scale + (proportional-chords 3) + exact.utils/make-readable)))) From cb07f5fef0417f552e9096074ba654e79fd42d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 21:27:53 -0600 Subject: [PATCH 42/54] Add tests for mos namespaces --- src/erv/mos/mos.cljc | 14 +- src/erv/mos/v3/core.clj | 101 ----- src/erv/mos/v3/core.cljc | 95 ++++ src/erv/utils/core.cljc | 6 +- src/erv/utils/exact.cljc | 13 + test/erv/mos/mos_test.cljc | 13 + test/erv/mos/submos_test.cljc | 729 +++++++++++++++++++++++++++++++ test/erv/mos/v2/submos_test.cljc | 103 +++++ test/erv/mos/v3/core_test.clj | 113 +++++ test/erv/mos/v3/core_test.cljs | 135 ++++++ 10 files changed, 1213 insertions(+), 109 deletions(-) delete mode 100644 src/erv/mos/v3/core.clj create mode 100644 src/erv/mos/v3/core.cljc create mode 100644 test/erv/mos/mos_test.cljc create mode 100644 test/erv/mos/submos_test.cljc create mode 100644 test/erv/mos/v2/submos_test.cljc create mode 100644 test/erv/mos/v3/core_test.clj create mode 100644 test/erv/mos/v3/core_test.cljs diff --git a/src/erv/mos/mos.cljc b/src/erv/mos/mos.cljc index 325d740..227d7d4 100755 --- a/src/erv/mos/mos.cljc +++ b/src/erv/mos/mos.cljc @@ -13,8 +13,10 @@ common factors other than 1. 5. The numerator (generator) and denominator (period) representing MOS are also co-prime." - (:require [taoensso.timbre :as timbre] - [erv.utils.core :refer [coprime?]])) + (:require + [erv.utils.core :refer [coprime?]] + [erv.utils.exact :as exact.utils] + [taoensso.timbre :as timbre])) (do ;; TODO, find a proper name and move to utils; called `degs->scale` in mawra.core @@ -39,12 +41,11 @@ (fn [{:keys [moses waiting]} point] (let [points (into [] (sort (concat waiting (last moses) [point]))) intervals (get-diffs points)] - (->> intervals frequencies vals (apply coprime?)) (cond (= #{1 2} (set intervals)) ;; NOTE Do we really not want any more MOS? (reduced {:moses (conj moses points) :waiting []}) (and (<= (count (set intervals)) 2) - (->> intervals frequencies vals (apply coprime?))) + (->> intervals frequencies vals (map exact.utils/->exact) (apply coprime?))) {:moses (conj moses points) :waiting []} :else {:moses moses :waiting (conj waiting point)}))) @@ -61,7 +62,8 @@ (def make-mos (memoize (fn [period generator] - (let [true-mos? (coprime? period generator)] + (let [true-mos? (coprime? (exact.utils/->exact period) + (exact.utils/->exact generator))] (when-not true-mos? (timbre/warn "The generated data is not a true MOS because the period (" period ") and generator (" generator ") are not coprime.")) (with-meta @@ -70,4 +72,4 @@ mos-as-intervals) {:true-mos? true-mos?}))))) -(def make make-mos) +(def make #'make-mos) diff --git a/src/erv/mos/v3/core.clj b/src/erv/mos/v3/core.clj deleted file mode 100644 index 09b9594..0000000 --- a/src/erv/mos/v3/core.clj +++ /dev/null @@ -1,101 +0,0 @@ -(ns erv.mos.v3.core - (:require - [clojure.string :as str] - [erv.utils.conversions :refer [ratio->cents]] - [erv.utils.core :refer [coprime? interval period-reduce round2]] - [erv.utils.ratios :refer [ratios->scale]])) - -;; rational mos - -;; TODO move to ratios -(defn seq-intervals - [period ratios] - (->> ratios - sort - (into []) - (#(conj % period)) - (partition 2 1) - (map #(apply interval %)))) -(seq-intervals 2 [1 3/2 9/8]) -(defn interval-frequencies - [period ratios] - (->> ratios - (seq-intervals period) - frequencies)) -(interval-frequencies 2 [1 9/8 81/64 729/512 3/2 27/16 243/128]) - -(do - (defn mos? - [period ratios] - (let [interval-freqs (interval-frequencies period ratios) - interval-quantities (vals interval-freqs)] - - (and (= 2 (count (keys interval-freqs))) - (apply coprime? interval-quantities)))) - - (mos? 2 [1 3/2 9/8 27/16 81/64 243/128])) - -(defn ratios->mos-data - [{:keys [period ratios gen]}] () - (let [ratio-intervals (seq-intervals period ratios) - interval-freqs (interval-frequencies period ratios) - [s L] (->> ratio-intervals set sort)] - {:meta {:scale :mos - :period period - :size (count ratios) - :intervals/ratios ratio-intervals - :intervals/cents (map ratio->cents ratio-intervals) - :mos/pattern.name (str (interval-freqs s) "s" (interval-freqs L) "L") - :mos/pattern (str/join (map (fn [interval] (if (= interval s) "s" "L")) ratio-intervals)) - :mos/s s - :mos/s.cents (ratio->cents s) - :mos/L L - :mos/L.cents (ratio->cents L) - :mos/sL-ratio (/ L s) - :mos/sL-ratio.float (float (/ (ratio->cents L) (ratio->cents s))) - :mos/sL-ratio.cents (ratio->cents (/ (ratio->cents L) (ratio->cents s))) - :mos/generator gen - :mos/normalized-by 1 - :mos/type :ratio} - :scale (ratios->scale period ratios)})) -(do - (defn gen->mos-ratios - ([gen period] (gen->mos-ratios gen period 100)) - ([gen period max-len] - (let [gen* (cond (int? gen) (bigint gen) - (rational? gen) gen - :else (rationalize gen)) - gen-seq (reductions * (repeat gen*)) - mos-ratios (->> (range max-len) - (map (fn [i] - (->> (conj (take i gen-seq) period) - (map #(period-reduce period %)) - sort))) - (filter #(mos? period %)))] - (map (fn [ratios] - (ratios->mos-data {:period period - :ratios ratios - :gen gen})) - mos-ratios)))) - - (map (comp (juxt :size :mos/pattern - :mos/pattern.name :mos/s.cents :mos/L.cents :mos/sL-ratio.float) - :meta) - (gen->mos-ratios 2 3)) - - (map (comp #(select-keys % - [:size :mos/pattern - :mos/pattern.name :mos/s.cents :mos/L.cents :mos/sL-ratio.float]) - :meta) - #_(comp (partial map :bounded-ratio) :scale) - (gen->mos-ratios 11/8 - (rationalize (round2 4 (erv.utils.conversions/cents->ratio 400))) 50)) - #_(map count (gen->mos 3/2 2 12))) - -(comment - (->> (gen->mos-ratios 7/4 3) - (filter #(-> % :meta :size (= 53))) - first - :scale) - (rotate-scale (:scale (nth (gen->mos-ratios 3/2 2) 2)) - 2)) diff --git a/src/erv/mos/v3/core.cljc b/src/erv/mos/v3/core.cljc new file mode 100644 index 0000000..8126608 --- /dev/null +++ b/src/erv/mos/v3/core.cljc @@ -0,0 +1,95 @@ +(ns erv.mos.v3.core + #?(:cljs (:refer-clojure :exclude [+ - * / - numerator denominator integer? + mod rem quot even? odd? rationalize])) + (:require + #?(:cljs [com.gfredericks.exact :as e :refer [* - - /]]) + [erv.utils.exact :as exact.utils #?@(:cljs [:refer [rationalize]])] + [clojure.core :as core] + [clojure.string :as str] + [erv.utils.conversions :refer [ratio->cents]] + [erv.utils.core :refer [coprime? interval period-reduce]] + [erv.utils.ratios :refer [ratios->scale]])) + +;; rational mos + +;; TODO move to ratios +(defn seq-intervals + [period ratios] + (->> ratios + sort + (into []) + (#(conj % period)) + (partition 2 1) + (map #(apply interval %)))) + +(comment + (seq-intervals (e/native->integer 2) (erv.utils.exact/parse-ratios "1 3/2 9/8"))) + +(defn interval-frequencies + [period ratios] + (->> ratios + (seq-intervals period) + frequencies)) +#_(interval-frequencies 2 [1 9/8 81/64 729/512 3/2 27/16 243/128]) + +(defn mos? + [period ratios] + (let [interval-freqs (interval-frequencies period ratios) + interval-quantities (vals interval-freqs)] + (and (= 2 (count (keys interval-freqs))) + (apply coprime? interval-quantities)))) +(comment + (mos? (erv.utils.exact/->exact 2) (erv.utils.exact/parse-ratios "1 3/2 9/8 27/16 81/64 243/128")) + (mos? 2 [1 3/2 9/8 27/16 81/64 243/128])) + +(defn ratios->mos-data + [{:keys [period ratios gen]}] () + (let [ratio-intervals (seq-intervals period ratios) + interval-freqs (interval-frequencies period ratios) + [s L] (->> ratio-intervals set sort)] + {:meta {:scale :mos + :period period + :size (count ratios) + :intervals/ratios ratio-intervals + :intervals/cents (map ratio->cents ratio-intervals) + :mos/pattern.name (str (interval-freqs s) "s" (interval-freqs L) "L") + :mos/pattern (str/join (map (fn [interval] (if (= interval s) "s" "L")) ratio-intervals)) + :mos/s s + :mos/s.cents (ratio->cents s) + :mos/L L + :mos/L.cents (ratio->cents L) + :mos/sL-ratio (/ L s) + :mos/sL-ratio.float (float (core// (ratio->cents L) (ratio->cents s))) + :mos/sL-ratio.cents (ratio->cents (core// (ratio->cents L) (ratio->cents s))) + :mos/generator gen + :mos/normalized-by 1 + :mos/type :ratio} + :scale (ratios->scale period ratios)})) + +(defn gen->mos-ratios + ([gen period] (gen->mos-ratios gen period 100)) + ([gen period max-len] + (let [gen* (cond (int? gen) (#?(:clj bigint :cljs e/native->integer) gen) + (#?(:clj rational? :cljs e/ratio?) gen) gen + :else (rationalize gen)) + gen-seq (reductions * (repeat gen*)) + period (exact.utils/->exact period) + mos-ratios (->> (range max-len) + (map (fn [i] + (->> (conj (take i gen-seq) period) + (map #(period-reduce period %)) + sort))) + (filter #(mos? period %)))] + + (map (fn [ratios] + (ratios->mos-data {:period period + :ratios ratios + :gen gen})) + mos-ratios)))) + +(comment + ;; TODO: nice scale, save somewhere + (->> (gen->mos-ratios 7/4 3) + (filter #(-> % :meta :size (= 53))) + first + :scale)) diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index d848386..850f246 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -36,7 +36,8 @@ (filter #(= 0 (rem n %)) (range 2 n))) (defn prime-factors [n] - (let [_2 (exact.utils/->exact 2)] + (let [_2 (exact.utils/->exact 2) + n (exact.utils/->exact n)] (loop [n n divisor _2 factors []] @@ -47,7 +48,8 @@ (recur n (inc divisor) factors)))))) (comment - (prime-factors (exact.utils/->exact 1))) + (prime-factors (exact.utils/->exact 1)) + (prime-factors 1)) (defn coprime? [& ns] (->> ns (map (comp set prime-factors)) (apply set/intersection) empty?)) diff --git a/src/erv/utils/exact.cljc b/src/erv/utils/exact.cljc index 665988b..c76a37f 100644 --- a/src/erv/utils/exact.cljc +++ b/src/erv/utils/exact.cljc @@ -1,6 +1,7 @@ (ns erv.utils.exact "Parse numbers into ratios using `gfredericks/exact`. Also provides helpers for working around `exact` based numbers." (:require + [clojure.math :refer [pow]] [clojure.string :as str] [clojure.walk :as walk] [com.gfredericks.exact :as e])) @@ -56,6 +57,10 @@ :else (throw (ex-info "Don't know how to turn value into number" {:value x})))) +(defn exact? + [x] + (or (e/integer? x) (e/ratio? x))) + (defn parse-ratios "Parses a string of ratios separated by `,` or whitespaces" [ratios-str] @@ -87,3 +92,11 @@ (exact->string x) x)) coll)) + +#?(:cljs + (defn rationalize + [num] + (let [decimal-places (-> num str (str/split ".") last count) + denom (int (pow 10 decimal-places))] + (e// (e/native->integer (* denom num)) + (e/native->integer denom))))) diff --git a/test/erv/mos/mos_test.cljc b/test/erv/mos/mos_test.cljc new file mode 100644 index 0000000..4bbc7b2 --- /dev/null +++ b/test/erv/mos/mos_test.cljc @@ -0,0 +1,13 @@ +(ns erv.mos.mos-test + (:require + [clojure.test :refer [deftest is]] + [erv.mos.mos :as subject])) + +(deftest make-test + (is (= [[12] + [7 5] + [2 5 5] + [2 2 3 2 3] + [2 2 2 1 2 2 1] + [1 1 1 1 1 1 1 1 1 1 1 1]] + (subject/make 12 7)))) diff --git a/test/erv/mos/submos_test.cljc b/test/erv/mos/submos_test.cljc new file mode 100644 index 0000000..22a10fe --- /dev/null +++ b/test/erv/mos/submos_test.cljc @@ -0,0 +1,729 @@ +(ns erv.mos.submos-test + (:require + [clojure.test :refer [deftest is]] + [erv.mos.submos :as subject])) + +(deftest make-all-submos-test + (let [all-submos '({:generator 1, + :pattern [1 6], + :period 7, + :submos + ({:degree 0, + :mos [1 11], + :mos-degrees [0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 11]}} + {:degree 1, + :mos [2 10], + :mos-degrees [1 2], + :rotation {:at-zero? false, :degree 1, :mos [2 10]}} + {:degree 2, + :mos [2 10], + :mos-degrees [2 3], + :rotation {:at-zero? false, :degree 2, :mos [2 10]}} + {:degree 3, + :mos [1 11], + :mos-degrees [3 4], + :rotation {:at-zero? false, :degree 3, :mos [1 11]}} + {:degree 4, + :mos [2 10], + :mos-degrees [4 5], + :rotation {:at-zero? false, :degree 4, :mos [2 10]}} + {:degree 5, + :mos [2 10], + :mos-degrees [5 6], + :rotation {:at-zero? false, :degree 5, :mos [2 10]}} + {:degree 6, + :mos [2 10], + :mos-degrees [6 0], + :rotation {:at-zero? true, :degree 0, :mos [10 2]}}), + :submos-by-mos + {[1 11] + [{:degree 0, + :mos [1 11], + :mos-degrees [0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 11]}} + {:degree 3, + :mos [1 11], + :mos-degrees [3 4], + :rotation {:at-zero? false, :degree 3, :mos [1 11]}}], + [2 10] + [{:degree 1, + :mos [2 10], + :mos-degrees [1 2], + :rotation {:at-zero? false, :degree 1, :mos [2 10]}} + {:degree 2, + :mos [2 10], + :mos-degrees [2 3], + :rotation {:at-zero? false, :degree 2, :mos [2 10]}} + {:degree 4, + :mos [2 10], + :mos-degrees [4 5], + :rotation {:at-zero? false, :degree 4, :mos [2 10]}} + {:degree 5, + :mos [2 10], + :mos-degrees [5 6], + :rotation {:at-zero? false, :degree 5, :mos [2 10]}} + {:degree 6, + :mos [2 10], + :mos-degrees [6 0], + :rotation {:at-zero? true, :degree 0, :mos [10 2]}}]}, + :true-submos? false} + {:generator 1, + :pattern [1 1 5], + :period 7, + :submos + ({:degree 0, + :mos [1 2 9], + :mos-degrees [0 1 2], + :rotation {:at-zero? true, :degree 0, :mos [1 2 9]}} + {:degree 1, + :mos [2 2 8], + :mos-degrees [1 2 3], + :rotation {:at-zero? false, :degree 1, :mos [2 2 8]}} + {:degree 2, + :mos [2 1 9], + :mos-degrees [2 3 4], + :rotation {:at-zero? false, :degree 2, :mos [2 1 9]}} + {:degree 3, + :mos [1 2 9], + :mos-degrees [3 4 5], + :rotation {:at-zero? false, :degree 3, :mos [1 2 9]}} + {:degree 4, + :mos [2 2 8], + :mos-degrees [4 5 6], + :rotation {:at-zero? false, :degree 4, :mos [2 2 8]}} + {:degree 5, + :mos [2 2 8], + :mos-degrees [5 6 0], + :rotation {:at-zero? true, :degree 0, :mos [8 2 2]}} + {:degree 6, + :mos [2 1 9], + :mos-degrees [6 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 9 2]}}), + :submos-by-mos + {[1 2 9] + [{:degree 0, + :mos [1 2 9], + :mos-degrees [0 1 2], + :rotation {:at-zero? true, :degree 0, :mos [1 2 9]}} + {:degree 3, + :mos [1 2 9], + :mos-degrees [3 4 5], + :rotation {:at-zero? false, :degree 3, :mos [1 2 9]}}], + [2 1 9] + [{:degree 2, + :mos [2 1 9], + :mos-degrees [2 3 4], + :rotation {:at-zero? false, :degree 2, :mos [2 1 9]}} + {:degree 6, + :mos [2 1 9], + :mos-degrees [6 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 9 2]}}], + [2 2 8] + [{:degree 1, + :mos [2 2 8], + :mos-degrees [1 2 3], + :rotation {:at-zero? false, :degree 1, :mos [2 2 8]}} + {:degree 4, + :mos [2 2 8], + :mos-degrees [4 5 6], + :rotation {:at-zero? false, :degree 4, :mos [2 2 8]}} + {:degree 5, + :mos [2 2 8], + :mos-degrees [5 6 0], + :rotation {:at-zero? true, :degree 0, :mos [8 2 2]}}]}, + :true-submos? false} + {:generator 1, + :pattern [1 1 1 4], + :period 7, + :submos + ({:degree 0, + :mos [1 2 2 7], + :mos-degrees [0 1 2 3], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 7]}} + {:degree 1, + :mos [2 2 1 7], + :mos-degrees [1 2 3 4], + :rotation {:at-zero? false, :degree 1, :mos [2 2 1 7]}} + {:degree 2, + :mos [2 1 2 7], + :mos-degrees [2 3 4 5], + :rotation {:at-zero? false, :degree 2, :mos [2 1 2 7]}} + {:degree 3, + :mos [1 2 2 7], + :mos-degrees [3 4 5 6], + :rotation {:at-zero? false, :degree 3, :mos [1 2 2 7]}} + {:degree 4, + :mos [2 2 2 6], + :mos-degrees [4 5 6 0], + :rotation {:at-zero? true, :degree 0, :mos [6 2 2 2]}} + {:degree 5, + :mos [2 2 1 7], + :mos-degrees [5 6 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 7 2 2]}} + {:degree 6, + :mos [2 1 2 7], + :mos-degrees [6 0 1 2], + :rotation {:at-zero? true, :degree 0, :mos [1 2 7 2]}}), + :submos-by-mos + {[1 2 2 7] + [{:degree 0, + :mos [1 2 2 7], + :mos-degrees [0 1 2 3], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 7]}} + {:degree 3, + :mos [1 2 2 7], + :mos-degrees [3 4 5 6], + :rotation {:at-zero? false, :degree 3, :mos [1 2 2 7]}}], + [2 1 2 7] + [{:degree 2, + :mos [2 1 2 7], + :mos-degrees [2 3 4 5], + :rotation {:at-zero? false, :degree 2, :mos [2 1 2 7]}} + {:degree 6, + :mos [2 1 2 7], + :mos-degrees [6 0 1 2], + :rotation {:at-zero? true, :degree 0, :mos [1 2 7 2]}}], + [2 2 1 7] + [{:degree 1, + :mos [2 2 1 7], + :mos-degrees [1 2 3 4], + :rotation {:at-zero? false, :degree 1, :mos [2 2 1 7]}} + {:degree 5, + :mos [2 2 1 7], + :mos-degrees [5 6 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 7 2 2]}}], + [2 2 2 6] + [{:degree 4, + :mos [2 2 2 6], + :mos-degrees [4 5 6 0], + :rotation {:at-zero? true, :degree 0, :mos [6 2 2 2]}}]}, + :true-submos? false} + {:generator 1, + :pattern [1 1 1 1 3], + :period 7, + :submos + ({:degree 0, + :mos [1 2 2 1 6], + :mos-degrees [0 1 2 3 4], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 1 6]}} + {:degree 1, + :mos [2 2 1 2 5], + :mos-degrees [1 2 3 4 5], + :rotation {:at-zero? false, :degree 1, :mos [2 2 1 2 5]}} + {:degree 2, + :mos [2 1 2 2 5], + :mos-degrees [2 3 4 5 6], + :rotation {:at-zero? false, :degree 2, :mos [2 1 2 2 5]}} + {:degree 3, + :mos [1 2 2 2 5], + :mos-degrees [3 4 5 6 0], + :rotation {:at-zero? true, :degree 0, :mos [5 1 2 2 2]}} + {:degree 4, + :mos [2 2 2 1 5], + :mos-degrees [4 5 6 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 5 2 2 2]}} + {:degree 5, + :mos [2 2 1 2 5], + :mos-degrees [5 6 0 1 2], + :rotation {:at-zero? true, :degree 0, :mos [1 2 5 2 2]}} + {:degree 6, + :mos [2 1 2 2 5], + :mos-degrees [6 0 1 2 3], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 5 2]}}), + :submos-by-mos + {[1 2 2 1 6] + [{:degree 0, + :mos [1 2 2 1 6], + :mos-degrees [0 1 2 3 4], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 1 6]}}], + [1 2 2 2 5] + [{:degree 3, + :mos [1 2 2 2 5], + :mos-degrees [3 4 5 6 0], + :rotation {:at-zero? true, :degree 0, :mos [5 1 2 2 2]}}], + [2 1 2 2 5] + [{:degree 2, + :mos [2 1 2 2 5], + :mos-degrees [2 3 4 5 6], + :rotation {:at-zero? false, :degree 2, :mos [2 1 2 2 5]}} + {:degree 6, + :mos [2 1 2 2 5], + :mos-degrees [6 0 1 2 3], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 5 2]}}], + [2 2 1 2 5] + [{:degree 1, + :mos [2 2 1 2 5], + :mos-degrees [1 2 3 4 5], + :rotation {:at-zero? false, :degree 1, :mos [2 2 1 2 5]}} + {:degree 5, + :mos [2 2 1 2 5], + :mos-degrees [5 6 0 1 2], + :rotation {:at-zero? true, :degree 0, :mos [1 2 5 2 2]}}], + [2 2 2 1 5] + [{:degree 4, + :mos [2 2 2 1 5], + :mos-degrees [4 5 6 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 5 2 2 2]}}]}, + :true-submos? false} + {:generator 1, + :pattern [1 1 1 1 1 2], + :period 7, + :submos + ({:degree 0, + :mos [1 2 2 1 2 4], + :mos-degrees [0 1 2 3 4 5], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 1 2 4]}} + {:degree 1, + :mos [2 2 1 2 2 3], + :mos-degrees [1 2 3 4 5 6], + :rotation {:at-zero? false, :degree 1, :mos [2 2 1 2 2 3]}} + {:degree 2, + :mos [2 1 2 2 2 3], + :mos-degrees [2 3 4 5 6 0], + :rotation {:at-zero? true, :degree 0, :mos [3 2 1 2 2 2]}} + {:degree 3, + :mos [1 2 2 2 1 4], + :mos-degrees [3 4 5 6 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 4 1 2 2 2]}} + {:degree 4, + :mos [2 2 2 1 2 3], + :mos-degrees [4 5 6 0 1 2], + :rotation {:at-zero? true, :degree 0, :mos [1 2 3 2 2 2]}} + {:degree 5, + :mos [2 2 1 2 2 3], + :mos-degrees [5 6 0 1 2 3], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 3 2 2]}} + {:degree 6, + :mos [2 1 2 2 1 4], + :mos-degrees [6 0 1 2 3 4], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 1 4 2]}}), + :submos-by-mos + {[1 2 2 1 2 4] + [{:degree 0, + :mos [1 2 2 1 2 4], + :mos-degrees [0 1 2 3 4 5], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 1 2 4]}}], + [1 2 2 2 1 4] + [{:degree 3, + :mos [1 2 2 2 1 4], + :mos-degrees [3 4 5 6 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 4 1 2 2 2]}}], + [2 1 2 2 1 4] + [{:degree 6, + :mos [2 1 2 2 1 4], + :mos-degrees [6 0 1 2 3 4], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 1 4 2]}}], + [2 1 2 2 2 3] + [{:degree 2, + :mos [2 1 2 2 2 3], + :mos-degrees [2 3 4 5 6 0], + :rotation {:at-zero? true, :degree 0, :mos [3 2 1 2 2 2]}}], + [2 2 1 2 2 3] + [{:degree 1, + :mos [2 2 1 2 2 3], + :mos-degrees [1 2 3 4 5 6], + :rotation {:at-zero? false, :degree 1, :mos [2 2 1 2 2 3]}} + {:degree 5, + :mos [2 2 1 2 2 3], + :mos-degrees [5 6 0 1 2 3], + :rotation {:at-zero? true, :degree 0, :mos [1 2 2 3 2 2]}}], + [2 2 2 1 2 3] + [{:degree 4, + :mos [2 2 2 1 2 3], + :mos-degrees [4 5 6 0 1 2], + :rotation {:at-zero? true, :degree 0, :mos [1 2 3 2 2 2]}}]}, + :true-submos? false} + {:generator 2, + :pattern [2 5], + :period 7, + :submos + ({:degree 0, + :mos [3 9], + :mos-degrees [0 2], + :rotation {:at-zero? true, :degree 0, :mos [3 9]}} + {:degree 1, + :mos [4 8], + :mos-degrees [1 3], + :rotation {:at-zero? false, :degree 1, :mos [4 8]}} + {:degree 2, + :mos [3 9], + :mos-degrees [2 4], + :rotation {:at-zero? false, :degree 2, :mos [3 9]}} + {:degree 3, + :mos [3 9], + :mos-degrees [3 5], + :rotation {:at-zero? false, :degree 3, :mos [3 9]}} + {:degree 4, + :mos [4 8], + :mos-degrees [4 6], + :rotation {:at-zero? false, :degree 4, :mos [4 8]}} + {:degree 5, + :mos [4 8], + :mos-degrees [5 0], + :rotation {:at-zero? true, :degree 0, :mos [8 4]}} + {:degree 6, + :mos [3 9], + :mos-degrees [6 1], + :rotation {:at-zero? false, :degree 1, :mos [9 3]}}), + :submos-by-mos + {[3 9] + [{:degree 0, + :mos [3 9], + :mos-degrees [0 2], + :rotation {:at-zero? true, :degree 0, :mos [3 9]}} + {:degree 2, + :mos [3 9], + :mos-degrees [2 4], + :rotation {:at-zero? false, :degree 2, :mos [3 9]}} + {:degree 3, + :mos [3 9], + :mos-degrees [3 5], + :rotation {:at-zero? false, :degree 3, :mos [3 9]}} + {:degree 6, + :mos [3 9], + :mos-degrees [6 1], + :rotation {:at-zero? false, :degree 1, :mos [9 3]}}], + [4 8] + [{:degree 1, + :mos [4 8], + :mos-degrees [1 3], + :rotation {:at-zero? false, :degree 1, :mos [4 8]}} + {:degree 4, + :mos [4 8], + :mos-degrees [4 6], + :rotation {:at-zero? false, :degree 4, :mos [4 8]}} + {:degree 5, + :mos [4 8], + :mos-degrees [5 0], + :rotation {:at-zero? true, :degree 0, :mos [8 4]}}]}, + :true-submos? false} + {:generator 2, + :pattern [2 2 3], + :period 7, + :submos + ({:degree 0, + :mos [3 3 6], + :mos-degrees [0 2 4], + :rotation {:at-zero? true, :degree 0, :mos [3 3 6]}} + {:degree 1, + :mos [4 3 5], + :mos-degrees [1 3 5], + :rotation {:at-zero? false, :degree 1, :mos [4 3 5]}} + {:degree 2, + :mos [3 4 5], + :mos-degrees [2 4 6], + :rotation {:at-zero? false, :degree 2, :mos [3 4 5]}} + {:degree 3, + :mos [3 4 5], + :mos-degrees [3 5 0], + :rotation {:at-zero? true, :degree 0, :mos [5 3 4]}} + {:degree 4, + :mos [4 3 5], + :mos-degrees [4 6 1], + :rotation {:at-zero? false, :degree 1, :mos [5 4 3]}} + {:degree 5, + :mos [4 3 5], + :mos-degrees [5 0 2], + :rotation {:at-zero? true, :degree 0, :mos [3 5 4]}} + {:degree 6, + :mos [3 4 5], + :mos-degrees [6 1 3], + :rotation {:at-zero? false, :degree 1, :mos [4 5 3]}}), + :submos-by-mos + {[3 3 6] + [{:degree 0, + :mos [3 3 6], + :mos-degrees [0 2 4], + :rotation {:at-zero? true, :degree 0, :mos [3 3 6]}}], + [3 4 5] + [{:degree 2, + :mos [3 4 5], + :mos-degrees [2 4 6], + :rotation {:at-zero? false, :degree 2, :mos [3 4 5]}} + {:degree 3, + :mos [3 4 5], + :mos-degrees [3 5 0], + :rotation {:at-zero? true, :degree 0, :mos [5 3 4]}} + {:degree 6, + :mos [3 4 5], + :mos-degrees [6 1 3], + :rotation {:at-zero? false, :degree 1, :mos [4 5 3]}}], + [4 3 5] + [{:degree 1, + :mos [4 3 5], + :mos-degrees [1 3 5], + :rotation {:at-zero? false, :degree 1, :mos [4 3 5]}} + {:degree 4, + :mos [4 3 5], + :mos-degrees [4 6 1], + :rotation {:at-zero? false, :degree 1, :mos [5 4 3]}} + {:degree 5, + :mos [4 3 5], + :mos-degrees [5 0 2], + :rotation {:at-zero? true, :degree 0, :mos [3 5 4]}}]}, + :true-submos? false} + {:generator 2, + :pattern [2 2 2 1], + :period 7, + :submos + ({:degree 0, + :mos [3 3 4 2], + :mos-degrees [0 2 4 6], + :rotation {:at-zero? true, :degree 0, :mos [3 3 4 2]}} + {:degree 1, + :mos [4 3 4 1], + :mos-degrees [1 3 5 0], + :rotation {:at-zero? true, :degree 0, :mos [1 4 3 4]}} + {:degree 2, + :mos [3 4 3 2], + :mos-degrees [2 4 6 1], + :rotation {:at-zero? false, :degree 1, :mos [2 3 4 3]}} + {:degree 3, + :mos [3 4 3 2], + :mos-degrees [3 5 0 2], + :rotation {:at-zero? true, :degree 0, :mos [3 2 3 4]}} + {:degree 4, + :mos [4 3 4 1], + :mos-degrees [4 6 1 3], + :rotation {:at-zero? false, :degree 1, :mos [4 1 4 3]}} + {:degree 5, + :mos [4 3 3 2], + :mos-degrees [5 0 2 4], + :rotation {:at-zero? true, :degree 0, :mos [3 3 2 4]}} + {:degree 6, + :mos [3 4 3 2], + :mos-degrees [6 1 3 5], + :rotation {:at-zero? false, :degree 1, :mos [4 3 2 3]}}), + :submos-by-mos + {[3 3 4 2] + [{:degree 0, + :mos [3 3 4 2], + :mos-degrees [0 2 4 6], + :rotation {:at-zero? true, :degree 0, :mos [3 3 4 2]}}], + [3 4 3 2] + [{:degree 2, + :mos [3 4 3 2], + :mos-degrees [2 4 6 1], + :rotation {:at-zero? false, :degree 1, :mos [2 3 4 3]}} + {:degree 3, + :mos [3 4 3 2], + :mos-degrees [3 5 0 2], + :rotation {:at-zero? true, :degree 0, :mos [3 2 3 4]}} + {:degree 6, + :mos [3 4 3 2], + :mos-degrees [6 1 3 5], + :rotation {:at-zero? false, :degree 1, :mos [4 3 2 3]}}], + [4 3 3 2] + [{:degree 5, + :mos [4 3 3 2], + :mos-degrees [5 0 2 4], + :rotation {:at-zero? true, :degree 0, :mos [3 3 2 4]}}], + [4 3 4 1] + [{:degree 1, + :mos [4 3 4 1], + :mos-degrees [1 3 5 0], + :rotation {:at-zero? true, :degree 0, :mos [1 4 3 4]}} + {:degree 4, + :mos [4 3 4 1], + :mos-degrees [4 6 1 3], + :rotation {:at-zero? false, :degree 1, :mos [4 1 4 3]}}]}, + :true-submos? false} + {:generator 3, + :pattern [3 4], + :period 7, + :submos + ({:degree 0, + :mos [5 7], + :mos-degrees [0 3], + :rotation {:at-zero? true, :degree 0, :mos [5 7]}} + {:degree 1, + :mos [5 7], + :mos-degrees [1 4], + :rotation {:at-zero? false, :degree 1, :mos [5 7]}} + {:degree 2, + :mos [5 7], + :mos-degrees [2 5], + :rotation {:at-zero? false, :degree 2, :mos [5 7]}} + {:degree 3, + :mos [5 7], + :mos-degrees [3 6], + :rotation {:at-zero? false, :degree 3, :mos [5 7]}} + {:degree 4, + :mos [6 6], + :mos-degrees [4 0], + :rotation {:at-zero? true, :degree 0, :mos [6 6]}} + {:degree 5, + :mos [5 7], + :mos-degrees [5 1], + :rotation {:at-zero? false, :degree 1, :mos [7 5]}} + {:degree 6, + :mos [5 7], + :mos-degrees [6 2], + :rotation {:at-zero? false, :degree 2, :mos [7 5]}}), + :submos-by-mos + {[5 7] + [{:degree 0, + :mos [5 7], + :mos-degrees [0 3], + :rotation {:at-zero? true, :degree 0, :mos [5 7]}} + {:degree 1, + :mos [5 7], + :mos-degrees [1 4], + :rotation {:at-zero? false, :degree 1, :mos [5 7]}} + {:degree 2, + :mos [5 7], + :mos-degrees [2 5], + :rotation {:at-zero? false, :degree 2, :mos [5 7]}} + {:degree 3, + :mos [5 7], + :mos-degrees [3 6], + :rotation {:at-zero? false, :degree 3, :mos [5 7]}} + {:degree 5, + :mos [5 7], + :mos-degrees [5 1], + :rotation {:at-zero? false, :degree 1, :mos [7 5]}} + {:degree 6, + :mos [5 7], + :mos-degrees [6 2], + :rotation {:at-zero? false, :degree 2, :mos [7 5]}}], + [6 6] + [{:degree 4, + :mos [6 6], + :mos-degrees [4 0], + :rotation {:at-zero? true, :degree 0, :mos [6 6]}}]}, + :true-submos? true} + {:generator 3, + :pattern [3 3 1], + :period 7, + :submos + ({:degree 0, + :mos [5 5 2], + :mos-degrees [0 3 6], + :rotation {:at-zero? true, :degree 0, :mos [5 5 2]}} + {:degree 1, + :mos [5 6 1], + :mos-degrees [1 4 0], + :rotation {:at-zero? true, :degree 0, :mos [1 5 6]}} + {:degree 2, + :mos [5 5 2], + :mos-degrees [2 5 1], + :rotation {:at-zero? false, :degree 1, :mos [2 5 5]}} + {:degree 3, + :mos [5 5 2], + :mos-degrees [3 6 2], + :rotation {:at-zero? false, :degree 2, :mos [2 5 5]}} + {:degree 4, + :mos [6 5 1], + :mos-degrees [4 0 3], + :rotation {:at-zero? true, :degree 0, :mos [5 1 6]}} + {:degree 5, + :mos [5 5 2], + :mos-degrees [5 1 4], + :rotation {:at-zero? false, :degree 1, :mos [5 2 5]}} + {:degree 6, + :mos [5 5 2], + :mos-degrees [6 2 5], + :rotation {:at-zero? false, :degree 2, :mos [5 2 5]}}), + :submos-by-mos + {[5 5 2] + [{:degree 0, + :mos [5 5 2], + :mos-degrees [0 3 6], + :rotation {:at-zero? true, :degree 0, :mos [5 5 2]}} + {:degree 2, + :mos [5 5 2], + :mos-degrees [2 5 1], + :rotation {:at-zero? false, :degree 1, :mos [2 5 5]}} + {:degree 3, + :mos [5 5 2], + :mos-degrees [3 6 2], + :rotation {:at-zero? false, :degree 2, :mos [2 5 5]}} + {:degree 5, + :mos [5 5 2], + :mos-degrees [5 1 4], + :rotation {:at-zero? false, :degree 1, :mos [5 2 5]}} + {:degree 6, + :mos [5 5 2], + :mos-degrees [6 2 5], + :rotation {:at-zero? false, :degree 2, :mos [5 2 5]}}], + [5 6 1] + [{:degree 1, + :mos [5 6 1], + :mos-degrees [1 4 0], + :rotation {:at-zero? true, :degree 0, :mos [1 5 6]}}], + [6 5 1] + [{:degree 4, + :mos [6 5 1], + :mos-degrees [4 0 3], + :rotation {:at-zero? true, :degree 0, :mos [5 1 6]}}]}, + :true-submos? true} + {:generator 3, + :pattern [2 1 2 1 1], + :period 7, + :submos + ({:degree 0, + :mos [3 2 3 2 2], + :mos-degrees [0 2 3 5 6], + :rotation {:at-zero? true, :degree 0, :mos [3 2 3 2 2]}} + {:degree 1, + :mos [4 1 4 2 1], + :mos-degrees [1 3 4 6 0], + :rotation {:at-zero? true, :degree 0, :mos [1 4 1 4 2]}} + {:degree 2, + :mos [3 2 4 1 2], + :mos-degrees [2 4 5 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 2 3 2 4]}} + {:degree 3, + :mos [3 2 3 2 2], + :mos-degrees [3 5 6 1 2], + :rotation {:at-zero? false, :degree 1, :mos [2 2 3 2 3]}} + {:degree 4, + :mos [4 2 3 2 1], + :mos-degrees [4 6 0 2 3], + :rotation {:at-zero? true, :degree 0, :mos [3 2 1 4 2]}} + {:degree 5, + :mos [4 1 4 1 2], + :mos-degrees [5 0 1 3 4], + :rotation {:at-zero? true, :degree 0, :mos [1 4 1 2 4]}} + {:degree 6, + :mos [3 2 3 2 2], + :mos-degrees [6 1 2 4 5], + :rotation {:at-zero? false, :degree 1, :mos [2 3 2 2 3]}}), + :submos-by-mos + {[3 2 3 2 2] + [{:degree 0, + :mos [3 2 3 2 2], + :mos-degrees [0 2 3 5 6], + :rotation {:at-zero? true, :degree 0, :mos [3 2 3 2 2]}} + {:degree 3, + :mos [3 2 3 2 2], + :mos-degrees [3 5 6 1 2], + :rotation {:at-zero? false, :degree 1, :mos [2 2 3 2 3]}} + {:degree 6, + :mos [3 2 3 2 2], + :mos-degrees [6 1 2 4 5], + :rotation {:at-zero? false, :degree 1, :mos [2 3 2 2 3]}}], + [3 2 4 1 2] + [{:degree 2, + :mos [3 2 4 1 2], + :mos-degrees [2 4 5 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 2 3 2 4]}}], + [4 1 4 1 2] + [{:degree 5, + :mos [4 1 4 1 2], + :mos-degrees [5 0 1 3 4], + :rotation {:at-zero? true, :degree 0, :mos [1 4 1 2 4]}}], + [4 1 4 2 1] + [{:degree 1, + :mos [4 1 4 2 1], + :mos-degrees [1 3 4 6 0], + :rotation {:at-zero? true, :degree 0, :mos [1 4 1 4 2]}}], + [4 2 3 2 1] + [{:degree 4, + :mos [4 2 3 2 1], + :mos-degrees [4 6 0 2 3], + :rotation {:at-zero? true, :degree 0, :mos [3 2 1 4 2]}}]}, + :true-submos? true})] + (is (= all-submos + (subject/make-all-submos [1 2 2 1 2 2 2] 5))))) diff --git a/test/erv/mos/v2/submos_test.cljc b/test/erv/mos/v2/submos_test.cljc new file mode 100644 index 0000000..8419669 --- /dev/null +++ b/test/erv/mos/v2/submos_test.cljc @@ -0,0 +1,103 @@ +(ns erv.mos.v2.submos-test + (:require + [clojure.test :refer [deftest is]] + [erv.mos.v2.submos :as subject])) + +(deftest make-all-submos-test + (is (= '({:pattern [3 4], + :period 7, + :submos + ({:degree 0, + :mos [5 7], + :mos-degrees [0 3], + :rotation {:at-zero? true, :degree 0, :mos [5 7]}} + {:degree 1, + :mos [5 7], + :mos-degrees [1 4], + :rotation {:at-zero? false, :degree 1, :mos [5 7]}} + {:degree 2, + :mos [5 7], + :mos-degrees [2 5], + :rotation {:at-zero? false, :degree 2, :mos [5 7]}} + {:degree 3, + :mos [5 7], + :mos-degrees [3 6], + :rotation {:at-zero? false, :degree 3, :mos [5 7]}} + {:degree 4, + :mos [6 6], + :mos-degrees [4 0], + :rotation {:at-zero? true, :degree 0, :mos [6 6]}} + {:degree 5, + :mos [5 7], + :mos-degrees [5 1], + :rotation {:at-zero? false, :degree 1, :mos [7 5]}} + {:degree 6, + :mos [5 7], + :mos-degrees [6 2], + :rotation {:at-zero? false, :degree 2, :mos [7 5]}}), + :true-submos? true} + {:pattern [3 3 1], + :period 7, + :submos + ({:degree 0, + :mos [5 5 2], + :mos-degrees [0 3 6], + :rotation {:at-zero? true, :degree 0, :mos [5 5 2]}} + {:degree 1, + :mos [5 6 1], + :mos-degrees [1 4 0], + :rotation {:at-zero? true, :degree 0, :mos [1 5 6]}} + {:degree 2, + :mos [5 5 2], + :mos-degrees [2 5 1], + :rotation {:at-zero? false, :degree 1, :mos [2 5 5]}} + {:degree 3, + :mos [5 5 2], + :mos-degrees [3 6 2], + :rotation {:at-zero? false, :degree 2, :mos [2 5 5]}} + {:degree 4, + :mos [6 5 1], + :mos-degrees [4 0 3], + :rotation {:at-zero? true, :degree 0, :mos [5 1 6]}} + {:degree 5, + :mos [5 5 2], + :mos-degrees [5 1 4], + :rotation {:at-zero? false, :degree 1, :mos [5 2 5]}} + {:degree 6, + :mos [5 5 2], + :mos-degrees [6 2 5], + :rotation {:at-zero? false, :degree 2, :mos [5 2 5]}}), + :true-submos? true} + {:pattern [2 1 2 1 1], + :period 7, + :submos + ({:degree 0, + :mos [3 2 3 2 2], + :mos-degrees [0 2 3 5 6], + :rotation {:at-zero? true, :degree 0, :mos [3 2 3 2 2]}} + {:degree 1, + :mos [4 1 4 2 1], + :mos-degrees [1 3 4 6 0], + :rotation {:at-zero? true, :degree 0, :mos [1 4 1 4 2]}} + {:degree 2, + :mos [3 2 4 1 2], + :mos-degrees [2 4 5 0 1], + :rotation {:at-zero? true, :degree 0, :mos [1 2 3 2 4]}} + {:degree 3, + :mos [3 2 3 2 2], + :mos-degrees [3 5 6 1 2], + :rotation {:at-zero? false, :degree 1, :mos [2 2 3 2 3]}} + {:degree 4, + :mos [4 2 3 2 1], + :mos-degrees [4 6 0 2 3], + :rotation {:at-zero? true, :degree 0, :mos [3 2 1 4 2]}} + {:degree 5, + :mos [4 1 4 1 2], + :mos-degrees [5 0 1 3 4], + :rotation {:at-zero? true, :degree 0, :mos [1 4 1 2 4]}} + {:degree 6, + :mos [3 2 3 2 2], + :mos-degrees [6 1 2 4 5], + :rotation {:at-zero? false, :degree 1, :mos [2 3 2 2 3]}}), + :true-submos? true}) + (subject/make-all-submos 5 [1 2 2 1 2 2 2])))) diff --git a/test/erv/mos/v3/core_test.clj b/test/erv/mos/v3/core_test.clj new file mode 100644 index 0000000..5071766 --- /dev/null +++ b/test/erv/mos/v3/core_test.clj @@ -0,0 +1,113 @@ +(ns erv.mos.v3.core-test + (:require + [clojure.test :refer [deftest is]] + [erv.mos.v3.core :as subject])) + +(deftest gen->mos-ratios-test + (is (= [{:meta + {:period 2, + :scale :mos, + :size 2, + :intervals/cents '(701.9550008653874 498.04499913461217), + :intervals/ratios '(3/2 4/3), + :mos/L 3/2, + :mos/L.cents 701.9550008653874, + :mos/generator 3/2, + :mos/normalized-by 1, + :mos/pattern "Ls", + :mos/pattern.name "1s1L", + :mos/s 4/3, + :mos/s.cents 498.04499913461217, + :mos/sL-ratio 9/8, + :mos/sL-ratio.cents 594.1229411833639, + :mos/sL-ratio.float (float 1.4094208), + :mos/type :ratio}, + :scale + '({:bounded-ratio 1, :bounding-period 2, :ratio 1} + {:bounded-ratio 3/2, :bounding-period 2, :ratio 3/2})} + {:meta + {:period 2, + :scale :mos, + :size 3, + :intervals/cents + '(203.91000173077484 498.04499913461217 498.04499913461217), + :intervals/ratios '(9/8 4/3 4/3), + :mos/L 4/3, + :mos/L.cents 498.04499913461217, + :mos/generator 3/2, + :mos/normalized-by 1, + :mos/pattern "sLL", + :mos/pattern.name "1s2L", + :mos/s 9/8, + :mos/s.cents 203.91000173077484, + :mos/sL-ratio 32/27, + :mos/sL-ratio.cents 1546.0122684152425, + :mos/sL-ratio.float (float 2.4424746), + :mos/type :ratio}, + :scale + '({:bounded-ratio 1, :bounding-period 2, :ratio 1} + {:bounded-ratio 9/8, :bounding-period 2, :ratio 9/8} + {:bounded-ratio 3/2, :bounding-period 2, :ratio 3/2})} + {:meta + {:period 2, + :scale :mos, + :size 5, + :intervals/cents + '(203.91000173077484 + 203.91000173077484 + 294.1349974038373 + 203.91000173077484 + 294.1349974038373), + :intervals/ratios '(9/8 9/8 32/27 9/8 32/27), + :mos/L 32/27, + :mos/L.cents 294.1349974038373, + :mos/generator 3/2, + :mos/normalized-by 1, + :mos/pattern "ssLsL", + :mos/pattern.name "3s2L", + :mos/s 9/8, + :mos/s.cents 203.91000173077484, + :mos/sL-ratio 256/243, + :mos/sL-ratio.cents 634.2550936716368, + :mos/sL-ratio.float (float 1.4424746), + :mos/type :ratio}, + :scale + '({:bounded-ratio 1, :bounding-period 2, :ratio 1} + {:bounded-ratio 9/8, :bounding-period 2, :ratio 9/8} + {:bounded-ratio 81/64, :bounding-period 2, :ratio 81/64} + {:bounded-ratio 3/2, :bounding-period 2, :ratio 3/2} + {:bounded-ratio 27/16, :bounding-period 2, :ratio 27/16})} + {:meta + {:period 2, + :scale :mos, + :size 7, + :intervals/cents + '(203.91000173077484 + 203.91000173077484 + 203.91000173077484 + 90.22499567306232 + 203.91000173077484 + 203.91000173077484 + 90.22499567306232), + :intervals/ratios '(9/8 9/8 9/8 256/243 9/8 9/8 256/243), + :mos/L 9/8, + :mos/L.cents 203.91000173077484, + :mos/generator 3/2, + :mos/normalized-by 1, + :mos/pattern "LLLsLLs", + :mos/pattern.name "2s5L", + :mos/s 256/243, + :mos/s.cents 90.22499567306232, + :mos/sL-ratio 2187/2048, + :mos/sL-ratio.cents 1411.6001602157576, + :mos/sL-ratio.float (float 2.2600167), + :mos/type :ratio}, + :scale + '({:bounded-ratio 1, :bounding-period 2, :ratio 1} + {:bounded-ratio 9/8, :bounding-period 2, :ratio 9/8} + {:bounded-ratio 81/64, :bounding-period 2, :ratio 81/64} + {:bounded-ratio 729/512, :bounding-period 2, :ratio 729/512} + {:bounded-ratio 3/2, :bounding-period 2, :ratio 3/2} + {:bounded-ratio 27/16, :bounding-period 2, :ratio 27/16} + {:bounded-ratio 243/128, :bounding-period 2, :ratio 243/128})}] + (subject/gen->mos-ratios 3/2 2 7)))) diff --git a/test/erv/mos/v3/core_test.cljs b/test/erv/mos/v3/core_test.cljs new file mode 100644 index 0000000..99c1aaa --- /dev/null +++ b/test/erv/mos/v3/core_test.cljs @@ -0,0 +1,135 @@ +(ns erv.mos.v3.core-test + (:require + [clojure.test :refer [deftest is testing]] + [erv.mos.v3.core :refer [gen->mos-ratios]] + [erv.utils.exact :as exact.utils])) + +(deftest gen->mos-ratios-test + (testing "Basic test" + (let [result (-> (gen->mos-ratios (exact.utils/parse-ratio "3/2") 2 7) + (exact.utils/make-readable))] + (is (= '{:meta + {:mos/pattern.name "2s5L", + :mos/normalized-by 1, + :mos/generator "3/2", + :scale :mos, + :mos/s.cents 90.22499567306306, + :mos/L.cents 203.91000173077484, + :mos/pattern "LLLsLLs", + :intervals/cents + (203.91000173077484 + 203.91000173077484 + 203.91000173077484 + 90.22499567306306 + 203.91000173077484 + 203.91000173077484 + 90.22499567306306), + :size 7, + :intervals/ratios ("9/8" "9/8" "9/8" "256/243" "9/8" "9/8" "256/243"), + :mos/s "256/243", + :mos/L "9/8", + :mos/sL-ratio.float 2.2600167526708206, + :period "2", + :mos/sL-ratio.cents 1411.600160215743, + :mos/type :ratio, + :mos/sL-ratio "2187/2048"}, + :scale + ({:ratio "1", :bounded-ratio "1", :bounding-period "2"} + {:ratio "9/8", :bounded-ratio "9/8", :bounding-period "2"} + {:ratio "81/64", :bounded-ratio "81/64", :bounding-period "2"} + {:ratio "729/512", :bounded-ratio "729/512", :bounding-period "2"} + {:ratio "3/2", :bounded-ratio "3/2", :bounding-period "2"} + {:ratio "27/16", :bounded-ratio "27/16", :bounding-period "2"} + {:ratio "243/128", :bounded-ratio "243/128", :bounding-period "2"})} + (last result))))) + (testing "Integer generator" + (let [result (-> (gen->mos-ratios 3 2 7) + (exact.utils/make-readable))] + (is (= '{:meta + {:mos/pattern.name "2s5L", + :mos/normalized-by 1, + :mos/generator 3, + :scale :mos, + :mos/s.cents 90.22499567306306, + :mos/L.cents 203.91000173077484, + :mos/pattern "LLLsLLs", + :intervals/cents + (203.91000173077484 + 203.91000173077484 + 203.91000173077484 + 90.22499567306306 + 203.91000173077484 + 203.91000173077484 + 90.22499567306306), + :size 7, + :intervals/ratios ("9/8" "9/8" "9/8" "256/243" "9/8" "9/8" "256/243"), + :mos/s "256/243", + :mos/L "9/8", + :mos/sL-ratio.float 2.2600167526708206, + :period "2", + :mos/sL-ratio.cents 1411.600160215743, + :mos/type :ratio, + :mos/sL-ratio "2187/2048"}, + :scale + ({:ratio "1", :bounded-ratio "1", :bounding-period "2"} + {:ratio "9/8", :bounded-ratio "9/8", :bounding-period "2"} + {:ratio "81/64", :bounded-ratio "81/64", :bounding-period "2"} + {:ratio "729/512", :bounded-ratio "729/512", :bounding-period "2"} + {:ratio "3/2", :bounded-ratio "3/2", :bounding-period "2"} + {:ratio "27/16", :bounded-ratio "27/16", :bounding-period "2"} + {:ratio "243/128", :bounded-ratio "243/128", :bounding-period "2"})} + (last result))))) + (testing "Floating point generator" + (let [result (-> (gen->mos-ratios 1.618 2 7) + (exact.utils/make-readable))] + (is (= '{:meta + {:mos/pattern.name "4s3L", + :mos/normalized-by 1, + :mos/generator 1.618, + :scale :mos, + :mos/s.cents 99.16178796440605, + :mos/L.cents 267.78428271412537, + :mos/pattern "ssLsLsL", + :intervals/cents + (99.16178796440605 + 99.16178796440605 + 267.78428271412537 + 99.16178796440605 + 267.78428271412537 + 99.16178796440605 + 267.78428271412537), + :size 7, + :intervals/ratios + ("529475129/500000000" + "529475129/500000000" + "500000000000/428345379361" + "529475129/500000000" + "500000000000/428345379361" + "529475129/500000000" + "500000000000/428345379361"), + :mos/s "529475129/500000000", + :mos/L "500000000000/428345379361", + :mos/sL-ratio.float 2.700478563478969, + :period "2", + :mos/sL-ratio.cents 1719.8581153882103, + :mos/type :ratio, + :mos/sL-ratio "250000000000000000000/226798224993719412569"}, + :scale + ({:ratio "1", :bounded-ratio "1", :bounding-period "2"} + {:ratio "529475129/500000000", + :bounded-ratio "529475129/500000000", + :bounding-period "2"} + {:ratio "280343912229566641/250000000000000000", + :bounded-ratio "280343912229566641/250000000000000000", + :bounding-period "2"} + {:ratio "654481/500000", + :bounded-ratio "654481/500000", + :bounding-period "2"} + {:ratio "346531411903049/250000000000000", + :bounded-ratio "346531411903049/250000000000000", + :bounding-period "2"} + {:ratio "809/500", :bounded-ratio "809/500", :bounding-period "2"} + {:ratio "428345379361/250000000000", + :bounded-ratio "428345379361/250000000000", + :bounding-period "2"})} + (last result)))))) From 8e5c421f4ae77a2c2e811845d8a04945564272dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 21:28:46 -0600 Subject: [PATCH 43/54] update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 739cc56..5ccf1af 100755 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ src/archive/huygens-fokker-scala-archive/*.scl **/.DS_Store /node_modules /dist +/out .cider-repl-history .shadow-cljs/ npm-test/ From 83d5b3aaed510401bb799b8f06dee3b0a2bdbbcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 23:46:25 -0600 Subject: [PATCH 44/54] Add edo tests --- src/erv/edo/core.cljc | 23 ----------- test/erv/edo/core_test.cljc | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 test/erv/edo/core_test.cljc diff --git a/src/erv/edo/core.cljc b/src/erv/edo/core.cljc index b5856df..1de425e 100644 --- a/src/erv/edo/core.cljc +++ b/src/erv/edo/core.cljc @@ -9,27 +9,6 @@ (defn pattern->degrees [pattern] (->> pattern (drop-last 1) (reduce #(conj %1 (+ (last %1) %2)) [0]))) -(comment - (require - '[erv.cps.core :as cps] - '[erv.utils.conversions :as conv] - '[erv.scale.core :refer [demo!]] - '[erv.mos.submos :refer [make-all-submos]] - '[erv.mos.mos :refer [make-mos]]) - - (map conv/ratio->cents (edo-ratios (* 2 3 5 7))) - - (def mos (make-mos 31 5)) - (-> mos) - (def submosi (make-all-submos (mos 6) 5)) - (do) - (def submos) (-> submosi #_(->> (filter :true-submos?)) - #_#_(nth 0) :submos - #_#_(nth 1) :mos) - (demo! (:scale (from-pattern submos)) :note-dur 200 :direction :down) - (demo! (:scale (from-pattern [3,5,2,5,3,5,4])) :note-dur 200 :direction :down) - (demo! (:scale (from-pattern [6, 3, 4, 3, 7, 4, 3, 1] 2)) :note-dur 200 :direction :up)) - (defn from-pattern "For use with `mos` patterns or other custom intervalic patterns, i.e. [3 2 3 2 2]" ([pattern] (from-pattern pattern 2)) @@ -46,5 +25,3 @@ :bounded-ratio (nth edo degree) :bounding-period period}) degrees)}))) - -(from-pattern [2, 2, 5, 2, 5, 2, 5, 2, 2, 5, 2, 5, 2, 5, 2, 5]) diff --git a/test/erv/edo/core_test.cljc b/test/erv/edo/core_test.cljc new file mode 100644 index 0000000..6229a0b --- /dev/null +++ b/test/erv/edo/core_test.cljc @@ -0,0 +1,80 @@ +(ns erv.edo.core-test + (:require + [clojure.math :refer [round]] + [clojure.test :refer [deftest is]] + [erv.edo.core :refer [from-pattern]] + [erv.utils.conversions :refer [ratio->cents]])) + +(deftest from-pattern-test + (is (= #?(:clj + {:meta {:edo/pattern [2 2 1 2 2 2 1] + :edo/divisions 12 + :edo/period 2}, + :scale + [{:bounded-ratio 1.0, + :bounding-period 2, + :edo/degree 0, + :edo/original-degree 0} + {:bounded-ratio 1.122462048309373, + :bounding-period 2, + :edo/degree 1, + :edo/original-degree 2} + {:bounded-ratio 1.2599210498948732, + :bounding-period 2, + :edo/degree 2, + :edo/original-degree 4} + {:bounded-ratio 1.3348398541700344, + :bounding-period 2, + :edo/degree 3, + :edo/original-degree 5} + {:bounded-ratio 1.4983070768766813, + :bounding-period 2, + :edo/degree 4, + :edo/original-degree 7} + {:bounded-ratio 1.6817928305074292, + :bounding-period 2, + :edo/degree 5, + :edo/original-degree 9} + {:bounded-ratio 1.887748625363387, + :bounding-period 2, + :edo/degree 6, + :edo/original-degree 11}]} + ;;NOTE slightly different value on cljs + :cljs + {:meta {:edo/pattern [2 2 1 2 2 2 1] + :edo/divisions 12 + :edo/period 2} + :scale [{:edo/original-degree 0 + :edo/degree 0 + :bounded-ratio 1 + :bounding-period 2} + {:edo/original-degree 2 + :edo/degree 1 + :bounded-ratio 1.1224620483093728 + :bounding-period 2} + {:edo/original-degree 4 + :edo/degree 2 + :bounded-ratio 1.2599210498948732 + :bounding-period 2} + {:edo/original-degree 5 + :edo/degree 3 + :bounded-ratio 1.3348398541700344 + :bounding-period 2} + {:edo/original-degree 7 + :edo/degree 4 + :bounded-ratio 1.4983070768766815 + :bounding-period 2} + {:edo/original-degree 9 + :edo/degree 5 + :bounded-ratio 1.6817928305074292 + :bounding-period 2} + {:edo/original-degree 11 + :edo/degree 6 + :bounded-ratio 1.8877486253633868 + :bounding-period 2}]}) + + (from-pattern [2 2 1 2 2 2 1]))) + (is (= [0 200 400 500 700 900 1100] + (->> (from-pattern [2 2 1 2 2 2 1]) + :scale + (map (comp round ratio->cents :bounded-ratio)))))) From 815a6215fc77783e2e08fc9e39d5cc61c585de40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 15 Oct 2025 23:47:59 -0600 Subject: [PATCH 45/54] add js pow rational function --- src/erv/utils/core.cljc | 33 ++++++++++++++++++++++++++------- src/erv/utils/exact.cljc | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index 850f246..d971412 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -77,13 +77,32 @@ (defn indexes-of [el coll] (keep-indexed #(when (= el %2) %1) coll)) -(defn ^:export pow [n power] - (when-not (int? power) - (throw (ex-info "`power` must be an int" {:power power}))) - (cond - (zero? power) 1 - (> power 0) (apply * (repeat power n)) - :else (apply / 1 (repeat (abs power) n)))) +#?(:clj + (defn ^:export pow [n power] + (when-not (int? power) + (throw (ex-info "`power` must be an int" {:power power}))) + (cond + (zero? power) 1 + (> power 0) (apply * (repeat power n)) + :else (apply / 1 (repeat (abs power) n)))) + :cljs + (defn ^:export pow [n power] + (when-not (int? power) + (throw (ex-info "`power` must be an int" {:power power}))) + (let [n (exact.utils/->exact n) + power (exact.utils/->exact power)] + (cond + (zero? power) (exact.utils/->exact 1) + (> power (exact.utils/->exact 0)) (apply * (repeat power n)) + :else (apply / (repeat (abs power) n)))))) +(comment + (pow (exact.utils/->exact 2) + (exact.utils/->exact 0)) + (pow (exact.utils/->exact 2) + (exact.utils/->exact 3)) + (pow (exact.utils/->exact 2) + (exact.utils/->exact -3)) + (pow 2 -3)) (defn pattern->degrees [pattern] diff --git a/src/erv/utils/exact.cljc b/src/erv/utils/exact.cljc index c76a37f..94c7cbb 100644 --- a/src/erv/utils/exact.cljc +++ b/src/erv/utils/exact.cljc @@ -1,7 +1,7 @@ (ns erv.utils.exact "Parse numbers into ratios using `gfredericks/exact`. Also provides helpers for working around `exact` based numbers." (:require - [clojure.math :refer [pow]] + #?(:cljs [clojure.math :refer [pow]]) [clojure.string :as str] [clojure.walk :as walk] [com.gfredericks.exact :as e])) From 50b344bb2691e49333b9fab9b774ce537405e50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 22 Oct 2025 22:59:38 -0600 Subject: [PATCH 46/54] Continue "exact" integration --- src/erv/utils/conversions.cljc | 11 +++++++---- src/erv/utils/core.cljc | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/erv/utils/conversions.cljc b/src/erv/utils/conversions.cljc index d9463c6..768de0e 100755 --- a/src/erv/utils/conversions.cljc +++ b/src/erv/utils/conversions.cljc @@ -41,10 +41,13 @@ "If note is not exactly in 12ET it adds a suffix to the note with the upwards deviation in cents. i.e. 60.4 -> C3+40" [midi] - (let [deviation (-> (mod midi 1) - (->> (round2 2)) - (* 100) - int) + (let [deviation (-> + midi + (exact.utils/->native) + (mod 1) + (->> (round2 2)) + (* 100) + int) octave (get-octave midi)] (str (nth note-names (mod (int midi) 12)) octave diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index d971412..6c7d06d 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -21,7 +21,7 @@ "Round a double to the given precision (number of significant digits)" [precision d] (let [factor (Math/pow 10 precision)] - (/ (Math/round (* d factor)) factor))) + (core// (Math/round (core/* d factor)) factor))) (defn rotate [xs n] (let [l (count xs) From 1f8547e1c7a21ead01fefdf1af0b8db0d2d8c1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 22 Oct 2025 23:00:09 -0600 Subject: [PATCH 47/54] Fix bug caused by exact/numerator --- src/erv/utils/core.cljc | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index 6c7d06d..09df602 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -167,10 +167,20 @@ (defn decompose-ratio ([ratio] - (try - {:numer (numerator ratio) :denom (denominator ratio)} - (catch #?(:clj Exception :cljs js/Error) _ - {:numer ratio :denom (exact.utils/->exact 1)})))) + ;; NOTE the following code does not work in prod, as e/numerator returns nil and doesn't throw on integers + ;; keeping the code here for documentation purposes and to avoid any refactoring to a similar procedure + #_(try + {:numer (numerator ratio) :denom (denominator ratio)} + (catch #?(:clj Exception :cljs js/Error) _ + {:numer ratio :denom (exact.utils/->exact 1)})) + #?(:cljs + (cond + (e/integer? ratio) {:numer ratio :denom (exact.utils/->exact 1)} + (e/ratio? ratio) {:numer (numerator ratio) :denom (denominator ratio)}) + :clj + (cond + (int? ratio) {:numer ratio :denom 1} + (ratio? ratio) {:numer (numerator ratio) :denom (denominator ratio)})))) (defn decompose-ratios ([ratios] (mapv decompose-ratio ratios))) From a357c2636a6d22e7c64c94041926a2552d006143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 18 Nov 2025 15:24:33 -0600 Subject: [PATCH 48/54] Use shadow-cljs deps Fixes several nightmarish issues with the build --- shadow-cljs.edn | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 13b35f6..b469c7e 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -1,5 +1,14 @@ -{;; :source-paths ["src" "test"] - :deps {:aliases [:cljs]} +{:source-paths ["src" "test"] + :dependencies [[binaryage/devtools "0.9.7"] + [cider/cider-nrepl "0.25.3"] + [refactor-nrepl "2.5.0"] + [com.taoensso/timbre "5.1.0"] + [table/table "0.5.0"] + [com.gfredericks/exact "0.1.11"] + [org.clojure/math.combinatorics "0.1.6"] + [quil/quil "4.0.0-SNAPSHOT"] + [metosin/malli "0.19.1"] + [hiccup/hiccup "2.0.0"]] :dev-http {5678 "build/browser" 3001 "out/test"} :builds ; https://shadow-cljs.github.io/docs/UsersGuide.html#target-node-script @@ -7,6 +16,5 @@ :output-to "dist/lib.js" :exports-fn js.export-fn/generate-exports} :browser-test {:target :browser-test - :test-dir "out/test"} - :test {:target :node-test - :output-to "out/node-tests.js"}}} + :devtools {:ignore-warnings true} + :test-dir "out/test"}}} From 9e691243708ae2297a43c921166587ee9d8b500e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 18 Nov 2025 15:25:21 -0600 Subject: [PATCH 49/54] Add intish? function --- src/erv/utils/core.cljc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/erv/utils/core.cljc b/src/erv/utils/core.cljc index 09df602..4955105 100755 --- a/src/erv/utils/core.cljc +++ b/src/erv/utils/core.cljc @@ -165,6 +165,8 @@ [nums] (reduce gcd nums)) +(defn intish? [n] (= n (int n))) + (defn decompose-ratio ([ratio] ;; NOTE the following code does not work in prod, as e/numerator returns nil and doesn't throw on integers @@ -179,9 +181,10 @@ (e/ratio? ratio) {:numer (numerator ratio) :denom (denominator ratio)}) :clj (cond - (int? ratio) {:numer ratio :denom 1} + (intish? ratio) {:numer ratio :denom 1} (ratio? ratio) {:numer (numerator ratio) :denom (denominator ratio)})))) - +(comment + (decompose-ratio 21/16)) (defn decompose-ratios ([ratios] (mapv decompose-ratio ratios))) From 6995e2ba16b194d37128b6ef4f08cac1d48fcadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 18 Nov 2025 15:25:57 -0600 Subject: [PATCH 50/54] HOTFIX Allow build on erv-web --- src/erv/constant_structures/core.cljc | 44 +++++++++++++-------------- src/erv/lattice/v2.cljc | 28 ++++++++++++++++- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/erv/constant_structures/core.cljc b/src/erv/constant_structures/core.cljc index 1d72438..801c22d 100644 --- a/src/erv/constant_structures/core.cljc +++ b/src/erv/constant_structures/core.cljc @@ -74,32 +74,32 @@ #_(:constant-structure? (analyze (:scale (cps/make 2 [11 13 5 7])))) -(:constant-structure? (analyze - (:scale (cps/make 2 [1 3 5 7])))) -(:constant-structure? (analyze - (:scale (edo/from-pattern [2 2 1 2 2 2 1])))) +#_(:constant-structure? (analyze + (:scale (cps/make 2 [1 3 5 7])))) +#_(:constant-structure? (analyze + (:scale (edo/from-pattern [2 2 1 2 2 2 1])))) #_(:non-cs-intervals (analyze (:scale (cps/make 3 (map #(* 1 %) [1 3 7 9 11 15]) :norm-fac (* 1 9 11))))) -(:non-cs-intervals (analyze +#_(:non-cs-intervals (analyze ;; taken from http://anaphoria.com/Wilson1-3-7-9-11-15x3_pascal_eikosany.scl ;; See also tieminos.learning.cps-lumatone.analysis.1-3-7-9-11-15 - (:scale - (-> (cps/make 3 (map #(* 1 %) [1 3 7 9 11 15]) - :norm-fac (* 1 9 11)) - (update :scale - (comp - #(map-indexed (fn [i n] (assoc n :index i)) %) - #(sort-by :bounded-ratio %) - concat) - [#_{:set #{4/3 7 15} - :ratio 140/99 - :bounded-ratio 140/99 - :bounding-period 2} - #_{:set #{11 9} - :set-vecs [[11 9 9] [11 9 3 3]] - :ratio 9 - :bounded-ratio 18/16 - :bounding-period 2}]))))) + (:scale + (-> (cps/make 3 (map #(* 1 %) [1 3 7 9 11 15]) + :norm-fac (* 1 9 11)) + (update :scale + (comp + #(map-indexed (fn [i n] (assoc n :index i)) %) + #(sort-by :bounded-ratio %) + concat) + [#_{:set #{4/3 7 15} + :ratio 140/99 + :bounded-ratio 140/99 + :bounding-period 2} + #_{:set #{11 9} + :set-vecs [[11 9 9] [11 9 3 3]] + :ratio 9 + :bounded-ratio 18/16 + :bounding-period 2}]))))) #_(< 12/11 19/17 7/6) diff --git a/src/erv/lattice/v2.cljc b/src/erv/lattice/v2.cljc index 3bfe26c..dba8171 100644 --- a/src/erv/lattice/v2.cljc +++ b/src/erv/lattice/v2.cljc @@ -1,6 +1,9 @@ (ns erv.lattice.v2 + #?(:cljs (:refer-clojure :exclude [/ +])) (:require [erv.utils.core :refer [period-reduce]] + #?(:cljs [com.gfredericks.exact :as e :refer [/ +]]) + [erv.utils.exact :as exact.utils] [erv.utils.ratios :refer [analyze-ratio]])) (def base-coords @@ -61,8 +64,10 @@ (defn custom-connection? [period point-data1 point-data2 custom-edges] + ;; (println (->> point-data1 :ratio) period) (let [r1 (->> point-data1 :ratio (period-reduce period)) r2 (->> point-data2 :ratio (period-reduce period))] + ;; (println (->> point-data1 :ratio)) (custom-edges (period-reduce period (/ r1 r2))))) @@ -90,12 +95,15 @@ period point-data1 point-data2) + ;; _ (println "PPPPPPPPPPDDDDDD") get-diff-count (comp #(apply + %) vals) num-diff (diffs :numer-factors) denom-diff (diffs :denom-factors) diff (diff-count-set (+ (get-diff-count num-diff) (get-diff-count denom-diff))) + ;; _ (println "BBBBBBBCBBBBCCCCC?") custom? (custom-connection? period point-data1 point-data2 custom-edges)] + ;; (println "CCCCCCCCCCCCCCCC?") (if (or diff custom?) (let [points #{(:ratio point-data1) (:ratio point-data2)}] @@ -165,6 +173,7 @@ max-distance (apply max distances-set) updated-edges (reduce (fn [edges* node] + ;; (println "MMMMMMMMMMMMMMMMC") (make-connection distances-set edges* period @@ -173,6 +182,7 @@ custom-edges)) edges ns)] + ;; (println updated-edges) (if (and (not (ref-ratio-in-ratio-edges? (:ratio ref-node) updated-edges)) (<= max-distance (count combined-nodes))) @@ -186,9 +196,11 @@ & {:keys [custom-edges period] :or {custom-edges #{} period 2}}] - (let [coords-data-map (->> ratios + (let [period (exact.utils/->exact period) + coords-data-map (->> ratios (map #(ratio->lattice-point % base-coords)) (into {})) + ;; _ (println coords-data-map) coords-data (vals coords-data-map) coords (->> coords-data (map :coords)) @@ -196,6 +208,7 @@ max-x (->> coords (map :x) (apply max)) min-y (->> coords (map :y) (apply min)) max-y (->> coords (map :y) (apply max)) + ;; _ (println "+++++++++++") edges (->> coords-data-map combine-nodes (#(connect-nodes period % {:custom-edges custom-edges})) @@ -211,6 +224,19 @@ :data coords-data :edges edges}))) +(comment + (ratios->lattice-data base-coords #_["1/1" "15/14" "5/4" "10/7" "3/2" "12/7"] + (map exact.utils/parse-ratio ["1/1" + "80/77" + "12/11" + "8/7" + "96/77" + "10/7" + "16/11" + "120/77" + "12/7" + "20/11"]))) + (defn swap-coords [coords coord-pairs] (reduce (fn [coords [prime1 prime2]] From 67af1950a2c8f3a61add66494bc559b3aba4e876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 18 Nov 2025 15:27:14 -0600 Subject: [PATCH 51/54] add beating-analyzer v1 --- src/erv/beating_analyzer/v1.cljc | 211 +++++++++++++++++++++++++ test/erv/beating_analyzer/v1_test.clj | 75 +++++++++ test/erv/beating_analyzer/v1_test.cljs | 38 +++++ 3 files changed, 324 insertions(+) create mode 100644 src/erv/beating_analyzer/v1.cljc create mode 100644 test/erv/beating_analyzer/v1_test.clj create mode 100644 test/erv/beating_analyzer/v1_test.cljs diff --git a/src/erv/beating_analyzer/v1.cljc b/src/erv/beating_analyzer/v1.cljc new file mode 100644 index 0000000..db77b89 --- /dev/null +++ b/src/erv/beating_analyzer/v1.cljc @@ -0,0 +1,211 @@ +(ns erv.beating-analyzer.v1 + #?(:cljs (:refer-clojure :exclude [>= <= > < + - * / - numerator denominator integer? + mod rem quot even? odd? abs])) + + (:require + [clojure.core :as core] + [clojure.math.combinatorics :as combo] + [clojure.string :as str] + [erv.utils.exact :as exact.utils] + #?(:cljs [com.gfredericks.exact :as e :refer [>= <= > < + - * / - abs]]) + [erv.utils.conversions :refer [cps->name*]] + [erv.utils.core :refer [decompose-ratio factorize-ratio factors->hiccup + make-map-by-key pow prime-factors]] + [erv.utils.ratios :refer [ratios->scale-data]] + [taoensso.timbre :as timbre] + #?(:clj [hiccup2.core :as h]))) + +(def ^:private default-partials (map exact.utils/->exact (range 1 9))) + +(defn get-beat-data-by-pairs + ([ratios] (get-beat-data-by-pairs default-partials ratios)) + ([partials ratios] + (->> ratios + (mapcat + (fn [degree ratio] + (map (fn [i] {:degree degree + :ratio ratio + :partial i + :partial*ratio (* i ratio)}) + (map exact.utils/->exact partials))) + (range)) + (#(combo/combinations % 2)) + (remove (fn [[x1 x2]] (= (:ratio x1) (:ratio x2)))) + (map (fn [pair] {:pair pair + :diff (abs (- (:partial*ratio (first pair)) + (:partial*ratio (second pair))))})) + (sort-by :diff) + #_(map (fn [pair] + (assoc pair + :diff-c4 (double (* root (:diff pair))) + :diff-c3 (double (/ (* root (:diff pair)) + 2)) + :diff-c2 (double (/ (* root (:diff pair)) + 4)) + :diff-c1 (double (/ (* root (:diff pair)) + 8)))))))) + +(comment + (->> (get-beat-data-by-pairs [1 2 3 4 5 6] [1 5/4 3/2]) + (map :pair) + (filter (fn [[a b]] + (and (= 0 (:degree a)) + (= 2 (:degree b)) + (= 3 (:partial a)) + (= 2 (:partial b)))))) + + (require '[erv.utils.exact :refer [parse-ratios]] + '[erv.utils.exact :as exact.utils]) + (def ratios (parse-ratios "1 + 67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64")) + (take 10 (get-beat-data-by-pairs ratios)) + (exact.utils/->exact period)) + +(def ^:private lowest-freq (exact.utils/->exact 20)) + +(defn- get-root-note-lowest-freq + [period freq] + (loop + [freq freq] + (cond + (< freq lowest-freq) (recur (* freq period)) + (>= freq (* lowest-freq period)) (recur (/ freq period)) + (and (>= freq lowest-freq) (> (* lowest-freq period) freq)) freq))) + +(comment + (get-root-note-lowest-freq (exact.utils/->exact 2) + (exact.utils/->exact 1))) + +(defn- get-freq-periods-range + [period initial-freq max-freq] + (->> (range) + (map #(* initial-freq (pow period %))) + (take-while #(<= % max-freq)))) + +(def ^:private max-freq (exact.utils/->exact 4000)) +(def ^:private max-beat-freq (exact.utils/->exact 20)) + +(comment + (require '[erv.utils.core :refer [interval]] + '[erv.utils.conversions :as conv]) + (conv/ratio->cents (interval 187/128 7/4)) + (conv/ratio->cents 6/5) + (get-beat-data-by-pairs (range 1 7) [5/4])) + +(defn get-beat-data + "Using a root note, map the beat-data over the a range from 20 to 4000hz" + [period root partials ratios] + (let [beat-data (get-beat-data-by-pairs partials ratios) + lowest-root (get-root-note-lowest-freq period root) + root-periods-freqs (get-freq-periods-range period lowest-root max-freq) + pair->beat-data (->> beat-data + (map + (fn [pair] + (->> root-periods-freqs + (map + (fn [period root-freq] + (let [k (keyword (str "diff-period-" period))] + ;; (println (:diff pair)) + #_(double) (* root-freq (:diff pair)))) + (range)) + #_(into {}) + (assoc pair + :root-hz lowest-root + :beat-hz-by-period)))) + (group-by (comp set #(map :ratio %) :pair)) + (map (fn [[k v]] + [k (sort-by (juxt + (comp :partial first :pair) + (comp :partial second :pair)) + v)])) + (into {}) + #_(make-map-by-key (comp set #(map :ratio %) :pair))) + ratio-pairs (->> (keys pair->beat-data) + (sort-by (juxt first second)))] + (mapcat + (fn [period root] + (mapcat + (fn [pair] + (->> (pair->beat-data pair) + (keep (fn [beat-data] + (let [data ((juxt (comp (juxt :partial :degree) first :pair) + (comp (juxt :partial :degree) second :pair) + :beat-hz-by-period) + beat-data) + [[pr1 deg1] [pr2 deg2] beats-by-period] data + beats (nth beats-by-period period) + [r1 r2] (sort pair)] + (when (< beats max-beat-freq) + {:period period + :root-hz (exact.utils/->native root) + :root (cps->name* root) + :degree-1 deg1 + :degree-2 deg2 + :ratio-1 r1 + :ratio-2 r2 + :ratio-1-partial pr1 + :ratio-2-partial pr2 + :beat-freq.ratio beats + :beat-freq.hz (float (exact.utils/->native beats)) + :beat-freq.factors (factorize-ratio beats)})))))) + ratio-pairs)) + (range) + root-periods-freqs))) + +(comment + ;; degrees 9&14 harmonics 6 & 5 should beat at 1hz + (->> (get-beat-data (exact.utils/->exact 2) + (exact.utils/->exact 1) + (map exact.utils/->exact (range 1 7)) + [67/64 + 279/256 + 9/8 + 75/64 + 39/32 + 5/4 + 167/128 + 87/64 + 45/32 + 187/128 + 3/2 + 25/16 + 417/256 + 27/16 + 7/4 + 233/128 + 15/8 + 125/64 + 2/1]) + (filter (fn [{:keys [period degree-1 degree-2]}] + (let [degs #{9 14}] + (and + (= 1 period) + (degs degree-1) + (degs degree-2))))) + #_#_(map :beat-freq.hz) + sort) + (exact.utils/make-readable (take 10 (get-beat-data (exact.utils/->exact 2) + (exact.utils/->exact 1) + (map exact.utils/->exact (range 1 6)) + ratios))) + (count (exact.utils/make-readable (get-beat-data (exact.utils/->exact 2) + (exact.utils/->exact 1) + (map exact.utils/->exact (range 1 6)) + (parse-ratios "1 5/4 3/2"))))) diff --git a/test/erv/beating_analyzer/v1_test.clj b/test/erv/beating_analyzer/v1_test.clj new file mode 100644 index 0000000..84e4da6 --- /dev/null +++ b/test/erv/beating_analyzer/v1_test.clj @@ -0,0 +1,75 @@ +(ns erv.beating-analyzer.v1-test + (:require + [clojure.edn :as edn] + [clojure.set :as set] + [clojure.test :refer [deftest is]] + [erv.beating-analyzer.v1 :as subject] + [erv.utils.exact :as exact.utils])) + +(deftest get-beat-data-test + (let [data (subject/get-beat-data 2 1 (range 1 6) + [1 5/4 3/2])] + #_(is (= [{:root-hz 64, + :degree-1 0, + :ratio-2-partial "1", + :degree-2 2, + :beat-freq.ratio "32", + :beat-freq.factors {:numer ["2" "2" "2" "2" "2"], :denom []}, + :root "B0+62", + :ratio-1 "1", + :ratio-1-partial "1", + :period 1, + :ratio-2 "3/2", + :beat-freq.hz 32}] + + (->> data + (drop 99) + (take 1) + (exact.utils/make-readable)))) + (is (= 30 (count data))))) + +(subject/get-beat-data 2 1 (range 1 6) [1 5/4 3/2]) + +(comment + + (def clj (->> + (subject/get-beat-data 2 1 (range 1 6) [1 5/4 3/2]) + (exact.utils/make-readable) + (into #{}))) + (-> clj count) + (->> + (subject/get-beat-data 2 1 (range 1 6) [1 5/4 3/2]) + (exact.utils/make-readable) + frequencies + vals + (sort) + reverse) + + (->> (edn/read-string (slurp "test_data.edn")) + (map #(-> % (update :beat-freq.hz float))) + (exact.utils/make-readable) + frequencies + vals + count) + + (def cljs (->> (edn/read-string (slurp "test_data.edn")) + (map #(-> % (update :beat-freq.hz float))) + (exact.utils/make-readable) + (into #{}))) + (->> clj (map #(:beat-freq.hz %))) + (->> clj (filter #(zero? (:beat-freq.hz %)))) + (->> clj (filter + (fn [%] + (and + (= (% :degree-1) "0") , + (= (% :degree-2) "2") , + (= (% :ratio-2-partial) "2") , + #_(= (% :ratio-1) "1") , + (= (% :ratio-1-partial) "3") , + (= (% :period) "1") , + #_(= (% :ratio-2) "3/2"))))) + (-> clj first) + (-> cljs first) + (set/difference cljs clj) + + (-> 90/72)) diff --git a/test/erv/beating_analyzer/v1_test.cljs b/test/erv/beating_analyzer/v1_test.cljs new file mode 100644 index 0000000..3d4f88a --- /dev/null +++ b/test/erv/beating_analyzer/v1_test.cljs @@ -0,0 +1,38 @@ +(ns erv.beating-analyzer.v1-test + (:require + [cljs.pprint :as pprint] + [clojure.test :refer [deftest is]] + [erv.beating-analyzer.v1 :as subject] + [erv.utils.exact :as exact.utils])) + +(deftest get-beat-data-test + (let [data (subject/get-beat-data + (exact.utils/->exact 2) + (exact.utils/->exact 1) + (map exact.utils/->exact (range 1 6)) + (exact.utils/parse-ratios "1 5/4 3/2"))] + (is (= [{:root-hz 32, + :degree-1 0, + :ratio-2-partial "1", + :degree-2 1, + :beat-freq.ratio "8", + :beat-freq.factors {:numer ["2" "2" "2"], :denom []}, + :root "B0+62", + :ratio-1 "1", + :ratio-1-partial "1", + :period 0, + :ratio-2 "5/4", + :beat-freq.hz 8}] + (->> data + (take 1) + (exact.utils/make-readable)))) + (is (= 30 (count data))))) + +#_(pprint/pprint + (first + (exact.utils/make-readable + (subject/get-beat-data + (exact.utils/->exact 2) + (exact.utils/->exact 1) + (map exact.utils/->exact (range 1 6)) + (exact.utils/parse-ratios "1 5/4 3/2"))))) From 43a9de7c34540f9566dd5934bf159ece307e89c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 18 Nov 2025 15:54:09 -0600 Subject: [PATCH 52/54] fix lattice.v2 on cljs --- src/erv/lattice/v2.cljc | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/erv/lattice/v2.cljc b/src/erv/lattice/v2.cljc index dba8171..e38456d 100644 --- a/src/erv/lattice/v2.cljc +++ b/src/erv/lattice/v2.cljc @@ -20,8 +20,8 @@ (defn make-coords [base-coords numerator-factors denominator-factors] (let [numer-coords (reduce (fn [{:keys [x y]} factor] - {:x (+ x (get-in base-coords [factor :x])) - :y (+ y (get-in base-coords [factor :y]))}) + {:x (clojure.core/+ x (get-in base-coords [factor :x])) + :y (clojure.core/+ y (get-in base-coords [factor :y]))}) {:x 0 :y 0} numerator-factors)] (reduce (fn [{:keys [x y]} factor] @@ -40,9 +40,6 @@ :denom-factors denom-factors :coords (make-coords base-coords numer-factors denom-factors)}})) -(comment - (ratio->lattice-point)) - (defn get-point-data-difference "`factor-type` should be `:numer-factors` or `:denom-factors`" [period point-data1 point-data2 factor-type] @@ -64,10 +61,8 @@ (defn custom-connection? [period point-data1 point-data2 custom-edges] - ;; (println (->> point-data1 :ratio) period) (let [r1 (->> point-data1 :ratio (period-reduce period)) r2 (->> point-data2 :ratio (period-reduce period))] - ;; (println (->> point-data1 :ratio)) (custom-edges (period-reduce period (/ r1 r2))))) @@ -90,20 +85,18 @@ (defn make-connection [diff-count-set connections-set period point-data1 point-data2 custom-edges] - #_(println period point-data1 point-data2) (let [diffs (partial get-point-data-difference period point-data1 point-data2) - ;; _ (println "PPPPPPPPPPDDDDDD") - get-diff-count (comp #(apply + %) vals) + get-diff-count (comp #(apply clojure.core/+ %) vals) num-diff (diffs :numer-factors) denom-diff (diffs :denom-factors) - diff (diff-count-set (+ (get-diff-count num-diff) - (get-diff-count denom-diff))) - ;; _ (println "BBBBBBBCBBBBCCCCC?") + diff (diff-count-set (clojure.core/+ (get-diff-count num-diff) + (get-diff-count denom-diff))) + custom? (custom-connection? period point-data1 point-data2 custom-edges)] - ;; (println "CCCCCCCCCCCCCCCC?") + (if (or diff custom?) (let [points #{(:ratio point-data1) (:ratio point-data2)}] @@ -173,7 +166,6 @@ max-distance (apply max distances-set) updated-edges (reduce (fn [edges* node] - ;; (println "MMMMMMMMMMMMMMMMC") (make-connection distances-set edges* period @@ -182,7 +174,6 @@ custom-edges)) edges ns)] - ;; (println updated-edges) (if (and (not (ref-ratio-in-ratio-edges? (:ratio ref-node) updated-edges)) (<= max-distance (count combined-nodes))) @@ -200,7 +191,6 @@ coords-data-map (->> ratios (map #(ratio->lattice-point % base-coords)) (into {})) - ;; _ (println coords-data-map) coords-data (vals coords-data-map) coords (->> coords-data (map :coords)) @@ -208,7 +198,6 @@ max-x (->> coords (map :x) (apply max)) min-y (->> coords (map :y) (apply min)) max-y (->> coords (map :y) (apply max)) - ;; _ (println "+++++++++++") edges (->> coords-data-map combine-nodes (#(connect-nodes period % {:custom-edges custom-edges})) @@ -225,6 +214,8 @@ :edges edges}))) (comment + (exact.utils/make-readable (ratios->lattice-data base-coords + (exact.utils/parse-ratios "3/2 9/8 2/1"))) (ratios->lattice-data base-coords #_["1/1" "15/14" "5/4" "10/7" "3/2" "12/7"] (map exact.utils/parse-ratio ["1/1" "80/77" From b1b471dbbd6061399f2d4ec1d195721a37e8c91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 18 Nov 2025 16:44:57 -0600 Subject: [PATCH 53/54] fix constant-structures.core on cljs --- src/erv/constant_structures/core.cljc | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/erv/constant_structures/core.cljc b/src/erv/constant_structures/core.cljc index 801c22d..7d664f3 100644 --- a/src/erv/constant_structures/core.cljc +++ b/src/erv/constant_structures/core.cljc @@ -1,17 +1,21 @@ (ns erv.constant-structures.core "A constant structure is a scale where every interval is subtended by the same number of steps." + #?(:cljs (:refer-clojure :exclude [* / <])) (:require + #?(:cljs [com.gfredericks.exact :as e :refer [/ * <]]) [clojure.math.combinatorics :as combo] [erv.cps.core :as cps] - [erv.edo.core :as edo] - [erv.utils.core :refer [interval round2]])) + [erv.utils.core :refer [interval round2]] + [erv.utils.exact :as exact.utils])) (defn maybe-round [n] #?(:clj (if (rational? n) n (round2 6 n)) - :cljs (round2 6 n))) + :cljs (if (e/ratio? n) + n + (round2 6 n)))) (defn maybe-rationalize [n decimals] @@ -21,6 +25,18 @@ ;; TODO implement :cljs (round2 6 n))) +(defn- invert-interval + [period intvl] + (* (exact.utils/->exact period) (/ #?(:clj 1 :cljs e/ONE) intvl))) + + +(comment + (def scale (map-indexed #(assoc %2 :index %1) (:scale (cps/make 2 [11 13 5 7])))) + (get-intervals 6 (take 2 scale)) + (maybe-round (interval (:bounded-ratio (second (take 2 scale))) + (:bounded-ratio (first (take 2 scale))))) + (invert-interval 2 (interval (:bounded-ratio (second (take 2 scale))) + (:bounded-ratio (first (take 2 scale)))))) (defn get-intervals [scale-size note-pair] (->> note-pair @@ -28,7 +44,7 @@ (let [period (:bounding-period a) intvl (maybe-round (interval (:bounded-ratio a) (:bounded-ratio b))) - inversion (* period (/ 1 intvl))] + inversion (invert-interval period intvl)] {intvl {:steps #{(- (:index b) (:index a))} :intervals [[a b]]} inversion {:steps #{(- scale-size (- (:index b) (:index a)))} @@ -59,7 +75,7 @@ (map :bounded-ratio pair)})))])) - (sort-by first)) + (sort-by first <)) non-cs-intervals (->> interval-data (filter (fn [[_interval {:keys [steps]}]] (> (count steps) 1))))] From 2a72ad12f22bc2c014ffb3eb323d94676c4f5231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Tue, 18 Nov 2025 16:45:20 -0600 Subject: [PATCH 54/54] Add tests for cs.core and lattive.v2 --- test/erv/constant_structures/core_test.clj | 84 ++++++++++++++++++++ test/erv/constant_structures/core_test.cljs | 85 +++++++++++++++++++++ test/erv/lattice/v2_test.cljs | 38 +++++++++ 3 files changed, 207 insertions(+) create mode 100644 test/erv/constant_structures/core_test.clj create mode 100644 test/erv/constant_structures/core_test.cljs create mode 100644 test/erv/lattice/v2_test.cljs diff --git a/test/erv/constant_structures/core_test.clj b/test/erv/constant_structures/core_test.clj new file mode 100644 index 0000000..a4abd51 --- /dev/null +++ b/test/erv/constant_structures/core_test.clj @@ -0,0 +1,84 @@ +(ns erv.constant-structures.core-test + (:require + [clojure.test :refer [deftest is]] + [erv.constant-structures.core :refer [analyze]] + [erv.cps.core :as cps])) + +(deftest analyze-test + (is (= '{:constant-structure? true, + :interval-data + ([143/140 + {:intervals ({:interval (35/32 143/128), :steps 1}), :steps #{1}}] + [14/13 + {:intervals + ({:interval (65/64 35/32), :steps 1} + {:interval (143/128 77/64), :steps 1}), + :steps #{1}}] + [11/10 + {:intervals + ({:interval (65/64 143/128), :steps 2} + {:interval (35/32 77/64), :steps 2}), + :steps #{2}}] + [13/11 + {:intervals + ({:interval (55/32 65/64), :steps -5} + {:interval (77/64 91/64), :steps 1}), + :steps #{1}}] + [77/65 + {:intervals ({:interval (65/64 77/64), :steps 3}), :steps #{3}}] + [110/91 + {:intervals ({:interval (91/64 55/32), :steps 1}), :steps #{1}}] + [14/11 + {:intervals + ({:interval (55/32 35/32), :steps -4} + {:interval (143/128 91/64), :steps 2}), + :steps #{2}}] + [13/10 + {:intervals + ({:interval (35/32 91/64), :steps 3} + {:interval (55/32 143/128), :steps -3}), + :steps #{3}}] + [7/5 + {:intervals + ({:interval (65/64 91/64), :steps 4} + {:interval (55/32 77/64), :steps -2}), + :steps #{4}}] + [10/7 + {:intervals + ({:interval (91/64 65/64), :steps -4} + {:interval (77/64 55/32), :steps 2}), + :steps #{2}}] + [20/13 + {:intervals + ({:interval (91/64 35/32), :steps -3} + {:interval (143/128 55/32), :steps 3}), + :steps #{3}}] + [11/7 + {:intervals + ({:interval (35/32 55/32), :steps 4} + {:interval (91/64 143/128), :steps -2}), + :steps #{4}}] + [91/55 + {:intervals ({:interval (55/32 91/64), :steps -1}), :steps #{5}}] + [130/77 + {:intervals ({:interval (77/64 65/64), :steps -3}), :steps #{3}}] + [22/13 + {:intervals + ({:interval (65/64 55/32), :steps 5} + {:interval (91/64 77/64), :steps -1}), + :steps #{5}}] + [20/11 + {:intervals + ({:interval (143/128 65/64), :steps -2} + {:interval (77/64 35/32), :steps -2}), + :steps #{4}}] + [13/7 + {:intervals + ({:interval (35/32 65/64), :steps -1} + {:interval (77/64 143/128), :steps -1}), + :steps #{5}}] + [280/143 + {:intervals ({:interval (143/128 35/32), :steps -1}), + :steps #{5}}]), + :non-cs-intervals {:intervals (), :total 0}} + (analyze (:scale (cps/make 2 [11 13 5 7])))))) diff --git a/test/erv/constant_structures/core_test.cljs b/test/erv/constant_structures/core_test.cljs new file mode 100644 index 0000000..818f791 --- /dev/null +++ b/test/erv/constant_structures/core_test.cljs @@ -0,0 +1,85 @@ +(ns erv.constant-structures.core-test + (:require + [clojure.test :refer [deftest is]] + [erv.constant-structures.core :refer [analyze]] + [erv.cps.core :as cps] + [erv.utils.exact :as exact.utils])) + +(deftest analyze-test + (is (= '{:interval-data + (["143/140" + {:steps #{1}, :intervals ({:steps 1, :interval ("35/32" "143/128")})}] + ["14/13" + {:steps #{1}, + :intervals + ({:steps 1, :interval ("65/64" "35/32")} + {:steps 1, :interval ("143/128" "77/64")})}] + ["11/10" + {:steps #{2}, + :intervals + ({:steps 2, :interval ("65/64" "143/128")} + {:steps 2, :interval ("35/32" "77/64")})}] + ["13/11" + {:steps #{1}, + :intervals + ({:steps -5, :interval ("55/32" "65/64")} + {:steps 1, :interval ("77/64" "91/64")})}] + ["77/65" {:steps #{3}, :intervals ({:steps 3, :interval ("65/64" "77/64")})}] + ["110/91" + {:steps #{1}, :intervals ({:steps 1, :interval ("91/64" "55/32")})}] + ["14/11" + {:steps #{2}, + :intervals + ({:steps -4, :interval ("55/32" "35/32")} + {:steps 2, :interval ("143/128" "91/64")})}] + ["13/10" + {:steps #{3}, + :intervals + ({:steps 3, :interval ("35/32" "91/64")} + {:steps -3, :interval ("55/32" "143/128")})}] + ["7/5" + {:steps #{4}, + :intervals + ({:steps 4, :interval ("65/64" "91/64")} + {:steps -2, :interval ("55/32" "77/64")})}] + ["10/7" + {:steps #{2}, + :intervals + ({:steps -4, :interval ("91/64" "65/64")} + {:steps 2, :interval ("77/64" "55/32")})}] + ["20/13" + {:steps #{3}, + :intervals + ({:steps -3, :interval ("91/64" "35/32")} + {:steps 3, :interval ("143/128" "55/32")})}] + ["11/7" + {:steps #{4}, + :intervals + ({:steps 4, :interval ("35/32" "55/32")} + {:steps -2, :interval ("91/64" "143/128")})}] + ["91/55" + {:steps #{5}, :intervals ({:steps -1, :interval ("55/32" "91/64")})}] + ["130/77" + {:steps #{3}, :intervals ({:steps -3, :interval ("77/64" "65/64")})}] + ["22/13" + {:steps #{5}, + :intervals + ({:steps 5, :interval ("65/64" "55/32")} + {:steps -1, :interval ("91/64" "77/64")})}] + ["20/11" + {:steps #{4}, + :intervals + ({:steps -2, :interval ("143/128" "65/64")} + {:steps -2, :interval ("77/64" "35/32")})}] + ["13/7" + {:steps #{5}, + :intervals + ({:steps -1, :interval ("35/32" "65/64")} + {:steps -1, :interval ("77/64" "143/128")})}] + ["280/143" + {:steps #{5}, :intervals ({:steps -1, :interval ("143/128" "35/32")})}]), + :non-cs-intervals {:total 0, :intervals ()}, + :constant-structure? true} + (->> (analyze + (:scale (cps/make 2 [11 13 5 7]))) + (exact.utils/make-readable))))) diff --git a/test/erv/lattice/v2_test.cljs b/test/erv/lattice/v2_test.cljs new file mode 100644 index 0000000..b87f85f --- /dev/null +++ b/test/erv/lattice/v2_test.cljs @@ -0,0 +1,38 @@ +(ns erv.lattice.v2-test + (:require + [clojure.test :refer [deftest is]] + [erv.lattice.v2 :refer [base-coords ratios->lattice-data]] + [erv.utils.exact :as exact.utils])) + +(deftest ratios->lattice-data-test + (is (= '{:period "2", + :min-x 0, + :max-x 0, + :min-y 0, + :max-y 0, + :data + ({:ratio "3/2", + :numerator "3", + :denominator "2", + :numer-factors ["3"], + :denom-factors ["2"], + :coords {:x 0, :y 0}} + {:ratio "9/8", + :numerator "9", + :denominator "8", + :numer-factors ["3" "3"], + :denom-factors ["2" "2" "2"], + :coords {:x 0, :y 0}} + {:ratio "2", + :numerator "2", + :denominator "1", + :numer-factors ["2"], + :denom-factors [], + :coords {:x 0, :y 0}}), + :edges (({:x 0, :y 0} {:x 0, :y 0}) + ({:x 0, :y 0} {:x 0, :y 0}))} + + (->> "3/2 9/8 2/1" + exact.utils/parse-ratios + (ratios->lattice-data base-coords) + exact.utils/make-readable))))