RAM2118 commited on
Commit
6cd53ef
·
verified ·
1 Parent(s): 95ee1fe

Update harmonic_engine.py

Browse files
Files changed (1) hide show
  1. harmonic_engine.py +147 -4
harmonic_engine.py CHANGED
@@ -58,7 +58,15 @@ CHORD_INTERVALS = {
58
  }
59
 
60
  def parse_chord_symbol(symbol):
 
61
  symbol = symbol.strip()
 
 
 
 
 
 
 
62
  if len(symbol) > 1 and symbol[1] in '#b':
63
  root_str = symbol[:2]
64
  quality_str = symbol[2:]
@@ -81,7 +89,7 @@ def parse_chord_symbol(symbol):
81
  quality = 'dom7'
82
  elif q in ('dim', 'dim7', 'o', 'o7'):
83
  quality = 'dim7' if '7' in q else 'dim'
84
- elif q in ('hdim7', 'm7b5', 'ø7', 'ø'):
85
  quality = 'hdim7'
86
  elif q in ('aug', '+'):
87
  quality = 'aug'
@@ -108,7 +116,7 @@ def parse_chord_symbol(symbol):
108
  else:
109
  quality = 'maj'
110
 
111
- return root_pc, quality
112
 
113
  def roman_to_chord(roman_str, key_root_pc):
114
  roman_str = roman_str.strip()
@@ -154,7 +162,7 @@ def roman_to_chord(roman_str, key_root_pc):
154
 
155
  class GenreVoicer:
156
  @staticmethod
157
- def voice(root_pc, quality, genre, octave_lh=2, octave_rh=4):
158
  lh_base = (octave_lh + 1) * 12
159
  rh_base = (octave_rh + 1) * 12
160
 
@@ -172,7 +180,13 @@ class GenreVoicer:
172
  break
173
 
174
  method = getattr(GenreVoicer, f'_voice_{genre.lower()}', GenreVoicer._voice_pop)
175
- return method(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality)
 
 
 
 
 
 
176
 
177
  @staticmethod
178
  def _voice_pop(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
@@ -278,6 +292,135 @@ class GenreVoicer:
278
  rh = [root_rh + 4, root_rh + 7]
279
  return lh, sorted(rh)
280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  class VoiceLeader:
282
  @staticmethod
283
  def lead(prev_rh, current_rh):
 
58
  }
59
 
60
  def parse_chord_symbol(symbol):
61
+ """Parse chord symbols including slash chords like C/E, Fmaj7/A"""
62
  symbol = symbol.strip()
63
+
64
+ slash_bass = None
65
+ if '/' in symbol:
66
+ parts = symbol.split('/')
67
+ symbol = parts[0]
68
+ slash_bass = parts[1]
69
+
70
  if len(symbol) > 1 and symbol[1] in '#b':
71
  root_str = symbol[:2]
72
  quality_str = symbol[2:]
 
89
  quality = 'dom7'
90
  elif q in ('dim', 'dim7', 'o', 'o7'):
91
  quality = 'dim7' if '7' in q else 'dim'
92
+ elif q in ('hdim7', 'm7b5'):
93
  quality = 'hdim7'
94
  elif q in ('aug', '+'):
95
  quality = 'aug'
 
116
  else:
117
  quality = 'maj'
118
 
119
+ return root_pc, quality, slash_bass
120
 
121
  def roman_to_chord(roman_str, key_root_pc):
122
  roman_str = roman_str.strip()
 
162
 
163
  class GenreVoicer:
164
  @staticmethod
165
+ def voice(root_pc, quality, genre, octave_lh=2, octave_rh=4, slash_bass=None):
166
  lh_base = (octave_lh + 1) * 12
167
  rh_base = (octave_rh + 1) * 12
168
 
 
180
  break
181
 
182
  method = getattr(GenreVoicer, f'_voice_{genre.lower()}', GenreVoicer._voice_pop)
183
+ lh, rh = method(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality)
184
+
185
+ if slash_bass:
186
+ bass_pc = note_name_to_pc(slash_bass)
187
+ lh = [lh_base + bass_pc]
188
+
189
+ return lh, rh
190
 
191
  @staticmethod
192
  def _voice_pop(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
 
292
  rh = [root_rh + 4, root_rh + 7]
293
  return lh, sorted(rh)
294
 
295
+
296
+ @staticmethod
297
+ def _voice_afrobeats(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
298
+ """Afrobeats: Open 5ths, stacked high, modern & spacious"""
299
+ lh = [root_lh]
300
+ rh = []
301
+ if fifth:
302
+ rh.append(root_rh + fifth)
303
+ if third:
304
+ rh.append(root_rh + 12 + third)
305
+ rh.append(root_rh + 19)
306
+ return lh, sorted(rh)
307
+
308
+ @staticmethod
309
+ def _voice_trap(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
310
+ """Trap: Dark, minor feel, extended voicings"""
311
+ lh = [root_lh, root_lh + 7]
312
+ rh = []
313
+ if third:
314
+ rh.append(root_rh + third)
315
+ rh.append(root_rh + 10)
316
+ rh.append(root_rh + 14)
317
+ return lh, sorted(rh)
318
+
319
+ @staticmethod
320
+ def _voice_bossa_nova(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
321
+ """Bossa Nova: Jazz harmony, gentle 7ths and 9ths"""
322
+ lh = [root_lh]
323
+ if seventh:
324
+ lh.append(root_lh + seventh)
325
+ else:
326
+ lh.append(root_lh + 11)
327
+
328
+ rh = []
329
+ if third:
330
+ rh.append(root_rh + third)
331
+ rh.append(root_rh + 9)
332
+ rh.append(root_rh + 14)
333
+ return lh, sorted(rh)
334
+
335
+ @staticmethod
336
+ def _voice_hindustani_classical(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
337
+ """Hindustani: Drone bass, melodic emphasis on 3rd"""
338
+ lh = [root_lh, root_lh + 7]
339
+ rh = []
340
+ if third:
341
+ rh.append(root_rh + third)
342
+ rh.append(root_rh + third + 12)
343
+ if fifth:
344
+ rh.append(root_rh + fifth)
345
+ return lh, sorted(rh)
346
+
347
+ @staticmethod
348
+ def _voice_neo_soul(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
349
+ """Neo-Soul: Rich 9ths, 11ths, no 5th"""
350
+ lh = [root_lh]
351
+ rh = []
352
+ if third:
353
+ rh.append(root_rh + third)
354
+ sev = seventh if seventh else 10
355
+ rh.append(root_rh + sev)
356
+ rh.append(root_rh + 14)
357
+ if third == 3:
358
+ rh.append(root_rh + 17)
359
+ else:
360
+ rh.append(root_rh + 21)
361
+ return lh, sorted(rh)
362
+
363
+ @staticmethod
364
+ def _voice_reggae(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
365
+ """Reggae: Upbeat skank, major 3rds, simple"""
366
+ lh = [root_lh]
367
+ rh = []
368
+ if third:
369
+ rh.append(root_rh + third)
370
+ if fifth:
371
+ rh.append(root_rh + fifth)
372
+ rh.append(root_rh + 12)
373
+ return lh, sorted(rh)
374
+
375
+ @staticmethod
376
+ def _voice_latin(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
377
+ """Latin: Montuno style, syncopated feel"""
378
+ lh = [root_lh, root_lh + 7]
379
+ rh = []
380
+ if third:
381
+ rh.append(root_rh + third)
382
+ if fifth:
383
+ rh.append(root_rh + fifth)
384
+ sev = seventh if seventh else 10
385
+ rh.append(root_rh + sev)
386
+ return lh, sorted(rh)
387
+
388
+ @staticmethod
389
+ def _voice_k_pop(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
390
+ """K-Pop: Bright, add9 chords, modern production"""
391
+ lh = [root_lh]
392
+ rh = []
393
+ if third:
394
+ rh.append(root_rh + third)
395
+ if fifth:
396
+ rh.append(root_rh + fifth)
397
+ rh.append(root_rh + 14)
398
+ rh.append(root_rh + 12)
399
+ return lh, sorted(rh)
400
+
401
+ @staticmethod
402
+ def _voice_lo_fi(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
403
+ """Lo-fi: Jazzy 7ths, close voicings, nostalgic"""
404
+ lh = [root_lh]
405
+ rh = []
406
+ if third:
407
+ rh.append(root_rh + third)
408
+ rh.append(root_rh + 7)
409
+ sev = seventh if seventh else 11
410
+ rh.append(root_rh + sev)
411
+ return lh, sorted(rh)
412
+
413
+ @staticmethod
414
+ def _voice_funk(root_pc, root_lh, root_rh, intervals, third, fifth, seventh, quality):
415
+ """Funk: Dominant 7ths, tight voicings, rhythmic"""
416
+ lh = [root_lh, root_lh + 10]
417
+ rh = []
418
+ if third:
419
+ rh.append(root_rh + third)
420
+ rh.append(root_rh + 10)
421
+ rh.append(root_rh + 14)
422
+ return lh, sorted(rh)
423
+
424
  class VoiceLeader:
425
  @staticmethod
426
  def lead(prev_rh, current_rh):