Spaces:
Running
on
Zero
Running
on
Zero
Upload 2 files
Browse files- TMIDIX.py +1442 -19
- midi_to_colab_audio.py +775 -228
TMIDIX.py
CHANGED
|
@@ -51,7 +51,7 @@ r'''############################################################################
|
|
| 51 |
|
| 52 |
###################################################################################
|
| 53 |
|
| 54 |
-
__version__ = "25.
|
| 55 |
|
| 56 |
print('=' * 70)
|
| 57 |
print('TMIDIX Python module')
|
|
@@ -1485,10 +1485,13 @@ import multiprocessing
|
|
| 1485 |
|
| 1486 |
from itertools import zip_longest
|
| 1487 |
from itertools import groupby
|
|
|
|
|
|
|
| 1488 |
|
| 1489 |
from collections import Counter
|
| 1490 |
from collections import defaultdict
|
| 1491 |
from collections import OrderedDict
|
|
|
|
| 1492 |
|
| 1493 |
from operator import itemgetter
|
| 1494 |
|
|
@@ -1498,6 +1501,9 @@ from difflib import SequenceMatcher as SM
|
|
| 1498 |
|
| 1499 |
import statistics
|
| 1500 |
import math
|
|
|
|
|
|
|
|
|
|
| 1501 |
|
| 1502 |
import matplotlib.pyplot as plt
|
| 1503 |
|
|
@@ -3903,7 +3909,8 @@ def chordify_score(score,
|
|
| 3903 |
|
| 3904 |
def fix_monophonic_score_durations(monophonic_score,
|
| 3905 |
min_notes_gap=1,
|
| 3906 |
-
min_notes_dur=1
|
|
|
|
| 3907 |
):
|
| 3908 |
|
| 3909 |
fixed_score = []
|
|
@@ -3918,7 +3925,11 @@ def fix_monophonic_score_durations(monophonic_score,
|
|
| 3918 |
if note[1]+note[2] >= nmt:
|
| 3919 |
note_dur = max(1, nmt-note[1]-min_notes_gap)
|
| 3920 |
else:
|
| 3921 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3922 |
|
| 3923 |
new_note = [note[0], note[1], note_dur] + note[3:]
|
| 3924 |
|
|
@@ -3936,9 +3947,13 @@ def fix_monophonic_score_durations(monophonic_score,
|
|
| 3936 |
nmt = monophonic_score[i+1][0]
|
| 3937 |
|
| 3938 |
if note[0]+note[1] >= nmt:
|
| 3939 |
-
|
| 3940 |
else:
|
| 3941 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3942 |
|
| 3943 |
new_note = [note[0], note_dur] + note[2:]
|
| 3944 |
|
|
@@ -3952,8 +3967,6 @@ def fix_monophonic_score_durations(monophonic_score,
|
|
| 3952 |
|
| 3953 |
###################################################################################
|
| 3954 |
|
| 3955 |
-
from itertools import product
|
| 3956 |
-
|
| 3957 |
ALL_CHORDS = [[0], [7], [5], [9], [2], [4], [11], [10], [8], [6], [3], [1], [0, 9], [2, 5],
|
| 3958 |
[4, 7], [7, 10], [2, 11], [0, 3], [6, 9], [1, 4], [8, 11], [5, 8], [1, 10],
|
| 3959 |
[3, 6], [0, 4], [5, 9], [7, 11], [0, 7], [0, 5], [2, 10], [2, 7], [2, 9],
|
|
@@ -7128,7 +7141,8 @@ def escore_notes_to_binary_matrix(escore_notes,
|
|
| 7128 |
channel=0,
|
| 7129 |
patch=0,
|
| 7130 |
flip_matrix=False,
|
| 7131 |
-
reverse_matrix=False
|
|
|
|
| 7132 |
):
|
| 7133 |
|
| 7134 |
escore = [e for e in escore_notes if e[3] == channel and e[6] == patch]
|
|
@@ -7152,14 +7166,17 @@ def escore_notes_to_binary_matrix(escore_notes,
|
|
| 7152 |
duration = max(1, duration)
|
| 7153 |
chan = max(0, min(15, chan))
|
| 7154 |
pitch = max(0, min(127, pitch))
|
| 7155 |
-
velocity = max(
|
| 7156 |
pat = max(0, min(128, pat))
|
| 7157 |
|
| 7158 |
if channel == chan and patch == pat:
|
| 7159 |
|
| 7160 |
for t in range(time, min(time + duration, time_range)):
|
| 7161 |
-
|
| 7162 |
-
|
|
|
|
|
|
|
|
|
|
| 7163 |
|
| 7164 |
if flip_matrix:
|
| 7165 |
|
|
@@ -7183,7 +7200,8 @@ def escore_notes_to_binary_matrix(escore_notes,
|
|
| 7183 |
def binary_matrix_to_original_escore_notes(binary_matrix,
|
| 7184 |
channel=0,
|
| 7185 |
patch=0,
|
| 7186 |
-
velocity=-1
|
|
|
|
| 7187 |
):
|
| 7188 |
|
| 7189 |
result = []
|
|
@@ -7222,8 +7240,11 @@ def binary_matrix_to_original_escore_notes(binary_matrix,
|
|
| 7222 |
|
| 7223 |
for r in result:
|
| 7224 |
|
| 7225 |
-
if velocity == -1:
|
| 7226 |
-
|
|
|
|
|
|
|
|
|
|
| 7227 |
|
| 7228 |
original_escore_notes.append(['note', r[0], r[1], channel, r[2], vel, patch])
|
| 7229 |
|
|
@@ -8048,7 +8069,7 @@ def solo_piano_escore_notes(escore_notes,
|
|
| 8048 |
keep_drums=False,
|
| 8049 |
):
|
| 8050 |
|
| 8051 |
-
cscore = chordify_score([1000, escore_notes])
|
| 8052 |
|
| 8053 |
sp_escore_notes = []
|
| 8054 |
|
|
@@ -9720,7 +9741,14 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 9720 |
song_name='',
|
| 9721 |
artist_name='',
|
| 9722 |
timings_divider=16,
|
|
|
|
|
|
|
| 9723 |
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9724 |
|
| 9725 |
#==============================================================================
|
| 9726 |
|
|
@@ -9734,6 +9762,9 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 9734 |
|
| 9735 |
elif song_time_min >= 2.5:
|
| 9736 |
song_length = 'long'
|
|
|
|
|
|
|
|
|
|
| 9737 |
|
| 9738 |
#==============================================================================
|
| 9739 |
|
|
@@ -9745,18 +9776,25 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 9745 |
if len(escore_times) == len(set(escore_times)):
|
| 9746 |
comp_type = 'monophonic melody'
|
| 9747 |
ctype = 'melody'
|
|
|
|
| 9748 |
|
| 9749 |
elif len(escore_times) >= len(set(escore_times)) and 1 in Counter(escore_times).values():
|
| 9750 |
comp_type = 'melody and accompaniment'
|
| 9751 |
ctype = 'song'
|
|
|
|
| 9752 |
|
| 9753 |
elif len(escore_times) >= len(set(escore_times)) and 1 not in Counter(escore_times).values():
|
| 9754 |
comp_type = 'accompaniment'
|
| 9755 |
ctype = 'song'
|
|
|
|
| 9756 |
|
| 9757 |
else:
|
| 9758 |
comp_type = 'drum track'
|
| 9759 |
ctype = 'drum track'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9760 |
|
| 9761 |
#==============================================================================
|
| 9762 |
|
|
@@ -9771,6 +9809,13 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 9771 |
nd_patches_counts = Counter([p for p in all_patches if p < 128]).most_common()
|
| 9772 |
|
| 9773 |
dominant_instrument = alpha_str(Number2patch[nd_patches_counts[0][0]])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9774 |
|
| 9775 |
if 128 in patches:
|
| 9776 |
drums_present = True
|
|
@@ -9778,9 +9823,16 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 9778 |
drums_pitches = [e[4] for e in escore_notes if e[3] == 9]
|
| 9779 |
|
| 9780 |
most_common_drums = [alpha_str(Notenum2percussion[p[0]]) for p in Counter(drums_pitches).most_common(3) if p[0] in Notenum2percussion]
|
|
|
|
|
|
|
|
|
|
| 9781 |
|
| 9782 |
else:
|
| 9783 |
drums_present = False
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9784 |
|
| 9785 |
#==============================================================================
|
| 9786 |
|
|
@@ -9790,60 +9842,111 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 9790 |
|
| 9791 |
if pitches:
|
| 9792 |
key = SEMITONES[statistics.mode(pitches) % 12]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9793 |
|
| 9794 |
#==============================================================================
|
| 9795 |
|
| 9796 |
scale = ''
|
| 9797 |
mood = ''
|
| 9798 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9799 |
if pitches:
|
| 9800 |
|
| 9801 |
result = escore_notes_scale(escore_notes)
|
| 9802 |
|
| 9803 |
scale = result[0]
|
| 9804 |
mood = result[1].split(' ')[0].lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9805 |
|
| 9806 |
#==============================================================================
|
| 9807 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9808 |
if pitches:
|
| 9809 |
|
| 9810 |
escore_averages = escore_notes_averages(escore_notes, return_ptcs_and_vels=True)
|
| 9811 |
|
| 9812 |
if escore_averages[0] < (128 / timings_divider):
|
| 9813 |
rythm = 'fast'
|
|
|
|
| 9814 |
|
| 9815 |
elif (128 / timings_divider) <= escore_averages[0] <= (192 / timings_divider):
|
| 9816 |
rythm = 'average'
|
|
|
|
| 9817 |
|
| 9818 |
elif escore_averages[0] > (192 / timings_divider):
|
| 9819 |
rythm = 'slow'
|
|
|
|
| 9820 |
|
| 9821 |
if escore_averages[1] < (256 / timings_divider):
|
| 9822 |
tempo = 'fast'
|
|
|
|
| 9823 |
|
| 9824 |
elif (256 / timings_divider) <= escore_averages[1] <= (384 / timings_divider):
|
| 9825 |
tempo = 'average'
|
|
|
|
| 9826 |
|
| 9827 |
elif escore_averages[1] > (384 / timings_divider):
|
| 9828 |
tempo = 'slow'
|
|
|
|
| 9829 |
|
| 9830 |
if escore_averages[2] < 50:
|
| 9831 |
tone = 'bass'
|
|
|
|
| 9832 |
|
| 9833 |
elif 50 <= escore_averages[2] <= 70:
|
| 9834 |
tone = 'midrange'
|
|
|
|
| 9835 |
|
| 9836 |
elif escore_averages[2] > 70:
|
| 9837 |
tone = 'treble'
|
|
|
|
| 9838 |
|
| 9839 |
if escore_averages[3] < 64:
|
| 9840 |
dynamics = 'quiet'
|
|
|
|
| 9841 |
|
| 9842 |
elif 64 <= escore_averages[3] <= 96:
|
| 9843 |
dynamics = 'average'
|
|
|
|
| 9844 |
|
| 9845 |
elif escore_averages[3] > 96:
|
| 9846 |
dynamics = 'loud'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9847 |
|
| 9848 |
#==============================================================================
|
| 9849 |
|
|
@@ -9851,6 +9954,12 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 9851 |
|
| 9852 |
lead_melodies = []
|
| 9853 |
base_melodies = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9854 |
|
| 9855 |
if mono_melodies:
|
| 9856 |
|
|
@@ -9860,15 +9969,19 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 9860 |
|
| 9861 |
if mel[0] in LEAD_INSTRUMENTS and escore_avgs[3] > 60:
|
| 9862 |
lead_melodies.append([Number2patch[mel[0]], mel[1]])
|
|
|
|
| 9863 |
|
| 9864 |
elif mel[0] in BASE_INSTRUMENTS and escore_avgs[3] <= 60:
|
| 9865 |
base_melodies.append([Number2patch[mel[0]], mel[1]])
|
|
|
|
| 9866 |
|
| 9867 |
if lead_melodies:
|
| 9868 |
lead_melodies.sort(key=lambda x: x[1], reverse=True)
|
|
|
|
| 9869 |
|
| 9870 |
if base_melodies:
|
| 9871 |
base_melodies.sort(key=lambda x: x[1], reverse=True)
|
|
|
|
| 9872 |
|
| 9873 |
#==============================================================================
|
| 9874 |
|
|
@@ -10055,8 +10168,20 @@ def escore_notes_to_text_description(escore_notes,
|
|
| 10055 |
description += '\n'
|
| 10056 |
|
| 10057 |
#==============================================================================
|
| 10058 |
-
|
| 10059 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10060 |
|
| 10061 |
###################################################################################
|
| 10062 |
|
|
@@ -11288,7 +11413,7 @@ def multiprocessing_wrapper(function, data_list, verbose=True):
|
|
| 11288 |
|
| 11289 |
results = []
|
| 11290 |
|
| 11291 |
-
for result in tqdm.tqdm(pool.
|
| 11292 |
total=len(data_list),
|
| 11293 |
disable=not verbose
|
| 11294 |
):
|
|
@@ -13604,6 +13729,1304 @@ PERCUSSION_GROUPS = {
|
|
| 13604 |
|
| 13605 |
###################################################################################
|
| 13606 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13607 |
print('Module loaded!')
|
| 13608 |
print('=' * 70)
|
| 13609 |
print('Enjoy! :)')
|
|
|
|
| 51 |
|
| 52 |
###################################################################################
|
| 53 |
|
| 54 |
+
__version__ = "25.9.22"
|
| 55 |
|
| 56 |
print('=' * 70)
|
| 57 |
print('TMIDIX Python module')
|
|
|
|
| 1485 |
|
| 1486 |
from itertools import zip_longest
|
| 1487 |
from itertools import groupby
|
| 1488 |
+
from itertools import cycle
|
| 1489 |
+
from itertools import product
|
| 1490 |
|
| 1491 |
from collections import Counter
|
| 1492 |
from collections import defaultdict
|
| 1493 |
from collections import OrderedDict
|
| 1494 |
+
from collections import deque
|
| 1495 |
|
| 1496 |
from operator import itemgetter
|
| 1497 |
|
|
|
|
| 1501 |
|
| 1502 |
import statistics
|
| 1503 |
import math
|
| 1504 |
+
from math import gcd
|
| 1505 |
+
|
| 1506 |
+
from functools import reduce
|
| 1507 |
|
| 1508 |
import matplotlib.pyplot as plt
|
| 1509 |
|
|
|
|
| 3909 |
|
| 3910 |
def fix_monophonic_score_durations(monophonic_score,
|
| 3911 |
min_notes_gap=1,
|
| 3912 |
+
min_notes_dur=1,
|
| 3913 |
+
extend_durs=False
|
| 3914 |
):
|
| 3915 |
|
| 3916 |
fixed_score = []
|
|
|
|
| 3925 |
if note[1]+note[2] >= nmt:
|
| 3926 |
note_dur = max(1, nmt-note[1]-min_notes_gap)
|
| 3927 |
else:
|
| 3928 |
+
if extend_durs:
|
| 3929 |
+
note_dur = max(1, nmt-note[1]-min_notes_gap)
|
| 3930 |
+
|
| 3931 |
+
else:
|
| 3932 |
+
note_dur = note[2]
|
| 3933 |
|
| 3934 |
new_note = [note[0], note[1], note_dur] + note[3:]
|
| 3935 |
|
|
|
|
| 3947 |
nmt = monophonic_score[i+1][0]
|
| 3948 |
|
| 3949 |
if note[0]+note[1] >= nmt:
|
| 3950 |
+
note_dur = max(1, nmt-note[0]-min_notes_gap)
|
| 3951 |
else:
|
| 3952 |
+
if extend_durs:
|
| 3953 |
+
note_dur = max(1, nmt-note[0]-min_notes_gap)
|
| 3954 |
+
|
| 3955 |
+
else:
|
| 3956 |
+
note_dur = note[1]
|
| 3957 |
|
| 3958 |
new_note = [note[0], note_dur] + note[2:]
|
| 3959 |
|
|
|
|
| 3967 |
|
| 3968 |
###################################################################################
|
| 3969 |
|
|
|
|
|
|
|
| 3970 |
ALL_CHORDS = [[0], [7], [5], [9], [2], [4], [11], [10], [8], [6], [3], [1], [0, 9], [2, 5],
|
| 3971 |
[4, 7], [7, 10], [2, 11], [0, 3], [6, 9], [1, 4], [8, 11], [5, 8], [1, 10],
|
| 3972 |
[3, 6], [0, 4], [5, 9], [7, 11], [0, 7], [0, 5], [2, 10], [2, 7], [2, 9],
|
|
|
|
| 7141 |
channel=0,
|
| 7142 |
patch=0,
|
| 7143 |
flip_matrix=False,
|
| 7144 |
+
reverse_matrix=False,
|
| 7145 |
+
encode_velocities=False
|
| 7146 |
):
|
| 7147 |
|
| 7148 |
escore = [e for e in escore_notes if e[3] == channel and e[6] == patch]
|
|
|
|
| 7166 |
duration = max(1, duration)
|
| 7167 |
chan = max(0, min(15, chan))
|
| 7168 |
pitch = max(0, min(127, pitch))
|
| 7169 |
+
velocity = max(1, min(127, velocity))
|
| 7170 |
pat = max(0, min(128, pat))
|
| 7171 |
|
| 7172 |
if channel == chan and patch == pat:
|
| 7173 |
|
| 7174 |
for t in range(time, min(time + duration, time_range)):
|
| 7175 |
+
if encode_velocities:
|
| 7176 |
+
escore_matrix[t][pitch] = velocity
|
| 7177 |
+
|
| 7178 |
+
else:
|
| 7179 |
+
escore_matrix[t][pitch] = 1
|
| 7180 |
|
| 7181 |
if flip_matrix:
|
| 7182 |
|
|
|
|
| 7200 |
def binary_matrix_to_original_escore_notes(binary_matrix,
|
| 7201 |
channel=0,
|
| 7202 |
patch=0,
|
| 7203 |
+
velocity=-1,
|
| 7204 |
+
decode_velocities=False
|
| 7205 |
):
|
| 7206 |
|
| 7207 |
result = []
|
|
|
|
| 7240 |
|
| 7241 |
for r in result:
|
| 7242 |
|
| 7243 |
+
if velocity == -1 and not decode_velocities:
|
| 7244 |
+
vel = max(40, r[2])
|
| 7245 |
+
|
| 7246 |
+
if decode_velocities:
|
| 7247 |
+
vel = r[3]
|
| 7248 |
|
| 7249 |
original_escore_notes.append(['note', r[0], r[1], channel, r[2], vel, patch])
|
| 7250 |
|
|
|
|
| 8069 |
keep_drums=False,
|
| 8070 |
):
|
| 8071 |
|
| 8072 |
+
cscore = chordify_score([1000, copy.deepcopy(escore_notes)])
|
| 8073 |
|
| 8074 |
sp_escore_notes = []
|
| 8075 |
|
|
|
|
| 9741 |
song_name='',
|
| 9742 |
artist_name='',
|
| 9743 |
timings_divider=16,
|
| 9744 |
+
return_feat_dict=False,
|
| 9745 |
+
return_feat_dict_vals=False
|
| 9746 |
):
|
| 9747 |
+
|
| 9748 |
+
#==============================================================================
|
| 9749 |
+
|
| 9750 |
+
feat_dict = {}
|
| 9751 |
+
feat_dict_vals = {}
|
| 9752 |
|
| 9753 |
#==============================================================================
|
| 9754 |
|
|
|
|
| 9762 |
|
| 9763 |
elif song_time_min >= 2.5:
|
| 9764 |
song_length = 'long'
|
| 9765 |
+
|
| 9766 |
+
feat_dict['song_len'] = song_length.capitalize()
|
| 9767 |
+
feat_dict_vals['song_len'] = song_time_min
|
| 9768 |
|
| 9769 |
#==============================================================================
|
| 9770 |
|
|
|
|
| 9776 |
if len(escore_times) == len(set(escore_times)):
|
| 9777 |
comp_type = 'monophonic melody'
|
| 9778 |
ctype = 'melody'
|
| 9779 |
+
ctv = 0
|
| 9780 |
|
| 9781 |
elif len(escore_times) >= len(set(escore_times)) and 1 in Counter(escore_times).values():
|
| 9782 |
comp_type = 'melody and accompaniment'
|
| 9783 |
ctype = 'song'
|
| 9784 |
+
ctv = 1
|
| 9785 |
|
| 9786 |
elif len(escore_times) >= len(set(escore_times)) and 1 not in Counter(escore_times).values():
|
| 9787 |
comp_type = 'accompaniment'
|
| 9788 |
ctype = 'song'
|
| 9789 |
+
ctv = 2
|
| 9790 |
|
| 9791 |
else:
|
| 9792 |
comp_type = 'drum track'
|
| 9793 |
ctype = 'drum track'
|
| 9794 |
+
ctv = 3
|
| 9795 |
+
|
| 9796 |
+
feat_dict['song_type'] = comp_type.capitalize()
|
| 9797 |
+
feat_dict_vals['song_type'] = ctv
|
| 9798 |
|
| 9799 |
#==============================================================================
|
| 9800 |
|
|
|
|
| 9809 |
nd_patches_counts = Counter([p for p in all_patches if p < 128]).most_common()
|
| 9810 |
|
| 9811 |
dominant_instrument = alpha_str(Number2patch[nd_patches_counts[0][0]])
|
| 9812 |
+
|
| 9813 |
+
feat_dict['most_com_instr'] = instruments
|
| 9814 |
+
feat_dict_vals['most_com_instr'] = [p for p in patches if p < 128]
|
| 9815 |
+
|
| 9816 |
+
else:
|
| 9817 |
+
feat_dict['most_com_instr'] = None
|
| 9818 |
+
feat_dict_vals['most_com_instr'] = []
|
| 9819 |
|
| 9820 |
if 128 in patches:
|
| 9821 |
drums_present = True
|
|
|
|
| 9823 |
drums_pitches = [e[4] for e in escore_notes if e[3] == 9]
|
| 9824 |
|
| 9825 |
most_common_drums = [alpha_str(Notenum2percussion[p[0]]) for p in Counter(drums_pitches).most_common(3) if p[0] in Notenum2percussion]
|
| 9826 |
+
|
| 9827 |
+
feat_dict['most_com_drums'] = most_common_drums
|
| 9828 |
+
feat_dict_vals['most_com_drums'] = [p[0] for p in Counter(drums_pitches).most_common(3)]
|
| 9829 |
|
| 9830 |
else:
|
| 9831 |
drums_present = False
|
| 9832 |
+
|
| 9833 |
+
feat_dict['most_com_drums'] = None
|
| 9834 |
+
|
| 9835 |
+
feat_dict_vals['most_com_drums'] = []
|
| 9836 |
|
| 9837 |
#==============================================================================
|
| 9838 |
|
|
|
|
| 9842 |
|
| 9843 |
if pitches:
|
| 9844 |
key = SEMITONES[statistics.mode(pitches) % 12]
|
| 9845 |
+
|
| 9846 |
+
feat_dict['key'] = key.title()
|
| 9847 |
+
feat_dict_vals['key'] = statistics.mode(pitches) % 12
|
| 9848 |
+
|
| 9849 |
+
else:
|
| 9850 |
+
feat_dict['key'] = None
|
| 9851 |
+
feat_dict_vals['key'] = -1
|
| 9852 |
|
| 9853 |
#==============================================================================
|
| 9854 |
|
| 9855 |
scale = ''
|
| 9856 |
mood = ''
|
| 9857 |
|
| 9858 |
+
feat_dict['scale'] = None
|
| 9859 |
+
feat_dict['mood'] = None
|
| 9860 |
+
feat_dict_vals['scale'] = -1
|
| 9861 |
+
feat_dict_vals['mood'] = -1
|
| 9862 |
+
|
| 9863 |
if pitches:
|
| 9864 |
|
| 9865 |
result = escore_notes_scale(escore_notes)
|
| 9866 |
|
| 9867 |
scale = result[0]
|
| 9868 |
mood = result[1].split(' ')[0].lower()
|
| 9869 |
+
|
| 9870 |
+
feat_dict['scale'] = scale.title()
|
| 9871 |
+
feat_dict['mood'] = mood.title()
|
| 9872 |
+
|
| 9873 |
+
res = escore_notes_scale(escore_notes, return_scale_indexes=True)
|
| 9874 |
+
feat_dict_vals['scale'] = res[0]
|
| 9875 |
+
feat_dict_vals['mood'] = res[1]
|
| 9876 |
|
| 9877 |
#==============================================================================
|
| 9878 |
+
|
| 9879 |
+
feat_dict['rythm'] = None
|
| 9880 |
+
feat_dict['tempo'] = None
|
| 9881 |
+
feat_dict['tone'] = None
|
| 9882 |
+
feat_dict['dynamics'] = None
|
| 9883 |
+
|
| 9884 |
+
feat_dict_vals['rythm'] = -1
|
| 9885 |
+
feat_dict_vals['tempo'] = -1
|
| 9886 |
+
feat_dict_vals['tone'] = -1
|
| 9887 |
+
feat_dict_vals['dynamics'] = -1
|
| 9888 |
+
|
| 9889 |
if pitches:
|
| 9890 |
|
| 9891 |
escore_averages = escore_notes_averages(escore_notes, return_ptcs_and_vels=True)
|
| 9892 |
|
| 9893 |
if escore_averages[0] < (128 / timings_divider):
|
| 9894 |
rythm = 'fast'
|
| 9895 |
+
ryv = 0
|
| 9896 |
|
| 9897 |
elif (128 / timings_divider) <= escore_averages[0] <= (192 / timings_divider):
|
| 9898 |
rythm = 'average'
|
| 9899 |
+
ryv = 1
|
| 9900 |
|
| 9901 |
elif escore_averages[0] > (192 / timings_divider):
|
| 9902 |
rythm = 'slow'
|
| 9903 |
+
ryv = 2
|
| 9904 |
|
| 9905 |
if escore_averages[1] < (256 / timings_divider):
|
| 9906 |
tempo = 'fast'
|
| 9907 |
+
tev = 0
|
| 9908 |
|
| 9909 |
elif (256 / timings_divider) <= escore_averages[1] <= (384 / timings_divider):
|
| 9910 |
tempo = 'average'
|
| 9911 |
+
tev = 1
|
| 9912 |
|
| 9913 |
elif escore_averages[1] > (384 / timings_divider):
|
| 9914 |
tempo = 'slow'
|
| 9915 |
+
tev = 2
|
| 9916 |
|
| 9917 |
if escore_averages[2] < 50:
|
| 9918 |
tone = 'bass'
|
| 9919 |
+
tov = 0
|
| 9920 |
|
| 9921 |
elif 50 <= escore_averages[2] <= 70:
|
| 9922 |
tone = 'midrange'
|
| 9923 |
+
tov = 1
|
| 9924 |
|
| 9925 |
elif escore_averages[2] > 70:
|
| 9926 |
tone = 'treble'
|
| 9927 |
+
tov = 2
|
| 9928 |
|
| 9929 |
if escore_averages[3] < 64:
|
| 9930 |
dynamics = 'quiet'
|
| 9931 |
+
dyn = 0
|
| 9932 |
|
| 9933 |
elif 64 <= escore_averages[3] <= 96:
|
| 9934 |
dynamics = 'average'
|
| 9935 |
+
dyn = 1
|
| 9936 |
|
| 9937 |
elif escore_averages[3] > 96:
|
| 9938 |
dynamics = 'loud'
|
| 9939 |
+
dyn = 2
|
| 9940 |
+
|
| 9941 |
+
feat_dict['rythm'] = rythm.title()
|
| 9942 |
+
feat_dict['tempo'] = tempo.title()
|
| 9943 |
+
feat_dict['tone'] = tone.title()
|
| 9944 |
+
feat_dict['dynamics'] = dynamics.title()
|
| 9945 |
+
|
| 9946 |
+
feat_dict_vals['rythm'] = ryv
|
| 9947 |
+
feat_dict_vals['tempo'] = tev
|
| 9948 |
+
feat_dict_vals['tone'] = tov
|
| 9949 |
+
feat_dict_vals['dynamics'] = dyn
|
| 9950 |
|
| 9951 |
#==============================================================================
|
| 9952 |
|
|
|
|
| 9954 |
|
| 9955 |
lead_melodies = []
|
| 9956 |
base_melodies = []
|
| 9957 |
+
|
| 9958 |
+
feat_dict['lead_mono_mels'] = None
|
| 9959 |
+
feat_dict['base_mono_mels'] = None
|
| 9960 |
+
|
| 9961 |
+
feat_dict_vals['lead_mono_mels'] = []
|
| 9962 |
+
feat_dict_vals['base_mono_mels'] = []
|
| 9963 |
|
| 9964 |
if mono_melodies:
|
| 9965 |
|
|
|
|
| 9969 |
|
| 9970 |
if mel[0] in LEAD_INSTRUMENTS and escore_avgs[3] > 60:
|
| 9971 |
lead_melodies.append([Number2patch[mel[0]], mel[1]])
|
| 9972 |
+
feat_dict_vals['lead_mono_mels'].append(mel[0])
|
| 9973 |
|
| 9974 |
elif mel[0] in BASE_INSTRUMENTS and escore_avgs[3] <= 60:
|
| 9975 |
base_melodies.append([Number2patch[mel[0]], mel[1]])
|
| 9976 |
+
feat_dict_vals['base_mono_mels'].append(mel[0])
|
| 9977 |
|
| 9978 |
if lead_melodies:
|
| 9979 |
lead_melodies.sort(key=lambda x: x[1], reverse=True)
|
| 9980 |
+
feat_dict['lead_mono_mels'] = lead_melodies
|
| 9981 |
|
| 9982 |
if base_melodies:
|
| 9983 |
base_melodies.sort(key=lambda x: x[1], reverse=True)
|
| 9984 |
+
feat_dict['base_mono_mels'] = base_melodies
|
| 9985 |
|
| 9986 |
#==============================================================================
|
| 9987 |
|
|
|
|
| 10168 |
description += '\n'
|
| 10169 |
|
| 10170 |
#==============================================================================
|
| 10171 |
+
|
| 10172 |
+
final_feat_dict = []
|
| 10173 |
+
|
| 10174 |
+
if return_feat_dict:
|
| 10175 |
+
final_feat_dict.append(feat_dict)
|
| 10176 |
+
|
| 10177 |
+
if return_feat_dict_vals:
|
| 10178 |
+
final_feat_dict.append(feat_dict_vals)
|
| 10179 |
+
|
| 10180 |
+
if return_feat_dict or return_feat_dict_vals:
|
| 10181 |
+
return final_feat_dict
|
| 10182 |
+
|
| 10183 |
+
else:
|
| 10184 |
+
return description
|
| 10185 |
|
| 10186 |
###################################################################################
|
| 10187 |
|
|
|
|
| 11413 |
|
| 11414 |
results = []
|
| 11415 |
|
| 11416 |
+
for result in tqdm.tqdm(pool.imap(function, data_list),
|
| 11417 |
total=len(data_list),
|
| 11418 |
disable=not verbose
|
| 11419 |
):
|
|
|
|
| 13729 |
|
| 13730 |
###################################################################################
|
| 13731 |
|
| 13732 |
+
def escore_notes_to_expanded_binary_matrix(escore_notes,
|
| 13733 |
+
channel=0,
|
| 13734 |
+
patch=0,
|
| 13735 |
+
flip_matrix=False,
|
| 13736 |
+
reverse_matrix=False,
|
| 13737 |
+
encode_velocities=True
|
| 13738 |
+
):
|
| 13739 |
+
|
| 13740 |
+
escore = [e for e in escore_notes if e[3] == channel and e[6] == patch]
|
| 13741 |
+
|
| 13742 |
+
if escore:
|
| 13743 |
+
last_time = escore[-1][1]
|
| 13744 |
+
last_notes = [e for e in escore if e[1] == last_time]
|
| 13745 |
+
max_last_dur = max([e[2] for e in last_notes])
|
| 13746 |
+
|
| 13747 |
+
time_range = last_time+max_last_dur
|
| 13748 |
+
|
| 13749 |
+
escore_matrix = []
|
| 13750 |
+
|
| 13751 |
+
escore_matrix = [[(0, 0)] * 128 for _ in range(time_range)]
|
| 13752 |
+
|
| 13753 |
+
for note in escore:
|
| 13754 |
+
|
| 13755 |
+
etype, time, duration, chan, pitch, velocity, pat = note
|
| 13756 |
+
|
| 13757 |
+
time = max(0, time)
|
| 13758 |
+
duration = max(1, duration)
|
| 13759 |
+
chan = max(0, min(15, chan))
|
| 13760 |
+
pitch = max(0, min(127, pitch))
|
| 13761 |
+
velocity = max(1, min(127, velocity))
|
| 13762 |
+
pat = max(0, min(128, pat))
|
| 13763 |
+
|
| 13764 |
+
if channel == chan and patch == pat:
|
| 13765 |
+
|
| 13766 |
+
count = 0
|
| 13767 |
+
|
| 13768 |
+
for t in range(time, min(time + duration, time_range)):
|
| 13769 |
+
if encode_velocities:
|
| 13770 |
+
escore_matrix[t][pitch] = velocity, count
|
| 13771 |
+
|
| 13772 |
+
else:
|
| 13773 |
+
escore_matrix[t][pitch] = 1, count
|
| 13774 |
+
count += 1
|
| 13775 |
+
|
| 13776 |
+
if flip_matrix:
|
| 13777 |
+
|
| 13778 |
+
temp_matrix = []
|
| 13779 |
+
|
| 13780 |
+
for m in escore_matrix:
|
| 13781 |
+
temp_matrix.append(m[::-1])
|
| 13782 |
+
|
| 13783 |
+
escore_matrix = temp_matrix
|
| 13784 |
+
|
| 13785 |
+
if reverse_matrix:
|
| 13786 |
+
escore_matrix = escore_matrix[::-1]
|
| 13787 |
+
|
| 13788 |
+
return escore_matrix
|
| 13789 |
+
|
| 13790 |
+
else:
|
| 13791 |
+
return None
|
| 13792 |
+
|
| 13793 |
+
###################################################################################
|
| 13794 |
+
|
| 13795 |
+
def transpose_list(lst):
|
| 13796 |
+
return [list(row) for row in zip(*lst)]
|
| 13797 |
+
|
| 13798 |
+
###################################################################################
|
| 13799 |
+
|
| 13800 |
+
def chunk_list(lst, size):
|
| 13801 |
+
return [lst[i:i + size] for i in range(0, len(lst), size)]
|
| 13802 |
+
|
| 13803 |
+
###################################################################################
|
| 13804 |
+
|
| 13805 |
+
def flip_list_rows(lst):
|
| 13806 |
+
return [row[::-1] for row in lst]
|
| 13807 |
+
|
| 13808 |
+
###################################################################################
|
| 13809 |
+
|
| 13810 |
+
def flip_list_columns(lst):
|
| 13811 |
+
return lst[::-1]
|
| 13812 |
+
|
| 13813 |
+
###################################################################################
|
| 13814 |
+
|
| 13815 |
+
def exists(sub, lst):
|
| 13816 |
+
sub_len = len(sub)
|
| 13817 |
+
return any(lst[i:i + sub_len] == sub for i in range(len(lst) - sub_len + 1))
|
| 13818 |
+
|
| 13819 |
+
###################################################################################
|
| 13820 |
+
|
| 13821 |
+
def exists_noncontig(sub, lst):
|
| 13822 |
+
it = iter(lst)
|
| 13823 |
+
return all(x in it for x in sub)
|
| 13824 |
+
|
| 13825 |
+
###################################################################################
|
| 13826 |
+
|
| 13827 |
+
def exists_ratio(sub, lst, ratio):
|
| 13828 |
+
matches = sum(x in set(lst) for x in sub)
|
| 13829 |
+
return matches / len(sub) >= ratio
|
| 13830 |
+
|
| 13831 |
+
###################################################################################
|
| 13832 |
+
|
| 13833 |
+
def top_k_list_value(lst, k, reverse=True):
|
| 13834 |
+
return sorted(lst, reverse=reverse)[k]
|
| 13835 |
+
|
| 13836 |
+
###################################################################################
|
| 13837 |
+
|
| 13838 |
+
def top_k_list_values(lst, k, reverse=True):
|
| 13839 |
+
return sorted(lst, reverse=reverse)[:k]
|
| 13840 |
+
|
| 13841 |
+
###################################################################################
|
| 13842 |
+
|
| 13843 |
+
def concat_rows(lst_A, lst_B):
|
| 13844 |
+
return [a + b for a, b in zip(lst_A, lst_B)]
|
| 13845 |
+
|
| 13846 |
+
###################################################################################
|
| 13847 |
+
|
| 13848 |
+
def concat_cols(lst_A, lst_B):
|
| 13849 |
+
return [[ra + rb for ra, rb in zip(a, b)] for a, b in zip(lst_A, lst_B)]
|
| 13850 |
+
|
| 13851 |
+
###################################################################################
|
| 13852 |
+
|
| 13853 |
+
def chunk_by_threshold_mode(nums, threshold=0, normalize=False):
|
| 13854 |
+
|
| 13855 |
+
if not nums:
|
| 13856 |
+
return []
|
| 13857 |
+
|
| 13858 |
+
chunks = []
|
| 13859 |
+
chunk = []
|
| 13860 |
+
freq = defaultdict(int)
|
| 13861 |
+
max_freq = 0
|
| 13862 |
+
mode_val = None
|
| 13863 |
+
|
| 13864 |
+
def try_add_and_validate(value):
|
| 13865 |
+
|
| 13866 |
+
nonlocal max_freq, mode_val
|
| 13867 |
+
|
| 13868 |
+
chunk.append(value)
|
| 13869 |
+
freq[value] += 1
|
| 13870 |
+
new_max_freq = max_freq
|
| 13871 |
+
candidate_mode = mode_val
|
| 13872 |
+
|
| 13873 |
+
if freq[value] > new_max_freq:
|
| 13874 |
+
new_max_freq = freq[value]
|
| 13875 |
+
candidate_mode = value
|
| 13876 |
+
|
| 13877 |
+
mode = candidate_mode
|
| 13878 |
+
valid = True
|
| 13879 |
+
|
| 13880 |
+
for v in chunk:
|
| 13881 |
+
if abs(v - mode) > threshold:
|
| 13882 |
+
valid = False
|
| 13883 |
+
break
|
| 13884 |
+
|
| 13885 |
+
if not valid:
|
| 13886 |
+
|
| 13887 |
+
chunk.pop()
|
| 13888 |
+
freq[value] -= 1
|
| 13889 |
+
if freq[value] == 0:
|
| 13890 |
+
del freq[value]
|
| 13891 |
+
|
| 13892 |
+
return False
|
| 13893 |
+
|
| 13894 |
+
max_freq = new_max_freq
|
| 13895 |
+
mode_val = mode
|
| 13896 |
+
return True
|
| 13897 |
+
|
| 13898 |
+
for num in nums:
|
| 13899 |
+
if not chunk:
|
| 13900 |
+
chunk.append(num)
|
| 13901 |
+
freq[num] = 1
|
| 13902 |
+
mode_val = num
|
| 13903 |
+
max_freq = 1
|
| 13904 |
+
|
| 13905 |
+
else:
|
| 13906 |
+
if not try_add_and_validate(num):
|
| 13907 |
+
if normalize:
|
| 13908 |
+
normalized_chunk = [mode_val] * len(chunk)
|
| 13909 |
+
chunks.append(normalized_chunk)
|
| 13910 |
+
|
| 13911 |
+
else:
|
| 13912 |
+
chunks.append(chunk[:])
|
| 13913 |
+
|
| 13914 |
+
chunk.clear()
|
| 13915 |
+
freq.clear()
|
| 13916 |
+
|
| 13917 |
+
chunk.append(num)
|
| 13918 |
+
freq[num] = 1
|
| 13919 |
+
mode_val = num
|
| 13920 |
+
max_freq = 1
|
| 13921 |
+
|
| 13922 |
+
if chunk:
|
| 13923 |
+
if normalize:
|
| 13924 |
+
normalized_chunk = [mode_val] * len(chunk)
|
| 13925 |
+
chunks.append(normalized_chunk)
|
| 13926 |
+
|
| 13927 |
+
else:
|
| 13928 |
+
chunks.append(chunk)
|
| 13929 |
+
|
| 13930 |
+
return chunks
|
| 13931 |
+
|
| 13932 |
+
###################################################################################
|
| 13933 |
+
|
| 13934 |
+
def proportional_adjust(values, target_sum, threshold):
|
| 13935 |
+
|
| 13936 |
+
n = len(values)
|
| 13937 |
+
if n == 0:
|
| 13938 |
+
return []
|
| 13939 |
+
|
| 13940 |
+
locked_idx = [i for i, v in enumerate(values) if v < threshold]
|
| 13941 |
+
adj_idx = [i for i in range(n) if i not in locked_idx]
|
| 13942 |
+
|
| 13943 |
+
locked_sum = sum(values[i] for i in locked_idx)
|
| 13944 |
+
adj_original_sum = sum(values[i] for i in adj_idx)
|
| 13945 |
+
adj_target_sum = target_sum - locked_sum
|
| 13946 |
+
|
| 13947 |
+
def _proportional_scale(idxs, original, target):
|
| 13948 |
+
|
| 13949 |
+
scaled_vals = {i: original[i] * (target / sum(original[i] for i in idxs))
|
| 13950 |
+
if sum(original[i] for i in idxs) > 0 else 0
|
| 13951 |
+
for i in idxs}
|
| 13952 |
+
|
| 13953 |
+
floored = {i: math.floor(scaled_vals[i]) for i in idxs}
|
| 13954 |
+
rem = target - sum(floored.values())
|
| 13955 |
+
|
| 13956 |
+
fracs = sorted(
|
| 13957 |
+
((scaled_vals[i] - floored[i], i) for i in idxs),
|
| 13958 |
+
key=lambda x: (x[0], -x[1]),
|
| 13959 |
+
reverse=True
|
| 13960 |
+
)
|
| 13961 |
+
|
| 13962 |
+
for _, idx in fracs[:rem]:
|
| 13963 |
+
floored[idx] += 1
|
| 13964 |
+
|
| 13965 |
+
result = original.copy()
|
| 13966 |
+
|
| 13967 |
+
for i in idxs:
|
| 13968 |
+
result[i] = floored[i]
|
| 13969 |
+
|
| 13970 |
+
return result
|
| 13971 |
+
|
| 13972 |
+
if not adj_idx:
|
| 13973 |
+
if locked_sum == target_sum:
|
| 13974 |
+
return values.copy()
|
| 13975 |
+
|
| 13976 |
+
return _proportional_scale(locked_idx, values, target_sum)
|
| 13977 |
+
|
| 13978 |
+
if adj_target_sum < 0:
|
| 13979 |
+
return _proportional_scale(range(n), values, target_sum)
|
| 13980 |
+
|
| 13981 |
+
if adj_original_sum == 0:
|
| 13982 |
+
base = adj_target_sum // len(adj_idx)
|
| 13983 |
+
rem = adj_target_sum - base * len(adj_idx)
|
| 13984 |
+
result = values.copy()
|
| 13985 |
+
|
| 13986 |
+
for j, idx in enumerate(sorted(adj_idx)):
|
| 13987 |
+
increment = base + (1 if j < rem else 0)
|
| 13988 |
+
result[idx] = values[idx] + increment
|
| 13989 |
+
|
| 13990 |
+
return result
|
| 13991 |
+
|
| 13992 |
+
result = values.copy()
|
| 13993 |
+
scaled = {i: values[i] * (adj_target_sum / adj_original_sum) for i in adj_idx}
|
| 13994 |
+
floored = {i: math.floor(scaled[i]) for i in adj_idx}
|
| 13995 |
+
floor_sum = sum(floored.values())
|
| 13996 |
+
rem = adj_target_sum - floor_sum
|
| 13997 |
+
|
| 13998 |
+
fracs = sorted(
|
| 13999 |
+
((scaled[i] - floored[i], i) for i in adj_idx),
|
| 14000 |
+
key=lambda x: (x[0], -x[1]),
|
| 14001 |
+
reverse=True
|
| 14002 |
+
)
|
| 14003 |
+
|
| 14004 |
+
for _, idx in fracs[:rem]:
|
| 14005 |
+
floored[idx] += 1
|
| 14006 |
+
|
| 14007 |
+
for i in adj_idx:
|
| 14008 |
+
result[i] = floored[i]
|
| 14009 |
+
|
| 14010 |
+
return result
|
| 14011 |
+
|
| 14012 |
+
###################################################################################
|
| 14013 |
+
|
| 14014 |
+
def advanced_align_escore_notes_to_bars(escore_notes,
|
| 14015 |
+
bar_dtime=200,
|
| 14016 |
+
dtimes_adj_thresh=4,
|
| 14017 |
+
min_dur_gap=0
|
| 14018 |
+
):
|
| 14019 |
+
|
| 14020 |
+
#========================================================
|
| 14021 |
+
|
| 14022 |
+
escore_notes = recalculate_score_timings(escore_notes)
|
| 14023 |
+
|
| 14024 |
+
cscore = chordify_score([1000, escore_notes])
|
| 14025 |
+
|
| 14026 |
+
#========================================================
|
| 14027 |
+
|
| 14028 |
+
dtimes = [0] + [min(199, b[1]-a[1]) for a, b in zip(escore_notes[:-1], escore_notes[1:]) if b[1]-a[1] != 0]
|
| 14029 |
+
|
| 14030 |
+
score_times = sorted(set([e[1] for e in escore_notes]))
|
| 14031 |
+
|
| 14032 |
+
#========================================================
|
| 14033 |
+
|
| 14034 |
+
dtimes_chunks = []
|
| 14035 |
+
|
| 14036 |
+
time = 0
|
| 14037 |
+
dtime = []
|
| 14038 |
+
|
| 14039 |
+
for i, dt in enumerate(dtimes):
|
| 14040 |
+
time += dt
|
| 14041 |
+
dtime.append(dt)
|
| 14042 |
+
|
| 14043 |
+
if time >= bar_dtime:
|
| 14044 |
+
dtimes_chunks.append(dtime)
|
| 14045 |
+
|
| 14046 |
+
time = 0
|
| 14047 |
+
dtime = []
|
| 14048 |
+
|
| 14049 |
+
dtimes_chunks.append(dtime)
|
| 14050 |
+
|
| 14051 |
+
#========================================================
|
| 14052 |
+
|
| 14053 |
+
fixed_times = []
|
| 14054 |
+
|
| 14055 |
+
time = 0
|
| 14056 |
+
|
| 14057 |
+
for i, dt in enumerate(dtimes_chunks):
|
| 14058 |
+
|
| 14059 |
+
adj_dt = proportional_adjust(dt,
|
| 14060 |
+
bar_dtime,
|
| 14061 |
+
dtimes_adj_thresh
|
| 14062 |
+
)
|
| 14063 |
+
|
| 14064 |
+
for t in adj_dt:
|
| 14065 |
+
|
| 14066 |
+
time += t
|
| 14067 |
+
|
| 14068 |
+
fixed_times.append(time)
|
| 14069 |
+
|
| 14070 |
+
#========================================================
|
| 14071 |
+
|
| 14072 |
+
output_score = []
|
| 14073 |
+
|
| 14074 |
+
for i, c in enumerate(cscore):
|
| 14075 |
+
|
| 14076 |
+
cc = copy.deepcopy(c)
|
| 14077 |
+
time = fixed_times[i]
|
| 14078 |
+
|
| 14079 |
+
for e in cc:
|
| 14080 |
+
e[1] = time
|
| 14081 |
+
|
| 14082 |
+
output_score.append(e)
|
| 14083 |
+
|
| 14084 |
+
#========================================================
|
| 14085 |
+
|
| 14086 |
+
output_score = fix_escore_notes_durations(output_score,
|
| 14087 |
+
min_notes_gap=min_dur_gap
|
| 14088 |
+
)
|
| 14089 |
+
|
| 14090 |
+
#========================================================
|
| 14091 |
+
|
| 14092 |
+
return output_score
|
| 14093 |
+
|
| 14094 |
+
###################################################################################
|
| 14095 |
+
|
| 14096 |
+
def check_monophonic_melody(escore_notes,
|
| 14097 |
+
times_idx=1,
|
| 14098 |
+
durs_idx=2
|
| 14099 |
+
):
|
| 14100 |
+
|
| 14101 |
+
bcount = 0
|
| 14102 |
+
|
| 14103 |
+
for i in range(len(escore_notes)-1):
|
| 14104 |
+
if escore_notes[i][times_idx]+escore_notes[i][durs_idx] > escore_notes[i+1][times_idx]:
|
| 14105 |
+
bcount += 1
|
| 14106 |
+
|
| 14107 |
+
return bcount / len(escore_notes)
|
| 14108 |
+
|
| 14109 |
+
###################################################################################
|
| 14110 |
+
|
| 14111 |
+
def longest_common_chunk(list1, list2):
|
| 14112 |
+
|
| 14113 |
+
base, mod = 257, 10**9 + 7
|
| 14114 |
+
max_len = min(len(list1), len(list2))
|
| 14115 |
+
|
| 14116 |
+
def get_hashes(seq, size):
|
| 14117 |
+
|
| 14118 |
+
h, power = 0, 1
|
| 14119 |
+
hashes = set()
|
| 14120 |
+
|
| 14121 |
+
for i in range(size):
|
| 14122 |
+
h = (h * base + seq[i]) % mod
|
| 14123 |
+
power = (power * base) % mod
|
| 14124 |
+
|
| 14125 |
+
hashes.add(h)
|
| 14126 |
+
|
| 14127 |
+
for i in range(size, len(seq)):
|
| 14128 |
+
h = (h * base - seq[i - size] * power + seq[i]) % mod
|
| 14129 |
+
hashes.add(h)
|
| 14130 |
+
|
| 14131 |
+
return hashes
|
| 14132 |
+
|
| 14133 |
+
def find_match(size):
|
| 14134 |
+
|
| 14135 |
+
hashes2 = get_hashes(list2, size)
|
| 14136 |
+
h, power = 0, 1
|
| 14137 |
+
|
| 14138 |
+
for i in range(size):
|
| 14139 |
+
h = (h * base + list1[i]) % mod
|
| 14140 |
+
power = (power * base) % mod
|
| 14141 |
+
|
| 14142 |
+
if h in hashes2:
|
| 14143 |
+
return list1[:size]
|
| 14144 |
+
|
| 14145 |
+
for i in range(size, len(list1)):
|
| 14146 |
+
h = (h * base - list1[i - size] * power + list1[i]) % mod
|
| 14147 |
+
if h in hashes2:
|
| 14148 |
+
return list1[i - size + 1:i + 1]
|
| 14149 |
+
|
| 14150 |
+
return []
|
| 14151 |
+
|
| 14152 |
+
left, right = 0, max_len
|
| 14153 |
+
result = []
|
| 14154 |
+
|
| 14155 |
+
while left <= right:
|
| 14156 |
+
mid = (left + right) // 2
|
| 14157 |
+
chunk = find_match(mid)
|
| 14158 |
+
|
| 14159 |
+
if chunk:
|
| 14160 |
+
result = chunk
|
| 14161 |
+
left = mid + 1
|
| 14162 |
+
else:
|
| 14163 |
+
|
| 14164 |
+
right = mid - 1
|
| 14165 |
+
|
| 14166 |
+
return result
|
| 14167 |
+
|
| 14168 |
+
###################################################################################
|
| 14169 |
+
|
| 14170 |
+
def detect_plateaus(data, min_len=2, tol=0.0):
|
| 14171 |
+
|
| 14172 |
+
plateaus = []
|
| 14173 |
+
n = len(data)
|
| 14174 |
+
if n < min_len:
|
| 14175 |
+
return plateaus
|
| 14176 |
+
|
| 14177 |
+
min_deque = deque()
|
| 14178 |
+
max_deque = deque()
|
| 14179 |
+
|
| 14180 |
+
start = 0
|
| 14181 |
+
idx = 0
|
| 14182 |
+
|
| 14183 |
+
while idx < n:
|
| 14184 |
+
v = data[idx]
|
| 14185 |
+
|
| 14186 |
+
if not isinstance(v, (int, float)) or math.isnan(v):
|
| 14187 |
+
|
| 14188 |
+
if idx - start >= min_len:
|
| 14189 |
+
plateaus.append(data[start:idx])
|
| 14190 |
+
|
| 14191 |
+
idx += 1
|
| 14192 |
+
start = idx
|
| 14193 |
+
min_deque.clear()
|
| 14194 |
+
max_deque.clear()
|
| 14195 |
+
|
| 14196 |
+
continue
|
| 14197 |
+
|
| 14198 |
+
while max_deque and data[max_deque[-1]] <= v:
|
| 14199 |
+
max_deque.pop()
|
| 14200 |
+
|
| 14201 |
+
max_deque.append(idx)
|
| 14202 |
+
|
| 14203 |
+
while min_deque and data[min_deque[-1]] >= v:
|
| 14204 |
+
min_deque.pop()
|
| 14205 |
+
|
| 14206 |
+
min_deque.append(idx)
|
| 14207 |
+
|
| 14208 |
+
if data[max_deque[0]] - data[min_deque[0]] > tol:
|
| 14209 |
+
|
| 14210 |
+
if idx - start >= min_len:
|
| 14211 |
+
plateaus.append(data[start:idx])
|
| 14212 |
+
|
| 14213 |
+
start = idx
|
| 14214 |
+
|
| 14215 |
+
min_deque.clear()
|
| 14216 |
+
max_deque.clear()
|
| 14217 |
+
|
| 14218 |
+
max_deque.append(idx)
|
| 14219 |
+
min_deque.append(idx)
|
| 14220 |
+
|
| 14221 |
+
idx += 1
|
| 14222 |
+
|
| 14223 |
+
if n - start >= min_len:
|
| 14224 |
+
plateaus.append(data[start:n])
|
| 14225 |
+
|
| 14226 |
+
return plateaus
|
| 14227 |
+
|
| 14228 |
+
###################################################################################
|
| 14229 |
+
|
| 14230 |
+
def alpha_str_to_toks(s, shift=0, add_seos=False):
|
| 14231 |
+
|
| 14232 |
+
tokens = []
|
| 14233 |
+
|
| 14234 |
+
if add_seos:
|
| 14235 |
+
tokens = [53+shift]
|
| 14236 |
+
|
| 14237 |
+
for char in s:
|
| 14238 |
+
if char == ' ':
|
| 14239 |
+
tokens.append(52+shift)
|
| 14240 |
+
|
| 14241 |
+
elif char.isalpha():
|
| 14242 |
+
base = 0 if char.isupper() else 26
|
| 14243 |
+
offset = ord(char.upper()) - ord('A')
|
| 14244 |
+
token = (base + offset + shift) % 52 # wrap A–Z/a–z
|
| 14245 |
+
tokens.append(token)
|
| 14246 |
+
|
| 14247 |
+
if add_seos:
|
| 14248 |
+
tokens.append(53+shift)
|
| 14249 |
+
|
| 14250 |
+
return tokens
|
| 14251 |
+
|
| 14252 |
+
###################################################################################
|
| 14253 |
+
|
| 14254 |
+
def toks_to_alpha_str(tokens, shift=0, sep=''):
|
| 14255 |
+
|
| 14256 |
+
chars = []
|
| 14257 |
+
|
| 14258 |
+
for token in tokens:
|
| 14259 |
+
if token == 53+shift:
|
| 14260 |
+
continue
|
| 14261 |
+
|
| 14262 |
+
elif token == 52+shift:
|
| 14263 |
+
chars.append(' ')
|
| 14264 |
+
|
| 14265 |
+
elif 0 <= token <= 25:
|
| 14266 |
+
original = (token - shift) % 52
|
| 14267 |
+
chars.append(chr(ord('A') + original))
|
| 14268 |
+
|
| 14269 |
+
elif 26 <= token <= 51:
|
| 14270 |
+
original = (token - shift) % 52
|
| 14271 |
+
chars.append(chr(ord('a') + (original - 26)))
|
| 14272 |
+
|
| 14273 |
+
return sep.join(chars)
|
| 14274 |
+
|
| 14275 |
+
###################################################################################
|
| 14276 |
+
|
| 14277 |
+
def insert_caps_newlines(text):
|
| 14278 |
+
|
| 14279 |
+
if bool(re.search(r'\b[A-Z][a-z]+\b', text)):
|
| 14280 |
+
pattern = re.compile(r'\s+(?=[A-Z])')
|
| 14281 |
+
|
| 14282 |
+
return pattern.sub('\n', text)
|
| 14283 |
+
|
| 14284 |
+
###################################################################################
|
| 14285 |
+
|
| 14286 |
+
def insert_newlines(text, every=4):
|
| 14287 |
+
|
| 14288 |
+
count = 0
|
| 14289 |
+
result = []
|
| 14290 |
+
|
| 14291 |
+
for char in text:
|
| 14292 |
+
result.append(char)
|
| 14293 |
+
|
| 14294 |
+
if char == '\n':
|
| 14295 |
+
count += 1
|
| 14296 |
+
|
| 14297 |
+
if count % every == 0:
|
| 14298 |
+
result.append('\n')
|
| 14299 |
+
|
| 14300 |
+
return ''.join(result)
|
| 14301 |
+
|
| 14302 |
+
###################################################################################
|
| 14303 |
+
|
| 14304 |
+
def symmetric_match_ratio(list_a, list_b, threshold=0):
|
| 14305 |
+
|
| 14306 |
+
a_sorted = sorted(list_a)
|
| 14307 |
+
b_sorted = sorted(list_b)
|
| 14308 |
+
|
| 14309 |
+
i, j = 0, 0
|
| 14310 |
+
matches = 0
|
| 14311 |
+
|
| 14312 |
+
used_a = set()
|
| 14313 |
+
used_b = set()
|
| 14314 |
+
|
| 14315 |
+
while i < len(a_sorted) and j < len(b_sorted):
|
| 14316 |
+
diff = abs(a_sorted[i] - b_sorted[j])
|
| 14317 |
+
|
| 14318 |
+
if diff <= threshold:
|
| 14319 |
+
matches += 1
|
| 14320 |
+
used_a.add(i)
|
| 14321 |
+
used_b.add(j)
|
| 14322 |
+
i += 1
|
| 14323 |
+
j += 1
|
| 14324 |
+
|
| 14325 |
+
elif a_sorted[i] < b_sorted[j]:
|
| 14326 |
+
i += 1
|
| 14327 |
+
|
| 14328 |
+
else:
|
| 14329 |
+
j += 1
|
| 14330 |
+
|
| 14331 |
+
avg_len = (len(list_a) + len(list_b)) / 2
|
| 14332 |
+
|
| 14333 |
+
return matches / avg_len if avg_len > 0 else 0.0
|
| 14334 |
+
|
| 14335 |
+
###################################################################################
|
| 14336 |
+
|
| 14337 |
+
def escore_notes_to_chords(escore_notes,
|
| 14338 |
+
use_full_chords=False,
|
| 14339 |
+
repair_bad_chords=True,
|
| 14340 |
+
skip_pitches=False,
|
| 14341 |
+
convert_pitches=True,
|
| 14342 |
+
shift_chords=False,
|
| 14343 |
+
return_tones_chords=False
|
| 14344 |
+
):
|
| 14345 |
+
|
| 14346 |
+
if use_full_chords:
|
| 14347 |
+
CHORDS = ALL_CHORDS_FULL
|
| 14348 |
+
|
| 14349 |
+
else:
|
| 14350 |
+
CHORDS = ALL_CHORDS_SORTED
|
| 14351 |
+
|
| 14352 |
+
sp_score = solo_piano_escore_notes(escore_notes)
|
| 14353 |
+
|
| 14354 |
+
cscore = chordify_score([1000, sp_score])
|
| 14355 |
+
|
| 14356 |
+
chords = []
|
| 14357 |
+
|
| 14358 |
+
for c in cscore:
|
| 14359 |
+
pitches = sorted(set([e[4] for e in c]))
|
| 14360 |
+
|
| 14361 |
+
tones_chord = sorted(set([p % 12 for p in pitches]))
|
| 14362 |
+
|
| 14363 |
+
if repair_bad_chords:
|
| 14364 |
+
if tones_chord not in CHORDS:
|
| 14365 |
+
tones_chord = check_and_fix_tones_chord(tones_chord,
|
| 14366 |
+
use_full_chords=use_full_chords
|
| 14367 |
+
)
|
| 14368 |
+
|
| 14369 |
+
if return_tones_chords:
|
| 14370 |
+
if convert_pitches:
|
| 14371 |
+
chords.append(tones_chord)
|
| 14372 |
+
|
| 14373 |
+
else:
|
| 14374 |
+
if len(pitches) > 1:
|
| 14375 |
+
chords.append(tones_chord)
|
| 14376 |
+
|
| 14377 |
+
else:
|
| 14378 |
+
chords.append([-pitches[0]])
|
| 14379 |
+
|
| 14380 |
+
else:
|
| 14381 |
+
if skip_pitches:
|
| 14382 |
+
if tones_chord in CHORDS:
|
| 14383 |
+
cho_tok = CHORDS.index(tones_chord)
|
| 14384 |
+
|
| 14385 |
+
else:
|
| 14386 |
+
cho_tok = -1
|
| 14387 |
+
|
| 14388 |
+
if len(pitches) > 1:
|
| 14389 |
+
chords.append(cho_tok)
|
| 14390 |
+
|
| 14391 |
+
else:
|
| 14392 |
+
if tones_chord in CHORDS:
|
| 14393 |
+
cho_tok = CHORDS.index(tones_chord)
|
| 14394 |
+
|
| 14395 |
+
else:
|
| 14396 |
+
cho_tok = -1
|
| 14397 |
+
|
| 14398 |
+
if cho_tok != -1:
|
| 14399 |
+
if convert_pitches:
|
| 14400 |
+
if shift_chords:
|
| 14401 |
+
if len(pitches) > 1:
|
| 14402 |
+
chords.append(cho_tok+12)
|
| 14403 |
+
|
| 14404 |
+
else:
|
| 14405 |
+
chords.append(pitches[0] % 12)
|
| 14406 |
+
|
| 14407 |
+
else:
|
| 14408 |
+
chords.append(cho_tok)
|
| 14409 |
+
|
| 14410 |
+
else:
|
| 14411 |
+
if len(pitches) > 1:
|
| 14412 |
+
chords.append(cho_tok+128)
|
| 14413 |
+
|
| 14414 |
+
else:
|
| 14415 |
+
chords.append(pitches[0])
|
| 14416 |
+
|
| 14417 |
+
return chords
|
| 14418 |
+
|
| 14419 |
+
###################################################################################
|
| 14420 |
+
|
| 14421 |
+
def replace_chords_in_escore_notes(escore_notes,
|
| 14422 |
+
chords_list=[-1],
|
| 14423 |
+
use_full_chords=False,
|
| 14424 |
+
use_shifted_chords=False
|
| 14425 |
+
):
|
| 14426 |
+
|
| 14427 |
+
if use_full_chords:
|
| 14428 |
+
CHORDS = ALL_CHORDS_FULL
|
| 14429 |
+
|
| 14430 |
+
else:
|
| 14431 |
+
CHORDS = ALL_CHORDS_SORTED
|
| 14432 |
+
|
| 14433 |
+
if use_shifted_chords:
|
| 14434 |
+
shift = 12
|
| 14435 |
+
|
| 14436 |
+
else:
|
| 14437 |
+
shift = 0
|
| 14438 |
+
|
| 14439 |
+
if min(chords_list) >= 0 and max(chords_list) <= len(CHORDS)+shift:
|
| 14440 |
+
|
| 14441 |
+
chords_list_iter = cycle(chords_list)
|
| 14442 |
+
|
| 14443 |
+
nd_score = [e for e in escore_notes if e[3] != 9]
|
| 14444 |
+
d_score = [e for e in escore_notes if e[3] == 9]
|
| 14445 |
+
|
| 14446 |
+
cscore = chordify_score([1000, nd_score])
|
| 14447 |
+
|
| 14448 |
+
new_score = []
|
| 14449 |
+
|
| 14450 |
+
for i, c in enumerate(cscore):
|
| 14451 |
+
|
| 14452 |
+
cur_chord = next(chords_list_iter)
|
| 14453 |
+
|
| 14454 |
+
cc = copy.deepcopy(c)
|
| 14455 |
+
|
| 14456 |
+
if use_shifted_chords:
|
| 14457 |
+
if cur_chord < 12:
|
| 14458 |
+
sub_tones_chord = [cur_chord]
|
| 14459 |
+
|
| 14460 |
+
else:
|
| 14461 |
+
sub_tones_chord = CHORDS[cur_chord-12]
|
| 14462 |
+
else:
|
| 14463 |
+
sub_tones_chord = CHORDS[cur_chord]
|
| 14464 |
+
|
| 14465 |
+
stcho = cycle(sub_tones_chord)
|
| 14466 |
+
|
| 14467 |
+
if len(sub_tones_chord) > len(c):
|
| 14468 |
+
cc = [copy.deepcopy(e) for e in cc for _ in range(len(sub_tones_chord))]
|
| 14469 |
+
|
| 14470 |
+
pseen = []
|
| 14471 |
+
|
| 14472 |
+
for e in cc:
|
| 14473 |
+
st = next(stcho)
|
| 14474 |
+
new_pitch = ((e[4] // 12) * 12) + st
|
| 14475 |
+
|
| 14476 |
+
if [new_pitch, e[6]] not in pseen:
|
| 14477 |
+
e[4] = new_pitch
|
| 14478 |
+
|
| 14479 |
+
new_score.append(e)
|
| 14480 |
+
pseen.append([new_pitch, e[6]])
|
| 14481 |
+
|
| 14482 |
+
final_score = sorted(new_score+d_score, key=lambda x: x[1])
|
| 14483 |
+
|
| 14484 |
+
return final_score
|
| 14485 |
+
|
| 14486 |
+
else:
|
| 14487 |
+
return []
|
| 14488 |
+
|
| 14489 |
+
###################################################################################
|
| 14490 |
+
|
| 14491 |
+
class Cell:
|
| 14492 |
+
def __init__(self, cost, segments, gaps, prev_dir):
|
| 14493 |
+
self.cost = cost
|
| 14494 |
+
self.segments = segments
|
| 14495 |
+
self.gaps = gaps
|
| 14496 |
+
self.prev_dir = prev_dir
|
| 14497 |
+
|
| 14498 |
+
def align_integer_lists(seq1, seq2):
|
| 14499 |
+
|
| 14500 |
+
n, m = len(seq1), len(seq2)
|
| 14501 |
+
|
| 14502 |
+
if n == 0:
|
| 14503 |
+
return [None]*m, seq2.copy(), sum(abs(x) for x in seq2)
|
| 14504 |
+
if m == 0:
|
| 14505 |
+
return seq1.copy(), [None]*n, sum(abs(x) for x in seq1)
|
| 14506 |
+
|
| 14507 |
+
priority = {'diag': 0, 'up': 1, 'left': 2}
|
| 14508 |
+
|
| 14509 |
+
dp = [
|
| 14510 |
+
[Cell(cost=math.inf, segments=math.inf, gaps=math.inf, prev_dir='') for _ in range(m+1)]
|
| 14511 |
+
for _ in range(n+1)
|
| 14512 |
+
]
|
| 14513 |
+
dp[0][0] = Cell(cost=0, segments=0, gaps=0, prev_dir='')
|
| 14514 |
+
|
| 14515 |
+
for i in range(1, n+1):
|
| 14516 |
+
prev = dp[i-1][0]
|
| 14517 |
+
new_cost = prev.cost + abs(seq1[i-1])
|
| 14518 |
+
new_seg = prev.segments + (1 if prev.prev_dir != 'up' else 0)
|
| 14519 |
+
new_gaps = prev.gaps + 1
|
| 14520 |
+
dp[i][0] = Cell(new_cost, new_seg, new_gaps, 'up')
|
| 14521 |
+
|
| 14522 |
+
for j in range(1, m+1):
|
| 14523 |
+
prev = dp[0][j-1]
|
| 14524 |
+
new_cost = prev.cost + abs(seq2[j-1])
|
| 14525 |
+
new_seg = prev.segments + (1 if prev.prev_dir != 'left' else 0)
|
| 14526 |
+
new_gaps = prev.gaps + 1
|
| 14527 |
+
dp[0][j] = Cell(new_cost, new_seg, new_gaps, 'left')
|
| 14528 |
+
|
| 14529 |
+
for i in range(1, n+1):
|
| 14530 |
+
for j in range(1, m+1):
|
| 14531 |
+
a, b = seq1[i-1], seq2[j-1]
|
| 14532 |
+
|
| 14533 |
+
c0 = dp[i-1][j-1]
|
| 14534 |
+
cand_diag = Cell(
|
| 14535 |
+
cost = c0.cost + abs(a - b),
|
| 14536 |
+
segments = c0.segments,
|
| 14537 |
+
gaps = c0.gaps,
|
| 14538 |
+
prev_dir = 'diag'
|
| 14539 |
+
)
|
| 14540 |
+
|
| 14541 |
+
c1 = dp[i-1][j]
|
| 14542 |
+
seg1 = c1.segments + (1 if c1.prev_dir != 'up' else 0)
|
| 14543 |
+
cand_up = Cell(
|
| 14544 |
+
cost = c1.cost + abs(a),
|
| 14545 |
+
segments = seg1,
|
| 14546 |
+
gaps = c1.gaps + 1,
|
| 14547 |
+
prev_dir = 'up'
|
| 14548 |
+
)
|
| 14549 |
+
|
| 14550 |
+
c2 = dp[i][j-1]
|
| 14551 |
+
seg2 = c2.segments + (1 if c2.prev_dir != 'left' else 0)
|
| 14552 |
+
cand_left = Cell(
|
| 14553 |
+
cost = c2.cost + abs(b),
|
| 14554 |
+
segments = seg2,
|
| 14555 |
+
gaps = c2.gaps + 1,
|
| 14556 |
+
prev_dir = 'left'
|
| 14557 |
+
)
|
| 14558 |
+
|
| 14559 |
+
best = min(
|
| 14560 |
+
(cand_diag, cand_up, cand_left),
|
| 14561 |
+
key=lambda c: (c.cost, c.segments, c.gaps, priority[c.prev_dir])
|
| 14562 |
+
)
|
| 14563 |
+
dp[i][j] = best
|
| 14564 |
+
|
| 14565 |
+
aligned1 = []
|
| 14566 |
+
aligned2 = []
|
| 14567 |
+
i, j = n, m
|
| 14568 |
+
|
| 14569 |
+
while i > 0 or j > 0:
|
| 14570 |
+
cell = dp[i][j]
|
| 14571 |
+
|
| 14572 |
+
if cell.prev_dir == 'diag':
|
| 14573 |
+
aligned1.append(seq1[i-1])
|
| 14574 |
+
aligned2.append(seq2[j-1])
|
| 14575 |
+
i, j = i-1, j-1
|
| 14576 |
+
|
| 14577 |
+
elif cell.prev_dir == 'up':
|
| 14578 |
+
aligned1.append(seq1[i-1])
|
| 14579 |
+
aligned2.append(None)
|
| 14580 |
+
i -= 1
|
| 14581 |
+
|
| 14582 |
+
else:
|
| 14583 |
+
aligned1.append(None)
|
| 14584 |
+
aligned2.append(seq2[j-1])
|
| 14585 |
+
j -= 1
|
| 14586 |
+
|
| 14587 |
+
aligned1.reverse()
|
| 14588 |
+
aligned2.reverse()
|
| 14589 |
+
|
| 14590 |
+
total_cost = int(dp[n][m].cost)
|
| 14591 |
+
|
| 14592 |
+
return aligned1, aligned2, total_cost
|
| 14593 |
+
|
| 14594 |
+
###################################################################################
|
| 14595 |
+
|
| 14596 |
+
def most_common_delta_time(escore_notes):
|
| 14597 |
+
|
| 14598 |
+
dscore = delta_score_notes(escore_notes)
|
| 14599 |
+
|
| 14600 |
+
dtimes = [t[1] for t in dscore if t[1] != 0]
|
| 14601 |
+
|
| 14602 |
+
cdtime, count = Counter(dtimes).most_common(1)[0]
|
| 14603 |
+
|
| 14604 |
+
return [cdtime, count / len(dtimes)]
|
| 14605 |
+
|
| 14606 |
+
###################################################################################
|
| 14607 |
+
|
| 14608 |
+
def delta_tones(escore_notes,
|
| 14609 |
+
ptcs_idx=4
|
| 14610 |
+
):
|
| 14611 |
+
|
| 14612 |
+
pitches = [p[ptcs_idx] for p in escore_notes]
|
| 14613 |
+
tones = [p % 12 for p in pitches]
|
| 14614 |
+
|
| 14615 |
+
return [b-a for a, b in zip(tones[:-1], tones[1:])]
|
| 14616 |
+
|
| 14617 |
+
###################################################################################
|
| 14618 |
+
|
| 14619 |
+
def find_divisors(val,
|
| 14620 |
+
reverse=False
|
| 14621 |
+
):
|
| 14622 |
+
|
| 14623 |
+
if val == 0:
|
| 14624 |
+
return []
|
| 14625 |
+
|
| 14626 |
+
n = abs(val)
|
| 14627 |
+
divisors = set()
|
| 14628 |
+
|
| 14629 |
+
for i in range(1, int(n**0.5) + 1):
|
| 14630 |
+
if n % i == 0:
|
| 14631 |
+
divisors.add(i)
|
| 14632 |
+
divisors.add(n // i)
|
| 14633 |
+
|
| 14634 |
+
return sorted(divisors, reverse=reverse)
|
| 14635 |
+
|
| 14636 |
+
###################################################################################
|
| 14637 |
+
|
| 14638 |
+
def find_common_divisors(values,
|
| 14639 |
+
reverse=False
|
| 14640 |
+
):
|
| 14641 |
+
|
| 14642 |
+
if not values:
|
| 14643 |
+
return []
|
| 14644 |
+
|
| 14645 |
+
non_zero = [abs(v) for v in values if v != 0]
|
| 14646 |
+
if not non_zero:
|
| 14647 |
+
return []
|
| 14648 |
+
|
| 14649 |
+
overall_gcd = reduce(gcd, non_zero)
|
| 14650 |
+
|
| 14651 |
+
divisors = set()
|
| 14652 |
+
|
| 14653 |
+
for i in range(1, int(overall_gcd**0.5) + 1):
|
| 14654 |
+
if overall_gcd % i == 0:
|
| 14655 |
+
divisors.add(i)
|
| 14656 |
+
divisors.add(overall_gcd // i)
|
| 14657 |
+
|
| 14658 |
+
return sorted(divisors, reverse=reverse)
|
| 14659 |
+
|
| 14660 |
+
###################################################################################
|
| 14661 |
+
|
| 14662 |
+
def strings_dict(list_of_strings,
|
| 14663 |
+
verbose=False
|
| 14664 |
+
):
|
| 14665 |
+
|
| 14666 |
+
str_set = set()
|
| 14667 |
+
|
| 14668 |
+
for st in tqdm.tqdm(list_of_strings, disable=not verbose):
|
| 14669 |
+
for cha in st:
|
| 14670 |
+
str_set.add(cha)
|
| 14671 |
+
|
| 14672 |
+
str_lst = sorted(str_set)
|
| 14673 |
+
|
| 14674 |
+
str_dic = dict(zip(str_lst, range(len(str_lst))))
|
| 14675 |
+
rev_str_dic = {v: k for k, v in str_dic.items()}
|
| 14676 |
+
|
| 14677 |
+
return str_dic, rev_str_dic
|
| 14678 |
+
|
| 14679 |
+
###################################################################################
|
| 14680 |
+
|
| 14681 |
+
def chords_common_tones_chain(chords,
|
| 14682 |
+
use_full_chords=False
|
| 14683 |
+
):
|
| 14684 |
+
|
| 14685 |
+
if use_full_chords:
|
| 14686 |
+
CHORDS = ALL_CHORDS_FULL
|
| 14687 |
+
|
| 14688 |
+
else:
|
| 14689 |
+
CHORDS = ALL_CHORDS_SORTED
|
| 14690 |
+
|
| 14691 |
+
tones_chords = [CHORDS[c] for c in chords if 0 <= c < len(CHORDS)]
|
| 14692 |
+
|
| 14693 |
+
n = len(tones_chords)
|
| 14694 |
+
|
| 14695 |
+
if not tones_chords:
|
| 14696 |
+
return []
|
| 14697 |
+
|
| 14698 |
+
if n < 2:
|
| 14699 |
+
return tones_chords
|
| 14700 |
+
|
| 14701 |
+
result = []
|
| 14702 |
+
|
| 14703 |
+
for i in range(n):
|
| 14704 |
+
if i == 0:
|
| 14705 |
+
common = set(tones_chords[0]) & set(tones_chords[1])
|
| 14706 |
+
|
| 14707 |
+
elif i == n - 1:
|
| 14708 |
+
common = set(tones_chords[n - 2]) & set(tones_chords[n - 1])
|
| 14709 |
+
|
| 14710 |
+
else:
|
| 14711 |
+
common = set(tones_chords[i - 1]) & set(tones_chords[i]) & set(tones_chords[i + 1])
|
| 14712 |
+
|
| 14713 |
+
result.append(min(common) if common else -1)
|
| 14714 |
+
|
| 14715 |
+
return result
|
| 14716 |
+
|
| 14717 |
+
###################################################################################
|
| 14718 |
+
|
| 14719 |
+
def tones_chord_to_int(tones_chord,
|
| 14720 |
+
reverse_bits=True
|
| 14721 |
+
):
|
| 14722 |
+
|
| 14723 |
+
cbits = tones_chord_to_bits(tones_chord,
|
| 14724 |
+
reverse=reverse_bits
|
| 14725 |
+
)
|
| 14726 |
+
|
| 14727 |
+
cint = bits_to_int(cbits)
|
| 14728 |
+
|
| 14729 |
+
return cint
|
| 14730 |
+
|
| 14731 |
+
###################################################################################
|
| 14732 |
+
|
| 14733 |
+
def int_to_tones_chord(integer,
|
| 14734 |
+
reverse_bits=True
|
| 14735 |
+
):
|
| 14736 |
+
|
| 14737 |
+
integer = integer % 4096
|
| 14738 |
+
|
| 14739 |
+
cbits = int_to_bits(integer)
|
| 14740 |
+
|
| 14741 |
+
if reverse_bits:
|
| 14742 |
+
cbits.reverse()
|
| 14743 |
+
|
| 14744 |
+
tones_chord = bits_to_tones_chord(cbits)
|
| 14745 |
+
|
| 14746 |
+
return tones_chord
|
| 14747 |
+
|
| 14748 |
+
###################################################################################
|
| 14749 |
+
|
| 14750 |
+
def fix_bad_chords_in_escore_notes(escore_notes,
|
| 14751 |
+
use_full_chords=False,
|
| 14752 |
+
return_bad_chords_count=False
|
| 14753 |
+
):
|
| 14754 |
+
|
| 14755 |
+
if use_full_chords:
|
| 14756 |
+
CHORDS = ALL_CHORDS_FULL
|
| 14757 |
+
|
| 14758 |
+
else:
|
| 14759 |
+
CHORDS = ALL_CHORDS_SORTED
|
| 14760 |
+
|
| 14761 |
+
bcount = 0
|
| 14762 |
+
|
| 14763 |
+
if escore_notes:
|
| 14764 |
+
|
| 14765 |
+
chords = chordify_score([1000, escore_notes])
|
| 14766 |
+
|
| 14767 |
+
fixed_chords = []
|
| 14768 |
+
|
| 14769 |
+
for c in chords:
|
| 14770 |
+
c.sort(key=lambda x: x[3])
|
| 14771 |
+
|
| 14772 |
+
if len(c) > 1:
|
| 14773 |
+
|
| 14774 |
+
groups = groupby(c, key=lambda x: x[3])
|
| 14775 |
+
|
| 14776 |
+
for cha, gr in groups:
|
| 14777 |
+
|
| 14778 |
+
if cha != 9:
|
| 14779 |
+
|
| 14780 |
+
gr = list(gr)
|
| 14781 |
+
|
| 14782 |
+
tones_chord = sorted(set([p[4] % 12 for p in gr]))
|
| 14783 |
+
|
| 14784 |
+
if tones_chord not in CHORDS:
|
| 14785 |
+
tones_chord = check_and_fix_tones_chord(tones_chord,
|
| 14786 |
+
use_full_chords=use_full_chords
|
| 14787 |
+
)
|
| 14788 |
+
|
| 14789 |
+
bcount += 1
|
| 14790 |
+
|
| 14791 |
+
ngr = []
|
| 14792 |
+
|
| 14793 |
+
for n in gr:
|
| 14794 |
+
if n[4] % 12 in tones_chord:
|
| 14795 |
+
ngr.append(n)
|
| 14796 |
+
|
| 14797 |
+
fixed_chords.extend(ngr)
|
| 14798 |
+
|
| 14799 |
+
else:
|
| 14800 |
+
fixed_chords.extend(gr)
|
| 14801 |
+
|
| 14802 |
+
|
| 14803 |
+
else:
|
| 14804 |
+
fixed_chords.extend(c)
|
| 14805 |
+
|
| 14806 |
+
fixed_chords.sort(key=lambda x: (x[1], -x[4]))
|
| 14807 |
+
|
| 14808 |
+
if return_bad_chords_count:
|
| 14809 |
+
return fixed_chords, bcount
|
| 14810 |
+
|
| 14811 |
+
else:
|
| 14812 |
+
return fixed_chords
|
| 14813 |
+
|
| 14814 |
+
else:
|
| 14815 |
+
if return_bad_chords_count:
|
| 14816 |
+
return escore_notes, bcount
|
| 14817 |
+
|
| 14818 |
+
else:
|
| 14819 |
+
return escore_notes
|
| 14820 |
+
|
| 14821 |
+
###################################################################################
|
| 14822 |
+
|
| 14823 |
+
def remove_events_from_escore_notes(escore_notes,
|
| 14824 |
+
ele_idx=2,
|
| 14825 |
+
ele_vals=[1],
|
| 14826 |
+
chan_idx=3,
|
| 14827 |
+
skip_drums=True
|
| 14828 |
+
):
|
| 14829 |
+
|
| 14830 |
+
new_escore_notes = []
|
| 14831 |
+
|
| 14832 |
+
for e in escore_notes:
|
| 14833 |
+
if skip_drums:
|
| 14834 |
+
if e[ele_idx] not in ele_vals or e[chan_idx] == 9:
|
| 14835 |
+
new_escore_notes.append(e)
|
| 14836 |
+
|
| 14837 |
+
else:
|
| 14838 |
+
if e[ele_idx] not in ele_vals:
|
| 14839 |
+
new_escore_notes.append(e)
|
| 14840 |
+
|
| 14841 |
+
return new_escore_notes
|
| 14842 |
+
|
| 14843 |
+
###################################################################################
|
| 14844 |
+
|
| 14845 |
+
def flatten_spikes(arr):
|
| 14846 |
+
|
| 14847 |
+
if len(arr) < 3:
|
| 14848 |
+
return arr[:]
|
| 14849 |
+
|
| 14850 |
+
result = arr[:]
|
| 14851 |
+
|
| 14852 |
+
for i in range(1, len(arr) - 1):
|
| 14853 |
+
prev, curr, next_ = arr[i - 1], arr[i], arr[i + 1]
|
| 14854 |
+
|
| 14855 |
+
if (prev <= next_ and (curr > prev and curr > next_)) or \
|
| 14856 |
+
(prev >= next_ and (curr < prev and curr < next_)):
|
| 14857 |
+
result[i] = max(min(prev, next_), min(max(prev, next_), curr))
|
| 14858 |
+
|
| 14859 |
+
return result
|
| 14860 |
+
|
| 14861 |
+
###################################################################################
|
| 14862 |
+
|
| 14863 |
+
def flatten_spikes_advanced(arr, window=1):
|
| 14864 |
+
|
| 14865 |
+
if len(arr) < 3:
|
| 14866 |
+
return arr[:]
|
| 14867 |
+
|
| 14868 |
+
result = arr[:]
|
| 14869 |
+
n = len(arr)
|
| 14870 |
+
|
| 14871 |
+
def is_spike(i):
|
| 14872 |
+
left = arr[i - window:i]
|
| 14873 |
+
right = arr[i + 1:i + 1 + window]
|
| 14874 |
+
|
| 14875 |
+
if not left or not right:
|
| 14876 |
+
return False
|
| 14877 |
+
|
| 14878 |
+
avg_left = sum(left) / len(left)
|
| 14879 |
+
avg_right = sum(right) / len(right)
|
| 14880 |
+
|
| 14881 |
+
if arr[i] > avg_left and arr[i] > avg_right:
|
| 14882 |
+
return True
|
| 14883 |
+
|
| 14884 |
+
if arr[i] < avg_left and arr[i] < avg_right:
|
| 14885 |
+
return True
|
| 14886 |
+
|
| 14887 |
+
return False
|
| 14888 |
+
|
| 14889 |
+
for i in range(window, n - window):
|
| 14890 |
+
if is_spike(i):
|
| 14891 |
+
neighbors = arr[i - window:i] + arr[i + 1:i + 1 + window]
|
| 14892 |
+
result[i] = int(sorted(neighbors)[len(neighbors) // 2])
|
| 14893 |
+
|
| 14894 |
+
return result
|
| 14895 |
+
|
| 14896 |
+
###################################################################################
|
| 14897 |
+
|
| 14898 |
+
def add_smooth_melody_to_enhanced_score_notes(escore_notes,
|
| 14899 |
+
melody_channel=3,
|
| 14900 |
+
melody_patch=40,
|
| 14901 |
+
melody_start_chord=0,
|
| 14902 |
+
min_notes_gap=0,
|
| 14903 |
+
exclude_durs=[1],
|
| 14904 |
+
adv_flattening=True,
|
| 14905 |
+
extend_durs=True,
|
| 14906 |
+
max_mel_vels=127,
|
| 14907 |
+
max_acc_vels=80,
|
| 14908 |
+
return_melody=False
|
| 14909 |
+
):
|
| 14910 |
+
|
| 14911 |
+
escore_notes1 = remove_duplicate_pitches_from_escore_notes(escore_notes)
|
| 14912 |
+
|
| 14913 |
+
escore_notes2 = fix_escore_notes_durations(escore_notes1,
|
| 14914 |
+
min_notes_gap=min_notes_gap
|
| 14915 |
+
)
|
| 14916 |
+
|
| 14917 |
+
escore_notes3 = fix_bad_chords_in_escore_notes(escore_notes2)
|
| 14918 |
+
|
| 14919 |
+
escore_notes4 = remove_events_from_escore_notes(escore_notes3,
|
| 14920 |
+
ele_vals=exclude_durs
|
| 14921 |
+
)
|
| 14922 |
+
|
| 14923 |
+
escore_notes5 = add_expressive_melody_to_enhanced_score_notes(escore_notes4,
|
| 14924 |
+
melody_channel=melody_channel,
|
| 14925 |
+
melody_patch=melody_patch,
|
| 14926 |
+
melody_start_chord=melody_start_chord,
|
| 14927 |
+
return_melody=True,
|
| 14928 |
+
)
|
| 14929 |
+
|
| 14930 |
+
mel_score = remove_events_from_escore_notes(escore_notes5,
|
| 14931 |
+
ele_vals=exclude_durs
|
| 14932 |
+
)
|
| 14933 |
+
|
| 14934 |
+
pitches = [p[4] for p in mel_score]
|
| 14935 |
+
|
| 14936 |
+
if adv_flattening:
|
| 14937 |
+
res = flatten_spikes_advanced(pitches)
|
| 14938 |
+
|
| 14939 |
+
else:
|
| 14940 |
+
res = flatten_spikes(pitches)
|
| 14941 |
+
|
| 14942 |
+
mel_score3 = copy.deepcopy(mel_score)
|
| 14943 |
+
|
| 14944 |
+
for i, e in enumerate(mel_score3):
|
| 14945 |
+
e[4] = res[i]
|
| 14946 |
+
|
| 14947 |
+
mel_score3 = fix_monophonic_score_durations(merge_melody_notes(mel_score3),
|
| 14948 |
+
extend_durs=extend_durs
|
| 14949 |
+
)
|
| 14950 |
+
|
| 14951 |
+
adjust_score_velocities(mel_score3, max_mel_vels)
|
| 14952 |
+
adjust_score_velocities(escore_notes4, max_acc_vels)
|
| 14953 |
+
|
| 14954 |
+
if return_melody:
|
| 14955 |
+
return sorted(mel_score3, key=lambda x: (x[1], -x[4]))
|
| 14956 |
+
|
| 14957 |
+
else:
|
| 14958 |
+
return sorted(mel_score3 + escore_notes4, key=lambda x: (x[1], -x[4]))
|
| 14959 |
+
|
| 14960 |
+
###################################################################################
|
| 14961 |
+
|
| 14962 |
+
def sorted_chords_to_full_chords(chords):
|
| 14963 |
+
|
| 14964 |
+
cchords = []
|
| 14965 |
+
|
| 14966 |
+
for c in chords:
|
| 14967 |
+
tones_chord = ALL_CHORDS_SORTED[c]
|
| 14968 |
+
|
| 14969 |
+
if tones_chord not in ALL_CHORDS_FULL:
|
| 14970 |
+
tones_chord = check_and_fix_tones_chord(tones_chord)
|
| 14971 |
+
|
| 14972 |
+
cchords.append(ALL_CHORDS_FULL.index(tones_chord))
|
| 14973 |
+
|
| 14974 |
+
return cchords
|
| 14975 |
+
|
| 14976 |
+
###################################################################################
|
| 14977 |
+
|
| 14978 |
+
def full_chords_to_sorted_chords(chords):
|
| 14979 |
+
|
| 14980 |
+
cchords = []
|
| 14981 |
+
|
| 14982 |
+
for c in chords:
|
| 14983 |
+
tones_chord = ALL_CHORDS_FULL[c]
|
| 14984 |
+
|
| 14985 |
+
if tones_chord not in ALL_CHORDS_SORTED:
|
| 14986 |
+
tones_chord = check_and_fix_tones_chord(tones_chord, use_full_chords=False)
|
| 14987 |
+
|
| 14988 |
+
cchords.append(ALL_CHORDS_SORTED.index(tones_chord))
|
| 14989 |
+
|
| 14990 |
+
return cchords
|
| 14991 |
+
|
| 14992 |
+
###################################################################################
|
| 14993 |
+
|
| 14994 |
+
def chords_to_escore_notes(chords,
|
| 14995 |
+
use_full_chords=False,
|
| 14996 |
+
chords_dtime=500,
|
| 14997 |
+
add_melody=True,
|
| 14998 |
+
add_texture=True,
|
| 14999 |
+
):
|
| 15000 |
+
|
| 15001 |
+
if use_full_chords:
|
| 15002 |
+
CHORDS = ALL_CHORDS_FULL
|
| 15003 |
+
|
| 15004 |
+
else:
|
| 15005 |
+
CHORDS = ALL_CHORDS_SORTED
|
| 15006 |
+
|
| 15007 |
+
score = []
|
| 15008 |
+
|
| 15009 |
+
dtime = 0
|
| 15010 |
+
|
| 15011 |
+
dur = chords_dtime
|
| 15012 |
+
|
| 15013 |
+
for c in chords:
|
| 15014 |
+
|
| 15015 |
+
if add_melody:
|
| 15016 |
+
score.append(['note', dtime, dur, 3, CHORDS[c][0]+72, 115+CHORDS[c][0], 40])
|
| 15017 |
+
|
| 15018 |
+
for cc in CHORDS[c]:
|
| 15019 |
+
score.append(['note', dtime, dur, 0, cc+48, 30+cc+48, 0])
|
| 15020 |
+
|
| 15021 |
+
if random.randint(0, 1) and add_texture:
|
| 15022 |
+
score.append(['note', dtime, dur, 0, cc+60, 20+cc+60, 0])
|
| 15023 |
+
|
| 15024 |
+
dtime += chords_dtime
|
| 15025 |
+
|
| 15026 |
+
return score
|
| 15027 |
+
|
| 15028 |
+
###################################################################################
|
| 15029 |
+
|
| 15030 |
print('Module loaded!')
|
| 15031 |
print('=' * 70)
|
| 15032 |
print('Enjoy! :)')
|
midi_to_colab_audio.py
CHANGED
|
@@ -5,14 +5,14 @@ r'''#===========================================================================
|
|
| 5 |
# Converts any MIDI file to raw audio which is compatible
|
| 6 |
# with Google Colab or HUgging Face Gradio
|
| 7 |
#
|
| 8 |
-
# Version
|
| 9 |
#
|
| 10 |
-
# Includes full source code of MIDI
|
| 11 |
#
|
| 12 |
-
# Original source code for all modules was retrieved on
|
| 13 |
#
|
| 14 |
# Project Los Angeles
|
| 15 |
-
# Tegridy Code
|
| 16 |
#
|
| 17 |
#===================================================================================================================
|
| 18 |
#
|
|
@@ -1773,7 +1773,7 @@ def _encode(events_lol, unknown_callback=None, never_add_eot=False,
|
|
| 1773 |
|
| 1774 |
Python bindings for FluidSynth
|
| 1775 |
|
| 1776 |
-
Copyright 2008, Nathan Whitehead <[email protected]>
|
| 1777 |
|
| 1778 |
|
| 1779 |
Released under the LGPL
|
|
@@ -1790,27 +1790,67 @@ def _encode(events_lol, unknown_callback=None, never_add_eot=False,
|
|
| 1790 |
================================================================================
|
| 1791 |
"""
|
| 1792 |
|
| 1793 |
-
from ctypes import *
|
| 1794 |
-
from ctypes.util import find_library
|
| 1795 |
import os
|
| 1796 |
-
|
| 1797 |
-
|
| 1798 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1799 |
|
| 1800 |
# DLL search method changed in Python 3.8
|
| 1801 |
# https://docs.python.org/3/library/os.html#os.add_dll_directory
|
| 1802 |
-
if hasattr(os, 'add_dll_directory'):
|
| 1803 |
os.add_dll_directory(os.getcwd())
|
|
|
|
|
|
|
|
|
|
| 1804 |
|
| 1805 |
-
|
| 1806 |
-
|
| 1807 |
-
|
| 1808 |
-
|
| 1809 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1810 |
|
| 1811 |
-
if lib is None:
|
| 1812 |
raise ImportError("Couldn't find the FluidSynth library.")
|
| 1813 |
|
|
|
|
|
|
|
| 1814 |
# Dynamically link the FluidSynth library
|
| 1815 |
# Architecture (32-/64-bit) must match your Python version
|
| 1816 |
_fl = CDLL(lib)
|
|
@@ -1829,7 +1869,7 @@ def cfunc(name, result, *args):
|
|
| 1829 |
return None
|
| 1830 |
|
| 1831 |
# Bump this up when changing the interface for users
|
| 1832 |
-
api_version = '1.3.
|
| 1833 |
|
| 1834 |
# Function prototypes for C versions of functions
|
| 1835 |
|
|
@@ -1843,10 +1883,7 @@ fluid_version = cfunc('fluid_version', c_void_p,
|
|
| 1843 |
|
| 1844 |
majver = c_int()
|
| 1845 |
fluid_version(majver, c_int(), c_int())
|
| 1846 |
-
if majver.value > 1
|
| 1847 |
-
FLUIDSETTING_EXISTS = FLUID_OK
|
| 1848 |
-
else:
|
| 1849 |
-
FLUIDSETTING_EXISTS = 1
|
| 1850 |
|
| 1851 |
# fluid settings
|
| 1852 |
new_fluid_settings = cfunc('new_fluid_settings', c_void_p)
|
|
@@ -2086,9 +2123,18 @@ fluid_synth_set_chorus_level = cfunc('fluid_synth_set_chorus_level', c_int,
|
|
| 2086 |
('synth', c_void_p, 1),
|
| 2087 |
('level', c_double, 1))
|
| 2088 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2089 |
fluid_synth_set_chorus_type = cfunc('fluid_synth_set_chorus_type', c_int,
|
| 2090 |
('synth', c_void_p, 1),
|
| 2091 |
('type', c_int, 1))
|
|
|
|
| 2092 |
fluid_synth_get_reverb_roomsize = cfunc('fluid_synth_get_reverb_roomsize', c_double,
|
| 2093 |
('synth', c_void_p, 1))
|
| 2094 |
|
|
@@ -2220,6 +2266,77 @@ fluid_midi_event_get_value = cfunc('fluid_midi_event_get_value', c_int,
|
|
| 2220 |
fluid_midi_event_get_velocity = cfunc('fluid_midi_event_get_velocity', c_int,
|
| 2221 |
('evt', c_void_p, 1))
|
| 2222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2223 |
# fluid_player_status returned by fluid_player_get_status()
|
| 2224 |
FLUID_PLAYER_READY = 0
|
| 2225 |
FLUID_PLAYER_PLAYING = 1
|
|
@@ -2281,6 +2398,9 @@ new_fluid_midi_driver = cfunc('new_fluid_midi_driver', c_void_p,
|
|
| 2281 |
('handler', CFUNCTYPE(c_int, c_void_p, c_void_p), 1),
|
| 2282 |
('event_handler_data', c_void_p, 1))
|
| 2283 |
|
|
|
|
|
|
|
|
|
|
| 2284 |
|
| 2285 |
# fluid midi router rule
|
| 2286 |
class fluid_midi_router_t(Structure):
|
|
@@ -2342,6 +2462,16 @@ fluid_midi_router_add_rule = cfunc('fluid_midi_router_add_rule', c_int,
|
|
| 2342 |
('rule', c_void_p, 1),
|
| 2343 |
('type', c_int, 1))
|
| 2344 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2345 |
# fluidsynth 2.x
|
| 2346 |
new_fluid_cmd_handler=cfunc('new_fluid_cmd_handler', c_void_p,
|
| 2347 |
('synth', c_void_p, 1),
|
|
@@ -2416,6 +2546,7 @@ class Synth:
|
|
| 2416 |
self.audio_driver = None
|
| 2417 |
self.midi_driver = None
|
| 2418 |
self.router = None
|
|
|
|
| 2419 |
def setting(self, opt, val):
|
| 2420 |
"""change an arbitrary synth setting, type-smart"""
|
| 2421 |
if isinstance(val, (str, bytes)):
|
|
@@ -2451,11 +2582,11 @@ class Synth:
|
|
| 2451 |
see http://www.fluidsynth.org/api/fluidsettings.xml for allowed values and defaults by platform
|
| 2452 |
"""
|
| 2453 |
driver = driver or self.get_setting('audio.driver')
|
| 2454 |
-
device = device or self.get_setting('audio
|
| 2455 |
midi_driver = midi_driver or self.get_setting('midi.driver')
|
| 2456 |
|
| 2457 |
self.setting('audio.driver', driver)
|
| 2458 |
-
self.setting('audio
|
| 2459 |
self.audio_driver = new_fluid_audio_driver(self.settings, self.synth)
|
| 2460 |
self.setting('midi.driver', midi_driver)
|
| 2461 |
self.router = new_fluid_midi_router(self.settings, fluid_synth_handle_midi_event, self.synth)
|
|
@@ -2463,7 +2594,7 @@ class Synth:
|
|
| 2463 |
new_fluid_cmd_handler(self.synth, self.router)
|
| 2464 |
else:
|
| 2465 |
fluid_synth_set_midi_router(self.synth, self.router)
|
| 2466 |
-
if midi_router
|
| 2467 |
self.midi_driver = new_fluid_midi_driver(self.settings, fluid_midi_router_handle_midi_event, self.router)
|
| 2468 |
self.custom_router_callback = None
|
| 2469 |
else: ## Supply an external MIDI event handler
|
|
@@ -2474,6 +2605,8 @@ class Synth:
|
|
| 2474 |
def delete(self):
|
| 2475 |
if self.audio_driver:
|
| 2476 |
delete_fluid_audio_driver(self.audio_driver)
|
|
|
|
|
|
|
| 2477 |
delete_fluid_synth(self.synth)
|
| 2478 |
delete_fluid_settings(self.settings)
|
| 2479 |
def sfload(self, filename, update_midi_preset=0):
|
|
@@ -2518,8 +2651,7 @@ class Synth:
|
|
| 2518 |
return None
|
| 2519 |
return fluid_preset_get_name(preset).decode('ascii')
|
| 2520 |
else:
|
| 2521 |
-
|
| 2522 |
-
return presetname
|
| 2523 |
def router_clear(self):
|
| 2524 |
if self.router is not None:
|
| 2525 |
fluid_midi_router_clear_rules(self.router)
|
|
@@ -2570,16 +2702,16 @@ class Synth:
|
|
| 2570 |
if fluid_synth_set_reverb is not None:
|
| 2571 |
return fluid_synth_set_reverb(self.synth, roomsize, damping, width, level)
|
| 2572 |
else:
|
| 2573 |
-
|
| 2574 |
if roomsize>=0:
|
| 2575 |
-
|
| 2576 |
if damping>=0:
|
| 2577 |
-
|
| 2578 |
if width>=0:
|
| 2579 |
-
|
| 2580 |
if level>=0:
|
| 2581 |
-
|
| 2582 |
-
return fluid_synth_set_reverb_full(self.synth,
|
| 2583 |
def set_chorus(self, nr=-1, level=-1.0, speed=-1.0, depth=-1.0, type=-1):
|
| 2584 |
"""
|
| 2585 |
nr Chorus voice count (0-99, CPU time consumption proportional to this value)
|
|
@@ -2632,17 +2764,17 @@ class Synth:
|
|
| 2632 |
if fluid_synth_set_chorus_level is not None:
|
| 2633 |
return fluid_synth_set_chorus_level(self.synth, level)
|
| 2634 |
else:
|
| 2635 |
-
return self.set_chorus(
|
| 2636 |
def set_chorus_speed(self, speed):
|
| 2637 |
if fluid_synth_set_chorus_speed is not None:
|
| 2638 |
return fluid_synth_set_chorus_speed(self.synth, speed)
|
| 2639 |
else:
|
| 2640 |
return self.set_chorus(speed=speed)
|
| 2641 |
-
def set_chorus_depth(self,
|
| 2642 |
if fluid_synth_set_chorus_depth is not None:
|
| 2643 |
-
return fluid_synth_set_chorus_depth(self.synth,
|
| 2644 |
else:
|
| 2645 |
-
return self.set_chorus(depth=
|
| 2646 |
def set_chorus_type(self, type):
|
| 2647 |
if fluid_synth_set_chorus_type is not None:
|
| 2648 |
return fluid_synth_set_chorus_type(self.synth, type)
|
|
@@ -2694,10 +2826,10 @@ class Synth:
|
|
| 2694 |
A pitch bend value of 0 is no pitch change from default.
|
| 2695 |
A value of -2048 is 1 semitone down.
|
| 2696 |
A value of 2048 is 1 semitone up.
|
| 2697 |
-
Maximum values are -8192 to +
|
| 2698 |
|
| 2699 |
"""
|
| 2700 |
-
return fluid_synth_pitch_bend(self.synth, chan, val + 8192)
|
| 2701 |
def cc(self, chan, ctrl, val):
|
| 2702 |
"""Send control change value
|
| 2703 |
|
|
@@ -2747,8 +2879,15 @@ class Synth:
|
|
| 2747 |
|
| 2748 |
"""
|
| 2749 |
return fluid_synth_write_s16_stereo(self.synth, len)
|
| 2750 |
-
def tuning_dump(self, bank, prog
|
| 2751 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2752 |
|
| 2753 |
def midi_event_get_type(self, event):
|
| 2754 |
return fluid_midi_event_get_type(event)
|
|
@@ -2767,17 +2906,20 @@ class Synth:
|
|
| 2767 |
|
| 2768 |
def play_midi_file(self, filename):
|
| 2769 |
self.player = new_fluid_player(self.synth)
|
| 2770 |
-
if self.player
|
| 2771 |
-
|
|
|
|
| 2772 |
fluid_player_set_playback_callback(self.player, self.custom_router_callback, self.synth)
|
| 2773 |
status = fluid_player_add(self.player, filename.encode())
|
| 2774 |
-
if status == FLUID_FAILED:
|
|
|
|
| 2775 |
status = fluid_player_play(self.player)
|
| 2776 |
return status
|
| 2777 |
|
| 2778 |
def play_midi_stop(self):
|
| 2779 |
status = fluid_player_stop(self.player)
|
| 2780 |
-
if status == FLUID_FAILED:
|
|
|
|
| 2781 |
status = fluid_player_seek(self.player, 0)
|
| 2782 |
delete_fluid_player(self.player)
|
| 2783 |
return status
|
|
@@ -2785,7 +2927,151 @@ class Synth:
|
|
| 2785 |
def player_set_tempo(self, tempo_type, tempo):
|
| 2786 |
return fluid_player_set_tempo(self.player, tempo_type, tempo)
|
| 2787 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2788 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2789 |
|
| 2790 |
class Sequencer:
|
| 2791 |
def __init__(self, time_scale=1000, use_system_timer=True):
|
|
@@ -2802,14 +3088,14 @@ class Sequencer:
|
|
| 2802 |
def register_fluidsynth(self, synth):
|
| 2803 |
response = fluid_sequencer_register_fluidsynth(self.sequencer, synth.synth)
|
| 2804 |
if response == FLUID_FAILED:
|
| 2805 |
-
raise
|
| 2806 |
return response
|
| 2807 |
|
| 2808 |
def register_client(self, name, callback, data=None):
|
| 2809 |
c_callback = CFUNCTYPE(None, c_uint, c_void_p, c_void_p, c_void_p)(callback)
|
| 2810 |
response = fluid_sequencer_register_client(self.sequencer, name.encode(), c_callback, data)
|
| 2811 |
if response == FLUID_FAILED:
|
| 2812 |
-
raise
|
| 2813 |
|
| 2814 |
# store in a list to prevent garbage collection
|
| 2815 |
self.client_callbacks.append(c_callback)
|
|
@@ -2849,7 +3135,7 @@ class Sequencer:
|
|
| 2849 |
def _schedule_event(self, evt, time, absolute=True):
|
| 2850 |
response = fluid_sequencer_send_at(self.sequencer, evt, time, absolute)
|
| 2851 |
if response == FLUID_FAILED:
|
| 2852 |
-
raise
|
| 2853 |
|
| 2854 |
def get_tick(self):
|
| 2855 |
return fluid_sequencer_get_tick(self.sequencer)
|
|
@@ -2868,123 +3154,307 @@ def raw_audio_string(data):
|
|
| 2868 |
|
| 2869 |
"""
|
| 2870 |
import numpy
|
| 2871 |
-
return (data.astype(numpy.int16)).
|
| 2872 |
|
| 2873 |
#===============================================================================
|
| 2874 |
|
| 2875 |
import numpy as np
|
| 2876 |
import wave
|
| 2877 |
|
| 2878 |
-
|
| 2879 |
-
soundfont_path='/usr/share/sounds/sf2/FluidR3_GM.sf2',
|
| 2880 |
-
sample_rate=16000, # 44100
|
| 2881 |
-
volume_scale=10,
|
| 2882 |
-
trim_silence=True,
|
| 2883 |
-
silence_threshold=0.1,
|
| 2884 |
-
output_for_gradio=False,
|
| 2885 |
-
write_audio_to_WAV=''
|
| 2886 |
-
):
|
| 2887 |
-
|
| 2888 |
-
def normalize_volume(matrix, factor=10):
|
| 2889 |
-
norm = np.linalg.norm(matrix)
|
| 2890 |
-
matrix = matrix/norm # normalized matrix
|
| 2891 |
-
mult_matrix = matrix * factor
|
| 2892 |
-
final_matrix = np.clip(mult_matrix, -1.0, 1.0)
|
| 2893 |
-
return final_matrix
|
| 2894 |
|
| 2895 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2896 |
|
| 2897 |
-
|
| 2898 |
-
|
| 2899 |
-
for track_idx, track in enumerate(midi_opus[1:]):
|
| 2900 |
-
abs_t = 0
|
| 2901 |
-
for event in track:
|
| 2902 |
-
abs_t += event[1]
|
| 2903 |
-
event_new = [*event]
|
| 2904 |
-
event_new[1] = abs_t
|
| 2905 |
-
event_list.append(event_new)
|
| 2906 |
-
event_list = sorted(event_list, key=lambda e: e[1])
|
| 2907 |
-
|
| 2908 |
-
tempo = int((60 / 120) * 10 ** 6) # default 120 bpm
|
| 2909 |
-
ss = np.empty((0, 2), dtype=np.int16)
|
| 2910 |
-
fl = Synth(samplerate=float(sample_rate))
|
| 2911 |
-
sfid = fl.sfload(soundfont_path)
|
| 2912 |
-
last_t = 0
|
| 2913 |
-
for c in range(16):
|
| 2914 |
-
fl.program_select(c, sfid, 128 if c == 9 else 0, 0)
|
| 2915 |
-
for event in event_list:
|
| 2916 |
-
name = event[0]
|
| 2917 |
-
sample_len = int(((event[1] / ticks_per_beat) * tempo / (10 ** 6)) * sample_rate)
|
| 2918 |
-
sample_len -= int(((last_t / ticks_per_beat) * tempo / (10 ** 6)) * sample_rate)
|
| 2919 |
-
last_t = event[1]
|
| 2920 |
-
if sample_len > 0:
|
| 2921 |
-
sample = fl.get_samples(sample_len).reshape(sample_len, 2)
|
| 2922 |
-
ss = np.concatenate([ss, sample])
|
| 2923 |
-
if name == "set_tempo":
|
| 2924 |
-
tempo = event[2]
|
| 2925 |
-
elif name == "patch_change":
|
| 2926 |
-
c, p = event[2:4]
|
| 2927 |
-
fl.program_select(c, sfid, 128 if c == 9 else 0, p)
|
| 2928 |
-
elif name == "control_change":
|
| 2929 |
-
c, cc, v = event[2:5]
|
| 2930 |
-
fl.cc(c, cc, v)
|
| 2931 |
-
elif name == "note_on" and event[3] > 0:
|
| 2932 |
-
c, p, v = event[2:5]
|
| 2933 |
-
fl.noteon(c, p, v)
|
| 2934 |
-
elif name == "note_off" or (name == "note_on" and event[3] == 0):
|
| 2935 |
-
c, p = event[2:4]
|
| 2936 |
-
fl.noteoff(c, p)
|
| 2937 |
-
|
| 2938 |
-
fl.delete()
|
| 2939 |
-
if ss.shape[0] > 0:
|
| 2940 |
-
max_val = np.abs(ss).max()
|
| 2941 |
-
if max_val != 0:
|
| 2942 |
-
ss = (ss / max_val) * np.iinfo(np.int16).max
|
| 2943 |
-
ss = ss.astype(np.int16)
|
| 2944 |
-
|
| 2945 |
-
if trim_silence:
|
| 2946 |
-
threshold = np.std(np.abs(ss)) * silence_threshold
|
| 2947 |
-
exceeded_thresh = np.abs(ss) > threshold
|
| 2948 |
-
if np.any(exceeded_thresh):
|
| 2949 |
-
last_idx = np.where(exceeded_thresh)[0][-1]
|
| 2950 |
-
ss = ss[:last_idx+1]
|
| 2951 |
-
|
| 2952 |
-
if output_for_gradio:
|
| 2953 |
-
return ss
|
| 2954 |
-
|
| 2955 |
-
ss = ss.swapaxes(1, 0)
|
| 2956 |
|
| 2957 |
-
|
| 2958 |
-
|
| 2959 |
-
|
| 2960 |
|
| 2961 |
-
|
|
|
|
| 2962 |
|
| 2963 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2964 |
|
| 2965 |
-
|
| 2966 |
-
|
| 2967 |
-
|
| 2968 |
-
|
| 2969 |
-
|
|
|
|
|
|
|
|
|
|
| 2970 |
|
| 2971 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2972 |
|
| 2973 |
else:
|
| 2974 |
return None
|
| 2975 |
|
| 2976 |
-
|
| 2977 |
-
|
| 2978 |
-
|
| 2979 |
-
|
|
|
|
|
|
|
| 2980 |
trim_silence=True,
|
| 2981 |
silence_threshold=0.1,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2982 |
output_for_gradio=False,
|
| 2983 |
-
write_audio_to_WAV=False
|
| 2984 |
-
|
| 2985 |
-
|
| 2986 |
-
|
| 2987 |
-
|
| 2988 |
Returns raw audio to pass to IPython.disaply.Audio func
|
| 2989 |
|
| 2990 |
Example usage:
|
|
@@ -2992,99 +3462,176 @@ def midi_to_colab_audio(midi_file,
|
|
| 2992 |
from IPython.display import Audio
|
| 2993 |
|
| 2994 |
display(Audio(raw_audio, rate=16000, normalize=False))
|
| 2995 |
-
|
| 2996 |
-
'''
|
| 2997 |
-
|
| 2998 |
-
def normalize_volume(matrix, factor=10):
|
| 2999 |
-
norm = np.linalg.norm(matrix)
|
| 3000 |
-
matrix = matrix/norm # normalized matrix
|
| 3001 |
-
mult_matrix = matrix * factor
|
| 3002 |
-
final_matrix = np.clip(mult_matrix, -1.0, 1.0)
|
| 3003 |
-
return final_matrix
|
| 3004 |
-
|
| 3005 |
-
midi_opus = midi2opus(open(midi_file, 'rb').read())
|
| 3006 |
|
| 3007 |
-
|
|
|
|
|
|
|
|
|
|
| 3008 |
|
| 3009 |
-
|
| 3010 |
-
|
| 3011 |
-
|
| 3012 |
-
|
| 3013 |
-
|
| 3014 |
-
|
| 3015 |
-
|
| 3016 |
-
|
| 3017 |
-
|
| 3018 |
-
|
| 3019 |
-
|
| 3020 |
-
|
| 3021 |
-
|
| 3022 |
-
|
| 3023 |
-
|
| 3024 |
-
|
| 3025 |
-
|
| 3026 |
-
|
| 3027 |
-
|
| 3028 |
-
|
| 3029 |
-
|
| 3030 |
-
|
| 3031 |
-
|
| 3032 |
-
|
| 3033 |
-
|
| 3034 |
-
|
| 3035 |
-
|
| 3036 |
-
|
| 3037 |
-
|
| 3038 |
-
c, p = event[2:4]
|
| 3039 |
-
fl.program_select(c, sfid, 128 if c == 9 else 0, p)
|
| 3040 |
-
elif name == "control_change":
|
| 3041 |
-
c, cc, v = event[2:5]
|
| 3042 |
-
fl.cc(c, cc, v)
|
| 3043 |
-
elif name == "note_on" and event[3] > 0:
|
| 3044 |
-
c, p, v = event[2:5]
|
| 3045 |
-
fl.noteon(c, p, v)
|
| 3046 |
-
elif name == "note_off" or (name == "note_on" and event[3] == 0):
|
| 3047 |
-
c, p = event[2:4]
|
| 3048 |
-
fl.noteoff(c, p)
|
| 3049 |
-
|
| 3050 |
-
fl.delete()
|
| 3051 |
-
if ss.shape[0] > 0:
|
| 3052 |
-
max_val = np.abs(ss).max()
|
| 3053 |
-
if max_val != 0:
|
| 3054 |
-
ss = (ss / max_val) * np.iinfo(np.int16).max
|
| 3055 |
-
ss = ss.astype(np.int16)
|
| 3056 |
-
|
| 3057 |
-
if trim_silence:
|
| 3058 |
-
threshold = np.std(np.abs(ss)) * silence_threshold
|
| 3059 |
-
exceeded_thresh = np.abs(ss) > threshold
|
| 3060 |
-
if np.any(exceeded_thresh):
|
| 3061 |
-
last_idx = np.where(exceeded_thresh)[0][-1]
|
| 3062 |
-
ss = ss[:last_idx+1]
|
| 3063 |
-
|
| 3064 |
-
if output_for_gradio:
|
| 3065 |
-
return ss
|
| 3066 |
|
| 3067 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3068 |
|
| 3069 |
-
|
|
|
|
|
|
|
| 3070 |
|
| 3071 |
-
|
|
|
|
| 3072 |
|
| 3073 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3074 |
|
| 3075 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3076 |
|
| 3077 |
-
|
|
|
|
|
|
|
| 3078 |
|
| 3079 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3080 |
wf.setframerate(sample_rate)
|
| 3081 |
wf.setsampwidth(2)
|
| 3082 |
-
wf.setnchannels(
|
| 3083 |
-
wf.writeframes(
|
|
|
|
|
|
|
| 3084 |
|
| 3085 |
-
return raw_audio
|
| 3086 |
-
|
| 3087 |
-
else:
|
| 3088 |
-
return None
|
| 3089 |
-
|
| 3090 |
#===================================================================================================================
|
|
|
|
| 5 |
# Converts any MIDI file to raw audio which is compatible
|
| 6 |
# with Google Colab or HUgging Face Gradio
|
| 7 |
#
|
| 8 |
+
# Version 2.0
|
| 9 |
#
|
| 10 |
+
# Includes full source code of MIDI and pyfluidsynth
|
| 11 |
#
|
| 12 |
+
# Original source code for all modules was retrieved on 07/31/2025
|
| 13 |
#
|
| 14 |
# Project Los Angeles
|
| 15 |
+
# Tegridy Code 2025
|
| 16 |
#
|
| 17 |
#===================================================================================================================
|
| 18 |
#
|
|
|
|
| 1773 |
|
| 1774 |
Python bindings for FluidSynth
|
| 1775 |
|
| 1776 |
+
Copyright 2008--2024, Nathan Whitehead <[email protected]> and others.
|
| 1777 |
|
| 1778 |
|
| 1779 |
Released under the LGPL
|
|
|
|
| 1790 |
================================================================================
|
| 1791 |
"""
|
| 1792 |
|
|
|
|
|
|
|
| 1793 |
import os
|
| 1794 |
+
from ctypes import (
|
| 1795 |
+
CDLL,
|
| 1796 |
+
CFUNCTYPE,
|
| 1797 |
+
POINTER,
|
| 1798 |
+
Structure,
|
| 1799 |
+
byref,
|
| 1800 |
+
c_char,
|
| 1801 |
+
c_char_p,
|
| 1802 |
+
c_double,
|
| 1803 |
+
c_float,
|
| 1804 |
+
c_int,
|
| 1805 |
+
c_short,
|
| 1806 |
+
c_uint,
|
| 1807 |
+
c_void_p,
|
| 1808 |
+
create_string_buffer,
|
| 1809 |
+
)
|
| 1810 |
+
from ctypes.util import find_library
|
| 1811 |
|
| 1812 |
# DLL search method changed in Python 3.8
|
| 1813 |
# https://docs.python.org/3/library/os.html#os.add_dll_directory
|
| 1814 |
+
if hasattr(os, 'add_dll_directory'): # Python 3.8+ on Windows only
|
| 1815 |
os.add_dll_directory(os.getcwd())
|
| 1816 |
+
os.add_dll_directory('C:\\tools\\fluidsynth\\bin')
|
| 1817 |
+
# Workaround bug in find_library, it doesn't recognize add_dll_directory
|
| 1818 |
+
os.environ['PATH'] += ';C:\\tools\\fluidsynth\\bin'
|
| 1819 |
|
| 1820 |
+
# A function to find the FluidSynth library
|
| 1821 |
+
# (mostly needed for Windows distributions of libfluidsynth supplied with QSynth)
|
| 1822 |
+
def find_libfluidsynth(debug_print: bool = False) -> str:
|
| 1823 |
+
r"""
|
| 1824 |
+
macOS X64:
|
| 1825 |
+
* 'fluidsynth' was found at /usr/local/opt/fluid-synth/lib/libfluidsynth.dylib.
|
| 1826 |
+
macOS ARM64:
|
| 1827 |
+
* 'fluidsynth' was found at /opt/homebrew/opt/fluid-synth/lib/libfluidsynth.dylib.
|
| 1828 |
+
Ubuntu X86:
|
| 1829 |
+
* 'fluidsynth' was found at libfluidsynth.so.3.
|
| 1830 |
+
Windows X86:
|
| 1831 |
+
* 'libfluidsynth-3' was found at C:\tools\fluidsynth\bin\libfluidsynth-3.dll. --or--
|
| 1832 |
+
* 'fluidsynth-3' was found as C:\tools\fluidsynth\bin\fluidsynth-3.dll. >= v2.4.5
|
| 1833 |
+
* https://github.com/FluidSynth/fluidsynth/issues/1543
|
| 1834 |
+
"""
|
| 1835 |
+
libs = "fluidsynth fluidsynth-3 libfluidsynth libfluidsynth-3 libfluidsynth-2 libfluidsynth-1"
|
| 1836 |
+
for lib_name in libs.split():
|
| 1837 |
+
lib = find_library(lib_name)
|
| 1838 |
+
if lib:
|
| 1839 |
+
if debug_print:
|
| 1840 |
+
print(f"'{lib_name}' was found at {lib}.")
|
| 1841 |
+
return lib
|
| 1842 |
+
|
| 1843 |
+
# On macOS on Apple silicon, non-Homebrew Python distributions fail to locate
|
| 1844 |
+
# homebrew-installed instances of FluidSynth. This workaround addresses this.
|
| 1845 |
+
if homebrew_prefix := os.getenv("HOMEBREW_PREFIX"):
|
| 1846 |
+
lib = os.path.join(homebrew_prefix, "lib", "libfluidsynth.dylib")
|
| 1847 |
+
if os.path.exists(lib):
|
| 1848 |
+
return lib
|
| 1849 |
|
|
|
|
| 1850 |
raise ImportError("Couldn't find the FluidSynth library.")
|
| 1851 |
|
| 1852 |
+
lib = find_libfluidsynth()
|
| 1853 |
+
|
| 1854 |
# Dynamically link the FluidSynth library
|
| 1855 |
# Architecture (32-/64-bit) must match your Python version
|
| 1856 |
_fl = CDLL(lib)
|
|
|
|
| 1869 |
return None
|
| 1870 |
|
| 1871 |
# Bump this up when changing the interface for users
|
| 1872 |
+
api_version = '1.3.5'
|
| 1873 |
|
| 1874 |
# Function prototypes for C versions of functions
|
| 1875 |
|
|
|
|
| 1883 |
|
| 1884 |
majver = c_int()
|
| 1885 |
fluid_version(majver, c_int(), c_int())
|
| 1886 |
+
FLUIDSETTING_EXISTS = FLUID_OK if majver.value > 1 else 1
|
|
|
|
|
|
|
|
|
|
| 1887 |
|
| 1888 |
# fluid settings
|
| 1889 |
new_fluid_settings = cfunc('new_fluid_settings', c_void_p)
|
|
|
|
| 2123 |
('synth', c_void_p, 1),
|
| 2124 |
('level', c_double, 1))
|
| 2125 |
|
| 2126 |
+
fluid_synth_set_chorus_speed = cfunc('fluid_synth_set_chorus_speed', c_int,
|
| 2127 |
+
('synth', c_void_p, 1),
|
| 2128 |
+
('speed', c_double, 1))
|
| 2129 |
+
|
| 2130 |
+
fluid_synth_set_chorus_depth = cfunc('fluid_synth_set_chorus_depth', c_int,
|
| 2131 |
+
('synth', c_void_p, 1),
|
| 2132 |
+
('depth_ms', c_double, 1))
|
| 2133 |
+
|
| 2134 |
fluid_synth_set_chorus_type = cfunc('fluid_synth_set_chorus_type', c_int,
|
| 2135 |
('synth', c_void_p, 1),
|
| 2136 |
('type', c_int, 1))
|
| 2137 |
+
|
| 2138 |
fluid_synth_get_reverb_roomsize = cfunc('fluid_synth_get_reverb_roomsize', c_double,
|
| 2139 |
('synth', c_void_p, 1))
|
| 2140 |
|
|
|
|
| 2266 |
fluid_midi_event_get_velocity = cfunc('fluid_midi_event_get_velocity', c_int,
|
| 2267 |
('evt', c_void_p, 1))
|
| 2268 |
|
| 2269 |
+
# fluid modulator
|
| 2270 |
+
new_fluid_mod = cfunc("new_fluid_mod", c_void_p)
|
| 2271 |
+
|
| 2272 |
+
delete_fluid_mod = cfunc("delete_fluid_mod", c_void_p, ("mod", c_void_p, 1))
|
| 2273 |
+
|
| 2274 |
+
fluid_mod_clone = cfunc(
|
| 2275 |
+
"fluid_mod_clone", c_void_p, ("mod", c_void_p, 1), ("src", c_void_p, 1),
|
| 2276 |
+
)
|
| 2277 |
+
|
| 2278 |
+
fluid_mod_get_amount = cfunc("fluid_mod_get_amount", c_void_p, ("mod", c_void_p, 1))
|
| 2279 |
+
|
| 2280 |
+
fluid_mod_get_dest = cfunc("fluid_mod_get_dest", c_void_p, ("mod", c_void_p, 1))
|
| 2281 |
+
|
| 2282 |
+
fluid_mod_get_flags1 = cfunc("fluid_mod_get_flags1", c_void_p, ("mod", c_void_p, 1))
|
| 2283 |
+
|
| 2284 |
+
fluid_mod_get_flags2 = cfunc("fluid_mod_get_flags2", c_void_p, ("mod", c_void_p, 1))
|
| 2285 |
+
|
| 2286 |
+
fluid_mod_get_source1 = cfunc("fluid_mod_get_source1", c_void_p, ("mod", c_void_p, 1))
|
| 2287 |
+
|
| 2288 |
+
fluid_mod_get_source2 = cfunc("fluid_mod_get_source2", c_void_p, ("mod", c_void_p, 1))
|
| 2289 |
+
|
| 2290 |
+
fluid_mod_get_transform = cfunc(
|
| 2291 |
+
"fluid_mod_get_transform", c_void_p, ("mod", c_void_p, 1),
|
| 2292 |
+
)
|
| 2293 |
+
|
| 2294 |
+
fluid_mod_has_dest = cfunc(
|
| 2295 |
+
"fluid_mod_has_dest", c_void_p, ("mod", c_void_p, 1), ("gen", c_uint, 1),
|
| 2296 |
+
)
|
| 2297 |
+
|
| 2298 |
+
fluid_mod_has_source = cfunc(
|
| 2299 |
+
"fluid_mod_has_dest",
|
| 2300 |
+
c_void_p,
|
| 2301 |
+
("mod", c_void_p, 1),
|
| 2302 |
+
("cc", c_uint, 1),
|
| 2303 |
+
("ctrl", c_uint, 1),
|
| 2304 |
+
)
|
| 2305 |
+
|
| 2306 |
+
fluid_mod_set_amount = cfunc(
|
| 2307 |
+
"fluid_mod_set_amount", c_void_p, ("mod", c_void_p, 1), ("amount", c_double, 1),
|
| 2308 |
+
)
|
| 2309 |
+
|
| 2310 |
+
fluid_mod_set_dest = cfunc(
|
| 2311 |
+
"fluid_mod_set_dest", c_void_p, ("mod", c_void_p, 1), ("dst", c_int, 1),
|
| 2312 |
+
)
|
| 2313 |
+
|
| 2314 |
+
fluid_mod_set_source1 = cfunc(
|
| 2315 |
+
"fluid_mod_set_source1",
|
| 2316 |
+
c_void_p,
|
| 2317 |
+
("mod", c_void_p, 1),
|
| 2318 |
+
("src", c_int, 1),
|
| 2319 |
+
("flags", c_int, 1),
|
| 2320 |
+
)
|
| 2321 |
+
|
| 2322 |
+
fluid_mod_set_source2 = cfunc(
|
| 2323 |
+
"fluid_mod_set_source2",
|
| 2324 |
+
c_void_p,
|
| 2325 |
+
("mod", c_void_p, 1),
|
| 2326 |
+
("src", c_int, 1),
|
| 2327 |
+
("flags", c_int, 1),
|
| 2328 |
+
)
|
| 2329 |
+
|
| 2330 |
+
fluid_mod_set_transform = cfunc(
|
| 2331 |
+
"fluid_mod_set_transform", c_void_p, ("mod", c_void_p, 1), ("type", c_int, 1),
|
| 2332 |
+
)
|
| 2333 |
+
|
| 2334 |
+
fluid_mod_sizeof = cfunc("fluid_mod_sizeof", c_void_p)
|
| 2335 |
+
|
| 2336 |
+
fluid_mod_test_identity = cfunc(
|
| 2337 |
+
"fluid_mod_test_identity", c_void_p, ("mod1", c_void_p, 1), ("mod2", c_void_p, 1),
|
| 2338 |
+
)
|
| 2339 |
+
|
| 2340 |
# fluid_player_status returned by fluid_player_get_status()
|
| 2341 |
FLUID_PLAYER_READY = 0
|
| 2342 |
FLUID_PLAYER_PLAYING = 1
|
|
|
|
| 2398 |
('handler', CFUNCTYPE(c_int, c_void_p, c_void_p), 1),
|
| 2399 |
('event_handler_data', c_void_p, 1))
|
| 2400 |
|
| 2401 |
+
delete_fluid_midi_driver = cfunc('delete_fluid_midi_driver', None,
|
| 2402 |
+
('driver', c_void_p, 1))
|
| 2403 |
+
|
| 2404 |
|
| 2405 |
# fluid midi router rule
|
| 2406 |
class fluid_midi_router_t(Structure):
|
|
|
|
| 2462 |
('rule', c_void_p, 1),
|
| 2463 |
('type', c_int, 1))
|
| 2464 |
|
| 2465 |
+
# fluid file renderer
|
| 2466 |
+
new_fluid_file_renderer = cfunc('new_fluid_file_renderer', c_void_p,
|
| 2467 |
+
('synth', c_void_p, 1))
|
| 2468 |
+
|
| 2469 |
+
delete_fluid_file_renderer = cfunc('delete_fluid_file_renderer', None,
|
| 2470 |
+
('renderer', c_void_p, 1))
|
| 2471 |
+
|
| 2472 |
+
fluid_file_renderer_process_block = cfunc('fluid_file_renderer_process_block', c_int,
|
| 2473 |
+
('render', c_void_p, 1))
|
| 2474 |
+
|
| 2475 |
# fluidsynth 2.x
|
| 2476 |
new_fluid_cmd_handler=cfunc('new_fluid_cmd_handler', c_void_p,
|
| 2477 |
('synth', c_void_p, 1),
|
|
|
|
| 2546 |
self.audio_driver = None
|
| 2547 |
self.midi_driver = None
|
| 2548 |
self.router = None
|
| 2549 |
+
self.custom_router_callback = None
|
| 2550 |
def setting(self, opt, val):
|
| 2551 |
"""change an arbitrary synth setting, type-smart"""
|
| 2552 |
if isinstance(val, (str, bytes)):
|
|
|
|
| 2582 |
see http://www.fluidsynth.org/api/fluidsettings.xml for allowed values and defaults by platform
|
| 2583 |
"""
|
| 2584 |
driver = driver or self.get_setting('audio.driver')
|
| 2585 |
+
device = device or self.get_setting(f'audio.{driver}.device')
|
| 2586 |
midi_driver = midi_driver or self.get_setting('midi.driver')
|
| 2587 |
|
| 2588 |
self.setting('audio.driver', driver)
|
| 2589 |
+
self.setting(f'audio.{driver}.device', device)
|
| 2590 |
self.audio_driver = new_fluid_audio_driver(self.settings, self.synth)
|
| 2591 |
self.setting('midi.driver', midi_driver)
|
| 2592 |
self.router = new_fluid_midi_router(self.settings, fluid_synth_handle_midi_event, self.synth)
|
|
|
|
| 2594 |
new_fluid_cmd_handler(self.synth, self.router)
|
| 2595 |
else:
|
| 2596 |
fluid_synth_set_midi_router(self.synth, self.router)
|
| 2597 |
+
if midi_router is None: ## Use fluidsynth to create a MIDI event handler
|
| 2598 |
self.midi_driver = new_fluid_midi_driver(self.settings, fluid_midi_router_handle_midi_event, self.router)
|
| 2599 |
self.custom_router_callback = None
|
| 2600 |
else: ## Supply an external MIDI event handler
|
|
|
|
| 2605 |
def delete(self):
|
| 2606 |
if self.audio_driver:
|
| 2607 |
delete_fluid_audio_driver(self.audio_driver)
|
| 2608 |
+
if self.midi_driver:
|
| 2609 |
+
delete_fluid_midi_driver(self.midi_driver)
|
| 2610 |
delete_fluid_synth(self.synth)
|
| 2611 |
delete_fluid_settings(self.settings)
|
| 2612 |
def sfload(self, filename, update_midi_preset=0):
|
|
|
|
| 2651 |
return None
|
| 2652 |
return fluid_preset_get_name(preset).decode('ascii')
|
| 2653 |
else:
|
| 2654 |
+
return None
|
|
|
|
| 2655 |
def router_clear(self):
|
| 2656 |
if self.router is not None:
|
| 2657 |
fluid_midi_router_clear_rules(self.router)
|
|
|
|
| 2702 |
if fluid_synth_set_reverb is not None:
|
| 2703 |
return fluid_synth_set_reverb(self.synth, roomsize, damping, width, level)
|
| 2704 |
else:
|
| 2705 |
+
flags=0
|
| 2706 |
if roomsize>=0:
|
| 2707 |
+
flags+=0b0001
|
| 2708 |
if damping>=0:
|
| 2709 |
+
flags+=0b0010
|
| 2710 |
if width>=0:
|
| 2711 |
+
flags+=0b0100
|
| 2712 |
if level>=0:
|
| 2713 |
+
flags+=0b1000
|
| 2714 |
+
return fluid_synth_set_reverb_full(self.synth, flags, roomsize, damping, width, level)
|
| 2715 |
def set_chorus(self, nr=-1, level=-1.0, speed=-1.0, depth=-1.0, type=-1):
|
| 2716 |
"""
|
| 2717 |
nr Chorus voice count (0-99, CPU time consumption proportional to this value)
|
|
|
|
| 2764 |
if fluid_synth_set_chorus_level is not None:
|
| 2765 |
return fluid_synth_set_chorus_level(self.synth, level)
|
| 2766 |
else:
|
| 2767 |
+
return self.set_chorus(level=level)
|
| 2768 |
def set_chorus_speed(self, speed):
|
| 2769 |
if fluid_synth_set_chorus_speed is not None:
|
| 2770 |
return fluid_synth_set_chorus_speed(self.synth, speed)
|
| 2771 |
else:
|
| 2772 |
return self.set_chorus(speed=speed)
|
| 2773 |
+
def set_chorus_depth(self, depth_ms):
|
| 2774 |
if fluid_synth_set_chorus_depth is not None:
|
| 2775 |
+
return fluid_synth_set_chorus_depth(self.synth, depth_ms)
|
| 2776 |
else:
|
| 2777 |
+
return self.set_chorus(depth=depth_ms)
|
| 2778 |
def set_chorus_type(self, type):
|
| 2779 |
if fluid_synth_set_chorus_type is not None:
|
| 2780 |
return fluid_synth_set_chorus_type(self.synth, type)
|
|
|
|
| 2826 |
A pitch bend value of 0 is no pitch change from default.
|
| 2827 |
A value of -2048 is 1 semitone down.
|
| 2828 |
A value of 2048 is 1 semitone up.
|
| 2829 |
+
Maximum values are -8192 to +8191 (transposing by 4 semitones).
|
| 2830 |
|
| 2831 |
"""
|
| 2832 |
+
return fluid_synth_pitch_bend(self.synth, chan, max(0, min(val + 8192, 16383)))
|
| 2833 |
def cc(self, chan, ctrl, val):
|
| 2834 |
"""Send control change value
|
| 2835 |
|
|
|
|
| 2879 |
|
| 2880 |
"""
|
| 2881 |
return fluid_synth_write_s16_stereo(self.synth, len)
|
| 2882 |
+
def tuning_dump(self, bank, prog):
|
| 2883 |
+
"""Get tuning information for given bank and preset
|
| 2884 |
+
|
| 2885 |
+
Return value is an array of length 128 with tuning factors for each MIDI note.
|
| 2886 |
+
Tuning factor of 0.0 in each position is standard tuning. Measured in cents.
|
| 2887 |
+
"""
|
| 2888 |
+
pitch = (c_double * 128)()
|
| 2889 |
+
fluid_synth_tuning_dump(self.synth, bank, prog, None, 0, pitch)
|
| 2890 |
+
return pitch[:]
|
| 2891 |
|
| 2892 |
def midi_event_get_type(self, event):
|
| 2893 |
return fluid_midi_event_get_type(event)
|
|
|
|
| 2906 |
|
| 2907 |
def play_midi_file(self, filename):
|
| 2908 |
self.player = new_fluid_player(self.synth)
|
| 2909 |
+
if self.player is None:
|
| 2910 |
+
return FLUID_FAILED
|
| 2911 |
+
if self.custom_router_callback is not None:
|
| 2912 |
fluid_player_set_playback_callback(self.player, self.custom_router_callback, self.synth)
|
| 2913 |
status = fluid_player_add(self.player, filename.encode())
|
| 2914 |
+
if status == FLUID_FAILED:
|
| 2915 |
+
return status
|
| 2916 |
status = fluid_player_play(self.player)
|
| 2917 |
return status
|
| 2918 |
|
| 2919 |
def play_midi_stop(self):
|
| 2920 |
status = fluid_player_stop(self.player)
|
| 2921 |
+
if status == FLUID_FAILED:
|
| 2922 |
+
return status
|
| 2923 |
status = fluid_player_seek(self.player, 0)
|
| 2924 |
delete_fluid_player(self.player)
|
| 2925 |
return status
|
|
|
|
| 2927 |
def player_set_tempo(self, tempo_type, tempo):
|
| 2928 |
return fluid_player_set_tempo(self.player, tempo_type, tempo)
|
| 2929 |
|
| 2930 |
+
def midi2audio(self, midifile, audiofile = "output.wav"):
|
| 2931 |
+
"""Convert a midi file to an audio file"""
|
| 2932 |
+
self.setting("audio.file.name", audiofile)
|
| 2933 |
+
player = new_fluid_player(self.synth)
|
| 2934 |
+
fluid_player_add(player, midifile.encode())
|
| 2935 |
+
fluid_player_play(player)
|
| 2936 |
+
renderer = new_fluid_file_renderer(self.synth)
|
| 2937 |
+
while fluid_player_get_status(player) == FLUID_PLAYER_PLAYING:
|
| 2938 |
+
if fluid_file_renderer_process_block(renderer) != FLUID_OK:
|
| 2939 |
+
break
|
| 2940 |
+
delete_fluid_file_renderer(renderer)
|
| 2941 |
+
delete_fluid_player(player)
|
| 2942 |
+
|
| 2943 |
+
# flag values
|
| 2944 |
+
FLUID_MOD_POSITIVE = 0
|
| 2945 |
+
FLUID_MOD_NEGATIVE = 1
|
| 2946 |
+
FLUID_MOD_UNIPOLAR = 0
|
| 2947 |
+
FLUID_MOD_BIPOLAR = 2
|
| 2948 |
+
FLUID_MOD_LINEAR = 0
|
| 2949 |
+
FLUID_MOD_CONCAVE = 4
|
| 2950 |
+
FLUID_MOD_CONVEX = 8
|
| 2951 |
+
FLUID_MOD_SWITCH = 12
|
| 2952 |
+
FLUID_MOD_GC = 0
|
| 2953 |
+
FLUID_MOD_CC = 16
|
| 2954 |
+
FLUID_MOD_SIN = 0x80
|
| 2955 |
+
|
| 2956 |
+
# src values
|
| 2957 |
+
FLUID_MOD_NONE = 0
|
| 2958 |
+
FLUID_MOD_VELOCITY = 2
|
| 2959 |
+
FLUID_MOD_KEY = 3
|
| 2960 |
+
FLUID_MOD_KEYPRESSURE = 10
|
| 2961 |
+
FLUID_MOD_CHANNELPRESSURE = 13
|
| 2962 |
+
FLUID_MOD_PITCHWHEEL = 14
|
| 2963 |
+
FLUID_MOD_PITCHWHEELSENS = 16
|
| 2964 |
+
|
| 2965 |
+
# Transforms
|
| 2966 |
+
FLUID_MOD_TRANSFORM_LINEAR = 0
|
| 2967 |
+
FLUID_MOD_TRANSFORM_ABS = 2
|
| 2968 |
+
|
| 2969 |
+
class Modulator:
|
| 2970 |
+
def __init__(self):
|
| 2971 |
+
"""Create new modulator object"""
|
| 2972 |
+
self.mod = new_fluid_mod()
|
| 2973 |
+
|
| 2974 |
+
def clone(self, src):
|
| 2975 |
+
response = fluid_mod_clone(self.mod, src)
|
| 2976 |
+
if response == FLUID_FAILED:
|
| 2977 |
+
raise Exception("Modulation clone failed")
|
| 2978 |
+
return response
|
| 2979 |
|
| 2980 |
+
def get_amount(self):
|
| 2981 |
+
response = fluid_mod_get_amount(self.mod)
|
| 2982 |
+
if response == FLUID_FAILED:
|
| 2983 |
+
raise Exception("Modulation amount get failed")
|
| 2984 |
+
return response
|
| 2985 |
+
|
| 2986 |
+
def get_dest(self):
|
| 2987 |
+
response = fluid_mod_get_dest(self.mod)
|
| 2988 |
+
if response == FLUID_FAILED:
|
| 2989 |
+
raise Exception("Modulation destination get failed")
|
| 2990 |
+
return response
|
| 2991 |
+
|
| 2992 |
+
def get_flags1(self):
|
| 2993 |
+
response = fluid_mod_get_flags1(self.mod)
|
| 2994 |
+
if response == FLUID_FAILED:
|
| 2995 |
+
raise Exception("Modulation flags1 get failed")
|
| 2996 |
+
return response
|
| 2997 |
+
|
| 2998 |
+
def get_flags2(self):
|
| 2999 |
+
response = fluid_mod_get_flags2(self.mod)
|
| 3000 |
+
if response == FLUID_FAILED:
|
| 3001 |
+
raise Exception("Modulation flags2 get failed")
|
| 3002 |
+
return response
|
| 3003 |
+
|
| 3004 |
+
def get_source1(self):
|
| 3005 |
+
response = fluid_mod_get_source1(self.mod)
|
| 3006 |
+
if response == FLUID_FAILED:
|
| 3007 |
+
raise Exception("Modulation source1 get failed")
|
| 3008 |
+
return response
|
| 3009 |
+
|
| 3010 |
+
def get_source2(self):
|
| 3011 |
+
response = fluid_mod_get_source2(self.mod)
|
| 3012 |
+
if response == FLUID_FAILED:
|
| 3013 |
+
raise Exception("Modulation source2 get failed")
|
| 3014 |
+
return response
|
| 3015 |
+
|
| 3016 |
+
def get_transform(self):
|
| 3017 |
+
response = fluid_mod_get_transform(self.mod)
|
| 3018 |
+
if response == FLUID_FAILED:
|
| 3019 |
+
raise Exception("Modulation transform get failed")
|
| 3020 |
+
return response
|
| 3021 |
+
|
| 3022 |
+
def has_dest(self, gen):
|
| 3023 |
+
response = fluid_mod_has_dest(self.mod, gen)
|
| 3024 |
+
if response == FLUID_FAILED:
|
| 3025 |
+
raise Exception("Modulation has destination check failed")
|
| 3026 |
+
return response
|
| 3027 |
+
|
| 3028 |
+
def has_source(self, cc, ctrl):
|
| 3029 |
+
response = fluid_mod_has_source(self.mod, cc, ctrl)
|
| 3030 |
+
if response == FLUID_FAILED:
|
| 3031 |
+
raise Exception("Modulation has source check failed")
|
| 3032 |
+
return response
|
| 3033 |
+
|
| 3034 |
+
def set_amount(self, amount):
|
| 3035 |
+
response = fluid_mod_set_amount(self.mod, amount)
|
| 3036 |
+
if response == FLUID_FAILED:
|
| 3037 |
+
raise Exception("Modulation set amount failed")
|
| 3038 |
+
return response
|
| 3039 |
+
|
| 3040 |
+
def set_dest(self, dest):
|
| 3041 |
+
response = fluid_mod_set_dest(self.mod, dest)
|
| 3042 |
+
if response == FLUID_FAILED:
|
| 3043 |
+
raise Exception("Modulation set dest failed")
|
| 3044 |
+
return response
|
| 3045 |
+
|
| 3046 |
+
def set_source1(self, src, flags):
|
| 3047 |
+
response = fluid_mod_set_source1(self.mod, src, flags)
|
| 3048 |
+
if response == FLUID_FAILED:
|
| 3049 |
+
raise Exception("Modulation set source 1 failed")
|
| 3050 |
+
return response
|
| 3051 |
+
|
| 3052 |
+
def set_source2(self, src, flags):
|
| 3053 |
+
response = fluid_mod_set_source2(self.mod, src, flags)
|
| 3054 |
+
if response == FLUID_FAILED:
|
| 3055 |
+
raise Exception("Modulation set source 2 failed")
|
| 3056 |
+
return response
|
| 3057 |
+
|
| 3058 |
+
def set_transform(self, type):
|
| 3059 |
+
response = fluid_mod_set_transform(self.mod, type)
|
| 3060 |
+
if response == FLUID_FAILED:
|
| 3061 |
+
raise Exception("Modulation set transform failed")
|
| 3062 |
+
return response
|
| 3063 |
+
|
| 3064 |
+
def sizeof(self):
|
| 3065 |
+
response = fluid_mod_sizeof()
|
| 3066 |
+
if response == FLUID_FAILED:
|
| 3067 |
+
raise Exception("Modulation sizeof failed")
|
| 3068 |
+
return response
|
| 3069 |
+
|
| 3070 |
+
def test_identity(self, mod2):
|
| 3071 |
+
response = fluid_mod_sizeof(self.mod, mod2)
|
| 3072 |
+
if response == FLUID_FAILED:
|
| 3073 |
+
raise Exception("Modulation identity check failed")
|
| 3074 |
+
return response
|
| 3075 |
|
| 3076 |
class Sequencer:
|
| 3077 |
def __init__(self, time_scale=1000, use_system_timer=True):
|
|
|
|
| 3088 |
def register_fluidsynth(self, synth):
|
| 3089 |
response = fluid_sequencer_register_fluidsynth(self.sequencer, synth.synth)
|
| 3090 |
if response == FLUID_FAILED:
|
| 3091 |
+
raise Exception("Registering fluid synth failed")
|
| 3092 |
return response
|
| 3093 |
|
| 3094 |
def register_client(self, name, callback, data=None):
|
| 3095 |
c_callback = CFUNCTYPE(None, c_uint, c_void_p, c_void_p, c_void_p)(callback)
|
| 3096 |
response = fluid_sequencer_register_client(self.sequencer, name.encode(), c_callback, data)
|
| 3097 |
if response == FLUID_FAILED:
|
| 3098 |
+
raise Exception("Registering client failed")
|
| 3099 |
|
| 3100 |
# store in a list to prevent garbage collection
|
| 3101 |
self.client_callbacks.append(c_callback)
|
|
|
|
| 3135 |
def _schedule_event(self, evt, time, absolute=True):
|
| 3136 |
response = fluid_sequencer_send_at(self.sequencer, evt, time, absolute)
|
| 3137 |
if response == FLUID_FAILED:
|
| 3138 |
+
raise Exception("Scheduling event failed")
|
| 3139 |
|
| 3140 |
def get_tick(self):
|
| 3141 |
return fluid_sequencer_get_tick(self.sequencer)
|
|
|
|
| 3154 |
|
| 3155 |
"""
|
| 3156 |
import numpy
|
| 3157 |
+
return (data.astype(numpy.int16)).tobytes()
|
| 3158 |
|
| 3159 |
#===============================================================================
|
| 3160 |
|
| 3161 |
import numpy as np
|
| 3162 |
import wave
|
| 3163 |
|
| 3164 |
+
#===============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3165 |
|
| 3166 |
+
def normalize_audio(audio: np.ndarray,
|
| 3167 |
+
method: str = 'peak',
|
| 3168 |
+
target_level_db: float = -1.0,
|
| 3169 |
+
per_channel: bool = False,
|
| 3170 |
+
eps: float = 1e-9
|
| 3171 |
+
) -> np.ndarray:
|
| 3172 |
+
|
| 3173 |
+
"""
|
| 3174 |
+
Normalize audio to a target dBFS level.
|
| 3175 |
+
|
| 3176 |
+
Parameters
|
| 3177 |
+
----------
|
| 3178 |
+
audio : np.ndarray
|
| 3179 |
+
Float-valued array in range [-1, 1] with shape (channels, samples)
|
| 3180 |
+
or (samples,) for mono.
|
| 3181 |
+
method : {'peak', 'rms'}
|
| 3182 |
+
- 'peak': scale so that max(|audio|) = target_level_lin
|
| 3183 |
+
- 'rms' : scale so that RMS(audio) = target_level_lin
|
| 3184 |
+
target_level_db : float
|
| 3185 |
+
Desired output level, in dBFS (0 dBFS = max digital full scale).
|
| 3186 |
+
e.g. -1.0 dBFS means ~0.8913 linear gain.
|
| 3187 |
+
per_channel : bool
|
| 3188 |
+
If True, normalize each channel independently. Otherwise, use a
|
| 3189 |
+
global measure across all channels.
|
| 3190 |
+
eps : float
|
| 3191 |
+
Small constant to avoid division by zero.
|
| 3192 |
+
|
| 3193 |
+
Returns
|
| 3194 |
+
-------
|
| 3195 |
+
normalized : np.ndarray
|
| 3196 |
+
Audio array of same shape, scaled so that levels meet the target.
|
| 3197 |
+
"""
|
| 3198 |
+
|
| 3199 |
+
# Convert target dB to linear gain
|
| 3200 |
+
target_lin = 10 ** (target_level_db / 20.0)
|
| 3201 |
|
| 3202 |
+
# Ensure audio is float
|
| 3203 |
+
audio = audio.astype(np.float32)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3204 |
|
| 3205 |
+
# if mono, make it (1, N)
|
| 3206 |
+
if audio.ndim == 1:
|
| 3207 |
+
audio = audio[np.newaxis, :]
|
| 3208 |
|
| 3209 |
+
# Choose measurement axis
|
| 3210 |
+
axis = 1 if per_channel else None
|
| 3211 |
|
| 3212 |
+
if method == 'peak':
|
| 3213 |
+
# Compute peak per channel or global
|
| 3214 |
+
peak = np.max(np.abs(audio), axis=axis, keepdims=True)
|
| 3215 |
+
peak = np.maximum(peak, eps)
|
| 3216 |
+
scales = target_lin / peak
|
| 3217 |
|
| 3218 |
+
elif method == 'rms':
|
| 3219 |
+
# Compute RMS per channel or global
|
| 3220 |
+
rms = np.sqrt(np.mean(audio ** 2, axis=axis, keepdims=True))
|
| 3221 |
+
rms = np.maximum(rms, eps)
|
| 3222 |
+
scales = target_lin / rms
|
| 3223 |
+
|
| 3224 |
+
else:
|
| 3225 |
+
raise ValueError(f"Unsupported method '{method}'; choose 'peak' or 'rms'.")
|
| 3226 |
|
| 3227 |
+
# Broadcast scales back to audio shape
|
| 3228 |
+
normalized = audio * scales
|
| 3229 |
+
|
| 3230 |
+
# Clip just in case of rounding
|
| 3231 |
+
return np.clip(normalized, -1.0, 1.0)
|
| 3232 |
+
|
| 3233 |
+
#===============================================================================
|
| 3234 |
+
|
| 3235 |
+
def midi_opus_to_colab_audio(midi_opus,
|
| 3236 |
+
soundfont_path='/usr/share/sounds/sf2/FluidR3_GM.sf2',
|
| 3237 |
+
sample_rate=16000, # 44100
|
| 3238 |
+
volume_level_db=-1,
|
| 3239 |
+
trim_silence=True,
|
| 3240 |
+
silence_threshold=0.1,
|
| 3241 |
+
enable_reverb=False,
|
| 3242 |
+
reverb_param_dic={'roomsize': 0,
|
| 3243 |
+
'damping': 0,
|
| 3244 |
+
'width': 0,
|
| 3245 |
+
'level': 0
|
| 3246 |
+
},
|
| 3247 |
+
enable_chorus=False,
|
| 3248 |
+
chorus_param_dic={'nr': 0,
|
| 3249 |
+
'level': 0,
|
| 3250 |
+
'speed': 0.1,
|
| 3251 |
+
'depth': 0,
|
| 3252 |
+
'type': 0},
|
| 3253 |
+
output_for_gradio=False,
|
| 3254 |
+
write_audio_to_WAV=False,
|
| 3255 |
+
output_WAV_name=''
|
| 3256 |
+
):
|
| 3257 |
+
|
| 3258 |
+
if midi_opus[1]:
|
| 3259 |
+
|
| 3260 |
+
ticks_per_beat, *tracks = midi_opus
|
| 3261 |
+
if not tracks:
|
| 3262 |
+
return None
|
| 3263 |
+
|
| 3264 |
+
# Flatten & convert delta-times to absolute-time
|
| 3265 |
+
events = []
|
| 3266 |
+
for track in tracks:
|
| 3267 |
+
abs_t = 0
|
| 3268 |
+
for name, dt, *data in track:
|
| 3269 |
+
abs_t += dt
|
| 3270 |
+
events.append([name, abs_t, *data])
|
| 3271 |
+
events.sort(key=lambda e: e[1])
|
| 3272 |
+
|
| 3273 |
+
# Setup FluidSynth
|
| 3274 |
+
fl = Synth(samplerate=float(sample_rate))
|
| 3275 |
+
sfid = fl.sfload(soundfont_path)
|
| 3276 |
+
for chan in range(16):
|
| 3277 |
+
# channel 9 = percussion GM bank 128
|
| 3278 |
+
fl.program_select(chan, sfid, 128 if chan == 9 else 0, 0)
|
| 3279 |
+
|
| 3280 |
+
if enable_reverb:
|
| 3281 |
+
fl.set_reverb(roomsize=reverb_param_dic['roomsize'],
|
| 3282 |
+
damping=reverb_param_dic['damping'],
|
| 3283 |
+
width=reverb_param_dic['width'],
|
| 3284 |
+
level=reverb_param_dic['level']
|
| 3285 |
+
)
|
| 3286 |
+
|
| 3287 |
+
"""
|
| 3288 |
+
roomsize Reverb room size value (0.0-1.0)
|
| 3289 |
+
damping Reverb damping value (0.0-1.0)
|
| 3290 |
+
width Reverb width value (0.0-100.0)
|
| 3291 |
+
level Reverb level value (0.0-1.0)
|
| 3292 |
+
"""
|
| 3293 |
+
|
| 3294 |
+
if enable_chorus:
|
| 3295 |
+
fl.set_chorus(nr=chorus_param_dic['nr'],
|
| 3296 |
+
level=chorus_param_dic['level'],
|
| 3297 |
+
speed=chorus_param_dic['speed'],
|
| 3298 |
+
depth=chorus_param_dic['depth'],
|
| 3299 |
+
type=chorus_param_dic['type']
|
| 3300 |
+
)
|
| 3301 |
+
|
| 3302 |
+
"""
|
| 3303 |
+
nr Chorus voice count (0-99, CPU time consumption proportional to this value)
|
| 3304 |
+
level Chorus level (0.0-10.0)
|
| 3305 |
+
speed Chorus speed in Hz (0.29-5.0)
|
| 3306 |
+
depth_ms Chorus depth (max value depends on synth sample rate, 0.0-21.0 is safe for sample rate values up to 96KHz)
|
| 3307 |
+
type Chorus waveform type (0=sine, 1=triangle)
|
| 3308 |
+
"""
|
| 3309 |
+
|
| 3310 |
+
# Playback vars
|
| 3311 |
+
tempo = int((60 / 120) * 1e6) # default 120bpm
|
| 3312 |
+
last_t = 0
|
| 3313 |
+
ss = np.empty((0, 2), dtype=np.int16)
|
| 3314 |
+
|
| 3315 |
+
for name, cur_t, *data in events:
|
| 3316 |
+
# compute how many samples have passed since the last event
|
| 3317 |
+
delta_ticks = cur_t - last_t
|
| 3318 |
+
last_t = cur_t
|
| 3319 |
+
dt_seconds = (delta_ticks / ticks_per_beat) * (tempo / 1e6)
|
| 3320 |
+
sample_len = int(dt_seconds * sample_rate)
|
| 3321 |
+
if sample_len > 0:
|
| 3322 |
+
buf = fl.get_samples(sample_len).reshape(-1, 2)
|
| 3323 |
+
ss = np.concatenate([ss, buf], axis=0)
|
| 3324 |
+
|
| 3325 |
+
# Dispatch every known event
|
| 3326 |
+
if name == "note_on" and data[2] > 0:
|
| 3327 |
+
chan, note, vel = data
|
| 3328 |
+
fl.noteon(chan, note, vel)
|
| 3329 |
+
|
| 3330 |
+
elif name == "note_off" or (name == "note_on" and data[2] == 0):
|
| 3331 |
+
chan, note = data[:2]
|
| 3332 |
+
fl.noteoff(chan, note)
|
| 3333 |
+
|
| 3334 |
+
elif name == "patch_change":
|
| 3335 |
+
chan, patch = data[:2]
|
| 3336 |
+
bank = 128 if chan == 9 else 0
|
| 3337 |
+
fl.program_select(chan, sfid, bank, patch)
|
| 3338 |
+
|
| 3339 |
+
elif name == "control_change":
|
| 3340 |
+
chan, ctrl, val = data[:3]
|
| 3341 |
+
fl.cc(chan, ctrl, val)
|
| 3342 |
+
|
| 3343 |
+
elif name == "key_after_touch":
|
| 3344 |
+
chan, note, vel = data
|
| 3345 |
+
# fl.key_pressure(chan, note, vel)
|
| 3346 |
+
|
| 3347 |
+
elif name == "channel_after_touch":
|
| 3348 |
+
chan, vel = data
|
| 3349 |
+
# fl.channel_pressure(chan, vel)
|
| 3350 |
+
|
| 3351 |
+
elif name == "pitch_wheel_change":
|
| 3352 |
+
chan, wheel = data
|
| 3353 |
+
fl.pitch_bend(chan, wheel)
|
| 3354 |
+
|
| 3355 |
+
elif name == "song_position":
|
| 3356 |
+
# song_pos = data[0]; # often not needed for playback
|
| 3357 |
+
pass
|
| 3358 |
+
|
| 3359 |
+
elif name == "song_select":
|
| 3360 |
+
# song_number = data[0]
|
| 3361 |
+
pass
|
| 3362 |
+
|
| 3363 |
+
elif name == "tune_request":
|
| 3364 |
+
# typically resets tuning; FS handles internally
|
| 3365 |
+
pass
|
| 3366 |
+
|
| 3367 |
+
elif name in ("sysex_f0", "sysex_f7"):
|
| 3368 |
+
raw_bytes = data[0]
|
| 3369 |
+
# fl.sysex(raw_bytes)
|
| 3370 |
+
pass
|
| 3371 |
+
|
| 3372 |
+
# Meta events & others—no direct audio effect, so we skip or log
|
| 3373 |
+
elif name in (
|
| 3374 |
+
"set_tempo", # handled below
|
| 3375 |
+
"end_track",
|
| 3376 |
+
"text_event", "text_event_08", "text_event_09", "text_event_0a",
|
| 3377 |
+
"text_event_0b", "text_event_0c", "text_event_0d", "text_event_0e", "text_event_0f",
|
| 3378 |
+
"copyright_text_event", "track_name", "instrument_name",
|
| 3379 |
+
"lyric", "marker", "cue_point",
|
| 3380 |
+
"smpte_offset", "time_signature", "key_signature",
|
| 3381 |
+
"sequencer_specific", "raw_meta_event"
|
| 3382 |
+
):
|
| 3383 |
+
if name == "set_tempo":
|
| 3384 |
+
tempo = data[0]
|
| 3385 |
+
# else: skip all other meta & text; you could hook in logging here
|
| 3386 |
+
continue
|
| 3387 |
+
|
| 3388 |
+
else:
|
| 3389 |
+
# unknown event type
|
| 3390 |
+
continue
|
| 3391 |
+
|
| 3392 |
+
# Cleanup synth
|
| 3393 |
+
fl.delete()
|
| 3394 |
+
|
| 3395 |
+
if ss.size:
|
| 3396 |
+
maxv = np.abs(ss).max()
|
| 3397 |
+
if maxv:
|
| 3398 |
+
ss = (ss / maxv) * np.iinfo(np.int16).max
|
| 3399 |
+
ss = ss.astype(np.int16)
|
| 3400 |
+
|
| 3401 |
+
# Optional trimming of trailing silence
|
| 3402 |
+
if trim_silence and ss.size:
|
| 3403 |
+
thresh = np.std(np.abs(ss)) * silence_threshold
|
| 3404 |
+
idx = np.where(np.abs(ss) > thresh)[0]
|
| 3405 |
+
if idx.size:
|
| 3406 |
+
ss = ss[: idx[-1] + 1]
|
| 3407 |
+
|
| 3408 |
+
# For Gradio you might want raw int16 PCM
|
| 3409 |
+
if output_for_gradio:
|
| 3410 |
+
return ss
|
| 3411 |
+
|
| 3412 |
+
# Swap to (channels, samples) and normalize for playback
|
| 3413 |
+
ss = ss.T
|
| 3414 |
+
raw_audio = normalize_audio(ss, target_level_db=volume_level_db)
|
| 3415 |
+
|
| 3416 |
+
# Optionally write WAV to disk
|
| 3417 |
+
if write_audio_to_WAV:
|
| 3418 |
+
wav_name = midi_file.rsplit('.', 1)[0] + '.wav'
|
| 3419 |
+
if output_WAV_name != '':
|
| 3420 |
+
wav_name = output_WAV_name
|
| 3421 |
+
pcm = np.int16(raw_audio.T / np.max(np.abs(raw_audio)) * 32767)
|
| 3422 |
+
with wave.open(wav_name, 'wb') as wf:
|
| 3423 |
+
wf.setframerate(sample_rate)
|
| 3424 |
+
wf.setsampwidth(2)
|
| 3425 |
+
wf.setnchannels(pcm.shape[1])
|
| 3426 |
+
wf.writeframes(pcm.tobytes())
|
| 3427 |
+
|
| 3428 |
+
return raw_audio
|
| 3429 |
|
| 3430 |
else:
|
| 3431 |
return None
|
| 3432 |
|
| 3433 |
+
#===============================================================================
|
| 3434 |
+
|
| 3435 |
+
def midi_to_colab_audio(midi_file,
|
| 3436 |
+
soundfont_path='/usr/share/sounds/sf2/FluidR3_GM.sf2',
|
| 3437 |
+
sample_rate=16000,
|
| 3438 |
+
volume_level_db=-1,
|
| 3439 |
trim_silence=True,
|
| 3440 |
silence_threshold=0.1,
|
| 3441 |
+
enable_reverb=False,
|
| 3442 |
+
reverb_param_dic={'roomsize': 0,
|
| 3443 |
+
'damping': 0,
|
| 3444 |
+
'width': 0,
|
| 3445 |
+
'level': 0
|
| 3446 |
+
},
|
| 3447 |
+
enable_chorus=False,
|
| 3448 |
+
chorus_param_dic={'nr': 0,
|
| 3449 |
+
'level': 0,
|
| 3450 |
+
'speed': 0.1,
|
| 3451 |
+
'depth': 0,
|
| 3452 |
+
'type': 0},
|
| 3453 |
output_for_gradio=False,
|
| 3454 |
+
write_audio_to_WAV=False,
|
| 3455 |
+
output_WAV_name=''
|
| 3456 |
+
):
|
| 3457 |
+
"""
|
|
|
|
| 3458 |
Returns raw audio to pass to IPython.disaply.Audio func
|
| 3459 |
|
| 3460 |
Example usage:
|
|
|
|
| 3462 |
from IPython.display import Audio
|
| 3463 |
|
| 3464 |
display(Audio(raw_audio, rate=16000, normalize=False))
|
| 3465 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3466 |
|
| 3467 |
+
# Read and decode MIDI → opus event list
|
| 3468 |
+
ticks_per_beat, *tracks = midi2opus(open(midi_file, 'rb').read())
|
| 3469 |
+
if not tracks:
|
| 3470 |
+
return None
|
| 3471 |
|
| 3472 |
+
# Flatten & convert delta-times to absolute-time
|
| 3473 |
+
events = []
|
| 3474 |
+
for track in tracks:
|
| 3475 |
+
abs_t = 0
|
| 3476 |
+
for name, dt, *data in track:
|
| 3477 |
+
abs_t += dt
|
| 3478 |
+
events.append([name, abs_t, *data])
|
| 3479 |
+
events.sort(key=lambda e: e[1])
|
| 3480 |
+
|
| 3481 |
+
# Setup FluidSynth
|
| 3482 |
+
fl = Synth(samplerate=float(sample_rate))
|
| 3483 |
+
sfid = fl.sfload(soundfont_path)
|
| 3484 |
+
for chan in range(16):
|
| 3485 |
+
# channel 9 = percussion GM bank 128
|
| 3486 |
+
fl.program_select(chan, sfid, 128 if chan == 9 else 0, 0)
|
| 3487 |
+
|
| 3488 |
+
if enable_reverb:
|
| 3489 |
+
fl.set_reverb(roomsize=reverb_param_dic['roomsize'],
|
| 3490 |
+
damping=reverb_param_dic['damping'],
|
| 3491 |
+
width=reverb_param_dic['width'],
|
| 3492 |
+
level=reverb_param_dic['level']
|
| 3493 |
+
)
|
| 3494 |
+
|
| 3495 |
+
"""
|
| 3496 |
+
roomsize Reverb room size value (0.0-1.0)
|
| 3497 |
+
damping Reverb damping value (0.0-1.0)
|
| 3498 |
+
width Reverb width value (0.0-100.0)
|
| 3499 |
+
level Reverb level value (0.0-1.0)
|
| 3500 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3501 |
|
| 3502 |
+
if enable_chorus:
|
| 3503 |
+
fl.set_chorus(nr=chorus_param_dic['nr'],
|
| 3504 |
+
level=chorus_param_dic['level'],
|
| 3505 |
+
speed=chorus_param_dic['speed'],
|
| 3506 |
+
depth=chorus_param_dic['depth'],
|
| 3507 |
+
type=chorus_param_dic['type']
|
| 3508 |
+
)
|
| 3509 |
+
|
| 3510 |
+
"""
|
| 3511 |
+
nr Chorus voice count (0-99, CPU time consumption proportional to this value)
|
| 3512 |
+
level Chorus level (0.0-10.0)
|
| 3513 |
+
speed Chorus speed in Hz (0.29-5.0)
|
| 3514 |
+
depth_ms Chorus depth (max value depends on synth sample rate, 0.0-21.0 is safe for sample rate values up to 96KHz)
|
| 3515 |
+
type Chorus waveform type (0=sine, 1=triangle)
|
| 3516 |
+
"""
|
| 3517 |
+
# Playback vars
|
| 3518 |
+
tempo = int((60 / 120) * 1e6) # default 120bpm
|
| 3519 |
+
last_t = 0
|
| 3520 |
+
ss = np.empty((0, 2), dtype=np.int16)
|
| 3521 |
+
|
| 3522 |
+
for name, cur_t, *data in events:
|
| 3523 |
+
# compute how many samples have passed since the last event
|
| 3524 |
+
delta_ticks = cur_t - last_t
|
| 3525 |
+
last_t = cur_t
|
| 3526 |
+
dt_seconds = (delta_ticks / ticks_per_beat) * (tempo / 1e6)
|
| 3527 |
+
sample_len = int(dt_seconds * sample_rate)
|
| 3528 |
+
if sample_len > 0:
|
| 3529 |
+
buf = fl.get_samples(sample_len).reshape(-1, 2)
|
| 3530 |
+
ss = np.concatenate([ss, buf], axis=0)
|
| 3531 |
+
|
| 3532 |
+
# Dispatch every known event
|
| 3533 |
+
if name == "note_on" and data[2] > 0:
|
| 3534 |
+
chan, note, vel = data
|
| 3535 |
+
fl.noteon(chan, note, vel)
|
| 3536 |
+
|
| 3537 |
+
elif name == "note_off" or (name == "note_on" and data[2] == 0):
|
| 3538 |
+
chan, note = data[:2]
|
| 3539 |
+
fl.noteoff(chan, note)
|
| 3540 |
+
|
| 3541 |
+
elif name == "patch_change":
|
| 3542 |
+
chan, patch = data[:2]
|
| 3543 |
+
bank = 128 if chan == 9 else 0
|
| 3544 |
+
fl.program_select(chan, sfid, bank, patch)
|
| 3545 |
+
|
| 3546 |
+
elif name == "control_change":
|
| 3547 |
+
chan, ctrl, val = data[:3]
|
| 3548 |
+
fl.cc(chan, ctrl, val)
|
| 3549 |
+
|
| 3550 |
+
elif name == "key_after_touch":
|
| 3551 |
+
chan, note, vel = data
|
| 3552 |
+
# fl.key_pressure(chan, note, vel)
|
| 3553 |
+
|
| 3554 |
+
elif name == "channel_after_touch":
|
| 3555 |
+
chan, vel = data
|
| 3556 |
+
# fl.channel_pressure(chan, vel)
|
| 3557 |
+
|
| 3558 |
+
elif name == "pitch_wheel_change":
|
| 3559 |
+
chan, wheel = data
|
| 3560 |
+
fl.pitch_bend(chan, wheel)
|
| 3561 |
+
|
| 3562 |
+
elif name == "song_position":
|
| 3563 |
+
# song_pos = data[0]; # often not needed for playback
|
| 3564 |
+
pass
|
| 3565 |
+
|
| 3566 |
+
elif name == "song_select":
|
| 3567 |
+
# song_number = data[0]
|
| 3568 |
+
pass
|
| 3569 |
+
|
| 3570 |
+
elif name == "tune_request":
|
| 3571 |
+
# typically resets tuning; FS handles internally
|
| 3572 |
+
pass
|
| 3573 |
+
|
| 3574 |
+
elif name in ("sysex_f0", "sysex_f7"):
|
| 3575 |
+
raw_bytes = data[0]
|
| 3576 |
+
# fl.sysex(raw_bytes)
|
| 3577 |
+
pass
|
| 3578 |
+
|
| 3579 |
+
# Meta events & others—no direct audio effect, so we skip or log
|
| 3580 |
+
elif name in (
|
| 3581 |
+
"set_tempo", # handled below
|
| 3582 |
+
"end_track",
|
| 3583 |
+
"text_event", "text_event_08", "text_event_09", "text_event_0a",
|
| 3584 |
+
"text_event_0b", "text_event_0c", "text_event_0d", "text_event_0e", "text_event_0f",
|
| 3585 |
+
"copyright_text_event", "track_name", "instrument_name",
|
| 3586 |
+
"lyric", "marker", "cue_point",
|
| 3587 |
+
"smpte_offset", "time_signature", "key_signature",
|
| 3588 |
+
"sequencer_specific", "raw_meta_event"
|
| 3589 |
+
):
|
| 3590 |
+
if name == "set_tempo":
|
| 3591 |
+
tempo = data[0]
|
| 3592 |
+
# else: skip all other meta & text; you could hook in logging here
|
| 3593 |
+
continue
|
| 3594 |
|
| 3595 |
+
else:
|
| 3596 |
+
# unknown event type
|
| 3597 |
+
continue
|
| 3598 |
|
| 3599 |
+
# Cleanup synth
|
| 3600 |
+
fl.delete()
|
| 3601 |
|
| 3602 |
+
if ss.size:
|
| 3603 |
+
maxv = np.abs(ss).max()
|
| 3604 |
+
if maxv:
|
| 3605 |
+
ss = (ss / maxv) * np.iinfo(np.int16).max
|
| 3606 |
+
ss = ss.astype(np.int16)
|
| 3607 |
|
| 3608 |
+
# Optional trimming of trailing silence
|
| 3609 |
+
if trim_silence and ss.size:
|
| 3610 |
+
thresh = np.std(np.abs(ss)) * silence_threshold
|
| 3611 |
+
idx = np.where(np.abs(ss) > thresh)[0]
|
| 3612 |
+
if idx.size:
|
| 3613 |
+
ss = ss[: idx[-1] + 1]
|
| 3614 |
|
| 3615 |
+
# For Gradio you might want raw int16 PCM
|
| 3616 |
+
if output_for_gradio:
|
| 3617 |
+
return ss
|
| 3618 |
|
| 3619 |
+
# Swap to (channels, samples) and normalize for playback
|
| 3620 |
+
ss = ss.T
|
| 3621 |
+
raw_audio = normalize_audio(ss, target_level_db=volume_level_db)
|
| 3622 |
+
|
| 3623 |
+
# Optionally write WAV to disk
|
| 3624 |
+
if write_audio_to_WAV:
|
| 3625 |
+
wav_name = midi_file.rsplit('.', 1)[0] + '.wav'
|
| 3626 |
+
if output_WAV_name != '':
|
| 3627 |
+
wav_name = output_WAV_name
|
| 3628 |
+
pcm = np.int16(raw_audio.T / np.max(np.abs(raw_audio)) * 32767)
|
| 3629 |
+
with wave.open(wav_name, 'wb') as wf:
|
| 3630 |
wf.setframerate(sample_rate)
|
| 3631 |
wf.setsampwidth(2)
|
| 3632 |
+
wf.setnchannels(pcm.shape[1])
|
| 3633 |
+
wf.writeframes(pcm.tobytes())
|
| 3634 |
+
|
| 3635 |
+
return raw_audio
|
| 3636 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3637 |
#===================================================================================================================
|