Spaces:
Configuration error
Configuration error
Upload _Decompressor.py
Browse files- _Decompressor.py +252 -0
_Decompressor.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Imports
|
| 3 |
+
'''
|
| 4 |
+
import guitarpro
|
| 5 |
+
from guitarpro import *
|
| 6 |
+
import numpy as np
|
| 7 |
+
import os
|
| 8 |
+
import pickle
|
| 9 |
+
import re
|
| 10 |
+
from tqdm import tqdm
|
| 11 |
+
|
| 12 |
+
from keras.utils import np_utils
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
'''
|
| 16 |
+
Constants
|
| 17 |
+
'''
|
| 18 |
+
# PITCH[i] = the pitch associated with midi note number i.
|
| 19 |
+
# For example, PITCH[69] = 'A4'
|
| 20 |
+
PITCH = {val : str(GuitarString(number=0, value=val)) for val in range(128)}
|
| 21 |
+
# MIDI[string] = the midi number associated with the note described by string.
|
| 22 |
+
# For example, MIDI['A4'] = 69.
|
| 23 |
+
MIDI = {str(GuitarString(number=0, value=val)) : val for val in range(128)}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
'''
|
| 28 |
+
get_strings function
|
| 29 |
+
'''
|
| 30 |
+
def get_strings(chord, tuning, as_fingerings=True):
|
| 31 |
+
|
| 32 |
+
lowest_string = len(tuning) # Bass has 4 strings, while metal guitars can have 6-8 strings.
|
| 33 |
+
|
| 34 |
+
if as_fingerings:
|
| 35 |
+
# NEW CODE:
|
| 36 |
+
# Represent the tuning as the number of semitones between the open strings and the lowest string.
|
| 37 |
+
# i.e., relative_tuning[lowest_string] = 0
|
| 38 |
+
relative_tuning = {k : v - tuning[lowest_string] for k, v in tuning.items()}
|
| 39 |
+
|
| 40 |
+
chord_parts = chord.split('_')
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
if as_fingerings:
|
| 44 |
+
# NEW CODE:
|
| 45 |
+
root_value = int(chord_parts[0])
|
| 46 |
+
else:
|
| 47 |
+
# NEW CODE:
|
| 48 |
+
root_value = MIDI[chord_parts[0]]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
if as_fingerings:
|
| 52 |
+
# NEW CODE:
|
| 53 |
+
if root_value < 0:
|
| 54 |
+
print(f'!!!!! error !!!!!\t {root_value} < 0')
|
| 55 |
+
else:
|
| 56 |
+
# OLD CODE:
|
| 57 |
+
if root_value < tuning[lowest_string]:
|
| 58 |
+
print(f'!!!!! error !!!!!\t {root_value} < {tuning[lowest_string]}')
|
| 59 |
+
|
| 60 |
+
# Using the tuning, get a list of all possible fret positions for the root note.
|
| 61 |
+
if as_fingerings:
|
| 62 |
+
# NEW CODE:
|
| 63 |
+
tuning_values = np.array(list(relative_tuning.values()))
|
| 64 |
+
else:
|
| 65 |
+
# OLD CODE:
|
| 66 |
+
tuning_values = np.array(list(tuning.values()))
|
| 67 |
+
|
| 68 |
+
fingerings = root_value - tuning_values
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# + 1 because tuning[] is 1-indexed.
|
| 72 |
+
string = np.where(fingerings >= 0, fingerings, np.inf).argmin() + 1
|
| 73 |
+
fret = fingerings[string-1]
|
| 74 |
+
|
| 75 |
+
# If we are just playing a single note, then the function can just return what it has now.
|
| 76 |
+
if len(chord_parts) == 1:
|
| 77 |
+
return [(fret, string)]
|
| 78 |
+
|
| 79 |
+
# If the chord requires a very high pitch, lower its fingering to the second-highest string,
|
| 80 |
+
# so as to save the highest string for the other part of the chord.
|
| 81 |
+
if string == 1:
|
| 82 |
+
string = 2
|
| 83 |
+
fret = fingerings[string-1]
|
| 84 |
+
|
| 85 |
+
if chord_parts[1] == '5':
|
| 86 |
+
upper_value = root_value + 7 # perfect fifth
|
| 87 |
+
elif chord_parts[1] == 'dim5':
|
| 88 |
+
upper_value = root_value + 6 # tritone
|
| 89 |
+
elif chord_parts[1] == '4':
|
| 90 |
+
upper_value = root_value + 5
|
| 91 |
+
else:
|
| 92 |
+
upper_value = root_value + 5 # in case of an error, assume that the upper value is a perfect 4th above the root.
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
if as_fingerings:
|
| 96 |
+
# NEW CODE:
|
| 97 |
+
upper_fret = upper_value - relative_tuning[string-1]
|
| 98 |
+
else:
|
| 99 |
+
# OLD CODE:
|
| 100 |
+
upper_fret = upper_value - tuning[string-1]
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
if upper_fret < 0:
|
| 104 |
+
# There are some rare cases where the chord cannot be played given a tuning.
|
| 105 |
+
# For example, a tritone or a perfect 4th with root C2 in a drop-C guitar.
|
| 106 |
+
# In that case, just return the root note.
|
| 107 |
+
return [(fret, string)]
|
| 108 |
+
|
| 109 |
+
return [(fret, string), (upper_fret, string-1)]
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class SongWriter:
|
| 115 |
+
|
| 116 |
+
def __init__(self, initialTempo):
|
| 117 |
+
self.song = guitarpro.models.Song(tempo=initialTempo)
|
| 118 |
+
self.song.tracks = [] # Initialize song.tracks to an empty list.
|
| 119 |
+
|
| 120 |
+
self.currentTempo = initialTempo
|
| 121 |
+
|
| 122 |
+
'''
|
| 123 |
+
decompress_track function
|
| 124 |
+
'''
|
| 125 |
+
def decompress_track(self, track, tuning, tempo=None, instrument=None, name=None, as_fingerings=True):
|
| 126 |
+
|
| 127 |
+
# Instantiate the Midi Channel / Instrument for this track,
|
| 128 |
+
# making sure to avoid channel 9 (the drum channel)
|
| 129 |
+
channel = MidiChannel(channel = len(self.song.tracks) + (len(self.song.tracks) >= 9),
|
| 130 |
+
effectChannel = len(self.song.tracks) + (len(self.song.tracks) >= 9))
|
| 131 |
+
if instrument is not None:
|
| 132 |
+
channel.instrument = instrument
|
| 133 |
+
|
| 134 |
+
# Instantiate the name for this track
|
| 135 |
+
if name is None:
|
| 136 |
+
name = f'Track {len(self.song.tracks) + 1}'
|
| 137 |
+
|
| 138 |
+
# Pre-format the name to avoid any characters that the tab file format doesn't like.
|
| 139 |
+
# Keep only spaces and alphanumeric characters.
|
| 140 |
+
name = name.split('(')[0]
|
| 141 |
+
name = re.sub(r' \W+', '', name)
|
| 142 |
+
|
| 143 |
+
# Instantiate the actual Track itself
|
| 144 |
+
self.song.tracks.append(Track(song=self.song, name=name, channel=channel))
|
| 145 |
+
|
| 146 |
+
# Calculate the dict key corresponding to the lowest-tuned string.
|
| 147 |
+
lowest_string = len(tuning)
|
| 148 |
+
|
| 149 |
+
# Set the guitar tuning for the instrument.
|
| 150 |
+
self.song.tracks[-1].strings = [GuitarString(number=num, value=val) for num, val in tuning.items()]
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# The first measureHeader and measure are already added by default. Let's remove them to make things more standard.
|
| 156 |
+
if len(self.song.tracks) == 1:
|
| 157 |
+
self.song.measureHeaders = []
|
| 158 |
+
self.song.tracks[0].measures = []
|
| 159 |
+
|
| 160 |
+
startingTrackIdx = len(self.song.measureHeaders)
|
| 161 |
+
|
| 162 |
+
for i in range(startingTrackIdx, startingTrackIdx+len(track)):
|
| 163 |
+
start = guitarpro.Duration.quarterTime * (1 + i*6)
|
| 164 |
+
|
| 165 |
+
self.song.addMeasureHeader(MeasureHeader(number=i+1, start=start))
|
| 166 |
+
|
| 167 |
+
# Add new measure to every existing track.
|
| 168 |
+
for existing_track in self.song.tracks:
|
| 169 |
+
existing_track.measures.append( Measure(existing_track, self.song.measureHeaders[i]) )
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
for m_i, measure in enumerate(self.song.tracks[-1].measures):
|
| 176 |
+
|
| 177 |
+
if m_i < startingTrackIdx:
|
| 178 |
+
continue # Skip tracks that have already been written to.
|
| 179 |
+
|
| 180 |
+
# "beats" starts off as an empy array [].
|
| 181 |
+
voice = measure.voices[0]
|
| 182 |
+
beats = voice.beats
|
| 183 |
+
|
| 184 |
+
#print(m_i - startingTrackIdx)
|
| 185 |
+
# For the m_i-th measure, get the indices b_i and the beats track_beat of the compressed song.
|
| 186 |
+
for b_i, track_beat in enumerate(track[m_i - startingTrackIdx]):
|
| 187 |
+
|
| 188 |
+
#print(f'\t{b_i}')
|
| 189 |
+
|
| 190 |
+
# If a tempo change is needed:
|
| 191 |
+
if tempo is not None and tempo != self.currentTempo:
|
| 192 |
+
# Implement the tempo change, then update the current tempo.
|
| 193 |
+
effect = BeatEffect(mixTableChange=MixTableChange(tempo=MixTableItem(value=tempo), hideTempo=False))
|
| 194 |
+
self.currentTempo = tempo
|
| 195 |
+
else:
|
| 196 |
+
effect = BeatEffect()
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
chord = track_beat[0]
|
| 200 |
+
duration = Duration(value=int(track_beat[1]), isDotted=bool(track_beat[2]))
|
| 201 |
+
|
| 202 |
+
# since "beats" is empty, we can append Beat objects to it.
|
| 203 |
+
beats.append(Beat(voice, duration=duration, effect=effect))
|
| 204 |
+
if chord == 'rest':
|
| 205 |
+
beats[b_i].status = guitarpro.BeatStatus.rest
|
| 206 |
+
|
| 207 |
+
elif chord == 'tied':
|
| 208 |
+
# If this tied note is the first beat in its measure:
|
| 209 |
+
if b_i == 0:
|
| 210 |
+
# If this tied note is the first beat in the first measure:
|
| 211 |
+
if m_i == 0:
|
| 212 |
+
# Designate this beat as a rest, then move on to the next beat.
|
| 213 |
+
beats[b_i].status = guitarpro.BeatStatus.rest
|
| 214 |
+
continue
|
| 215 |
+
else:
|
| 216 |
+
# Get the last Beat object from the previous Measure.
|
| 217 |
+
previous_beats = self.song.tracks[-1].measures[m_i-1].voices[0].beats
|
| 218 |
+
if len(previous_beats) == 0:
|
| 219 |
+
beats[b_i].status = guitarpro.BeatStatus.rest
|
| 220 |
+
continue
|
| 221 |
+
previous_beat = previous_beats[-1]
|
| 222 |
+
else:
|
| 223 |
+
# Get the previous Beat object from the current Measure.
|
| 224 |
+
previous_beat = beats[b_i-1]
|
| 225 |
+
|
| 226 |
+
for note in previous_beat.notes:
|
| 227 |
+
beats[b_i].notes.append(Note(beat=beats[b_i], value=note.value, string=note.string, type=NoteType.tie))
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
elif chord == 'dead':
|
| 233 |
+
beats[b_i].notes.append(Note(beat=beats[b_i], value=0, string=lowest_string, type=NoteType.dead))
|
| 234 |
+
beats[b_i].notes.append(Note(beat=beats[b_i], value=0, string=lowest_string-1, type=NoteType.dead))
|
| 235 |
+
beats[b_i].notes.append(Note(beat=beats[b_i], value=0, string=lowest_string-2, type=NoteType.dead))
|
| 236 |
+
|
| 237 |
+
else:
|
| 238 |
+
for fret, string in get_strings(chord, tuning, as_fingerings):
|
| 239 |
+
noteEffect = NoteEffect(palmMute=track_beat[3])
|
| 240 |
+
beats[b_i].notes.append(Note(beat=beats[b_i], value=fret, string=string,
|
| 241 |
+
type=NoteType.normal, effect=noteEffect))
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
#print('\t\t', chord, '\t', duration)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
# Lastly, return the song so that it can be saved to a .gp5 file.
|
| 248 |
+
# return new_song
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def write(self, filename):
|
| 252 |
+
guitarpro.write(self.song, filename)
|