CreativePython 0.3.5__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {creativepython-0.3.5/src/CreativePython.egg-info → creativepython-1.0.0}/PKG-INFO +2 -4
- {creativepython-0.3.5 → creativepython-1.0.0}/pyproject.toml +5 -7
- {creativepython-0.3.5 → creativepython-1.0.0}/src/CreativePython/__init__.py +1 -1
- creativepython-1.0.0/src/CreativePython/nevmuse/Java-Comparison-Tests/advMetricRunner.pythonSurvey.py +224 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/Java-Comparison-Tests/compareMetrics_Java-Vs-Python.py +274 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/RunMetrics.py +150 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/Surveyor.py +105 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/__init__.py +18 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/Confidence.py +10 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/Contig.py +63 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/ExtendedNote.py +165 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/Histogram.py +66 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/Judgement.py +9 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/Measurement.py +108 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/PianoRoll.py +1509 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/PianoRollOld.py +1441 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/__init__.py +21 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/test_ExtendedNote.py +193 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/test_Histogram.py +90 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/test_Measurement.py +110 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/test_PianoRoll_assertions.py +463 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/test_PianoRoll_integration.py +189 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/test_PianoRoll_quantization.py +159 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/data/test_PianoRoll_unit.py +326 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/Metric.py +306 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/ZipfMetrics.py +20 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/__init__.py +11 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ChordDensityMetric.py +76 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ChordDistanceMetric.py +87 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ChordMetric.py +74 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ChordNormalizedMetric.py +70 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ChromaticToneMetric.py +45 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ContourBasslineDurationMetric.py +47 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ContourBasslineDurationQuantizedMetric.py +47 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ContourBasslinePitchMetric.py +47 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ContourMelodyDurationMetric.py +47 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ContourMelodyDurationQuantizedMetric.py +47 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/ContourMelodyPitchMetric.py +47 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/DurationBigramMetric.py +58 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/DurationDistanceMetric.py +52 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/DurationMetric.py +45 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/DurationQuantizedBigramMetric.py +52 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/DurationQuantizedDistanceMetric.py +52 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/DurationQuantizedMetric.py +45 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/HarmonicBigramMetric.py +64 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/HarmonicConsonanceMetric.py +96 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/HarmonicIntervalMetric.py +58 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/MelodicBigramMetric.py +58 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/MelodicConsonanceMetric.py +83 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/MelodicIntervalMetric.py +51 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/PitchDistanceMetric.py +54 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/PitchDurationMetric.py +54 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/PitchDurationQuantizedMetric.py +53 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/PitchMetric.py +45 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/RestMetric.py +60 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/__init__.py +64 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/test_DurationMetric.py +69 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/test_Metrics_BasicIntervalsAndBigrams.py +126 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/test_Metrics_ChordsAndConsonance.py +127 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/test_Metrics_ContoursAndChromatic.py +104 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/test_Metrics_QuantizedDurationsAndDistances.py +108 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/test_PitchMetric.py +87 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/simple/test_RestMetric.py +86 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/metrics/test_Metric.py +117 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/utilities/CSVWriter.py +126 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/utilities/PowerLawRandom.py +101 -0
- creativepython-1.0.0/src/CreativePython/nevmuse/utilities/__init__.py +11 -0
- creativepython-0.3.5/src/_notationRenderer.py → creativepython-1.0.0/src/CreativePython/notationRenderer.py +70 -20
- {creativepython-0.3.5 → creativepython-1.0.0/src/CreativePython.egg-info}/PKG-INFO +2 -4
- creativepython-1.0.0/src/CreativePython.egg-info/SOURCES.txt +90 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/src/CreativePython.egg-info/requires.txt +1 -4
- {creativepython-0.3.5 → creativepython-1.0.0}/src/CreativePython.egg-info/top_level.txt +0 -2
- {creativepython-0.3.5 → creativepython-1.0.0}/src/gui.py +240 -82
- {creativepython-0.3.5 → creativepython-1.0.0}/src/image.py +0 -1
- {creativepython-0.3.5 → creativepython-1.0.0}/src/midi.py +6 -1
- {creativepython-0.3.5 → creativepython-1.0.0}/src/music.py +163 -72
- {creativepython-0.3.5 → creativepython-1.0.0}/src/timer.py +10 -12
- {creativepython-0.3.5 → creativepython-1.0.0}/src/zipf.py +316 -22
- creativepython-0.3.5/src/CreativePython/examples/ArvoPart.CantusInMemoriam.py +0 -76
- creativepython-0.3.5/src/CreativePython/examples/ConcretPH_Xenakis.py +0 -41
- creativepython-0.3.5/src/CreativePython/examples/DeepPurple.SmokeOnTheWater.py +0 -67
- creativepython-0.3.5/src/CreativePython/examples/JS_Bach.Canon.TriasHarmonica.BWV1072.py +0 -77
- creativepython-0.3.5/src/CreativePython/examples/JS_Bach.Canon_1.GoldbergGround.BWV1087.py +0 -39
- creativepython-0.3.5/src/CreativePython/examples/Mozart.MusikalischesWurfelspiel.py +0 -101
- creativepython-0.3.5/src/CreativePython/examples/PierreCage.StructuresPourDeuxChances.py +0 -47
- creativepython-0.3.5/src/CreativePython/examples/RGB_Display.py +0 -73
- creativepython-0.3.5/src/CreativePython/examples/TerryRiley.InC.py +0 -34
- creativepython-0.3.5/src/CreativePython/examples/arpeggiator1.py +0 -30
- creativepython-0.3.5/src/CreativePython/examples/arpeggiator2.py +0 -32
- creativepython-0.3.5/src/CreativePython/examples/autumnLeaves.py +0 -77
- creativepython-0.3.5/src/CreativePython/examples/axelF.py +0 -26
- creativepython-0.3.5/src/CreativePython/examples/biosignals.txt +0 -3555
- creativepython-0.3.5/src/CreativePython/examples/boids.py +0 -268
- creativepython-0.3.5/src/CreativePython/examples/brownianMelody.py +0 -47
- creativepython-0.3.5/src/CreativePython/examples/changesByTupac.py +0 -23
- creativepython-0.3.5/src/CreativePython/examples/clementine.py +0 -140
- creativepython-0.3.5/src/CreativePython/examples/continuousPitchInstrumentAudio.py +0 -54
- creativepython-0.3.5/src/CreativePython/examples/drumExample.py +0 -22
- creativepython-0.3.5/src/CreativePython/examples/drumMachinePattern1.py +0 -50
- creativepython-0.3.5/src/CreativePython/examples/drumsComeAlive.py +0 -87
- creativepython-0.3.5/src/CreativePython/examples/fibonacci.py +0 -19
- creativepython-0.3.5/src/CreativePython/examples/findPitchOctave.py +0 -14
- creativepython-0.3.5/src/CreativePython/examples/furElise.py +0 -27
- creativepython-0.3.5/src/CreativePython/examples/generativeMusic.py +0 -47
- creativepython-0.3.5/src/CreativePython/examples/goldenTree.py +0 -55
- creativepython-0.3.5/src/CreativePython/examples/guidoWordMusic.py +0 -70
- creativepython-0.3.5/src/CreativePython/examples/harmonicesMundi.py +0 -67
- creativepython-0.3.5/src/CreativePython/examples/harmonicesMundiRevisisted.py +0 -65
- creativepython-0.3.5/src/CreativePython/examples/harmonographLateral.py +0 -47
- creativepython-0.3.5/src/CreativePython/examples/harmonographRotary.py +0 -70
- creativepython-0.3.5/src/CreativePython/examples/iPianoBlackDown.png +0 -0
- creativepython-0.3.5/src/CreativePython/examples/iPianoOctave.png +0 -0
- creativepython-0.3.5/src/CreativePython/examples/iPianoParallel.py +0 -73
- creativepython-0.3.5/src/CreativePython/examples/iPianoSimple.py +0 -86
- creativepython-0.3.5/src/CreativePython/examples/iPianoWhiteCenterDown.png +0 -0
- creativepython-0.3.5/src/CreativePython/examples/iPianoWhiteLeftDown.png +0 -0
- creativepython-0.3.5/src/CreativePython/examples/iPianoWhiteRightDown.png +0 -0
- creativepython-0.3.5/src/CreativePython/examples/midiIn1.py +0 -14
- creativepython-0.3.5/src/CreativePython/examples/midiIn2.py +0 -13
- creativepython-0.3.5/src/CreativePython/examples/midiIn3.py +0 -25
- creativepython-0.3.5/src/CreativePython/examples/midiOut.a.py +0 -12
- creativepython-0.3.5/src/CreativePython/examples/midiOut.b.py +0 -14
- creativepython-0.3.5/src/CreativePython/examples/midiSynthesizer.py +0 -34
- creativepython-0.3.5/src/CreativePython/examples/midiSynthesizer2.py +0 -55
- creativepython-0.3.5/src/CreativePython/examples/moondog-bird_slament.wav +0 -0
- creativepython-0.3.5/src/CreativePython/examples/musicalSphere.py +0 -178
- creativepython-0.3.5/src/CreativePython/examples/note.py +0 -55
- creativepython-0.3.5/src/CreativePython/examples/octoplus.py +0 -56
- creativepython-0.3.5/src/CreativePython/examples/oscIn1.py +0 -13
- creativepython-0.3.5/src/CreativePython/examples/oscIn2.py +0 -19
- creativepython-0.3.5/src/CreativePython/examples/pentatonicMelody.py +0 -33
- creativepython-0.3.5/src/CreativePython/examples/pianoPhase.py +0 -28
- creativepython-0.3.5/src/CreativePython/examples/pianoRollGenerator.py +0 -43
- creativepython-0.3.5/src/CreativePython/examples/playNote.py +0 -7
- creativepython-0.3.5/src/CreativePython/examples/proteinMusic.py +0 -59
- creativepython-0.3.5/src/CreativePython/examples/randomCircles.py +0 -36
- creativepython-0.3.5/src/CreativePython/examples/randomCirclesThroughMidiInput.py +0 -52
- creativepython-0.3.5/src/CreativePython/examples/randomCirclesTimed.py +0 -67
- creativepython-0.3.5/src/CreativePython/examples/retrograde.a.py +0 -22
- creativepython-0.3.5/src/CreativePython/examples/retrograde.b.py +0 -36
- creativepython-0.3.5/src/CreativePython/examples/retrograde.c.py +0 -41
- creativepython-0.3.5/src/CreativePython/examples/rowYourBoat.py +0 -61
- creativepython-0.3.5/src/CreativePython/examples/scaleTutor.py +0 -27
- creativepython-0.3.5/src/CreativePython/examples/sierpinskiTriangle.py +0 -52
- creativepython-0.3.5/src/CreativePython/examples/simpleButtonInstrument.py +0 -34
- creativepython-0.3.5/src/CreativePython/examples/simpleCircleInstrument.py +0 -70
- creativepython-0.3.5/src/CreativePython/examples/sineMelody.py +0 -23
- creativepython-0.3.5/src/CreativePython/examples/sineMelodyPlus.py +0 -28
- creativepython-0.3.5/src/CreativePython/examples/sliderControl.py +0 -66
- creativepython-0.3.5/src/CreativePython/examples/sonifyBiosignals.py +0 -79
- creativepython-0.3.5/src/CreativePython/examples/sonifyImage.py +0 -109
- creativepython-0.3.5/src/CreativePython/examples/soundscapeLoutrakiSunset.jpg +0 -0
- creativepython-0.3.5/src/CreativePython/examples/stringQuartet.py +0 -36
- creativepython-0.3.5/src/CreativePython/examples/textMusic.py +0 -62
- creativepython-0.3.5/src/CreativePython/examples/theWayItIs.py +0 -33
- creativepython-0.3.5/src/CreativePython/examples/themeAndVariations.py +0 -64
- creativepython-0.3.5/src/CreativePython/examples/throwingDice.py +0 -37
- creativepython-0.3.5/src/CreativePython/examples/windChimes.py +0 -56
- creativepython-0.3.5/src/CreativePython/examples/zipfMetrics.py +0 -81
- creativepython-0.3.5/src/CreativePython/resources/550973__luizguilherme_a__clean-guitarr-riff.mp3 +0 -0
- creativepython-0.3.5/src/CreativePython/resources/chopper.jpg +0 -0
- creativepython-0.3.5/src/CreativePython/resources/de-brazzas-monkey.jpg +0 -0
- creativepython-0.3.5/src/CreativePython.egg-info/SOURCES.txt +0 -110
- {creativepython-0.3.5 → creativepython-1.0.0}/LICENSE +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/LICENSE-PSF +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/MANIFEST.in +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/README.md +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/setup.cfg +0 -0
- /creativepython-0.3.5/src/_RealtimeAudioPlayer.py → /creativepython-1.0.0/src/CreativePython/RealtimeAudioPlayer.py +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/src/CreativePython.egg-info/dependency_links.txt +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/src/bin/libportaudio.2.dylib +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/src/iannix.py +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/src/markov.py +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/src/osc.py +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/tests/testAnimate.py +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/tests/testPeer.py +0 -0
- {creativepython-0.3.5 → creativepython-1.0.0}/tests/test_keyEvent.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: CreativePython
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A Python-based software environment for developing algorithmic art projects.
|
|
5
5
|
Author-email: "Dr. Bill Manaris" <manaris@cofc.edu>, Taj Ballinger <ballingertj@g.cofc.edu>, Trevor Ritchie <ritchiets@g.cofc.edu>
|
|
6
6
|
License: MIT License
|
|
@@ -65,9 +65,7 @@ Requires-Dist: pooch>=1.8
|
|
|
65
65
|
Requires-Dist: pypianoroll>=1.0
|
|
66
66
|
Requires-Dist: verovio>=5.6.0
|
|
67
67
|
Requires-Dist: pymusicxml>=0.5.6
|
|
68
|
-
|
|
69
|
-
Requires-Dist: build; extra == "dev"
|
|
70
|
-
Requires-Dist: twine; extra == "dev"
|
|
68
|
+
Requires-Dist: pyinstaller>=6.16.0
|
|
71
69
|
Dynamic: license-file
|
|
72
70
|
|
|
73
71
|
# CreativePython
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "CreativePython"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "1.0.0"
|
|
8
8
|
description = "A Python-based software environment for developing algorithmic art projects."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -33,12 +33,13 @@ dependencies = [
|
|
|
33
33
|
"pooch>=1.8",
|
|
34
34
|
"pypianoroll>=1.0",
|
|
35
35
|
"verovio>=5.6.0",
|
|
36
|
-
"pymusicxml>=0.5.6"
|
|
36
|
+
"pymusicxml>=0.5.6",
|
|
37
|
+
"pyinstaller>=6.16.0"
|
|
37
38
|
]
|
|
38
39
|
|
|
39
40
|
[tool.setuptools]
|
|
40
41
|
package-dir = {"" = "src"}
|
|
41
|
-
py-modules = ["gui", "image", "midi", "music", "osc", "timer", "iannix", "markov", "zipf"
|
|
42
|
+
py-modules = ["gui", "image", "midi", "music", "osc", "timer", "iannix", "markov", "zipf"]
|
|
42
43
|
|
|
43
44
|
[tool.setuptools.packages.find]
|
|
44
45
|
where = ["src"]
|
|
@@ -47,7 +48,4 @@ where = ["src"]
|
|
|
47
48
|
"CreativePython" = ["bin/libportaudio.2.dylib"]
|
|
48
49
|
|
|
49
50
|
[project.urls]
|
|
50
|
-
Homepage = "https://jythonmusic.me"
|
|
51
|
-
|
|
52
|
-
[project.optional-dependencies]
|
|
53
|
-
dev = ["build", "twine"]
|
|
51
|
+
Homepage = "https://jythonmusic.me"
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#######################################################################################################
|
|
2
|
+
# advMetricRunner.jythonSurvey.py Version 1.1 20-Sep-2025 Jimmy Cyganek, David Johnson, John Emerson,
|
|
3
|
+
# Luca Pellicoro, Patrick Roos, Bill Manaris
|
|
4
|
+
#######################################################################################################
|
|
5
|
+
|
|
6
|
+
### Description: Runs metrics on a folder of MIDI files, these metrics are saved into a CSV file
|
|
7
|
+
|
|
8
|
+
### 1.1 (Sep. 20, 2025) - Removed DurationDistanceMetric from Surveyor, as it returns innacurate measurements
|
|
9
|
+
|
|
10
|
+
# import libraries
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import unicodedata
|
|
14
|
+
|
|
15
|
+
# add src to path if needed
|
|
16
|
+
srcDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
17
|
+
if srcDir not in sys.path:
|
|
18
|
+
sys.path.insert(0, srcDir)
|
|
19
|
+
|
|
20
|
+
from music import *
|
|
21
|
+
from ..Surveyor import Surveyor
|
|
22
|
+
from ..metrics.simple import * # import simple metrics
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#####
|
|
26
|
+
# set MIDI file path
|
|
27
|
+
midiFolderPath = "Bach AoF Fugues"
|
|
28
|
+
|
|
29
|
+
# using os module, find everything in folder that ends in ".mid"
|
|
30
|
+
folderFileNames = os.listdir(midiFolderPath) # get filenames in folder (as strings)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#####
|
|
34
|
+
# keep only MIDI files
|
|
35
|
+
midiFileNames = []
|
|
36
|
+
for name in folderFileNames: # iterate through all filenames
|
|
37
|
+
|
|
38
|
+
if name[-4:] == ".mid": # is it a MIDI file?
|
|
39
|
+
midiFileNames.append(name) # yes, so remember it
|
|
40
|
+
|
|
41
|
+
# now, midiFileNames contains all the names of MIDI files in midiFolderPath
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#####
|
|
45
|
+
# define metrics to apply (we only need to create them once - they will be reused for every MIDI file)
|
|
46
|
+
metrics = [
|
|
47
|
+
PitchMetric(2),
|
|
48
|
+
# PitchDistanceMetric(2),
|
|
49
|
+
PitchDurationMetric(2),
|
|
50
|
+
PitchDurationQuantizedMetric(2), # added Jan. 6 2010
|
|
51
|
+
|
|
52
|
+
ChromaticToneMetric(2),
|
|
53
|
+
|
|
54
|
+
DurationMetric(2),
|
|
55
|
+
DurationBigramMetric(2),
|
|
56
|
+
# DurationDistanceMetric(2), # added Sep. 30 2009
|
|
57
|
+
|
|
58
|
+
DurationQuantizedMetric(2), # added Jan. 6 2010
|
|
59
|
+
DurationQuantizedBigramMetric(2), # added Jan. 7 2010
|
|
60
|
+
DurationQuantizedDistanceMetric(2), # added Jan. 6 2010
|
|
61
|
+
|
|
62
|
+
ContourMelodyPitchMetric(2), # added Jan. 7 2010
|
|
63
|
+
ContourMelodyDurationMetric(2), # added Jan. 7 2010
|
|
64
|
+
ContourMelodyDurationQuantizedMetric(2), # added Jan. 7 2010
|
|
65
|
+
|
|
66
|
+
ContourBasslinePitchMetric(2), # added Jan. 7 2010
|
|
67
|
+
ContourBasslineDurationMetric(2), # added Jan. 7 2010
|
|
68
|
+
ContourBasslineDurationQuantizedMetric(2), # added Jan. 7 2010
|
|
69
|
+
|
|
70
|
+
MelodicIntervalMetric(2),
|
|
71
|
+
MelodicBigramMetric(2),
|
|
72
|
+
MelodicConsonanceMetric(2),
|
|
73
|
+
|
|
74
|
+
HarmonicIntervalMetric(2),
|
|
75
|
+
HarmonicBigramMetric(2),
|
|
76
|
+
HarmonicConsonanceMetric(2),
|
|
77
|
+
|
|
78
|
+
ChordMetric(2),
|
|
79
|
+
RestMetric(2)
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
#####
|
|
84
|
+
# function to return metrics for a MIDI file
|
|
85
|
+
def runMetrics(midiFilePath):
|
|
86
|
+
|
|
87
|
+
# load MIDI into Score object
|
|
88
|
+
score = Score("Metric Score")
|
|
89
|
+
Read.midi(score, midiFilePath)
|
|
90
|
+
|
|
91
|
+
# initialize Surveyor and apply metrics to the score
|
|
92
|
+
quantum = 0.25
|
|
93
|
+
surveyor = Surveyor()
|
|
94
|
+
surveyor.survey(score, metrics, quantum)
|
|
95
|
+
|
|
96
|
+
# get measurements for each metric
|
|
97
|
+
measurements = []
|
|
98
|
+
for metric in metrics: # iterate through each metric
|
|
99
|
+
|
|
100
|
+
# get measurement
|
|
101
|
+
measurement = metric.getMeasurement()
|
|
102
|
+
measurements.append(measurement)
|
|
103
|
+
|
|
104
|
+
#print measurements
|
|
105
|
+
return measurements
|
|
106
|
+
|
|
107
|
+
#####
|
|
108
|
+
# create a unique identifier for each piece - used as an attribute in Weka!!!
|
|
109
|
+
index = 0 # initialize
|
|
110
|
+
|
|
111
|
+
# function to create CSV file using list of measurements
|
|
112
|
+
def writeCSVFile(measurements, midiFolderPath, midiFileName, csvfile):
|
|
113
|
+
|
|
114
|
+
global index # used for index Weka attribute
|
|
115
|
+
|
|
116
|
+
index = index + 1 # create unique identifier for this piece
|
|
117
|
+
|
|
118
|
+
csvfile.write("\n") # move down a row
|
|
119
|
+
|
|
120
|
+
# for each metric, get each measurement and add it to the corresponding column
|
|
121
|
+
for measurement in measurements:
|
|
122
|
+
|
|
123
|
+
values = measurement.valuesToArray() # get attribute values for each metric
|
|
124
|
+
|
|
125
|
+
# get value of each measurement and write it to CSV file
|
|
126
|
+
for value in values:
|
|
127
|
+
|
|
128
|
+
csvfile.write(convertToASCII(value) + ",") # write it!!
|
|
129
|
+
|
|
130
|
+
csvfile.write(convertToASCII(index) + ",") # write index value to index column
|
|
131
|
+
|
|
132
|
+
# Caution: remove comma from filename - if any (since CSV files use comma as delimiter)
|
|
133
|
+
noCommaFileName = midiFileName.replace(",", "")
|
|
134
|
+
|
|
135
|
+
# write filename to the filename column (last column)
|
|
136
|
+
csvfile.write(convertToASCII(noCommaFileName))
|
|
137
|
+
|
|
138
|
+
# ***
|
|
139
|
+
print("Wrote row for", midiFileName)
|
|
140
|
+
|
|
141
|
+
#####
|
|
142
|
+
# function to remove non-ASCII characters
|
|
143
|
+
def convertToASCII(string):
|
|
144
|
+
|
|
145
|
+
# ***
|
|
146
|
+
#try:
|
|
147
|
+
# convert input to unicode string (in Python 3, str is already unicode)
|
|
148
|
+
unicodeString = str(string)
|
|
149
|
+
|
|
150
|
+
# convert string to NFKD format (separate characters from accents - creates two characters from one)
|
|
151
|
+
nfkdString = unicodedata.normalize('NFKD', unicodeString)
|
|
152
|
+
|
|
153
|
+
# re-encode to ASCII - ignore non-ASCII characters (like accents or symbols)
|
|
154
|
+
asciiBytes = nfkdString.encode('ascii', 'ignore')
|
|
155
|
+
|
|
156
|
+
# decode back to string (Python 3 returns bytes from encode)
|
|
157
|
+
asciiString = asciiBytes.decode('ascii')
|
|
158
|
+
|
|
159
|
+
# return ASCII-only version of the string
|
|
160
|
+
return asciiString
|
|
161
|
+
|
|
162
|
+
#except: # ***
|
|
163
|
+
|
|
164
|
+
# if there is no error, return as a string
|
|
165
|
+
#return str(string)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
##### Main #####
|
|
169
|
+
|
|
170
|
+
# Goal: process all MIDI files, get their metrics, then put them into CSV file
|
|
171
|
+
# How I plan to do this:
|
|
172
|
+
# - Run runMetrics on the first item in midiFileNames
|
|
173
|
+
# - This will return it's metrics
|
|
174
|
+
# - Create the first row of the CSV (metric measurement names, will become attribute names in Weka) of this first item
|
|
175
|
+
# - Then, run a for loop for every item in midiFileNames, take it's values, and add them as a new row to the CSV
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
#####
|
|
179
|
+
|
|
180
|
+
# output metrics headers, by running metrics on first MIDI file
|
|
181
|
+
|
|
182
|
+
# create file path to the first MIDI file (midiFileNames[0])
|
|
183
|
+
midiFilePath = os.path.join(midiFolderPath, midiFileNames[0])
|
|
184
|
+
|
|
185
|
+
# run runMetrics on the first item in midiFileNames
|
|
186
|
+
measurements = runMetrics(midiFilePath)
|
|
187
|
+
|
|
188
|
+
# open CSV file for output
|
|
189
|
+
with open(midiFolderPath + "_metrics.csv", mode="w") as csvfile:
|
|
190
|
+
|
|
191
|
+
# output headers for all measurements to CSV file
|
|
192
|
+
for measurement in measurements:
|
|
193
|
+
|
|
194
|
+
keys = measurement.keysToArray() # get attribute names for each metric
|
|
195
|
+
|
|
196
|
+
# output headers for this measurement
|
|
197
|
+
for key in keys:
|
|
198
|
+
csvfile.write(key+",") # write them in CSV file - comma used to separate columns
|
|
199
|
+
|
|
200
|
+
# add final headers
|
|
201
|
+
csvfile.write("Index,") # write index attribute header
|
|
202
|
+
csvfile.write("Filename") # write filename attribute header
|
|
203
|
+
|
|
204
|
+
# run metrics for each MIDI file, and store results in CSV file
|
|
205
|
+
for midiFileName in midiFileNames:
|
|
206
|
+
|
|
207
|
+
# create path to MIDI file
|
|
208
|
+
midiFilePath = os.path.join(midiFolderPath, midiFileName)
|
|
209
|
+
|
|
210
|
+
# calculate metrics for this MIDI file
|
|
211
|
+
measurements = runMetrics(midiFilePath)
|
|
212
|
+
|
|
213
|
+
# write results to CSV file
|
|
214
|
+
writeCSVFile(measurements, midiFolderPath, midiFileName, csvfile)
|
|
215
|
+
|
|
216
|
+
# now, all metrics for all MIDI files have been written to CSV file
|
|
217
|
+
|
|
218
|
+
# ***
|
|
219
|
+
# Is this needed???
|
|
220
|
+
# remember to close CSV file!!!
|
|
221
|
+
#csvfile.close()
|
|
222
|
+
|
|
223
|
+
# ***
|
|
224
|
+
print(midiFolderPath + "_metrics.csv written.")
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compare two metrics CSV files cell by cell and compute statistics.
|
|
3
|
+
Tracks average difference, standard deviation, and maximum difference.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import csv
|
|
7
|
+
import numpy as np
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
import sys
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def compareMetricsCsv(file1Path, file2Path, outputFile=None):
|
|
15
|
+
"""
|
|
16
|
+
Compare two CSV files cell by cell and compute statistics.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
file1Path: Path to first CSV file (new/comparison version)
|
|
20
|
+
file2Path: Path to second CSV file (original version)
|
|
21
|
+
outputFile: Optional file object to write output to
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Dictionary with statistics about the differences
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def printAndLog(message=""):
|
|
28
|
+
"""Print to console and optionally to file"""
|
|
29
|
+
print(message)
|
|
30
|
+
if outputFile:
|
|
31
|
+
outputFile.write(message + "\n")
|
|
32
|
+
|
|
33
|
+
# read both CSV files
|
|
34
|
+
with open(file1Path, 'r', encoding='utf-8') as f1:
|
|
35
|
+
reader1 = csv.reader(f1)
|
|
36
|
+
headers1 = next(reader1)
|
|
37
|
+
rows1 = list(reader1)
|
|
38
|
+
|
|
39
|
+
with open(file2Path, 'r', encoding='utf-8') as f2:
|
|
40
|
+
reader2 = csv.reader(f2)
|
|
41
|
+
headers2 = next(reader2)
|
|
42
|
+
rows2 = list(reader2)
|
|
43
|
+
|
|
44
|
+
# verify headers match
|
|
45
|
+
if headers1 != headers2:
|
|
46
|
+
printAndLog("WARNING: Headers do not match!")
|
|
47
|
+
printAndLog(f"File 1 has {len(headers1)} columns")
|
|
48
|
+
printAndLog(f"File 2 has {len(headers2)} columns")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
printAndLog(f"Headers match: {len(headers1)} columns")
|
|
52
|
+
printAndLog(f"File 1 rows: {len(rows1)}")
|
|
53
|
+
printAndLog(f"File 2 rows: {len(rows2)}")
|
|
54
|
+
|
|
55
|
+
# verify same number of rows
|
|
56
|
+
if len(rows1) != len(rows2):
|
|
57
|
+
printAndLog("WARNING: Different number of rows!")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
# collect all numeric differences
|
|
61
|
+
allDifferences = []
|
|
62
|
+
maxDifference = 0.0
|
|
63
|
+
maxDifferenceLocation = None
|
|
64
|
+
|
|
65
|
+
# statistics by column
|
|
66
|
+
columnStats = {}
|
|
67
|
+
for colIdx, colName in enumerate(headers1):
|
|
68
|
+
columnStats[colName] = []
|
|
69
|
+
|
|
70
|
+
# statistics by metric type
|
|
71
|
+
metricTypeStats = {}
|
|
72
|
+
|
|
73
|
+
# compare each cell
|
|
74
|
+
totalCells = 0
|
|
75
|
+
numericCells = 0
|
|
76
|
+
identicalCells = 0
|
|
77
|
+
|
|
78
|
+
for rowIdx, (row1, row2) in enumerate(zip(rows1, rows2)):
|
|
79
|
+
if len(row1) != len(row2):
|
|
80
|
+
printAndLog(f"WARNING: Row {rowIdx} has different number of columns!")
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
for colIdx, (val1, val2) in enumerate(zip(row1, row2)):
|
|
84
|
+
totalCells += 1
|
|
85
|
+
colName = headers1[colIdx]
|
|
86
|
+
|
|
87
|
+
# try to compare as numbers
|
|
88
|
+
try:
|
|
89
|
+
num1 = float(val1)
|
|
90
|
+
num2 = float(val2)
|
|
91
|
+
numericCells += 1
|
|
92
|
+
|
|
93
|
+
# compute absolute difference
|
|
94
|
+
diff = abs(num1 - num2)
|
|
95
|
+
allDifferences.append(diff)
|
|
96
|
+
columnStats[colName].append(diff)
|
|
97
|
+
|
|
98
|
+
# extract metric type from column name (e.g., "PitchDuration_0_Slope" -> "PitchDuration")
|
|
99
|
+
metricType = colName.split('_')[0]
|
|
100
|
+
if metricType not in metricTypeStats:
|
|
101
|
+
metricTypeStats[metricType] = []
|
|
102
|
+
metricTypeStats[metricType].append(diff)
|
|
103
|
+
|
|
104
|
+
# track maximum difference
|
|
105
|
+
if diff > maxDifference:
|
|
106
|
+
maxDifference = diff
|
|
107
|
+
maxDifferenceLocation = (rowIdx, colIdx, colName, num1, num2)
|
|
108
|
+
|
|
109
|
+
# track identical cells
|
|
110
|
+
if diff < 1e-10: # essentially zero
|
|
111
|
+
identicalCells += 1
|
|
112
|
+
|
|
113
|
+
except ValueError:
|
|
114
|
+
# non-numeric cell (like filename), check if identical
|
|
115
|
+
if val1 == val2:
|
|
116
|
+
identicalCells += 1
|
|
117
|
+
|
|
118
|
+
# compute statistics
|
|
119
|
+
if len(allDifferences) == 0:
|
|
120
|
+
printAndLog("No numeric differences found!")
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
avgDifference = np.mean(allDifferences)
|
|
124
|
+
stdDeviation = np.std(allDifferences)
|
|
125
|
+
medianDifference = np.median(allDifferences)
|
|
126
|
+
|
|
127
|
+
# print results
|
|
128
|
+
printAndLog()
|
|
129
|
+
printAndLog("=" * 80)
|
|
130
|
+
printAndLog("OVERALL STATISTICS")
|
|
131
|
+
printAndLog("=" * 80)
|
|
132
|
+
printAndLog()
|
|
133
|
+
printAndLog(f" Total cells compared: {totalCells:>8,}")
|
|
134
|
+
printAndLog(f" Numeric cells: {numericCells:>8,}")
|
|
135
|
+
printAndLog(f" Identical cells: {identicalCells:>8,} ({100*identicalCells/totalCells:>5.2f}%)")
|
|
136
|
+
printAndLog(f" Different cells: {totalCells-identicalCells:>8,} ({100*(totalCells-identicalCells)/totalCells:>5.2f}%)")
|
|
137
|
+
printAndLog()
|
|
138
|
+
printAndLog(" Difference Metrics:")
|
|
139
|
+
printAndLog(f" Average difference: {avgDifference:>12.6f}")
|
|
140
|
+
printAndLog(f" Median difference: {medianDifference:>12.6f}")
|
|
141
|
+
printAndLog(f" Std deviation: {stdDeviation:>12.6f}")
|
|
142
|
+
printAndLog(f" Maximum difference: {maxDifference:>12.6f}")
|
|
143
|
+
|
|
144
|
+
if maxDifferenceLocation:
|
|
145
|
+
rowIdx, colIdx, colName, val1, val2 = maxDifferenceLocation
|
|
146
|
+
printAndLog()
|
|
147
|
+
printAndLog(" Maximum Difference Details:")
|
|
148
|
+
printAndLog(f" Row: {rowIdx + 2} (CSV line {rowIdx + 2})")
|
|
149
|
+
printAndLog(f" Column: {colIdx}")
|
|
150
|
+
printAndLog(f" Column name: {colName}")
|
|
151
|
+
printAndLog(f" Python value: {val1}")
|
|
152
|
+
printAndLog(f" Java value: {val2}")
|
|
153
|
+
printAndLog(f" Absolute difference: {abs(val1 - val2):.10f}")
|
|
154
|
+
|
|
155
|
+
# find columns with largest average differences
|
|
156
|
+
printAndLog()
|
|
157
|
+
printAndLog("=" * 80)
|
|
158
|
+
printAndLog("TOP 10 COLUMNS WITH LARGEST AVERAGE DIFFERENCES")
|
|
159
|
+
printAndLog("=" * 80)
|
|
160
|
+
printAndLog()
|
|
161
|
+
|
|
162
|
+
columnAvgDiffs = {}
|
|
163
|
+
for colName, diffs in columnStats.items():
|
|
164
|
+
if len(diffs) > 0:
|
|
165
|
+
columnAvgDiffs[colName] = np.mean(diffs)
|
|
166
|
+
|
|
167
|
+
sortedColumns = sorted(columnAvgDiffs.items(), key=lambda x: x[1], reverse=True)
|
|
168
|
+
for rank, (colName, avgDiff) in enumerate(sortedColumns[:10], 1):
|
|
169
|
+
printAndLog(f" {rank:2d}. {colName:55s} {avgDiff:.8f}")
|
|
170
|
+
|
|
171
|
+
# find columns with smallest average differences (most accurate)
|
|
172
|
+
printAndLog()
|
|
173
|
+
printAndLog("=" * 80)
|
|
174
|
+
printAndLog("TOP 10 COLUMNS WITH SMALLEST AVERAGE DIFFERENCES (Most Accurate)")
|
|
175
|
+
printAndLog("=" * 80)
|
|
176
|
+
printAndLog()
|
|
177
|
+
|
|
178
|
+
for rank, (colName, avgDiff) in enumerate(reversed(sortedColumns[-10:]), 1):
|
|
179
|
+
printAndLog(f" {rank:2d}. {colName:55s} {avgDiff:.8f}")
|
|
180
|
+
|
|
181
|
+
# analyze by metric type
|
|
182
|
+
printAndLog()
|
|
183
|
+
printAndLog("=" * 80)
|
|
184
|
+
printAndLog("DIFFERENCES BY METRIC TYPE")
|
|
185
|
+
printAndLog("=" * 80)
|
|
186
|
+
printAndLog()
|
|
187
|
+
|
|
188
|
+
metricTypeAvgs = {}
|
|
189
|
+
for metricType, diffs in metricTypeStats.items():
|
|
190
|
+
if len(diffs) > 0:
|
|
191
|
+
metricTypeAvgs[metricType] = {
|
|
192
|
+
'avg': np.mean(diffs),
|
|
193
|
+
'max': np.max(diffs),
|
|
194
|
+
'median': np.median(diffs),
|
|
195
|
+
'std': np.std(diffs),
|
|
196
|
+
'count': len(diffs)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
sortedMetricTypes = sorted(metricTypeAvgs.items(), key=lambda x: x[1]['max'], reverse=True)
|
|
200
|
+
|
|
201
|
+
printAndLog(f"{'Metric Type':<30} {'Max Diff':>12} {'Avg Diff':>12} {'Median':>12} {'Std Dev':>12} {'Count':>8}")
|
|
202
|
+
printAndLog("-" * 80)
|
|
203
|
+
for metricType, stats in sortedMetricTypes:
|
|
204
|
+
printAndLog(f"{metricType:<30} {stats['max']:>12.6f} {stats['avg']:>12.6f} {stats['median']:>12.6f} {stats['std']:>12.6f} {stats['count']:>8,}")
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
'avgDifference': avgDifference,
|
|
208
|
+
'stdDeviation': stdDeviation,
|
|
209
|
+
'maxDifference': maxDifference,
|
|
210
|
+
'medianDifference': medianDifference,
|
|
211
|
+
'totalCells': totalCells,
|
|
212
|
+
'numericCells': numericCells,
|
|
213
|
+
'identicalCells': identicalCells,
|
|
214
|
+
'columnStats': columnStats,
|
|
215
|
+
'metricTypeStats': metricTypeStats
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
# get script directory to make paths relative to script location
|
|
221
|
+
scriptDir = Path(__file__).parent
|
|
222
|
+
|
|
223
|
+
# define file paths relative to script location
|
|
224
|
+
newFile = scriptDir / "Bach AoF Fugues_metrics.csv"
|
|
225
|
+
originalFile = scriptDir / "ORIGINAL-JAVA_Bach AoF Fugues_metrics.csv"
|
|
226
|
+
|
|
227
|
+
# create results directory if it doesn't exist
|
|
228
|
+
resultsDir = scriptDir / "results"
|
|
229
|
+
resultsDir.mkdir(exist_ok=True)
|
|
230
|
+
|
|
231
|
+
# create timestamped output filename
|
|
232
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
233
|
+
outputFilename = resultsDir / f"comparison_{timestamp}.txt"
|
|
234
|
+
|
|
235
|
+
print("=" * 80)
|
|
236
|
+
print("METRICS COMPARISON: Python Implementation vs Java Implementation")
|
|
237
|
+
print("=" * 80)
|
|
238
|
+
print()
|
|
239
|
+
print(f"Python results file: {newFile.name}")
|
|
240
|
+
print(f"Java results file: {originalFile.name}")
|
|
241
|
+
print(f"Output file: {outputFilename.relative_to(scriptDir)}")
|
|
242
|
+
print()
|
|
243
|
+
|
|
244
|
+
# verify files exist
|
|
245
|
+
if not newFile.exists():
|
|
246
|
+
print(f"ERROR: File not found: {newFile}")
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
if not originalFile.exists():
|
|
249
|
+
print(f"ERROR: File not found: {originalFile}")
|
|
250
|
+
sys.exit(1)
|
|
251
|
+
|
|
252
|
+
# run comparison and save to file
|
|
253
|
+
with open(outputFilename, 'w', encoding='utf-8') as outFile:
|
|
254
|
+
# write header to file
|
|
255
|
+
outFile.write("=" * 80 + "\n")
|
|
256
|
+
outFile.write("METRICS COMPARISON: Python Implementation vs Java Implementation\n")
|
|
257
|
+
outFile.write("=" * 80 + "\n")
|
|
258
|
+
outFile.write(f"\nGenerated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
259
|
+
outFile.write(f"Python results file: {newFile.name}\n")
|
|
260
|
+
outFile.write(f"Java results file: {originalFile.name}\n")
|
|
261
|
+
outFile.write("\n")
|
|
262
|
+
|
|
263
|
+
# run comparison
|
|
264
|
+
results = compareMetricsCsv(newFile, originalFile, outFile)
|
|
265
|
+
|
|
266
|
+
if results:
|
|
267
|
+
print()
|
|
268
|
+
print("=" * 80)
|
|
269
|
+
print("COMPARISON COMPLETE!")
|
|
270
|
+
print("=" * 80)
|
|
271
|
+
print(f"\nResults saved to: {outputFilename}")
|
|
272
|
+
else:
|
|
273
|
+
print("\nComparison failed!")
|
|
274
|
+
sys.exit(1)
|