-
Notifications
You must be signed in to change notification settings - Fork 0
/
chorddisplay.py
193 lines (155 loc) · 6.9 KB
/
chorddisplay.py
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stencilview import StencilView
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, StringProperty, ListProperty, ObjectProperty
from kivy.clock import Clock
from kivy.metrics import dp
from kivy.lang import Builder
from music_constants import major_chord_shapes, minor_chord_shapes, dom_chord_shapes, \
sus_chord_shapes, dim_chord_shapes, aug_chord_shapes, chrom_scale, standard_tuning
from chord_voicing_generator import get_chord_num_master_voicings
chord_groups = {
'Major' : major_chord_shapes, 'Minor': minor_chord_shapes, 'Dominant': dom_chord_shapes,
'Suspended': sus_chord_shapes, 'Diminished': dim_chord_shapes, 'Augmented': aug_chord_shapes
}
Builder.load_file('chorddiagram.kv')
ROW_HEIGHT = dp(95)
class BackGroundColorWidget(Widget):
pass
class ChordDisplay(ScrollView):
root_note_idx = NumericProperty(0)
mode_filter = NumericProperty(0b101011010101)
note_idxs = ListProperty([0, 0, 0, 0, 0, 0, 0])
tuner_tuning = ListProperty(standard_tuning)
tuning = ListProperty(standard_tuning)
top_prop = NumericProperty(0)
instrument_rack = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.is_shown = False
# No text in some groups and fold_buttons are tiny initially without this.
Clock.schedule_once(self.top_justify_all, 5)
def top_justify_all(self, *args):
if self.ids.scroll_child and self.ids.titlebar:
self.ids.titlebar.top = self.ids.scroll_child.top
for child in self.ids.scroll_child.children:
child.top_justify()
def slide(self, keysigtitlebar, state):
if state == 'down' and not self.is_shown:
self.height = keysigtitlebar.top
self.tuning = self.tuner_tuning
self.top = keysigtitlebar.top
self.is_shown = True
else:
self.top = 0
self.is_shown = False
def slide_to_rack(self, *args):
if self.instrument_rack:
instrument_rack = self.instrument_rack
if not self.is_shown:
self.height = instrument_rack.y # Change the size,
self.top_prop = instrument_rack.y # before changing the position.
instrument_rack.bind(y=self.top_prop_setter)
self.is_shown = True
else:
instrument_rack.unbind(y=self.top_prop_setter)
self.top_prop = 0
self.is_shown = False
def top_prop_setter(self, instrument_rack, instrument_rack_y):
# Unbinding from the self.setter('top_prop') callback is not working.
self.height = instrument_rack.y
self.top_prop = instrument_rack.y
def on_mode_filter(self, *args):
self.update_note_idxs()
def on_root_note_idx(self, *args):
self.note_idxs[0] = self.root_note_idx
self.update_note_idxs()
def update_note_idxs(self):
write_idx = 0
for i, bit in enumerate(bin(self.mode_filter)[2:]):
if int(bit) == 1:
note_idx = (self.root_note_idx + i) % 12
# Modes with more than 7 notes (Melodic Minor) don't work.
if write_idx < len(self.note_idxs):
self.note_idxs[write_idx] = note_idx
write_idx += 1
class ChordGroup(StencilView, BackGroundColorWidget):
group_height = NumericProperty(0)
chord_group = StringProperty('')
note_idxs = ListProperty([0, 0, 0, 0, 0, 0, 0])
display = ObjectProperty(None)
mode_filter = NumericProperty(0)
tuning = ListProperty(standard_tuning)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.box = BoxLayout(orientation='vertical', size_hint_x=None)
self.fold_button = Button()
self.fold_button.bind(on_press=self.fold)
self.add_widget(self.fold_button)
self.add_widget(self.box)
self.bind(pos=self.top_justify, size=self.top_justify)
def on_kv_post(self, base_widget):
self.box.children[0].label.bind(width=self.fold_button.setter('width'))
def on_chord_group(self, *args):
# Using this as a sort of __init__ method to avoid repetitive classes.
# self.chord_group is just some text in kv.
chord_group_list = chord_groups[self.chord_group]
for chord_name, chord_num in chord_group_list.items():
chord_row = ChordRow()
self.bind(note_idxs=chord_row.setter('note_idxs'))
self.bind(display=chord_row.setter('display'))
self.bind(mode_filter=chord_row.setter('mode_filter'))
self.bind(root_note_idx=chord_row.setter('root_note_idx'))
self.bind(tuning=chord_row.setter('tuning'))
chord_row.label.text = chord_name
chord_row.chord_num = chord_num
self.box.add_widget(chord_row)
# ChordDiagram size being set explicitly. ChordRow width set to sum of its ChordDiagrams.
# ChordGroup (and ChordGroup.box) should match ChordRow.width. Can't use bindings,
# because ChordRow.width never actually changes.
self.box.width = self.width = chord_row.width
self.group_height = ROW_HEIGHT * len(chord_group_list)
self.box.height = self.group_height
self.fold_button.height = self.group_height
self.fold_button.x = self.box.children[0].ids.label.x if self.box.children else 0
self.top_justify()
def fold(self, *args):
self.height = ROW_HEIGHT if self.height > ROW_HEIGHT else dp(self.group_height)
self.display.top_justify_all()
def top_justify(self, *args):
self.box.top = self.top
self.fold_button.top = self.top
class ChordTitleBar(BoxLayout):
note_idxs = ListProperty([0, 0, 0, 0, 0, 0, 0])
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.chrom_scale = chrom_scale
def top_justify(self, *args):
pass
class ChordRow(BoxLayout):
chord_num = NumericProperty(0b000000000000)
note_idxs = ListProperty([0, 0, 0, 0, 0, 0, 0])
display = ObjectProperty(None)
voicings = ListProperty()
mode_filter = NumericProperty(0)
root_note_idx = NumericProperty(0)
tuning = ListProperty(standard_tuning)
label = ObjectProperty(None)
def on_chord_num(self, *args):
self.update_voicings()
def on_tuning(self, *args):
self.update_voicings()
def update_voicings(self):
if self.display:
self.voicings = get_chord_num_master_voicings(self.display.tuning, self.chord_num)
else:
self.voicings = get_chord_num_master_voicings(standard_tuning, self.chord_num)
class ChordDisplayApp(App):
def build(self):
return ChordDisplay()
if __name__ == "__main__":
ChordDisplayApp().run()