midiharmony 26.1.27__py3-none-any.whl → 26.1.30__py3-none-any.whl
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.
- midiharmony/README.md +122 -1
- midiharmony/midiharmony.py +134 -16
- midiharmony-26.1.30.dist-info/METADATA +194 -0
- {midiharmony-26.1.27.dist-info → midiharmony-26.1.30.dist-info}/RECORD +7 -7
- midiharmony-26.1.27.dist-info/METADATA +0 -72
- {midiharmony-26.1.27.dist-info → midiharmony-26.1.30.dist-info}/WHEEL +0 -0
- {midiharmony-26.1.27.dist-info → midiharmony-26.1.30.dist-info}/licenses/LICENSE +0 -0
- {midiharmony-26.1.27.dist-info → midiharmony-26.1.30.dist-info}/top_level.txt +0 -0
midiharmony/README.md
CHANGED
|
@@ -13,17 +13,138 @@
|
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
|
+
### Standard processing on CPU (default)
|
|
17
|
+
|
|
16
18
|
```sh
|
|
17
19
|
!pip install -U midiharmony
|
|
18
20
|
```
|
|
19
21
|
|
|
22
|
+
### Accelerated processing on GPU
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
!pip install -U midiharmony[gpu]
|
|
26
|
+
```
|
|
27
|
+
|
|
20
28
|
***
|
|
21
29
|
|
|
22
|
-
## Basic use
|
|
30
|
+
## Basic use examples
|
|
31
|
+
*Using midiharmony is easy and requires just two lines of code :)*
|
|
32
|
+
|
|
33
|
+
### Analyze a single MIDI
|
|
23
34
|
|
|
24
35
|
```python
|
|
25
36
|
import midiharmony
|
|
37
|
+
|
|
38
|
+
midi_harmony_dict = midiharmony.analyze_midi('Come To My Window.mid')
|
|
39
|
+
|
|
40
|
+
# This code will return a single dictionary with harmony stats
|
|
41
|
+
{'midi_path': 'Come To My Window.mid',
|
|
42
|
+
'midi_name': 'Come To My Window',
|
|
43
|
+
'total_chords_count': 2287,
|
|
44
|
+
'bad_chords_count': 11,
|
|
45
|
+
'grouped_chords_count': 1861,
|
|
46
|
+
'total_quads_count': 1861,
|
|
47
|
+
'unique_quads_count': 644,
|
|
48
|
+
'harmonic_quads_count': 470,
|
|
49
|
+
'harmony_ratio': 0.7298136645962733
|
|
50
|
+
}
|
|
26
51
|
```
|
|
27
52
|
|
|
53
|
+
### Analyze MIDI folder(s)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import midiharmony
|
|
57
|
+
|
|
58
|
+
midi_harmony_dicts_list = midiharmony.analyze_midi_folders(['./midi_folder_1',
|
|
59
|
+
'./midi_folder_2',
|
|
60
|
+
'./midi_folder_3'
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
# This code will return a list of dictionaries with harmony stats
|
|
64
|
+
# for all processed MIDIs from all specified folders
|
|
65
|
+
[
|
|
66
|
+
{'midi_path': 'midi_folder_1/Bach Violin 2.mid',
|
|
67
|
+
'midi_name': 'Bach Violin 2',
|
|
68
|
+
'total_chords_count': 1089,
|
|
69
|
+
'bad_chords_count': 71,
|
|
70
|
+
'grouped_chords_count': 1045,
|
|
71
|
+
'total_quads_count': 1045,
|
|
72
|
+
'unique_quads_count': 974,
|
|
73
|
+
'harmonic_quads_count': 867,
|
|
74
|
+
'harmony_ratio': 0.8901437371663244},
|
|
75
|
+
{'midi_path': 'midi_folder_2/Camping at Aylm.mid',
|
|
76
|
+
'midi_name': 'Camping at Aylm',
|
|
77
|
+
'total_chords_count': 417,
|
|
78
|
+
'bad_chords_count': 7,
|
|
79
|
+
'grouped_chords_count': 383,
|
|
80
|
+
'total_quads_count': 383,
|
|
81
|
+
'unique_quads_count': 369,
|
|
82
|
+
'harmonic_quads_count': 326,
|
|
83
|
+
'harmony_ratio': 0.8834688346883469},
|
|
84
|
+
{'midi_path': 'midi_folder_3/Come To My Window.mid',
|
|
85
|
+
'midi_name': 'Come To My Window',
|
|
86
|
+
'total_chords_count': 2287,
|
|
87
|
+
'bad_chords_count': 11,
|
|
88
|
+
'grouped_chords_count': 1861,
|
|
89
|
+
'total_quads_count': 1861,
|
|
90
|
+
'unique_quads_count': 644,
|
|
91
|
+
'harmonic_quads_count': 470,
|
|
92
|
+
'harmony_ratio': 0.7298136645962733}
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
***
|
|
97
|
+
|
|
98
|
+
## NOTES
|
|
99
|
+
|
|
100
|
+
* Most important value in each returned midi_harmony_dictionary is the "harmony_ratio"
|
|
101
|
+
* High harmony_ratio (>=0.75) indicates good harmony
|
|
102
|
+
* Exceptional harmony is indicated by harmony_ratio >= 0.9
|
|
103
|
+
|
|
104
|
+
***
|
|
105
|
+
|
|
106
|
+
## midiharmony API reference list
|
|
107
|
+
|
|
108
|
+
```midiharmony.find_quads_fast_cupy```
|
|
109
|
+
*Count matching 4‑chord rows between two arrays using a GPU‑accelerated FNV‑1a hash.*
|
|
110
|
+
|
|
111
|
+
```midiharmony.find_quads_fast_numpy```
|
|
112
|
+
*Count matching 4‑chord rows between two arrays using NumPy on CPU.*
|
|
113
|
+
|
|
114
|
+
```midiharmony.get_trg_array```
|
|
115
|
+
*Load and cache the target harmonic‑quad array in NumPy or CuPy form.*
|
|
116
|
+
|
|
117
|
+
```midiharmony.process_midi```
|
|
118
|
+
*Extract chords, grouped chords, and unique 4‑chord quads from a MIDI file.*
|
|
119
|
+
|
|
120
|
+
```midiharmony.analyze_processed_midi```
|
|
121
|
+
*Compare extracted quads against the target database and compute harmony metrics.*
|
|
122
|
+
|
|
123
|
+
```midiharmony.analyze_midi```
|
|
124
|
+
*Run the full pipeline: process a MIDI file and evaluate its harmonic quality.*
|
|
125
|
+
|
|
126
|
+
```midiharmony.analyze_midi_folders```
|
|
127
|
+
*Batch‑analyze all MIDI files in one or more folders and return harmony reports.*
|
|
128
|
+
|
|
129
|
+
```midiharmony.helpers.get_package_data```
|
|
130
|
+
*Return a sorted list of packaged `.npz` data files with their resolved paths.*
|
|
131
|
+
|
|
132
|
+
```midiharmony.helpers.get_normalized_midi_md5_hash```
|
|
133
|
+
*Compute original and normalized MD5 hashes for any MIDI file.*
|
|
134
|
+
|
|
135
|
+
```midiharmony.helpers.normalize_midi_file```
|
|
136
|
+
*Normalize a MIDI file and write the normalized version to disk.*
|
|
137
|
+
|
|
138
|
+
```midiharmony.helpers.is_installed```
|
|
139
|
+
*Check whether a Debian package is installed using `dpkg-query`.*
|
|
140
|
+
|
|
141
|
+
```midiharmony.helpers._run_apt_get```
|
|
142
|
+
*Internal helper to run `apt-get` commands with consistent flags and timeout.*
|
|
143
|
+
|
|
144
|
+
```midiharmony.helpers.install_apt_package```
|
|
145
|
+
*Idempotently install an apt package with retries, optional sudo, and optional python‑apt.*
|
|
146
|
+
|
|
147
|
+
***
|
|
148
|
+
|
|
28
149
|
### Project Los Angeles
|
|
29
150
|
### Tegridy Code 2026
|
midiharmony/midiharmony.py
CHANGED
|
@@ -62,8 +62,6 @@ print('=' * 70)
|
|
|
62
62
|
|
|
63
63
|
import os, copy, time
|
|
64
64
|
|
|
65
|
-
from typing import List, Optional, Union, Tuple, Dict, Any
|
|
66
|
-
|
|
67
65
|
import tqdm
|
|
68
66
|
|
|
69
67
|
###################################################################################
|
|
@@ -157,8 +155,25 @@ def find_quads_fast_numpy(src_array: np.ndarray, trg_array: np.ndarray) -> int:
|
|
|
157
155
|
_trg_cache_np = None
|
|
158
156
|
_trg_cache_cp = None
|
|
159
157
|
|
|
160
|
-
def get_trg_array(use_gpu=False, verbose=True):
|
|
158
|
+
def get_trg_array(use_gpu: bool = False, verbose: bool = True):
|
|
161
159
|
|
|
160
|
+
"""
|
|
161
|
+
Load and cache the target harmonic‑chord array in either NumPy (CPU) or CuPy (GPU) form.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
use_gpu : bool, optional
|
|
166
|
+
If True, return a cached CuPy array; otherwise return a cached NumPy array.
|
|
167
|
+
Falls back to NumPy automatically if CuPy is unavailable.
|
|
168
|
+
verbose : bool, optional
|
|
169
|
+
If True, print diagnostic messages when loading arrays for the first time.
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
numpy.ndarray or cupy.ndarray
|
|
174
|
+
The cached target array, loaded once and reused across calls.
|
|
175
|
+
"""
|
|
176
|
+
|
|
162
177
|
global _trg_cache_np, _trg_cache_cp
|
|
163
178
|
|
|
164
179
|
if not use_gpu:
|
|
@@ -191,9 +206,44 @@ def get_trg_array(use_gpu=False, verbose=True):
|
|
|
191
206
|
|
|
192
207
|
###################################################################################
|
|
193
208
|
|
|
194
|
-
def process_midi(midi_file_path,
|
|
195
|
-
|
|
196
|
-
|
|
209
|
+
def process_midi(midi_file_path: str, verbose: bool = True):
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
Analyze a MIDI file and extract harmonic information, chord statistics,
|
|
213
|
+
and unique 4‑chord progression quads.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
midi_file_path : str
|
|
218
|
+
Path to the MIDI file to process.
|
|
219
|
+
verbose : bool, optional
|
|
220
|
+
If True, print progress messages and execution timing.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
dict
|
|
225
|
+
A dictionary containing:
|
|
226
|
+
|
|
227
|
+
- 'midi_path' : str
|
|
228
|
+
Original file path.
|
|
229
|
+
- 'midi_name' : str
|
|
230
|
+
File name without extension.
|
|
231
|
+
- 'total_chords_count' : int
|
|
232
|
+
Number of chordified events.
|
|
233
|
+
- 'bad_chords_count' : int
|
|
234
|
+
Number of chords that required correction.
|
|
235
|
+
- 'grouped_chords_count' : int
|
|
236
|
+
Length of the grouped chord sequence.
|
|
237
|
+
- 'total_quads_count' : int
|
|
238
|
+
Total number of extracted 4‑chord windows.
|
|
239
|
+
- 'unique_quads_count' : int
|
|
240
|
+
Number of unique 4‑chord quads.
|
|
241
|
+
- 'quads' : list[tuple[int]]
|
|
242
|
+
Unique 4‑chord progressions discovered.
|
|
243
|
+
|
|
244
|
+
Returns an empty dictionary if the MIDI contains no events,
|
|
245
|
+
only drums, or an exception occurs.
|
|
246
|
+
"""
|
|
197
247
|
|
|
198
248
|
midi_name = os.path.splitext(os.path.basename(midi_file_path))[0]
|
|
199
249
|
|
|
@@ -351,9 +401,37 @@ def process_midi(midi_file_path,
|
|
|
351
401
|
###################################################################################
|
|
352
402
|
|
|
353
403
|
def analyze_processed_midi(processed_midi_dict,
|
|
354
|
-
keep_quads=False,
|
|
355
|
-
verbose=False
|
|
404
|
+
keep_quads: bool = False,
|
|
405
|
+
verbose: bool = False
|
|
356
406
|
):
|
|
407
|
+
|
|
408
|
+
"""
|
|
409
|
+
Evaluate harmonic quality of a processed MIDI file by comparing its
|
|
410
|
+
extracted chord‑quad sequences against the target quad database.
|
|
411
|
+
|
|
412
|
+
Parameters
|
|
413
|
+
----------
|
|
414
|
+
processed_midi_dict : dict
|
|
415
|
+
Output dictionary from `process_midi`, containing extracted chord
|
|
416
|
+
information and a 'quads' list of 4‑chord progressions.
|
|
417
|
+
keep_quads : bool, optional
|
|
418
|
+
If True, preserve the original 'quads' list in the returned dictionary.
|
|
419
|
+
If False, omit it to reduce memory usage.
|
|
420
|
+
verbose : bool, optional
|
|
421
|
+
If True, print progress messages and timing information.
|
|
422
|
+
|
|
423
|
+
Returns
|
|
424
|
+
-------
|
|
425
|
+
dict
|
|
426
|
+
A dictionary containing the original processed‑MIDI metadata plus:
|
|
427
|
+
|
|
428
|
+
- 'harmonic_quads_count' : int
|
|
429
|
+
Number of quads that match entries in the target quad database.
|
|
430
|
+
- 'harmony_ratio' : float
|
|
431
|
+
Fraction of matching quads relative to total quads.
|
|
432
|
+
|
|
433
|
+
If `keep_quads` is False, the 'quads' key is removed.
|
|
434
|
+
"""
|
|
357
435
|
|
|
358
436
|
if verbose:
|
|
359
437
|
|
|
@@ -418,9 +496,26 @@ def analyze_processed_midi(processed_midi_dict,
|
|
|
418
496
|
|
|
419
497
|
###################################################################################
|
|
420
498
|
|
|
421
|
-
def analyze_midi(midi_file_path,
|
|
422
|
-
|
|
423
|
-
|
|
499
|
+
def analyze_midi(midi_file_path: str, verbose: bool = True):
|
|
500
|
+
|
|
501
|
+
"""
|
|
502
|
+
End‑to‑end MIDI harmony analysis pipeline combining chord extraction
|
|
503
|
+
and harmonic‑quad evaluation.
|
|
504
|
+
|
|
505
|
+
Parameters
|
|
506
|
+
----------
|
|
507
|
+
midi_file_path : str
|
|
508
|
+
Path to the MIDI file to analyze.
|
|
509
|
+
verbose : bool, optional
|
|
510
|
+
If True, print progress messages and total execution time.
|
|
511
|
+
|
|
512
|
+
Returns
|
|
513
|
+
-------
|
|
514
|
+
dict
|
|
515
|
+
The harmony‑analysis dictionary produced by `analyze_processed_midi`.
|
|
516
|
+
Returns an empty dictionary if processing fails or the MIDI contains
|
|
517
|
+
no analyzable harmonic content.
|
|
518
|
+
"""
|
|
424
519
|
|
|
425
520
|
processed_midi_dict = {}
|
|
426
521
|
harmony_dict = {}
|
|
@@ -450,12 +545,35 @@ def analyze_midi(midi_file_path,
|
|
|
450
545
|
|
|
451
546
|
###################################################################################
|
|
452
547
|
|
|
453
|
-
def
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
548
|
+
def analyze_midi_folders(midi_folders_paths_list,
|
|
549
|
+
midi_files_extensions=['.mid', '.midi', '.kar'],
|
|
550
|
+
show_progress_bar: bool = True,
|
|
551
|
+
verbose: bool = False
|
|
552
|
+
):
|
|
458
553
|
|
|
554
|
+
"""
|
|
555
|
+
Batch‑analyze all MIDI files inside one or more folders, producing
|
|
556
|
+
harmony‑analysis dictionaries for each valid file.
|
|
557
|
+
|
|
558
|
+
Parameters
|
|
559
|
+
----------
|
|
560
|
+
midi_folders_paths_list : list[str]
|
|
561
|
+
List of folder paths to search for MIDI files.
|
|
562
|
+
midi_files_extensions : list[str], optional
|
|
563
|
+
File extensions to include when scanning folders.
|
|
564
|
+
show_progress_bar : bool, optional
|
|
565
|
+
If True, display a tqdm progress bar while processing files.
|
|
566
|
+
verbose : bool, optional
|
|
567
|
+
If True, print diagnostic information and timing details.
|
|
568
|
+
|
|
569
|
+
Returns
|
|
570
|
+
-------
|
|
571
|
+
list[dict]
|
|
572
|
+
A list of harmony‑analysis dictionaries, one per successfully
|
|
573
|
+
processed MIDI file. Files that fail processing or contain no
|
|
574
|
+
analyzable harmonic content are skipped.
|
|
575
|
+
"""
|
|
576
|
+
|
|
459
577
|
if verbose:
|
|
460
578
|
start_time = time.time()
|
|
461
579
|
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: midiharmony
|
|
3
|
+
Version: 26.1.30
|
|
4
|
+
Summary: Fast, data‑driven harmony analysis for any MIDI file
|
|
5
|
+
Author-email: Alex Lev <alexlev61@proton.me>
|
|
6
|
+
Maintainer-email: Alex Lev <alexlev61@proton.me>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Project-URL: Homepage, https://github.com/asigalov61/midiharmony
|
|
9
|
+
Project-URL: SoundCloud, https://soundcloud.com/aleksandr-sigalov-61/
|
|
10
|
+
Project-URL: Documentation, https://github.com/asigalov61/midiharmony
|
|
11
|
+
Project-URL: Issues, https://github.com/asigalov61/midiharmony/issues
|
|
12
|
+
Project-URL: Discussions, https://github.com/asigalov61/midiharmony/discussions
|
|
13
|
+
Project-URL: Dataset, https://huggingface.co/datasets/projectlosangeles/Discover-MIDI-Dataset
|
|
14
|
+
Keywords: MIDI,MIDI harmony,harmony,symbolic music,music,music harmony,harmony analysis,harmonic analysis,music information retrieval,MIR,music discovery,music search,music quality,music metadata,music recommendation,research,musicology,music analysis
|
|
15
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Intended Audience :: Education
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
28
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
|
|
29
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
30
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
31
|
+
Classifier: Environment :: Console
|
|
32
|
+
Classifier: Natural Language :: English
|
|
33
|
+
Classifier: Typing :: Typed
|
|
34
|
+
Requires-Python: >=3.8
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: tqdm
|
|
38
|
+
Requires-Dist: matplotlib
|
|
39
|
+
Requires-Dist: numpy==1.26.4
|
|
40
|
+
Provides-Extra: gpu
|
|
41
|
+
Requires-Dist: cupy-cuda12x; extra == "gpu"
|
|
42
|
+
Requires-Dist: numpy==1.26.4; extra == "gpu"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# midiharmony
|
|
46
|
+
## Fast, data‑driven harmony analysis for any MIDI file
|
|
47
|
+
|
|
48
|
+
<img width="1024" height="1024" alt="midiharmony" src="https://github.com/user-attachments/assets/586cf022-ce06-4cea-bfdc-ffcf9b0365d1" />
|
|
49
|
+
|
|
50
|
+
***
|
|
51
|
+
|
|
52
|
+
## Abstract
|
|
53
|
+
|
|
54
|
+
*midiharmony provides fast, stand‑alone harmony analysis for MIDI files by comparing their chord and note relationships against a high‑quality database of extracted chord quads. This approach enables reliable detection of strong harmonic structure, musically coherent progressions, and potential inconsistencies, making it a practical tool for music‑AI pipelines, composition analysis, and large‑scale MIDI processing.*
|
|
55
|
+
|
|
56
|
+
***
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
### Standard processing on CPU (default)
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
!pip install -U midiharmony
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Accelerated processing on GPU
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
!pip install -U midiharmony[gpu]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
***
|
|
73
|
+
|
|
74
|
+
## Basic use examples
|
|
75
|
+
*Using midiharmony is easy and requires just two lines of code :)*
|
|
76
|
+
|
|
77
|
+
### Analyze a single MIDI
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import midiharmony
|
|
81
|
+
|
|
82
|
+
midi_harmony_dict = midiharmony.analyze_midi('Come To My Window.mid')
|
|
83
|
+
|
|
84
|
+
# This code will return a single dictionary with harmony stats
|
|
85
|
+
{'midi_path': 'Come To My Window.mid',
|
|
86
|
+
'midi_name': 'Come To My Window',
|
|
87
|
+
'total_chords_count': 2287,
|
|
88
|
+
'bad_chords_count': 11,
|
|
89
|
+
'grouped_chords_count': 1861,
|
|
90
|
+
'total_quads_count': 1861,
|
|
91
|
+
'unique_quads_count': 644,
|
|
92
|
+
'harmonic_quads_count': 470,
|
|
93
|
+
'harmony_ratio': 0.7298136645962733
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Analyze MIDI folder(s)
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import midiharmony
|
|
101
|
+
|
|
102
|
+
midi_harmony_dicts_list = midiharmony.analyze_midi_folders(['./midi_folder_1',
|
|
103
|
+
'./midi_folder_2',
|
|
104
|
+
'./midi_folder_3'
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
# This code will return a list of dictionaries with harmony stats
|
|
108
|
+
# for all processed MIDIs from all specified folders
|
|
109
|
+
[
|
|
110
|
+
{'midi_path': 'midi_folder_1/Bach Violin 2.mid',
|
|
111
|
+
'midi_name': 'Bach Violin 2',
|
|
112
|
+
'total_chords_count': 1089,
|
|
113
|
+
'bad_chords_count': 71,
|
|
114
|
+
'grouped_chords_count': 1045,
|
|
115
|
+
'total_quads_count': 1045,
|
|
116
|
+
'unique_quads_count': 974,
|
|
117
|
+
'harmonic_quads_count': 867,
|
|
118
|
+
'harmony_ratio': 0.8901437371663244},
|
|
119
|
+
{'midi_path': 'midi_folder_2/Camping at Aylm.mid',
|
|
120
|
+
'midi_name': 'Camping at Aylm',
|
|
121
|
+
'total_chords_count': 417,
|
|
122
|
+
'bad_chords_count': 7,
|
|
123
|
+
'grouped_chords_count': 383,
|
|
124
|
+
'total_quads_count': 383,
|
|
125
|
+
'unique_quads_count': 369,
|
|
126
|
+
'harmonic_quads_count': 326,
|
|
127
|
+
'harmony_ratio': 0.8834688346883469},
|
|
128
|
+
{'midi_path': 'midi_folder_3/Come To My Window.mid',
|
|
129
|
+
'midi_name': 'Come To My Window',
|
|
130
|
+
'total_chords_count': 2287,
|
|
131
|
+
'bad_chords_count': 11,
|
|
132
|
+
'grouped_chords_count': 1861,
|
|
133
|
+
'total_quads_count': 1861,
|
|
134
|
+
'unique_quads_count': 644,
|
|
135
|
+
'harmonic_quads_count': 470,
|
|
136
|
+
'harmony_ratio': 0.7298136645962733}
|
|
137
|
+
]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
***
|
|
141
|
+
|
|
142
|
+
## NOTES
|
|
143
|
+
|
|
144
|
+
* Most important value in each returned midi_harmony_dictionary is the "harmony_ratio"
|
|
145
|
+
* High harmony_ratio (>=0.75) indicates good harmony
|
|
146
|
+
* Exceptional harmony is indicated by harmony_ratio >= 0.9
|
|
147
|
+
|
|
148
|
+
***
|
|
149
|
+
|
|
150
|
+
## midiharmony API reference list
|
|
151
|
+
|
|
152
|
+
```midiharmony.find_quads_fast_cupy```
|
|
153
|
+
*Count matching 4‑chord rows between two arrays using a GPU‑accelerated FNV‑1a hash.*
|
|
154
|
+
|
|
155
|
+
```midiharmony.find_quads_fast_numpy```
|
|
156
|
+
*Count matching 4‑chord rows between two arrays using NumPy on CPU.*
|
|
157
|
+
|
|
158
|
+
```midiharmony.get_trg_array```
|
|
159
|
+
*Load and cache the target harmonic‑quad array in NumPy or CuPy form.*
|
|
160
|
+
|
|
161
|
+
```midiharmony.process_midi```
|
|
162
|
+
*Extract chords, grouped chords, and unique 4‑chord quads from a MIDI file.*
|
|
163
|
+
|
|
164
|
+
```midiharmony.analyze_processed_midi```
|
|
165
|
+
*Compare extracted quads against the target database and compute harmony metrics.*
|
|
166
|
+
|
|
167
|
+
```midiharmony.analyze_midi```
|
|
168
|
+
*Run the full pipeline: process a MIDI file and evaluate its harmonic quality.*
|
|
169
|
+
|
|
170
|
+
```midiharmony.analyze_midi_folders```
|
|
171
|
+
*Batch‑analyze all MIDI files in one or more folders and return harmony reports.*
|
|
172
|
+
|
|
173
|
+
```midiharmony.helpers.get_package_data```
|
|
174
|
+
*Return a sorted list of packaged `.npz` data files with their resolved paths.*
|
|
175
|
+
|
|
176
|
+
```midiharmony.helpers.get_normalized_midi_md5_hash```
|
|
177
|
+
*Compute original and normalized MD5 hashes for any MIDI file.*
|
|
178
|
+
|
|
179
|
+
```midiharmony.helpers.normalize_midi_file```
|
|
180
|
+
*Normalize a MIDI file and write the normalized version to disk.*
|
|
181
|
+
|
|
182
|
+
```midiharmony.helpers.is_installed```
|
|
183
|
+
*Check whether a Debian package is installed using `dpkg-query`.*
|
|
184
|
+
|
|
185
|
+
```midiharmony.helpers._run_apt_get```
|
|
186
|
+
*Internal helper to run `apt-get` commands with consistent flags and timeout.*
|
|
187
|
+
|
|
188
|
+
```midiharmony.helpers.install_apt_package```
|
|
189
|
+
*Idempotently install an apt package with retries, optional sudo, and optional python‑apt.*
|
|
190
|
+
|
|
191
|
+
***
|
|
192
|
+
|
|
193
|
+
### Project Los Angeles
|
|
194
|
+
### Tegridy Code 2026
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
midiharmony/MIDI.py,sha256=kANk5j1-EMnW0cWlrKQca5A-KM7oy0BtE5eYUKPuG-k,69614
|
|
2
|
-
midiharmony/README.md,sha256
|
|
2
|
+
midiharmony/README.md,sha256=PzIQOOQvwNK41JqEvTviD0e0EatpNPdDe_nw-ooOEL0,4686
|
|
3
3
|
midiharmony/TMIDIX.py,sha256=KhSxEMs8tB0PXOHcBRbYqa63kgDBxAsvyFa9oAO2bu0,548390
|
|
4
4
|
midiharmony/__init__.py,sha256=7gSewe5Li5yGW1RHy3H5fupLaL2NmXNgkpFpzSqMx2E,50
|
|
5
5
|
midiharmony/helpers.py,sha256=xc6RWrNz_T2HXWJlzRDDgvKb-zexNJKpmRrAIZ9Obtc,8886
|
|
6
6
|
midiharmony/midi_to_colab_audio.py,sha256=dP-YMJVrCN_orOs3iYAmlRMwlHOzme3irahnLubJohw,145302
|
|
7
|
-
midiharmony/midiharmony.py,sha256=
|
|
7
|
+
midiharmony/midiharmony.py,sha256=uYthvc-DFq43Mo3HJsoD0YxLRDse5ACaMYjG5tkHhWo,20872
|
|
8
8
|
midiharmony/artwork/Project-Los-Angeles.png,sha256=6SBoK-YgXOo7mluLjS_MH0KZljkhd5-9N0s3sMbax4E,989875
|
|
9
9
|
midiharmony/artwork/README.md,sha256=_7QXLfyPNFWzs96MnoPecTJNWigEfSr3uWRnG6uZjk4,159
|
|
10
10
|
midiharmony/artwork/Tegridy-Code-2026.png,sha256=gzDtGxeTNFvQPKzP6isxXtYIdMN7RbkTlcE6xywc_S8,594966
|
|
@@ -13,8 +13,8 @@ midiharmony/artwork/midiharmony.png,sha256=7TwKQScUh8D8-ZKEnlOO8gFSSnmXICYC3XvHN
|
|
|
13
13
|
midiharmony/data/README.md,sha256=vpxZ5ZERJeO9HkmEI91DqtvTKnFhxfwnrVI18prYEtg,694
|
|
14
14
|
midiharmony/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
midiharmony/data/all_harmonic_chords_quads.npz,sha256=-oxuq8KbyAS1CfEo-Ud1Dr6WQgAJfEMMPthaPlGRODk,37146464
|
|
16
|
-
midiharmony-26.1.
|
|
17
|
-
midiharmony-26.1.
|
|
18
|
-
midiharmony-26.1.
|
|
19
|
-
midiharmony-26.1.
|
|
20
|
-
midiharmony-26.1.
|
|
16
|
+
midiharmony-26.1.30.dist-info/licenses/LICENSE,sha256=Uar9eD8GksWLFWX8YWqJDo8uIstPH1at58jnS_FEDZw,11364
|
|
17
|
+
midiharmony-26.1.30.dist-info/METADATA,sha256=f_g76NHjkO5HIxkH5_kUx1sjiUuhXjxz5az5r-hvh3U,7062
|
|
18
|
+
midiharmony-26.1.30.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
19
|
+
midiharmony-26.1.30.dist-info/top_level.txt,sha256=5SwvhlIS9lrTsXPh_XuuIkfxNmPMbPqOk0CVQz9eMog,12
|
|
20
|
+
midiharmony-26.1.30.dist-info/RECORD,,
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: midiharmony
|
|
3
|
-
Version: 26.1.27
|
|
4
|
-
Summary: Fast, data‑driven harmony analysis for any MIDI file
|
|
5
|
-
Author-email: Alex Lev <alexlev61@proton.me>
|
|
6
|
-
Maintainer-email: Alex Lev <alexlev61@proton.me>
|
|
7
|
-
License-Expression: Apache-2.0
|
|
8
|
-
Project-URL: Homepage, https://github.com/asigalov61/midiharmony
|
|
9
|
-
Project-URL: SoundCloud, https://soundcloud.com/aleksandr-sigalov-61/
|
|
10
|
-
Project-URL: Documentation, https://github.com/asigalov61/midiharmony
|
|
11
|
-
Project-URL: Issues, https://github.com/asigalov61/midiharmony/issues
|
|
12
|
-
Project-URL: Discussions, https://github.com/asigalov61/midiharmony/discussions
|
|
13
|
-
Project-URL: Dataset, https://huggingface.co/datasets/projectlosangeles/Discover-MIDI-Dataset
|
|
14
|
-
Keywords: MIDI,MIDI harmony,harmony,symbolic music,music,music harmony,harmony analysis,harmonic analysis,music information retrieval,MIR,music discovery,music search,music quality,music metadata,music recommendation,research,musicology,music analysis
|
|
15
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
16
|
-
Classifier: Intended Audience :: Developers
|
|
17
|
-
Classifier: Intended Audience :: Science/Research
|
|
18
|
-
Classifier: Intended Audience :: Education
|
|
19
|
-
Classifier: Operating System :: OS Independent
|
|
20
|
-
Classifier: Programming Language :: Python :: 3
|
|
21
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
24
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
-
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
28
|
-
Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
|
|
29
|
-
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
30
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
31
|
-
Classifier: Environment :: Console
|
|
32
|
-
Classifier: Natural Language :: English
|
|
33
|
-
Classifier: Typing :: Typed
|
|
34
|
-
Requires-Python: >=3.8
|
|
35
|
-
Description-Content-Type: text/markdown
|
|
36
|
-
License-File: LICENSE
|
|
37
|
-
Requires-Dist: tqdm
|
|
38
|
-
Requires-Dist: matplotlib
|
|
39
|
-
Requires-Dist: midirenderer
|
|
40
|
-
Requires-Dist: cupy-cuda12x
|
|
41
|
-
Requires-Dist: numpy==1.26.4
|
|
42
|
-
Dynamic: license-file
|
|
43
|
-
|
|
44
|
-
# midiharmony
|
|
45
|
-
## Fast, data‑driven harmony analysis for any MIDI file
|
|
46
|
-
|
|
47
|
-
<img width="1024" height="1024" alt="midiharmony" src="https://github.com/user-attachments/assets/586cf022-ce06-4cea-bfdc-ffcf9b0365d1" />
|
|
48
|
-
|
|
49
|
-
***
|
|
50
|
-
|
|
51
|
-
## Abstract
|
|
52
|
-
|
|
53
|
-
*midiharmony provides fast, stand‑alone harmony analysis for MIDI files by comparing their chord and note relationships against a high‑quality database of extracted chord quads. This approach enables reliable detection of strong harmonic structure, musically coherent progressions, and potential inconsistencies, making it a practical tool for music‑AI pipelines, composition analysis, and large‑scale MIDI processing.*
|
|
54
|
-
|
|
55
|
-
***
|
|
56
|
-
|
|
57
|
-
## Install
|
|
58
|
-
|
|
59
|
-
```sh
|
|
60
|
-
!pip install -U midiharmony
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
***
|
|
64
|
-
|
|
65
|
-
## Basic use example
|
|
66
|
-
|
|
67
|
-
```python
|
|
68
|
-
import midiharmony
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Project Los Angeles
|
|
72
|
-
### Tegridy Code 2026
|
|
File without changes
|
|
File without changes
|
|
File without changes
|