-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtranspose_chords.py
More file actions
50 lines (43 loc) · 1.6 KB
/
transpose_chords.py
File metadata and controls
50 lines (43 loc) · 1.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import re
CHORDS_SHARP = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
CHORDS_FLAT = ['C','Db','D','Eb','E','F','Gb','G','Ab','A','Bb','B']
# SMART_NOTES maps the 12 semitones to the most common/readable representation
SMART_NOTES = ['C','Db','D','Eb','E','F','F#','G','Ab','A','Bb','B']
def transpose_note(note, semitones):
if note in CHORDS_SHARP:
i = CHORDS_SHARP.index(note)
elif note in CHORDS_FLAT:
i = CHORDS_FLAT.index(note)
else:
return note
target_idx = (i + semitones) % 12
return SMART_NOTES[target_idx]
def transpose_chord(chord, semitones):
match = re.match(r'([A-G][b#]?)(.*)', chord)
if not match:
return chord
root, rest = match.groups()
return transpose_note(root, semitones) + rest
# More robust regex for chords, including common jazz and tension notation
CHORD_REGEX = re.compile(
r'\b[A-G][b#]?(?:m|maj|min|dim|aug|sus|add|M)?\d*(?:(?:add|no|sus|b|#)\d+)*(?:\/[A-G][b#]?)?\b'
)
def transpose_text(text, semitones):
def repl(match):
chord = match.group(0)
if '/' in chord:
parts = chord.split('/')
return "/".join(transpose_chord(p, semitones) for p in parts)
return transpose_chord(chord, semitones)
return CHORD_REGEX.sub(repl, text)
if __name__ == "__main__":
import sys
# Example usage: cat song.txt | python transpose_chords.py 2
semitones = 1
if len(sys.argv) > 1:
try:
semitones = int(sys.argv[1])
except ValueError:
pass
input_text = sys.stdin.read()
print(transpose_text(input_text, semitones))