Gemini Xrossband (GemX) - LR1121 Driver (#2540)
[ExpressLRS.git] / src / python / melodyparser.py
blob1bfd010eec42b82e6d0063bbe5749d9d5e468e0b
1 import sys
3 notesChars = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
4 pauseChar = 'P'
6 def window(iterable, size=2):
7 i = iter(iterable)
8 win = []
9 for e in range(0, size):
10 win.append(next(i))
11 yield win
12 for e in i:
13 win = win[1:] + [e]
14 yield win
16 def parseMelody(melodyString, bpm=120, transposeBySemitones=0):
17 # parse string to python list
18 tokenizedNotes = melodyString.split(' ')
19 operations = []
20 for token, nextToken in window(tokenizedNotes + [None], 2):
21 if token.startswith(pauseChar):
22 # Token is a pause operation, use frequency 0
23 operations.append([0, getDurationInMs(bpm, token[1:])])
24 elif token.startswith(tuple(notesChars)):
25 # Token is a note; next token will be duration of this note
26 frequency = getFrequency(token, transposeBySemitones)
27 duration = getDurationInMs(bpm, nextToken)
28 operations.append([frequency, duration])
29 else:
30 continue
31 return operations
33 def getFrequency(note, transposeBy=0, A4=440):
34 # example note: A#5, meaning: 5th octave A sharp
35 notes = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#']
36 octave = int(note[2]) if len(note) == 3 else int(note[1])
37 keyNumber = notes.index(note[0:-1])
38 if keyNumber < 3:
39 keyNumber = keyNumber + 12 + ((octave - 1) * 12) + 1
40 else:
41 keyNumber = keyNumber + ((octave - 1) * 12) + 1
42 keyNumber += transposeBy
43 return int(A4 * 2 ** ((keyNumber - 49) / 12.0))
45 def getDurationInMs(bpm, duration):
46 return int((1000 * (60 * 4 / bpm)) / float(duration))
48 def generateArrayString(melodyArray):
49 # generate C-style array string from python list
50 elements = []
51 for element in melodyArray:
52 elements.append("{" + str(int(element[0])) + "," + str(int(element[1])) + "}")
53 return "{" + ','.join(elements) + "}"
55 def parseToArray(melodyOrRTTTL):
56 # If | in melody it is original notes|bpm|transpose format
57 if ('|' in melodyOrRTTTL):
58 defineValue = melodyOrRTTTL.split("|")
59 transposeBySemitones = int(defineValue[2]) if len(defineValue) > 2 else 0
60 return parseMelody(defineValue[0].strip(), int(defineValue[1]), transposeBySemitones)
61 # Else assume RTTL
62 else:
63 from external.rtttl import RTTTL
64 return RTTTL(melodyOrRTTTL).notes()
66 def parse(melodyOrRTTTL):
67 operations = parseToArray(melodyOrRTTTL)
68 return generateArrayString(operations)
71 if __name__ == '__main__':
72 print(parse(sys.argv[1]))