Euterpea 2.0.2 → 2.0.3
raw patch · 145 files changed
+439/−211 lines, 145 filesdep ~heapbinary-addedPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: heap
API changes (from Hackage documentation)
Files
- .git/COMMIT_EDITMSG +0/−1
- .git/FETCH_HEAD +1/−1
- .git/ORIG_HEAD +1/−1
- .git/config +0/−1
- .git/hooks/pre-receive.sample +24/−0
- .git/hooks/update.sample +1/−1
- .git/index binary
- .git/logs/HEAD +2/−12
- .git/logs/refs/heads/master +2/−8
- .git/logs/refs/remotes/origin/HEAD +1/−1
- .git/logs/refs/remotes/origin/master +1/−7
- .git/objects/08/1fa266eebe94ad9d1401de24163f05d6d378b3 binary
- .git/objects/08/6557985556cc77f5a559d74d0502a2d761babd +0/−1
- .git/objects/08/cf131880f52ce6a6d1e8dd2f0afb8ee0f30bcf binary
- .git/objects/09/53e4337fcc90c2a5c2ec957f9d46b4aa32ca99 binary
- .git/objects/0c/262c7a72c9e17768fa826a90472e376c09767a binary
- .git/objects/0e/40a7618f3033863399b53fd837057d05c90d33 binary
- .git/objects/10/aedf5be4cd4cba55b64d5f6995b2848da5156b binary
- .git/objects/10/f805b915ea4190789c13856fc1c8e0003fd2aa binary
- .git/objects/13/ff9346b625728e873af12be8d41ba411d16d3a binary
- .git/objects/16/053d551af0017fc14257241ace886edd2afc02 binary
- .git/objects/18/2a0398ba6a070c251b036c128fb0d923b1b04a binary
- .git/objects/1b/963cb16e69a4719c07d01041419fc0016888e9 binary
- .git/objects/1d/865ab036c3dafe78300b3876f46e7fc91b1836 binary
- .git/objects/1e/e46d0b56ddaa82b9a081a21369c1509b84402e binary
- .git/objects/21/e4b87b26d4717ebc40733493992d3025b1d90e binary
- .git/objects/26/d1e77943b1fe7cb446dd7ac0fef7cc1c495845 +0/−6
- .git/objects/27/f49fd3429321b1ee2c1546eb63256dc0730c1f binary
- .git/objects/29/3bd1d6053e165fb63a1bc860ce65d28160759f too large to diff
- .git/objects/2c/230b496eb8d7ee15595fc8bb6e0893af19008b binary
- .git/objects/32/eee8d97247283b82f847ae18b0739f135577c6 +0/−1
- .git/objects/35/05de91ee4ca4ab2c00ab4d0e05daae68dde53b binary
- .git/objects/36/e8447f23fe4c9fc02644fae5965b7b9fe966fd binary
- .git/objects/37/3df86f01c69ae7dcc599f9c11e786415e4d1e6 binary
- .git/objects/38/b9dc35dfc14d5f4d529bdbdffe7a3ea8b12458 binary
- .git/objects/39/c0cba701ff610264b64dcb238120dc18c08501 binary
- .git/objects/3d/2457f12c9429a1f6df0d9005b8ee39c2b8515a binary
- .git/objects/3f/fdf51a212612f37f71d8456e980e3a9445dea9 binary
- .git/objects/40/0528d8d6e72ec2edc4fee57e208bc972fdc90f +0/−2
- .git/objects/40/d6be12def8fc357df4248d9943bd3cc1f75f89 +0/−1
- .git/objects/41/2224f723a4330a7004dc7c1eb1f62ddef2c206 binary
- .git/objects/42/4bc74689367dde97ffde93f82558e7dc6c0bf2 binary
- .git/objects/44/4f7257c52f2671cbe413f4bdcf8ca94ca23263 +5/−0
- .git/objects/46/6a9d4591dd2b033e07fa515b559afe6166f0a3 binary
- .git/objects/4a/fd6ed7d81c474bf5c88c395a584e4f68b039ce binary
- .git/objects/52/c3e648f28c37c75cbad302221d9d7e63b8b83e binary
- .git/objects/54/7dcd3ab2847acaaa664560053063485e9799af +0/−2
- .git/objects/55/ec0b6acfed4cda746ba489cbfaa0a78a12330e binary
- .git/objects/57/c5213c0484db428f6c4cf6e6214e89a8ae1495 binary
- .git/objects/5a/0a4abb675bb7edeeb1581ef36c3107d9ddabc0 binary
- .git/objects/5a/db5e8db2e5525db664ff2e17de8b87ff0f8670 binary
- .git/objects/60/492b4c521313c25cb4c46b57d0b72cc74d7cbd binary
- .git/objects/65/0d6db099811f2892c4dd0d24c34de9a8d07435 binary
- .git/objects/67/eab6e41d9b7d3b8cce4cfc4b5ad44649fba760 binary
- .git/objects/6b/cae40f355478a4d15eee9b66d161e7a0a12dd2 binary
- .git/objects/6e/b5f25f3191008705cf5d25da4253992c579f87 binary
- .git/objects/70/c3238c627e9fc1187b9828d5583a470e9ad056 binary
- .git/objects/79/3937ccb5e6531cc7ad87b1d34f4ef1a23a6e72 binary
- .git/objects/7a/5e865406dc8dc8d2bd730b7625656fc4c80036 binary
- .git/objects/7a/e36bd46d77905af4da2001aedd031cc773f6c2 binary
- .git/objects/80/c8d821ef0e1310670d9383a0164a161ef6d896 binary
- .git/objects/81/64db06d50157acee8946c245af5c82e93298f7 binary
- .git/objects/87/c4b67904dfc03dcabd7f4c0ec98fc4031f8531 +0/−2
- .git/objects/89/6621f361866ce787d0bad1dfd5434d371b5679 +0/−1
- .git/objects/8b/b417a4b89ec3ecea4151d2ac8e0de8499e1eda binary
- .git/objects/8b/c8ec9664745b51066a29467fdcc9ce217a69b9 binary
- .git/objects/8f/926490ec9977aaa657c03492a2092cd96ed879 binary
- .git/objects/90/f10f0ae98fa550ed4f3ba96b2726b2fa0f5948 binary
- .git/objects/92/93d3861772b24047a535829d74e8eeb2b46735 +1/−0
- .git/objects/95/0a22f91621d654a3fc02109d04e693efd601dd binary
- .git/objects/95/530bd9f8b33aa4403e449541aca8782fcf58d7 binary
- .git/objects/97/d0dbaf178d03770a923bdd241b477208b116bf binary
- .git/objects/98/2001952233e68b31aa67fd4fce281e33f0f1c6 binary
- .git/objects/98/70f262ba46f8c63923b4c9d373b5502d534bff binary
- .git/objects/9d/73156b57d5c16c87c54244219f4be0df7a4d9c binary
- .git/objects/9e/ad253d2b2018bf4a7fb68a0470bb225793668e binary
- .git/objects/a2/3d8e779deb983f7f4d0b32ada14d2249281d48 binary
- .git/objects/a3/69e39dd54db4cf0df66937ea62949fc00175e5 binary
- .git/objects/a3/6b03bd6d8dc290929d43a93ba9ff0609ffd6de binary
- .git/objects/a7/1c310b22d3873f19dbef3fd1847db262523a4a binary
- .git/objects/a9/232c0b57605a3ad8a06faad4de4c75fde6ba1a binary
- .git/objects/a9/54bb4608151754bc3ef6b10f354257fc23247e binary
- .git/objects/aa/6d02eafa38dd2e10940244f0951551753f3060 binary
- .git/objects/aa/72de41851f42d3f1023c01ae0edc5832c47ebd binary
- .git/objects/aa/81ebb1eb76e5ad15763b0d1d4706c5e4e9b390 binary
- .git/objects/aa/9e74daedbcb4fbae1920ed99ce78a3b122d07f binary
- .git/objects/ab/dbdc6ad3b69bcd244b80ee836215e015658a56 binary
- .git/objects/ad/f2673657fa321a214a529e015ee10b062af2f4 binary
- .git/objects/af/80f1983c7a72bae981b15def1d8cc05c9ec872 binary
- .git/objects/af/a69c96947b53fb4d8c3f13d2597f42f166b954 binary
- .git/objects/b1/127851608df2ad2af6966e3558157bf768a183 binary
- .git/objects/b1/3d346f7417c0e401862c9a2473787ff583afd1 binary
- .git/objects/b1/eb4e6bd0c330b7cbe720d59df11bf3105b2b85 binary
- .git/objects/b3/dac8fd881bc1fd8dfe20ab121cfa7344ace238 binary
- .git/objects/b6/af0dcb1cc69563bda463208ca520057e2e11b3 binary
- .git/objects/b7/d28caad2a4b8583bee99a026cabed13ed818d6 binary
- .git/objects/b8/724b568863051d8deac9e814dc00174f5281a9 binary
- .git/objects/bc/7e70f191403e2d3c97a09b11bcf2476f038a60 binary
- .git/objects/be/ff554d699578a58c434cccfd0b09b8be1b872d binary
- .git/objects/c4/2b297f598a10a933a3e8a3856946c737e25a90 binary
- .git/objects/c4/3ce9302dcd23cedd4cf553e09c685e528e587d binary
- .git/objects/c5/fc3c5cebdf7ad00414095d73e7f628cd20d4b4 binary
- .git/objects/c6/5ac01da8f5dfafd421c9c0deadc2dc429057e3 binary
- .git/objects/c8/5e4a4f82193fd374d9808476f8439ff61d79bc +0/−3
- .git/objects/c8/66ed10fcb19f4a2fb6382721b1d617638ba958 binary
- .git/objects/cc/07b21458a8fc31fe26df3ebb0b95a3fb3dabda binary
- .git/objects/cd/023c5127ad0de2f22259fb58b303ab534b75b4 binary
- .git/objects/cd/bcf48274b5ddfec1701e1002ccc9a53cfb5757 binary
- .git/objects/ce/7bb09495716bdbad1408f0d4969fdc39100142 binary
- .git/objects/cf/2cd659a50d600cb0315d2897a67e8b19635ac1 binary
- .git/objects/cf/df673ee21dc78ee19e2f2c4403ff9c81ffa1f4 binary
- .git/objects/d2/cf53ff238e7f97c925f41e114e7100ed259734 binary
- .git/objects/d3/9da1469f1f05215ea3284392a585730464e8de binary
- .git/objects/db/b2c0d96968285e950f91cef5c54071d6e1c387 binary
- .git/objects/df/9b55b1972552b0142c70dcd1ab0d1b80a6127d binary
- .git/objects/df/b22ee42cf26aa1d33211add3c6fdc1a8d117be binary
- .git/objects/e1/a838d747ab8dcfe9e50965ef0ca4bf7c7a3779 +0/−2
- .git/objects/e2/bdf760d4a28556c3c8bdcec92c5cb51c376e8a binary
- .git/objects/e4/61c0ab2477605902ab7a9c42969603f38bd586 +0/−2
- .git/objects/e5/c124001e0bd84a9c3f6f42ee520c39fcba32cc binary
- .git/objects/eb/2a0d24372b9029567ea502f851b922edc3f462 binary
- .git/objects/eb/4dfc2eece871c74eeab502a3943f85884afdad binary
- .git/objects/ed/8ca5964cb76d44755cdcb3ed22fd070676ebc7 binary
- .git/objects/ef/06402d714aa53dae0ba9c74b68d8b4ddea1329 +3/−0
- .git/objects/f0/716627a25c33e08df74920306b4fe6f446ac00 binary
- .git/objects/f2/2b65e167b5ea54b84305158d31368b9c10f0a7 binary
- .git/objects/f2/bba23a1c3bbf6ff9964d90731485399a69b8a9 binary
- .git/objects/f5/06729c57606b06dadd36d037346bbaf4cd92bf binary
- .git/objects/f5/602af44f15d195f373445ff4ea9525fe659cdc binary
- .git/objects/fd/70ab6de3c28ef42e6450fabade4e4bbdd5869e binary
- .git/objects/fd/7be762cd1a7bbca6b0eeec10ab260858db340d binary
- .git/objects/fd/f2171184f9e6cf6d1dad23f970d6fb284ede26 +0/−1
- .git/objects/fe/3a486f7a94b12d196d8e17a1fdb728e26dbff3 binary
- .git/objects/pack/pack-070189755adac644d6a1c3fd2d044e268ceae11e.idx binary
- .git/objects/pack/pack-070189755adac644d6a1c3fd2d044e268ceae11e.pack binary
- .git/objects/pack/pack-9b722e175e2c2aebda7ff55a097590198546bb01.idx binary
- .git/objects/pack/pack-9b722e175e2c2aebda7ff55a097590198546bb01.pack too large to diff
- .git/packed-refs +1/−1
- .git/refs/heads/master +1/−1
- .git/refs/remotes/origin/master +1/−1
- Euterpea.cabal +3/−2
- Euterpea/IO/Audio.hs +2/−2
- Euterpea/IO/MIDI/FromMidi2.lhs +249/−0
- Euterpea/IO/MIDI/MidiIO.lhs +138/−145
- ReadMe.txt +2/−2
@@ -1,1 +0,0 @@-Dependency versions narrowed again
@@ -1,1 +1,1 @@-e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779 branch 'master' of https://github.com/Euterpea/Euterpea2+ef06402d714aa53dae0ba9c74b68d8b4ddea1329 branch 'master' of https://github.com/Euterpea/Euterpea2
@@ -1,1 +1,1 @@-fd7be762cd1a7bbca6b0eeec10ab260858db340d+23b0fe619f7f91be4123ef976dd9284c11c1093a
@@ -5,7 +5,6 @@ logallrefupdates = true symlinks = false ignorecase = true- hideDotFiles = dotGitOnly [remote "origin"] url = https://github.com/Euterpea/Euterpea2.git fetch = +refs/heads/*:refs/remotes/origin/*
@@ -0,0 +1,24 @@+#!/bin/sh+#+# An example hook script to make use of push options.+# The example simply echoes all push options that start with 'echoback='+# and rejects all pushes when the "reject" push option is used.+#+# To enable this hook, rename this file to "pre-receive".++if test -n "$GIT_PUSH_OPTION_COUNT"+then+ i=0+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"+ do+ eval "value=\$GIT_PUSH_OPTION_$i"+ case "$value" in+ echoback=*)+ echo "echo from the pre-receive-hook: ${value#*=}" >&2+ ;;+ reject)+ exit 1+ esac+ i=$((i + 1))+ done+fi
@@ -1,6 +1,6 @@ #!/bin/sh #-# An example hook script to blocks unannotated tags from entering.+# An example hook script to block unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update".
binary file changed (2340 → 2436 bytes)
@@ -1,12 +1,2 @@-0000000000000000000000000000000000000000 61d09a71180e5a1a4ea1d893f82a4709d8e79c27 donya <donyavq@netscape.net> 1453085786 -0500 clone: from https://github.com/Euterpea/Euterpea2.git-61d09a71180e5a1a4ea1d893f82a4709d8e79c27 b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85 donya <donyavq@netscape.net> 1455231002 -0500 pull --progress --rebase --prune origin master: checkout b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85-b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85 b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85 donya <donyavq@netscape.net> 1455231002 -0500 rebase finished: returning to refs/heads/master-b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85 086557985556cc77f5a559d74d0502a2d761babd donya <donyavq@netscape.net> 1456848084 -0500 commit: ToMusic1 instance for AbsPitch added-086557985556cc77f5a559d74d0502a2d761babd e461c0ab2477605902ab7a9c42969603f38bd586 donya <donyavq@netscape.net> 1465925605 -0500 pull --progress --rebase --prune origin master: checkout e461c0ab2477605902ab7a9c42969603f38bd586-e461c0ab2477605902ab7a9c42969603f38bd586 e461c0ab2477605902ab7a9c42969603f38bd586 donya <donyavq@netscape.net> 1465925606 -0500 rebase finished: returning to refs/heads/master-e461c0ab2477605902ab7a9c42969603f38bd586 aa72de41851f42d3f1023c01ae0edc5832c47ebd donya <donyavq@netscape.net> 1465930431 -0500 commit: Dependency versions temporarily relaxed-aa72de41851f42d3f1023c01ae0edc5832c47ebd 40d6be12def8fc357df4248d9943bd3cc1f75f89 donya <donyavq@netscape.net> 1466564399 -0500 pull --progress --rebase --prune origin master: checkout 40d6be12def8fc357df4248d9943bd3cc1f75f89-40d6be12def8fc357df4248d9943bd3cc1f75f89 40d6be12def8fc357df4248d9943bd3cc1f75f89 donya <donyavq@netscape.net> 1466564400 -0500 rebase finished: returning to refs/heads/master-40d6be12def8fc357df4248d9943bd3cc1f75f89 fd7be762cd1a7bbca6b0eeec10ab260858db340d donya <donyavq@netscape.net> 1467320139 -0500 commit: Dependency versions narrowed again-fd7be762cd1a7bbca6b0eeec10ab260858db340d e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779 donya <donyavq@netscape.net> 1468034494 -0500 pull --progress --rebase --prune origin master: checkout e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779-e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779 e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779 donya <donyavq@netscape.net> 1468034494 -0500 rebase finished: returning to refs/heads/master+0000000000000000000000000000000000000000 23b0fe619f7f91be4123ef976dd9284c11c1093a Donya Quick <donyaquick@gmail.com> 1495035254 -0700 clone: from https://github.com/Euterpea/Euterpea2.git+23b0fe619f7f91be4123ef976dd9284c11c1093a ef06402d714aa53dae0ba9c74b68d8b4ddea1329 Donya Quick <donyaquick@gmail.com> 1496256684 -0500 pull --no-rebase --progress origin: Fast-forward
@@ -1,8 +1,2 @@-0000000000000000000000000000000000000000 61d09a71180e5a1a4ea1d893f82a4709d8e79c27 donya <donyavq@netscape.net> 1453085786 -0500 clone: from https://github.com/Euterpea/Euterpea2.git-61d09a71180e5a1a4ea1d893f82a4709d8e79c27 b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85 donya <donyavq@netscape.net> 1455231002 -0500 rebase finished: refs/heads/master onto b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85-b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85 086557985556cc77f5a559d74d0502a2d761babd donya <donyavq@netscape.net> 1456848084 -0500 commit: ToMusic1 instance for AbsPitch added-086557985556cc77f5a559d74d0502a2d761babd e461c0ab2477605902ab7a9c42969603f38bd586 donya <donyavq@netscape.net> 1465925606 -0500 rebase finished: refs/heads/master onto e461c0ab2477605902ab7a9c42969603f38bd586-e461c0ab2477605902ab7a9c42969603f38bd586 aa72de41851f42d3f1023c01ae0edc5832c47ebd donya <donyavq@netscape.net> 1465930431 -0500 commit: Dependency versions temporarily relaxed-aa72de41851f42d3f1023c01ae0edc5832c47ebd 40d6be12def8fc357df4248d9943bd3cc1f75f89 donya <donyavq@netscape.net> 1466564400 -0500 rebase finished: refs/heads/master onto 40d6be12def8fc357df4248d9943bd3cc1f75f89-40d6be12def8fc357df4248d9943bd3cc1f75f89 fd7be762cd1a7bbca6b0eeec10ab260858db340d donya <donyavq@netscape.net> 1467320139 -0500 commit: Dependency versions narrowed again-fd7be762cd1a7bbca6b0eeec10ab260858db340d e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779 donya <donyavq@netscape.net> 1468034494 -0500 rebase finished: refs/heads/master onto e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779+0000000000000000000000000000000000000000 23b0fe619f7f91be4123ef976dd9284c11c1093a Donya Quick <donyaquick@gmail.com> 1495035254 -0700 clone: from https://github.com/Euterpea/Euterpea2.git+23b0fe619f7f91be4123ef976dd9284c11c1093a ef06402d714aa53dae0ba9c74b68d8b4ddea1329 Donya Quick <donyaquick@gmail.com> 1496256684 -0500 pull --no-rebase --progress origin: Fast-forward
@@ -1,1 +1,1 @@-0000000000000000000000000000000000000000 61d09a71180e5a1a4ea1d893f82a4709d8e79c27 donya <donyavq@netscape.net> 1453085786 -0500 clone: from https://github.com/Euterpea/Euterpea2.git+0000000000000000000000000000000000000000 23b0fe619f7f91be4123ef976dd9284c11c1093a Donya Quick <donyaquick@gmail.com> 1495035254 -0700 clone: from https://github.com/Euterpea/Euterpea2.git
@@ -1,7 +1,1 @@-61d09a71180e5a1a4ea1d893f82a4709d8e79c27 b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85 donya <donyavq@netscape.net> 1455231001 -0500 pull --progress --rebase --prune origin master: fast-forward-b1eb4e6bd0c330b7cbe720d59df11bf3105b2b85 086557985556cc77f5a559d74d0502a2d761babd donya <donyavq@netscape.net> 1456848102 -0500 update by push-086557985556cc77f5a559d74d0502a2d761babd e461c0ab2477605902ab7a9c42969603f38bd586 donya <donyavq@netscape.net> 1465925602 -0500 pull --progress --rebase --prune origin master: fast-forward-e461c0ab2477605902ab7a9c42969603f38bd586 aa72de41851f42d3f1023c01ae0edc5832c47ebd donya <donyavq@netscape.net> 1465930440 -0500 update by push-aa72de41851f42d3f1023c01ae0edc5832c47ebd 40d6be12def8fc357df4248d9943bd3cc1f75f89 donya <donyavq@netscape.net> 1466564397 -0500 pull --progress --rebase --prune origin master: fast-forward-40d6be12def8fc357df4248d9943bd3cc1f75f89 fd7be762cd1a7bbca6b0eeec10ab260858db340d donya <donyavq@netscape.net> 1468028809 -0500 update by push-fd7be762cd1a7bbca6b0eeec10ab260858db340d e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779 donya <donyavq@netscape.net> 1468029425 -0500 fetch --prune --recurse-submodules=on-demand origin: fast-forward+23b0fe619f7f91be4123ef976dd9284c11c1093a ef06402d714aa53dae0ba9c74b68d8b4ddea1329 Donya Quick <donyaquick@gmail.com> 1496256683 -0500 fetch --progress --prune origin: fast-forward
binary file changed (168 → absent bytes)
@@ -1,1 +0,0 @@-xKÂ0Yû}P·q$49ÀH,¸»ÝV²Àa4·å¬^mJõd¹ÝæÖÓ¡¯ª0&Hc°Ö9åZ|µÔ¹${^µu`Rö¹ 8<ë`±±T"®0°åL~ôiY¡,í?ÃyçßwÓ¾I¾ëé _@>Ää&GFöw]?õÌuù}l³Ìmë¹B}Çx»Ì]&È¥h1/v(M£
binary file changed (1048 → absent bytes)
binary file changed (1548 → absent bytes)
binary file changed (233 → absent bytes)
binary file changed (155285 → absent bytes)
binary file changed (15321 → absent bytes)
binary file changed (244 → absent bytes)
binary file changed (10765 → absent bytes)
binary file changed (188 → absent bytes)
binary file changed (108 → absent bytes)
binary file changed (4844 → absent bytes)
binary file changed (579 → absent bytes)
binary file changed (158 → absent bytes)
binary file changed (244 → absent bytes)
@@ -1,6 +0,0 @@-xTQoÚ@Þóý-+OJ"J7:¡1i+tªZvÓ¦I½-]+\Xr)l¿¾vrPÄç³?Ûí³"Ó·çïÞäEZgn YVóimd¹|ø{e¥-Ýki>+=QeP¼Q±'Â'£ZndBGçD×*_E.×瘝Ðh&¬²©|U~×"SJ¦pQhSY4Ý$re0 tgOèÑÏ6OðàGQ8gdMõ³*KmÀÇðÜÜ2«SL"&Y^Í`4ü~¿]Í:nÈ"pBç:Þæ5pY Õ&Ø©ÆN÷^¿×û+Ío5:±ÛÈUË"ÊKSu¦îÁ¼ç?*¤óOi®4~V+âóo @ÅóDÄ"ã£z¬ ùÞêìý0\câź-Ë$<NûÑçn¤ÂAÔúá¥þõ·Nn×_òé©®îfÃåÆc¶ÆãÓ~eà¦4KÞļ²G¼4ÇØ±ëEK¸Ý$;±ÝthX«j;õà¹æí yø|N\JSZu@)µk~¤=VÝмMpN¥bû¿cõ.û¸ÂȶÑ{hÛ«p¿C¹m;»¸½-R©î±ÛÝKûLìöçS#ø$ÒJ©¿dÄ¡ÎvƧ»ð8÷èKn{é+ñ@
binary file changed (3699 → absent bytes)
file too large to diff
binary file changed (7054 → absent bytes)
@@ -1,1 +0,0 @@-x+)JMU°0g040031QH,É(w--I-*HMÔË(fP»ø¼Òyã¿-nw«üû~FÆ3ª891)1'>71¹(¿X/áö¦7334â¦òO<÷õ¨CáµÛÌJ(s
binary file changed (12169 → absent bytes)
binary file changed (28935 → absent bytes)
binary file changed (27398 → absent bytes)
binary file changed (245 → absent bytes)
binary file changed (244 → absent bytes)
binary file changed (6844 → absent bytes)
binary file changed (1174 → absent bytes)
@@ -1,2 +0,0 @@-x+)JMU03e040031QvÓËÈdØQäÖÌ*Ûûêä;âþA+-ò>i§>Lßú*d3«h¯¡Y÷Ël
@@ -1,1 +0,0 @@-xMNÃ0FYûs*ÿ%R @p0« RoOÚ]·ì¾·xÕesOÚD ëìØ#eáH)ö#Æá Æh1 è|VÌMbò,Ñ ËÑsÈÎú@Ö¡Xaêà)&Ùà®Úà³+Â×>Ñ7ù¿?oEt#\åtWp±ïC\Lðl;k ÝUþg\×ùúQ¶:o°Ô&Ðê¸ojÌ;³0 ó¤S-8á&ä#4VÐÀãMÞÝùß[hÀ
binary file changed (12653 → absent bytes)
binary file changed (18249 → absent bytes)
@@ -0,0 +1,5 @@+x¥½+Â0FówlA++:hÁ+Ω¹j IJ~,¾½ihÁ19'_òÝÔªa¹ZÌb®AØ9ºEe¶q+À̶ÔðKÅo¹fîU¨1éôl1æeä¦#Jz:Í-éc¼:(-ükSèî¨.Z¥íoùQ¿e¦yh5r ]y÷«¤ïiÖfÞ ÍMê7t(_åöRà\(gsî?x8ÏÀ|2ûiüû²_=LÂ
binary file changed (245 → absent bytes)
binary file changed (263 → absent bytes)
binary file changed (82 → absent bytes)
@@ -1,2 +0,0 @@-xA-Â0E]çs%iÒv".¼GÌL°HZSÁÛ[=»÷á}x\Æq¨ÐxÜÕEmZ$Ìì]Ö¦ì5%bK>'/ÌLNeéTzAÇ¡)·È~S[rÉúÈjh·²À¥Loë:ðò¯ÇyÒúdõ°Á \èt1Æö6Xkø×Xõ¿·¹^ÇêP&Xg¡ªb> ÿN©
binary file changed (298 → absent bytes)
binary file changed (1896 → absent bytes)
binary file changed (8803 → absent bytes)
binary file changed (356980 → absent bytes)
binary file changed (136 → absent bytes)
binary file changed (244 → absent bytes)
binary file changed (242 → absent bytes)
binary file changed (1133 → absent bytes)
binary file changed (136 → absent bytes)
binary file changed (6697 → absent bytes)
binary file changed (2445 → absent bytes)
binary file changed (200 → absent bytes)
binary file changed (82 → absent bytes)
binary file changed (6112 → absent bytes)
binary file changed (2953 → absent bytes)
@@ -1,2 +0,0 @@-x+)JMU022e040031QpJ,ÎLÎLw+Í+ÖËÉ(fh|W°Æ}Ê7ÕÏ=SÄ÷püpYm±PQP_Î ÇsçîTk½eÏ »ó½¿®A9çæ¥Ú0AèÃgk %g÷Ú:iºö}{¨*O1æg¶>9¾¶}ãe¿¬ò -RóRR@ÔTµÎw±ÜØÙUÊUêÛ]}äþ]¨¢ÊT°{ÎnÌòyxwK~WºêLµS²^Î
@@ -1,1 +0,0 @@-x;NADç}¬Þùd!$ÀGèínËíe=áöØNÈ*¨<©Ûé´ï`S~è³*ÄeÄRò0lm.½õì¼h¡,¼f¢YkDAs£p¾Å)ÚCܲç袡¥ïÚ¯þ|,{>ÀZnðõù\µ&]]Ë>ÎÑ#¾oìú?Û¼}Omîg c^vT«ßiª¸Y*\OAmл£²2¿Y¨
binary file changed (absent → 7569 bytes)
binary file changed (1130 → absent bytes)
binary file changed (36212 → absent bytes)
binary file changed (79 → absent bytes)
@@ -0,0 +1,1 @@+x+)JMU06´d040031Qp(È/*ñÍLÉtËÌIÕËÉ(f[ñ«Ùä¶|>yâw}éÖKæPÕnEù¹ µ`uJ³3å÷·ªÝñsxÛý1ÍI¾*4uF` ÓýÕ?³E¯Üüywñ¬#IÛVï*tOÍK-JÌ ´ú¾ qYjcY©Õ²9;ô>vPªôu-KÍ+~TÑ¥å¶SÏ·g~+ÖL ©:ÎÓ¬ª{øó¿yåxiMïÏyr· ªr+ÁjËr+]n·¼ýÞþbKí¦¤ +/¨|¸GsgØý1þu>qþû;Yôó¥Âvà¨
binary file changed (absent → 82 bytes)
binary file changed (107426 → absent bytes)
binary file changed (1290 → absent bytes)
binary file changed (244 → absent bytes)
binary file changed (73 → absent bytes)
binary file changed (106 → absent bytes)
binary file changed (41122 → absent bytes)
binary file changed (1131 → absent bytes)
binary file changed (82 → absent bytes)
binary file changed (240 → absent bytes)
binary file changed (4678 → absent bytes)
binary file changed (4984 → absent bytes)
binary file changed (137 → absent bytes)
binary file changed (2597 → absent bytes)
binary file changed (255 → absent bytes)
binary file changed (absent → 1137 bytes)
binary file changed (244 → absent bytes)
binary file changed (8294 → absent bytes)
binary file changed (82 → absent bytes)
binary file changed (82 → absent bytes)
binary file changed (1971 → absent bytes)
binary file changed (21353 → absent bytes)
binary file changed (482058 → absent bytes)
binary file changed (223 → absent bytes)
binary file changed (11887 → absent bytes)
binary file changed (245 → absent bytes)
binary file changed (244 → absent bytes)
binary file changed (6690 → absent bytes)
binary file changed (664 → absent bytes)
binary file changed (270 → absent bytes)
binary file changed (1309 → absent bytes)
binary file changed (1244 → absent bytes)
binary file changed (5132 → absent bytes)
binary file changed (168 → absent bytes)
@@ -1,3 +0,0 @@-x¥½-Â0F]§¸c ÒMÐÁAÁB-XÁ95W 4IÉÅ·7m©(R2¸ /÷KªZU°X®fB1W#ìEÝ Mó"Ý8Æa-¦[jø¥ä·ÌIxjH:=9y¸é¡¤VsgúHH4.J?êÚ;j$FiûÛü«\À2Ó¼¯4ób E'«¤ïiÖÐ?xb¹ßÐ}ó2ó´zÎ%r6ãþwóÞ¡ÝsüüÑ j$Â,
binary file changed (9292 → absent bytes)
binary file changed (244 → absent bytes)
binary file changed (4551 → absent bytes)
binary file changed (absent → 244 bytes)
binary file changed (244 → absent bytes)
binary file changed (6864 → absent bytes)
binary file changed (262 → absent bytes)
binary file changed (1055 → absent bytes)
binary file changed (1129 → absent bytes)
binary file changed (958 → absent bytes)
binary file changed (244 → absent bytes)
binary file changed (13625 → absent bytes)
@@ -1,2 +0,0 @@-xQ-Â0DýÎ)öÊv7¦]ñÃx$»bÓÚ¦··zy&G_<oêdÔO](¨oÖRöØ2{aRFÚ§FÍq²R!rHÈIvIPHÔsNQ®W¸æ:K½ òpYú|~áõ<«s£íÖrÆIlqèòïcµÿlw¶ÑZÉoxÙ4÷CeÔXmvìÙN~
binary file changed (3615 → absent bytes)
@@ -1,2 +0,0 @@-xÎM-Â0a×9Å\@Ió;½GdF,Ò¤ÖTðöVàîû¼¹NÓØÀXܵERd ¹mé{Ò&dJÂÆ9¨) ¼ÙR2è"mBp>híÖ¡>nþªhm·ºÀPËà²ùGþ×ã\¤=3ÍrØÆ :§½vZ«üklòVCÍë´µRkufjÂêDM\
binary file changed (23186 → absent bytes)
binary file changed (4005 → absent bytes)
binary file changed (28085 → absent bytes)
binary file changed (356 → absent bytes)
@@ -0,0 +1,3 @@+x±NÄ0D©ý[BAd'qKAÅ/X¯×$RbÛÓý=ÉÁPÍJ3³ó(.ËT éä]IÌ@ÎoºovÎ3©^*VRÖDdP7äîu/VL+Ô;e|ï²ÜªºaoúÎ9S-)EJ˸®O7Ù¾_L¸rµÏ Z£»Fk9À£ÔR+ºÑþoO¼1®°qÊS@#/v§ÓAþçfÆ#âpã Üÿv2§ÉMïÕ<fÀà¡Å¯8?TðY^¡ =ÆÒ÷¡%nû3â¯g7ÅjÌøKÊxR
binary file changed (1278 → absent bytes)
binary file changed (7384 → absent bytes)
binary file changed (15403 → absent bytes)
binary file changed (absent → 136 bytes)
binary file changed (423 → absent bytes)
binary file changed (21394 → absent bytes)
binary file changed (172 → absent bytes)
@@ -1,1 +0,0 @@-x[J1EýÎ*jG§ ¸¤ª262éNöîýpþÝÃáÀ¥õz]9<.6RN3êR¼Fã]ö)´A&ÃZµÔ¥pÈ>6,%²ÓÌÅ8Fg3["]橨Ò>Þ×çµ Þö >ààöùRelîã´ó1ºhà'DE¿ü¯VgiRY*p¾-k ½q¢Ôë1d}©?ÕS½ÈWK÷OêÒÙ[f
binary file changed (59 → absent bytes)
binary file changed (4488 → absent bytes)
binary file changed (76560 → absent bytes)
binary file changed (absent → 8212 bytes)
file too large to diff
@@ -1,2 +1,2 @@ # pack-refs with: peeled fully-peeled -61d09a71180e5a1a4ea1d893f82a4709d8e79c27 refs/remotes/origin/master+23b0fe619f7f91be4123ef976dd9284c11c1093a refs/remotes/origin/master
@@ -1,1 +1,1 @@-e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779+ef06402d714aa53dae0ba9c74b68d8b4ddea1329
@@ -1,1 +1,1 @@-e1a838d747ab8dcfe9e50965ef0ca4bf7c7a3779+ef06402d714aa53dae0ba9c74b68d8b4ddea1329
@@ -1,5 +1,5 @@ name: Euterpea -version: 2.0.2 +version: 2.0.3 Cabal-Version: >= 1.8 license: BSD3 license-file: License @@ -49,6 +49,7 @@ Euterpea.IO.MIDI.MEvent, Euterpea.IO.MIDI.MidiIO, Euterpea.IO.MIDI.FromMidi, + Euterpea.IO.MIDI.FromMidi2, Euterpea.IO.MIDI.GeneralMidi, Euterpea.IO.MIDI.ToMidi, Euterpea.IO.MIDI.Play, @@ -66,5 +67,5 @@ stm==2.4.2, containers>=0.5.5.1 && <=0.5.7.1, bytestring>=0.10.4.0 && <= 0.10.9, - heap == 0.6.0, + heap >= 1.0 && < 2.0, ghc-prim
@@ -5,7 +5,7 @@ module Euterpea.IO.Audio.IO, module Euterpea.IO.Audio.Render, writeWav, - writeWavNorm + writeWavNorm ) where import Euterpea.IO.Audio.BasicSigFuns @@ -17,7 +17,7 @@ writeWav fname iMap m = let (d,s) = renderSF m iMap in outFile fname d s - + writeWavNorm fname iMap m = let (d,s) = renderSF m iMap in outFileNorm fname d s
@@ -0,0 +1,249 @@+FromMidi2: an alternative Midi-to-Music conversion algorithm. +Author: Donya Quick +Last modified: 28-Dec-2016 + +The goal of this module is to provide a more intelligent +parse from MIDI files to Music structures. The fromMidi +function will convert Midi into Music, but the resulting +structure is one big parallel composition with no other +relationships between the notes. The fromMidi2 function +here is an attempt to provide a parse of musical features +that is more in line with how a human might write them +in Euterpea or perceive them by ear. It works best on +MIDI files that are very close to paper score in terms +of how the events are structured. The functions here are +not intended for use with "messy" MIDI files that have +been recorded from a live performance without quantization. + +You can use fromMidi2 as an alternative to fromMidi to +parse a Midi value into a Music value with a better +method of grouping events together. The same algorithm +can be applied directly to a Music value with the +restructure function. + +Examples of how to use fromMidi2 and restructure: + +testMidi file = do + x <- importFile file + case x of Left err -> error err + Right m -> do + let v = fromMidi2 m + putStrLn $ show v + play v + +myMusic :: Music (Pitch, Volume) +myMusic = ... +newMusic :: Music (Pitch, Volume) +newMusic = restructure myMusic + +Restructuring is done from the MEvent level. Importantly, +this means that there are no tempo changes or other Modify +nodes in the resulting Music value! A global tempo of +120BPM is assumed. If your MIDI file has a different BPM, +you can use fromMidi in combination with restructure and +then apply a tempo modifier afterwards. + +The method for organizing events is: +(1) Identify and group chords where every note + has the same start time and duration. +(2) Identify and group sequential patterns where items + are back-to-back. Note that this may include a mix of + single notes and chords from step 1. +(3) Greedily group any patterns with gaps between + them into a sequence with rests. + + +> module Euterpea.IO.MIDI.FromMidi2 (fromMidi2, restructure, Chunk, chunkEvents, chunkToMusic)where +> import Euterpea.Music hiding (E) +> import Euterpea.IO.MIDI.ToMidi +> import Euterpea.IO.MIDI.GeneralMidi +> import Euterpea.IO.MIDI.MEvent +> import Euterpea.IO.MIDI.FromMidi +> import Data.List +> import Codec.Midi + +The primary exported functions for this module are: + +> fromMidi2 :: Midi -> Music (Pitch, Volume) +> fromMidi2 = restructure . fromMidi + +> restructure :: (ToMusic1 a) => Music a -> Music (Pitch, Volume) +> restructure = parseFeaturesI + +Other exported features are related to the Chunk datatype. + +A Chunk is the data structure used to group events by the algorithm +described at the top of this file. Par and Chord correspond to features +that will be composed in parallel (:=:) at different levels, and Seq +corresponds to features that will be composed in sequence (:+:). E is +a wrapper for single events and R is a rest place-holder. + +> type Onset = Dur -- to clarify some type signatures + +> data Chunk = Par [Chunk] | Seq [Chunk] | Chord [Chunk] | E MEvent | R Onset Dur +> deriving Eq + +Initially, each MEvent is placed in its own chunk. + +> initChunk :: [MEvent] -> [Chunk] +> initChunk mevs = +> let mevs' = sortBy sortFun mevs +> in map E mevs' + +The chunkChord function looks for chunks that share the same +onset and duration and places them together in Chord chunks. + +> chunkChord :: [Chunk] -> [Chunk] +> chunkChord [] = [] +> chunkChord (c:cs) = +> let cChord = filter (chordWith c) cs +> notInChord = filter (\v -> not $ elem v cChord) cs +> in if null cChord then c : chunkChord cs +> else Chord (c:cChord) : chunkChord notInChord + +> chordWith :: Chunk -> Chunk -> Bool +> chordWith c0 c = chunkOnset c == chunkOnset c0 && chunkDur c == chunkDur c0 + +The chunkMel function looks for sequences of chunks (which need +not be adjacent in the input list) where the end time of one chunk +is equal to the start time of the next chunk. There are no gaps +permitted, so notes separated by rests will not be grouped here. + +> chunkMel :: [Chunk] -> [Chunk] +> chunkMel [] = [] +> chunkMel x@(c:cs) = +> let cMel = buildMelFrom (chunkOnset c) x -- get ALL possible melody elements +> notInMel = filter (\v -> not $ elem v cMel) x +> in if null cMel then c : chunkMel cs +> else Seq cMel : chunkMel notInMel + +> buildMelFrom :: Onset -> [Chunk] -> [Chunk] +> buildMelFrom t [] = [] +> buildMelFrom t (c:cs) = +> if chunkOnset c == t then c : buildMelFrom (t + chunkDur c) cs +> else buildMelFrom t cs + +The chunkSeqs function is more general and will look for anything +that can be grouped together linearly in time, even if it requires +inserting a rest. This will group together all non-overlapping +chunks in a greedy fashion. + +> chunkSeqs :: [Chunk] -> [Chunk] +> chunkSeqs [] = [] +> chunkSeqs x@(c:cs) = +> let s = seqWithRests (chunkOnset c) x +> notInS = filter (\v -> not $ elem v s) x +> in if s == [c] then c : chunkSeqs cs +> else Seq s : chunkSeqs notInS + +> seqWithRests :: Onset -> [Chunk] -> [Chunk] +> seqWithRests t [] = [] +> seqWithRests t x@(c:cs) = +> let tc = chunkOnset c +> dt = tc - t +> in if dt == 0 then c : seqWithRests (tc + chunkDur c) cs +> else if dt > 0 then R t dt : c : seqWithRests (tc + chunkDur c) cs +> else seqWithRests t cs + +Finally, chunkEvents combines all of these methods in a particular +order that establishes preference for chords first, then melodies +(which may include chords), and then sequences including rests. +Anything left over will be handled by an outer Par. + +> chunkEvents :: [MEvent] -> Chunk +> chunkEvents = Par . chunkSeqs . chunkMel . chunkChord. initChunk + +Chunks can be converted directly to Music. Durations have to be +divided in half because MEvents deal with seconds, while Music +deals with duration as whole notes (1 whole note = 2 seconds). + +> chunkToMusic :: Chunk -> Music (Pitch, Volume) +> chunkToMusic (E e) = note (eDur e / 2) (pitch $ ePitch e, eVol e) +> chunkToMusic (R o d) = rest (d/2) +> chunkToMusic (Seq x) = line(map chunkToMusic x) +> chunkToMusic (Chord x) = chord(map chunkToMusic x) +> chunkToMusic (Par x) = chord $ map (\v -> rest (chunkOnset v / 2) :+: chunkToMusic v) x + +The parseFeatures function will take an existing Music value, such +as one returned by fromMidi, and use the algorithms above to identify +musical features (chords and melodies) and construct a new Music +tree that is performance-equivalent to the original. + +> parseFeatures :: (ToMusic1 a) => Music a -> Music (Pitch, Volume) +> parseFeatures = removeZeros . chunkToMusic . chunkEvents . perform + +> parseFeaturesI :: (ToMusic1 a) => Music a -> Music (Pitch, Volume) +> parseFeaturesI m = +> let mevs = perform m +> (iList, mevsI) = unzip $ splitByInst mevs +> parsesI = map (removeZeros . chunkToMusic . chunkEvents) mevsI +> in chord $ zipWith instrument iList parsesI + +================ + +Utility Functions and Type Class Instances + +First, some functions to pretty-up printing of things for debugging purposes + +> doubleShow :: Rational -> String +> doubleShow x = show (fromRational x :: Double) + +> pcShow :: AbsPitch -> String +> pcShow = show . fst . pitch + +> listShow, listShowN :: (Show a) => [a] -> String +> listShow x = "["++(concat $ intersperse ", " $ map show x)++"]" +> listShowN x = "[\n "++(concat $ intersperse ",\n " $ map show x)++"\n]" + +> listShowX :: (Show a) => Int -> [a] -> String +> listShowX i x = let v = concat (take i (repeat " ")) in +> "[\n"++v++(concat $ intersperse (",\n"++v) $ map show x)++"\n"++v++"]" + +> instance Show Chunk where +> show (E e) = "E "++doubleShow (eTime e)++" "++pcShow (ePitch e)++" "++doubleShow (eDur e) +> show s@(Seq x) = "S "++doubleShow (chunkOnset s)++" "++listShowX 4 x +> show c@(Chord x) = "C "++doubleShow (chunkOnset c)++" "++listShowX 6 x +> show p@(Par x) = "P "++doubleShow (chunkOnset p)++" "++listShowX 2 x +> show (R o d) = "R "++doubleShow o++" "++doubleShow d + +An Ord instance for Chunk that enforces sorting based on onset time. No +other features are considered. + +> instance Ord Chunk where +> compare x1 x2 = compare (chunkOnset x1) (chunkOnset x2) + +Functions to determine the start time (onset) and duration of a Chunk. + +> chunkOnset :: Chunk -> Onset +> chunkOnset (Seq x) = if null x then error "Empty Seq!" else chunkOnset (head x) +> chunkOnset (Chord x) = if null x then error "Empty Chord!" else chunkOnset (head x) +> chunkOnset (Par x) = if null x then 0 else minimum $ map chunkOnset x +> chunkOnset (E e) = eTime e +> chunkOnset (R o d) = o + +> chunkEnd :: Chunk -> Onset +> chunkEnd (Seq x) = if null x then error "Empty Seq!" else chunkEnd (last x) +> chunkEnd (Chord x) = if null x then error "Empty Chord!" else chunkEnd (head x) +> chunkEnd (Par x) = if null x then 0 else maximum $ map chunkEnd x +> chunkEnd (E e) = eTime e + eDur e +> chunkEnd (R o d) = o + d + +> chunkDur :: Chunk -> Dur +> chunkDur (Seq x) = if null x then error "Empty Seq!" else sum $ map chunkDur x +> chunkDur (Chord x) = if null x then error "Empty Chord!" else chunkDur (head x) +> chunkDur c@(Par x) = if null x then 0 else +> let o = chunkOnset c +> e = chunkEnd c +> in e-o +> chunkDur (E e) = eDur e +> chunkDur (R o d) = d + +Special sorting function for MEvents. + +> sortFun :: MEvent -> MEvent -> Ordering +> sortFun e1 e2 = +> if eTime e1 == eTime e2 then compare (ePitch e1) (ePitch e2) +> else compare (eTime e1) (eTime e2) + + +
@@ -1,4 +1,3 @@- > {-# LANGUAGE GeneralizedNewtypeDeriving #-} > module Euterpea.IO.MIDI.MidiIO ( > getAllDevices, --isValidInputDevice, isValidOutputDevice, -- Used only by Euterpea.IO.MUI.MidiWidgets @@ -6,22 +5,22 @@ > outputMidi, deliverMidiEvent, -- Used only by Euterpea.IO.MUI.MidiWidgets (particularly by midiOut) > pollMidi, -- Used only by Euterpea.IO.MUI.MidiWidgets (particularly by midiIn) > defaultOutput, defaultInput, -> playMidi, -> MidiMessage (ANote, Std), +> playMidi, +> MidiMessage (ANote, Std), > getTimeNow, > DeviceInfo(..), InputDeviceID, OutputDeviceID, Message(..), Time, > unsafeInputID, unsafeOutputID, > ) where -> import Codec.Midi (Time, Channel, Key, Velocity, -> Message (..), Midi (..), Track, +> import Codec.Midi (Time, Channel, Key, Velocity, +> Message (..), Midi (..), Track, > toRealTime, toAbsTime, toSingleTrack, isTrackEnd) -> import Sound.PortMidi (DeviceInfo (..), getDeviceInfo, -> DeviceID, countDevices, time, -> getDefaultOutputDeviceID, getDefaultInputDeviceID, -> openInput, openOutput, readEvents, -> close, writeShort, getErrorText, terminate, initialize, -> PMError (NoError, BufferOverflow), PMStream, +> import Sound.PortMidi (DeviceInfo (..), getDeviceInfo, +> DeviceID, countDevices, time, +> getDefaultOutputDeviceID, getDefaultInputDeviceID, +> openInput, openOutput, readEvents, +> close, writeShort, getErrorText, terminate, initialize, +> PMError (NoError, BufferOverflow), PMStream, > PMEvent (..), PMMsg (PMMsg)) > import Control.Exception (finally) > import Control.Concurrent @@ -40,7 +39,7 @@ ---------------------------- - | Midi Type declarations | + | Midi Type declarations | ---------------------------- > type MidiEvent = (Time, MidiMessage) @@ -62,20 +61,20 @@ > unsafeOutputID = OutputDeviceID ---------- - | Time | + | Time | ---------- -Is this the time we want? This comes from PortMidi, but there's also the +Is this the time we want? This comes from PortMidi, but there's also the function FRP.UISF.SOE.timeGetTime which uses time data from GLFW. -> getTimeNow :: IO Time +> getTimeNow :: IO Time > getTimeNow = do > t <- time > return (fromIntegral t / 1000) ---------------------- - | Device Functions | + | Device Functions | ---------------------- getAllDevices returns a list of all of the DeviceInfos found. @@ -86,11 +85,11 @@ > n <- countDevices > deviceInfos <- mapM getDeviceInfo [0..n-1] > let devs = zip [0..n-1] deviceInfos -> return ([ (InputDeviceID d, i) | (d,i) <- devs, input i], +> return ([ (InputDeviceID d, i) | (d,i) <- devs, input i], > [ (OutputDeviceID d, i) | (d,i) <- devs, output i]) -isValidInputDevice and isValideOutputDevice check whether the given +isValidInputDevice and isValideOutputDevice check whether the given devices are respectively valid for input or output. isValidInputDevice, isValidOutputDevice :: DeviceID -> IO Bool @@ -98,21 +97,21 @@ isValidOutputDevice = isValidDevice output isValidDevice :: (DeviceInfo -> Bool) -> DeviceID -> IO Bool isValidDevice pred i = do - n <- countDevices + n <- countDevices info <- getAllDevices - return $ + return $ i >= 0 && i < n && pred (snd $ info !! i) --------------------- - | Default devices | + | Default devices | --------------------- -Rather than export the deviceIDs directly, these two functions allow +Rather than export the deviceIDs directly, these two functions allow the caller to use the DeviceID without directly controlling it. -They take a function (such as playMidi) and an auxiary argument and -apply them together with the default device. If no default device +They take a function (such as playMidi) and an auxiary argument and +apply them together with the default device. If no default device exists, an error is thrown. > defaultOutput :: (OutputDeviceID -> a -> IO b) -> a -> IO b @@ -121,7 +120,7 @@ > case i of > Nothing -> error "No MIDI output device found" > Just i -> f (OutputDeviceID i) a -> +> > defaultInput :: (InputDeviceID -> a -> IO b) -> a -> IO b > defaultInput f a = do > i <- getDefaultInputDeviceID @@ -131,10 +130,10 @@ ----------------------- - | Priority Channels | + | Priority Channels | ----------------------- -The priority channel data type and a constructor for it will be used +The priority channel data type and a constructor for it will be used by devices. We define them here. > data PrioChannel a b = PrioChannel @@ -150,41 +149,39 @@ > push a b = modifyIORef heapRef (Heap.insert (a,b)) > pop = do > h <- get -> let (a, h') = Heap.extractHead h +> let Just (a, h') = Heap.view h > modifyIORef heapRef (\_ -> h') > return a > peek = do > h <- get -> if Heap.isEmpty h -> then return Nothing -> else return $ Just $ Heap.head h -> +> return $ Heap.viewHead h +> > return $ PrioChannel get push pop peek ------------------------ - | Global Device Data | + | Global Device Data | ------------------------ We keep a mapping from DeviceID to the priority channel for keeping -track of future MIDI messages, an output function to produce sound, -and a stop function. This mapping is stored in the global ref -outDevMap, and it is accessed by getOutDev (which looks up info -and adds associations if necessary) and terminateMidi (which calls +track of future MIDI messages, an output function to produce sound, +and a stop function. This mapping is stored in the global ref +outDevMap, and it is accessed by getOutDev (which looks up info +and adds associations if necessary) and terminateMidi (which calls the stop function on all elements and clears the mapping). outDevMap is the global mapping. -> outDevMap :: IORef [(OutputDeviceID, +> outDevMap :: IORef [(OutputDeviceID, > (PrioChannel Time Message, -- priority channel > (Time, Message) -> IO (), -- sound output function > IO ()))] -- stop/terminate function > outDevMap = unsafePerformIO $ newIORef [] -outPort and inPort are global memory refs that contain a mapping of -DeviceID to Port Midi Streams. They are modified with addPort (which -adds a new mapping to the list) and lookupPort (which, given a DeviceID, +outPort and inPort are global memory refs that contain a mapping of +DeviceID to Port Midi Streams. They are modified with addPort (which +adds a new mapping to the list) and lookupPort (which, given a DeviceID, returns the Port Midi Stream associated with it). > outPort :: IORef [(OutputDeviceID, PMStream)] @@ -200,7 +197,7 @@ -------------------------------------------------- - | Global Device Initialization and Termination | + | Global Device Initialization and Termination | -------------------------------------------------- initializeMidi just initializes PortMidi @@ -208,11 +205,11 @@ > initializeMidi :: IO () > initializeMidi = do > e <- initialize -> if e == NoError -> then return () +> if e == NoError +> then return () > else reportError "initializeMidi" e -terminateMidi calls the stop function on all elements of outDevMap +terminateMidi calls the stop function on all elements of outDevMap and clears the mapping entirely. It also clears outPort and inPort. > terminateMidi :: IO () @@ -226,10 +223,10 @@ ------------------- - | Device Lookup | + | Device Lookup | ------------------- -getOutDev looks up info in outDevMap and adds associations if necessary. +getOutDev looks up info in outDevMap and adds associations if necessary. It is accessed as a helper function for outputMidi and deliverMidiEvent. > getOutDev :: OutputDeviceID -> IO (PrioChannel Time Message, (Time, Message) -> IO (), IO ()) @@ -247,31 +244,31 @@ ---------------- - | Midi Input | + | Midi Input | ---------------- -pollMidi take an input device and a callback function and polls the device -for midi events. Any events are sent, along with the current time, to +pollMidi take an input device and a callback function and polls the device +for midi events. Any events are sent, along with the current time, to the callback function. DWC NOTE: Why is the time even used? All messages get the same time? > pollMidiCB :: InputDeviceID -> ((Time, [Message]) -> IO ()) -> IO () > pollMidiCB idid@(InputDeviceID devId) callback = do -> s <- lookupPort inPort idid +> s <- lookupPort inPort idid > case s of > Nothing -> do -> r <- openInput devId +> r <- openInput devId > case r of > Right e -> reportError "pollMidiCB" e > Left s -> addPort inPort (idid, s) >> input s -> Just s -> input s +> Just s -> input s > where > input :: PMStream -> IO () > input s = do > e <- readEvents s > case e of -> Right e -> if e == NoError -> then return () +> Right e -> if e == NoError +> then return () > else reportError "pollMidiCB" e > Left l -> do > now <- getTimeNow @@ -281,20 +278,20 @@ > pollMidi :: InputDeviceID -> IO (Maybe (Time, [Message])) > pollMidi idid@(InputDeviceID devId) = do -> s <- lookupPort inPort idid +> s <- lookupPort inPort idid > case s of > Nothing -> do -> r <- openInput devId +> r <- openInput devId > case r of > Right e -> reportError "pollMIDI" e >> return Nothing > Left s -> addPort inPort (idid, s) >> input s -> Just s -> input s +> Just s -> input s > where > input :: PMStream -> IO (Maybe (Time, [Message])) > input s = do > e <- readEvents s > case e of -> Right e -> if e == NoError +> Right e -> if e == NoError > then return Nothing > else reportError "pollMIDI" e >> return Nothing > Left l -> do @@ -305,19 +302,19 @@ --------------------------------------------- - | Midi Output for inidividual Midi events | + | Midi Output for inidividual Midi events | --------------------------------------------- -The following two functions are for sending and playing individual -Midi events to devices. Typically, usage will be to call outputMidi -to play anything that's ready to play and then send in the latest -messages with deliverMidiEvent. Of course, if no new messages are -ready to be delivered, that step can be omitted. Either way, -outputMidi should be called many times per second to assure that +The following two functions are for sending and playing individual +Midi events to devices. Typically, usage will be to call outputMidi +to play anything that's ready to play and then send in the latest +messages with deliverMidiEvent. Of course, if no new messages are +ready to be delivered, that step can be omitted. Either way, +outputMidi should be called many times per second to assure that all Midi messages are played approximately when scheduled. -deliverMidiEvent sends the given MidiEvent to the given device. If -the event is scheduled to happen ``now'', then it is immediately +deliverMidiEvent sends the given MidiEvent to the given device. If +the event is scheduled to happen ``now'', then it is immediately played. Otherwise, it is queued for later. > deliverMidiEvent :: OutputDeviceID -> MidiEvent -> IO () @@ -326,9 +323,9 @@ > now <- getTimeNow > let deliver t m = do > if t == 0 -> then out (now,m) +> then out (now,m) > else push pChan (now+t) m -> +> > case m of > Std m -> deliver t m > ANote c k v d -> do @@ -336,7 +333,7 @@ > deliver (t+d) (NoteOff c k v) -outputMidi plays all midi events that are waiting in this device's +outputMidi plays all midi events that are waiting in this device's priority queue whose time to play has come. > outputMidi :: OutputDeviceID -> IO () @@ -348,7 +345,7 @@ > Nothing -> return () > Just (t,m) -> do > now <- getTimeNow -> if t <= now +> if t <= now > then out (now, m) >> pop pChan >> loop > else return () > loop @@ -356,13 +353,13 @@ ------------------------------------------- - | Midi Output for a complete Midi track | + | Midi Output for a complete Midi track | ------------------------------------------- -When an entire Midi track is ready to be played, the playMidi function +When an entire Midi track is ready to be played, the playMidi function may be more appropriate than deliverMidiEvent and outputMidi. -playMidi will queue up the entire Midi track given to it and then close +playMidi will queue up the entire Midi track given to it and then close the output device. > playMidi :: OutputDeviceID -> Midi -> IO () @@ -372,44 +369,44 @@ > case out of > Nothing -> return () > Just (out, stop) -> do -> t0 <- getTimeNow +> t0 <- getTimeNow > finally (playTrack t0 0 out track) stop > where > playTrack t0 t' out [] = out (t0 + t', TrackEnd) > playTrack t0 t' out (e@(t, m) : s) = do -> out (t0 + t, m) -> if isTrackEnd m +> out (t0 + t, m) +> if isTrackEnd m > then return () > else playTrack t0 t out s --------------------- - | midiOutRealTime | + | midiOutRealTime | --------------------- -The following two functions are used to open a device for Midi output. -They should only be called when the device hasn't yet been opened, and +The following two functions are used to open a device for Midi output. +They should only be called when the device hasn't yet been opened, and they both return a ``play'' function and a ``stop'' function. -Currently, midiOutRealTime' is used for Midi output for inidividual -Midi events, and midiOutRealTime is used for Midi output for a complete +Currently, midiOutRealTime' is used for Midi output for inidividual +Midi events, and midiOutRealTime is used for Midi output for a complete Midi track. DWC Notes: -I'm not entirely sure how they both work yet. midiOutRealTime' -actually looks pretty straightforward in that it just creates the process -and stop functions and adds this device to the outPort device list. The -process function will look up the device in the outPort device list, and -if it finds it, it writes the message to it. The stop function removes +I'm not entirely sure how they both work yet. midiOutRealTime' +actually looks pretty straightforward in that it just creates the process +and stop functions and adds this device to the outPort device list. The +process function will look up the device in the outPort device list, and +if it finds it, it writes the message to it. The stop function removes the device from the outPort list and closes it. -On the other hand, midiOutRealTime spawns a new thread and does some -concurrent stuff. Really, it looks similar, but I don't know when to +On the other hand, midiOutRealTime spawns a new thread and does some +concurrent stuff. Really, it looks similar, but I don't know when to use one and when to use the other. > midiOutRealTime' :: OutputDeviceID -> IO (Maybe ((Time, Message) -> IO (), IO ())) > midiOutRealTime' odid@(OutputDeviceID devId) = do -> s <- openOutput devId 1 +> s <- openOutput devId 1 > case s of > Right e -> reportError "Unable to open output device in midiOutRealTime'" e >> return Nothing > Left s -> do @@ -421,7 +418,7 @@ > case s of > Nothing -> error ("midiOutRealTime': port " ++ show odid ++ " is not open for output") > Just s -> do -> if isTrackEnd msg +> if isTrackEnd msg > then return () > else case midiEvent msg of > Just m -> writeMsg s t m @@ -429,23 +426,23 @@ > writeMsg s t m = do > e <- writeShort s (PMEvent m (round (t * 1e3))) > case e of -> NoError -> return () +> NoError -> return () > _ -> reportError "midiOutRealTime'" e > finalize odid = do > s <- lookupPort outPort odid > e <- maybe (return NoError) close s > case e of -> NoError -> return () +> NoError -> return () > _ -> reportError "midiOutRealTime'" e > midiOutRealTime :: OutputDeviceID -> IO (Maybe ((Time, Message) -> IO (), IO ())) > midiOutRealTime odid@(OutputDeviceID devId) = do -> s <- openOutput devId 1 +> s <- openOutput devId 1 > case s of > Right e -> reportError "outputMidi" e >> return Nothing > Left s -> do -> ch <- atomically newTChan +> ch <- atomically newTChan > wait <- newEmptyMVar > fin <- newEmptyMVar > forkIO (pump s ch wait fin) @@ -457,50 +454,50 @@ > if isTrackEnd m then takeMVar wait else return () > pump s ch wait fin = loop > where -> loop = do +> loop = do > e <- atomically $ readTChan ch > case e of > Nothing -> close s >> putMVar fin () > Just (t, msg) -> do > now <- getTimeNow -> if (t > now + 5) +> if (t > now + 5) > then atomically (unGetTChan ch e) >> threadDelay 10000 >> loop -> else do +> else do > done <- process t msg -> if done +> if done > then waitUntil (t + 1) -> else loop +> else loop > where > waitUntil t = do > now <- getTimeNow -> if t > now +> if t > now > then do -> threadDelay $ min 10000 (round((t - now) * 1E6)) +> threadDelay $ min 10000 (round((t - now) * 1E6)) > empty <- atomically $ isEmptyTChan ch -> if empty +> if empty > then waitUntil t > else do > e <- atomically $ readTChan ch > case e of -> Nothing -> finishup +> Nothing -> finishup > _ -> waitUntil t > else finishup > finishup = putMVar wait () >> close s >> putMVar fin () -> process t msg = if isTrackEnd msg -> then return True +> process t msg = if isTrackEnd msg +> then return True > else case midiEvent msg of > Just m -> writeMsg t m -> Nothing -> return False +> Nothing -> return False > writeMsg t m = do > e <- writeShort s (PMEvent m (round (t * 1e3))) > case e of -> NoError -> return False +> NoError -> return False > BufferOverflow -> putStrLn "overflow" >> threadDelay 10000 >> writeMsg t m -> _ -> reportError "outputMidi" e >> return True +> _ -> reportError "outputMidi" e >> return True --------------------- - | MIDI Conversion | + | MIDI Conversion | --------------------- A conversion function from Codec.Midi Messages to PortMidi PMMsgs. @@ -514,13 +511,13 @@ > midiEvent (ChannelPressure c pr) = Just $ PMMsg (208 .|. (fromIntegral c .&. 0xF)) (fromIntegral pr) 0 > midiEvent (PitchWheel c pb) = Just $ PMMsg (224 .|. (fromIntegral c .&. 0xF)) (fromIntegral lo) (fromIntegral hi) > where (hi,lo) = (pb `shiftR` 8, pb .&. 0xFF) -> midiEvent _ = Nothing +> midiEvent _ = Nothing A conversion function from PortMidi PMMsgs to Codec.Midi Messages. > msgToMidi :: PMMsg -> Maybe Message -> msgToMidi (PMMsg m d1 d2) = +> msgToMidi (PMMsg m d1 d2) = > let k = (m .&. 0xF0) `shiftR` 4 > c = fromIntegral (m .&. 0x0F) > in case k of @@ -536,12 +533,12 @@ --------------------- - | Error Reporting | + | Error Reporting | --------------------- > reportError :: String -> PMError -> IO () > reportError prompt e = do -> err <- getErrorText e +> err <- getErrorText e > hPutStrLn stderr $ prompt ++ ": " ++ err @@ -549,7 +546,7 @@ ---------------------- - | Unused Functions | + | Unused Functions | ---------------------- > -- Prints all DeviceInfo found by getAllDevices. @@ -559,12 +556,12 @@ > mapM_ (print . snd) indevs > mapM_ (print . snd) outdevs --- Given whether the device is an input device and the device name, +-- Given whether the device is an input device and the device name, -- returns the DeviceID. -getDeviceId :: Bool -> String -> IO (Maybe DeviceID) -getDeviceId isInput n = do - devs <- getAllDevices - return $ findIndex (\(_,d) -> name d == n && input d == isInput) devs + getDeviceId :: Bool -> String -> IO (Maybe DeviceID) + getDeviceId isInput n = do + devs <- getAllDevices + return $ findIndex (\(_,d) -> name d == n && input d == isInput) devs > playTrackRealTime :: OutputDeviceID -> [(t, Message)] -> IO () > playTrackRealTime device track = do @@ -578,59 +575,55 @@ > out (t, TrackEnd) > playTrack out (e@(_, m) : s) = do > t <- getTimeNow -> out (t, m) -> if isTrackEnd m +> out (t, m) +> if isTrackEnd m > then return () > else playTrack out s - > {- > ticksPerBeat = case division of > TicksPerBeat n -> n -> TicksPerSecond mode nticks -> (256 - mode - 128) * nticks `div` 2 +> TicksPerSecond mode nticks -> (256 - mode - 128) * nticks `div` 2 > -} - > {- > runTrack tpb = runTrack' 0 0 120 -- 120 beat/s is the default tempo > where -> runTrack' t t0 bps ((_, TempoChange tempo) : l) = -> let bps' = 1000000 `div` fromIntegral tempo +> runTrack' t t0 bps ((_, TempoChange tempo) : l) = +> let bps' = 1000000 `div` fromIntegral tempo > in runTrack' t t0 bps' l -> runTrack' t t0 bps ((t1, m) : l) = +> runTrack' t t0 bps ((t1, m) : l) = > let t' = t + 1000 * fromIntegral (t1 - t0) `div` (tpb * bps) > in (t', m) : runTrack' t' t1 bps l -> runTrack' _ _ _ [] = [] +> runTrack' _ _ _ [] = [] > > playTrack s ch t0 = playTrack' 0 > where > playTrack' t [] = putStrLn "done" >> putMVar ch Nothing >> return (round (t * 1.0E3)) -> playTrack' _ ((t, e):es) = putMVar ch (Just io) >> playTrack' t es -> where +> playTrack' _ ((t, e):es) = putMVar ch (Just io) >> playTrack' t es +> where > io = case midiEvent e of > Just m -> writeShort s (PMEvent m (t0 + round (t * 1.0E3))) -> Nothing -> return NoError +> Nothing -> return NoError > -} - > recordMidi :: DeviceID -> (Track Time -> IO ()) -> IO () > recordMidi device f = do > ch <- newChan > final <- midiInRealTime device (\e -> writeChan ch e >> return False) -> case final of -> Nothing -> return () +> case final of +> Nothing -> return () > Just fin -> do > track <- getChanContents ch -> done <- newEmptyMVar -> forkIO (f track >> putMVar done ()) +> done <- newEmptyMVar +> forkIO (f track >> putMVar done ()) > putStrLn "Start recording, hit ENTER when you are done." > getLine -> fin +> fin > takeMVar done > return () - > midiInRealTime :: DeviceID -> ((Time, Message) -> IO Bool) -> IO (Maybe (IO ())) > midiInRealTime device callback = do -> r <- openInput device +> r <- openInput device > case r of -> Right e -> reportError "midiInRealTime" e >> return Nothing +> Right e -> reportError "midiInRealTime" e >> return Nothing > Left s -> do > fin <- newEmptyMVar > forkIO (loop Nothing s fin) @@ -644,16 +637,16 @@ > Nothing -> do > e <- readEvents s > case e of -> Right e -> if e == NoError +> Right e -> if e == NoError > then threadDelay 1000 >> loop start s fin > else do -> reportError "midiInRealTime" e +> reportError "midiInRealTime" e > callback (t, TrackEnd) > return () > Left l -> do > t <- getTimeNow > sendEvts start t l -> where +> where > sendEvts start now [] = loop start s fin > sendEvts start now (e@(PMEvent m t):l) = do > let t0 = maybe t id start
@@ -1,5 +1,5 @@-Version 2.0.2 -Last modified: 09-July-2016 +Version 2.0.3 +Last modified: 27-Dec-2016 _____ _ | ___| | | | |__ _ _| |_ ___ _ __ _ __ ___ __ _