-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
1098 lines (975 loc) · 48.7 KB
/
index.html
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tabalchi Documentation</title>
<link href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css" rel="stylesheet">
<style>
body {
margin: 20px;
}
pre {
max-height: 300px;
overflow: auto;
background-color: #f6f8fa;
padding: 10px;
border-radius: 5px;
}
</style>
</head>
<body>
<section class="section">
<h1 class="title">Tabalchi Documentation <img src="https://github.com/user-attachments/assets/0d47f705-74d9-43e4-91b0-a6c8806965ba" width=100/></h1>
<h2 class="subtitle">(C) Shreyan Mitra</h2>
<div class="box">
<h3 class="title is-4">Installation</h3>
<p>To install the Tabalchi package, use the command:</p>
<pre><code>$ pip install Tabalchi</code></pre><br>
</div>
<!-- List of classes -->
<div class="box">
<h3 class="title is-4">Classes</h3>
<!-- BeatRange Class -->
<div>
<br><h4 class="title is-5">BeatRange</h4>
<br>
<p>A class representing an interval of beats.</p>
<br>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>begin (int):</b> The start beat of the beat range, inclusive.</li>
<li><b>end (int):</b> The end beat of the beat range, exclusive.</li>
</ul>
<br>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, begin:int, end:int):</b>
<p>Initializes a beat range with the given start (inclusive) and end (exclusive).</p>
</li>
<li>
<b>fromString(self, spec:str) -> BeatRange:</b>
<p>Returns a BeatRange object from a string specification.</p>
</li>
<li>
<b>range(self) -> int:</b>
<p>Returns the number of beats represented by this BeatRange.</p>
</li>
<li>
<b>isContiguousSequence(cls, ranges:List[BeatRange], totalBeats:int) -> bool:</b>
<p>Determines if the provided beat ranges cover a contiguous sequence from 1 to the total number of beats.</p>
</li>
<li>
<b>getSubsequence(cls, ranges:List[BeatRange], begin:int, end:int) -> List[BeatRange]:</b>
<p>Returns a sorted list of beat ranges between a given beginning and end beat.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<br>
<pre><code>
# A class representing an interval of beats
class BeatRange():
'''
Class representing a beat range
Parameters:
begin(int): The start beat of the beat range, inclusive
end(int): The end beat of the beat range, exclusive
'''
def __init__(self, begin:int, end:int):
assert begin < end, "BeatRange end beat must be greater than begin beat"
self.begin = begin
self.end = end
@classmethod
def fromString(self, spec:str) -> BeatRange:
numbers = spec.split("-")
num1 = int(numbers[0])
num2 = int(numbers[1])
return BeatRange(num1, num2)
def range(self) -> int:
'''
Returns the number of beats represented by this beat range
'''
return self.end - self.begin
@classmethod
def isContiguousSequence(cls, ranges:List[BeatRange], totalBeats:int) -> bool:
'''
Returns if a particular beat range convers all beats from 1 to the given total number of beats
Parameters:
ranges(List[BeatRange]): A list of beat ranges
totalBeats(int): The total number of beats in the sequence to check the ranges against
'''
ranges = sorted(ranges, lambda range: range.begin)
for i in range(1, len(ranges)):
if ranges[i].begin != ranges[i-1].end:
return False
if(ranges[-1].end < totalBeats or ranges[0].begin != 1):
return False
return True
@classmethod
def getSubsequence(cls, ranges:List[BeatRange], begin:int, end:int) -> List[BeatRange]:
'''
Returns the ranges, in sorted order, that fall between a given begin and end beat
Parameters:
ranges(List[BeatRange]): A list of beat ranges to choose from
begin(int): The start beat of the desired sequence
end(int): The end beat of the desired sequence
'''
subsequence = []
for i in range(len(ranges)):
range = ranges[i]
if range.begin >= begin and range.end <= end:
subsequence.append(range)
elif range.begin >= begin and range.end > end:
subsequence.append(BeatRange(range.begin, end))
elif range.begin < begin and range.end <= end:
subsequence.append(BeatRange(begin, range.end))
else:
subsequence.append(BeatRange(begin, end))
return sorted(subsequence)
</code></pre><br>
<br>
</div>
<!-- Add documentation for other classes and their methods here -->
<!-- CompositionType Class -->
<div>
<br><h4 class="title is-5">CompositionType</h4>
<p>A class representing a composition type. Ex. Kayda, Rela, etc.</p>
<br><h5 class="title is-6">Class Variables (Selected if too many to write)</h5>
<ul>
<li><b>registeredTypes:</b> A class variable keeping track of the list of registered composition types.</li>
</ul>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>name (str):</b> The name of the composition type.</li>
<li><b>schema (dict):</b> The structure of the components field of the .tabla file.</li>
<li><b>validityCheck (Callable[[Bol],[bool]]):</b> A function that checks if a given Bol is of the considered composition type.</li>
<li><b>assembler (Callable[[SimpleNamespace], [list[str]]]):</b> Instructions on how to assemble components of the composition.</li>
<li><b>register (bool):</b> Whether to register the composition type.</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, name:str, schema:dict, validityCheck:Callable[[Bol],[bool]], assembler:Callable[[SimpleNamespace], [list[str]]], register:bool = True):</b>
<p>Initializes a CompositionType object with the given parameters.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>
# A class representing a composition type. Ex. Kayda, Rela, etc.
# For descriptions of the different types of tabla compositions, visit www.tablalegacy.com (not affiliated with this product or the author in any way)
# Sometimes, differences between types of compositions are hard to quantify, and come down to the "feel" of the composition.
class CompositionType():
registeredTypes = {} # A class variable keeping track of the list of registered composition types
'''
A class to represent a composition type
Parameters:
name(str): The name of the composition type. Ex. Kayda, Rela, etc.
schema(dict): The structure of the components field of the .tabla file
validityCheck(Callable[[Bol],[bool]]): A function that returns whether a given Bol is of the composition type being considered
assembler(Callable[[SimpleNamespace], [list[str]]]): Gives instructions on how to put together the disjointed components of the composition
register(bool): Whether to register the composition type (i.e. to save it for future use). By default, True
'''
def __init__(self, name:str, schema:dict, validityCheck:Callable[[Bol],[bool]], assembler:Callable[[SimpleNamespace], [list[str]]], register:bool = True):
self.name = name
self.schema = schema
self.assembler = assembler
def preValidityCheck(bol:dict) -> bool:
try:
validate(instance = bol, schema = schema)
return True
except Exception:
return False
self.preCheck = preValidityCheck
self.mainCheck = validityCheck
if register:
CompositionType.registeredTypes.update({name: self})
</code></pre><br>
</div>
<!-- Numeric Class -->
<div>
<br><h4 class="title is-5">Numeric</h4>
<p>A base class representing something that has an associated number. Implemented as an abstract base class (ABC).</p>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>name (abstract property):</b>
<p>A property that subclasses must implement, representing the name of the numeric entity.</p>
</li>
<li>
<b>number (abstract property):</b>
<p>A property that subclasses must implement, representing the number associated with the entity.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>
# A class representing something with an associated number. Ex. Taal, Jati, Speed, etc.
class Numeric(ABC):
'''
A class representing something that has an associated number
'''
@property
@abstractmethod
def name(self):
...
@property
@abstractmethod
def number(self):
...
</code></pre><br>
</div>
<!-- Taal Class -->
<div>
<br><h4 class="title is-5">Taal</h4>
<p>A class representing a Taal. Ex. Teental, Rupaak, etc.</p>
<br><h5 class="title is-6">Class Variables (Selected if too many to write)</h5>
<ul>
<li><b>registeredTaals:</b> A class variable keeping track of the list of registered taals.</li>
</ul>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>beats (int):</b> The number of beats in the taal.</li>
<li><b>taali (list[int]):</b> A list of beats where the clap is marked.</li>
<li><b>khali (list[int]):</b> A list of beats where the wave (khali) is marked.</li>
<li><b>name (Union[str, None]):</b> The name of the taal, defaults to number of beats if not provided.</li>
<li><b>theka (Union[str, None]):</b> The standard theka for the taal.</li>
<li><b>register (bool):</b> Whether to register the taal (default: True).</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, beats:int, taali:list[int] = [], khali:list[int] = [], name:Union[str, None] = None, theka:Union[str, None] = None, register:bool = True):</b>
<p>Initializes a Taal object with the given parameters and registers it if specified.</p>
</li>
<li>
<b>name (property):</b>
<p>Returns the name of the taal.</p>
</li>
<li>
<b>number (property):</b>
<p>Returns the number of beats in the taal.</p>
</li>
<li>
<b>theka (property):</b>
<p>Returns the theka of the taal.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>
# A class representing a Taal
class Taal(Numeric):
registeredTaals = {}
'''
A class representing a taal. Ex. Teental, Rupaak, etc.
'''
def __init__(self, beats:int, taali:list[int] = [], khali:list[int] = [], name:Union[str, None] = None, theka:Union[str, None] = None, register:bool = True):
self.beats = beats
self.taali = taali
self.khali = khali
if not name:
self.id = str(beats)
else:
self.id = name
self.theka = theka
if register:
Taal.registeredTaals.update({self.id: self})
@property
def name(self):
return self.id
@property
def number(self):
return self.beats
@property
def theka(self):
return self.theka
</code></pre><br>
</div>
<!-- Documentation for other classes will continue here -->
<!-- Jati Class -->
<div>
<br><h4 class="title is-5">Jati</h4>
<p>A class representing a Jati, which denotes the number of syllables per beat.</p>
<br><h5 class="title is-6">Class Variables (Selected if too many to write)</h5>
<ul>
<li><b>registeredJatis:</b> A class variable keeping track of the list of registered Jatis.</li>
</ul>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>syllables (int):</b> The number of syllables in this Jati.</li>
<li><b>name (Union[str, None]):</b> The name of the Jati, defaults to number of syllables if not provided.</li>
<li><b>register (bool):</b> Whether to register the Jati (default: True).</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, syllables:int, name:Union[str, None] = None, register = True):</b>
<p>Initializes a Jati object with the given parameters and registers it if specified.</p>
</li>
<li>
<b>name (property):</b>
<p>Returns the name of the Jati.</p>
</li>
<li>
<b>number (property):</b>
<p>Returns the number of syllables in the Jati.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>
# A class representing a jati
class Jati(Numeric):
registeredJatis = {}
'''
A class representing a Jati
'''
def __init__(self, syllables:int, name:Union[str, None] = None, register = True):
self.syllables = syllables
if not name:
self.id = str(syllables)
else:
self.id = name
if register:
Jati.registeredJatis.update({self.id: self})
@property
def name(self):
return self.id
@property
def number(self):
return self.syllables
</code></pre><br>
</div>
<!-- SpeedClasses Class -->
<div>
<br><h4 class="title is-5">SpeedClasses</h4>
<p>A class representing different categories of speed for a composition.</p>
<br><h5 class="title is-6">Class Variables (Selected if too many to write)</h5>
<ul>
<li><b>registeredSpeeds:</b> A class variable keeping track of the list of registered speed classes.</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, inClassCheck: Callable[[int], bool], randomGenerate: Callable[[], int], name: str, register: bool = True):</b>
<p>Initializes a speed class with checks and generators for speed.</p>
</li>
<li>
<b>getSpeedClassFromBPM(cls, bpm: int) -> str:</b>
<p>Returns the name of the speed class based on beats per minute (BPM).</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code># A class that represents a speed category
class SpeedClasses:
registeredSpeeds = {}
'''
A class representing a Speed class
'''
def __init__(self, inClassCheck: Callable[[int], bool], randomGenerate:Callable[[], int], name:str, register:bool = True):
self.check = inClassCheck
self.generator = randomGenerate
self.id = name
if register:
SpeedClasses.registeredSpeeds.update({name: self})
@classmethod
def getSpeedClassFromBPM(cls, bpm:int) -> str:
for key, value in SpeedClasses.registeredSpeeds.items():
if value.check(bpm):
return key
</code></pre><br>
</div>
<!-- Speed Class -->
<div>
<br><h4 class="title is-5">Speed</h4>
<p>A class representing a specific speed in beats per minute (BPM).</p>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>specifier (Union[int, str]):</b> Either an integer for BPM or a string specifying the speed class.</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, specifier: Union[int, str]):</b>
<p>Initializes a Speed object based on the specified BPM or speed class.</p>
</li>
<li>
<b>name (property):</b>
<p>Returns the name of the speed class.</p>
</li>
<li>
<b>number (property):</b>
<p>Returns the BPM.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code># A class that represents a specific speed
class Speed(Numeric):
'''
Class that represents a particular speed. Ex. 62bpm
'''
def __init__(self, specifier:Union[int, str]):
if isinstance(specifier, str):
self.name = specifier
self.bpm = SpeedClasses[specifier].generator()
else:
self.bpm = specifier
self.name = SpeedClasses.getSpeedClassFromBPM(specifier)
@property
def name(self):
return self.name
@property
def number(self):
return self.bpm
</code></pre><br>
</div>
<!-- Notation Class -->
<div>
<br><h4 class="title is-5">Notation</h4>
<p>An abstract base class for all types of notations.</p>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>toString(cls, bol: Bol):</b>
<p> An abstract method to be implemented by subclasses to convert a Bol to string.</p>
</li>
<li>
<b>display(cls, bol: Bol, fileName: str):</b>
<p>Displays the notation to a specified file.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>class Notation(ABC):
VALID_NOTATIONS = ["Bhatkande", "Paluskar"]
@classmethod
@abstractmethod
def toString(self, bol:Bol):
...
@classmethod
def display(cls, bol:Bol, fileName:str):
print(Notation.toString(bol), file=fileName)
</code></pre><br>
</div>
<!-- Bol Class -->
<div>
<br><h4 class="title is-5">Bol</h4>
<p>A class representing a Bol, which is a collection of beats in a tabla composition.</p>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>beats (list[Beat]):</b> A list of Beat objects that constitute the bol.</li>
<li><b>notationClass (Union[Type[Notation], None]):</b> The notation class used for displaying the bol.</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, beats: list[Beat], notationClass: Union[Type[Notation], None] = None):</b>
<p>Initializes a Bol object with the specified beats and optional notation.</p>
</li>
<li>
<b>play(self):</b>
<p>Plays the entire Bol composition.</p>
</li>
<li>
<b>write(self, filename: str, notationClass: Union[Type[Notation], None]):</b>
<p> Writes the Bol to a file using the desired notation.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>class Bol():
'''
A class representing a bol, a collection of beats
'''
def __init__(self, beats:list[Beat], notationClass:Union[Type[Notation], None] = None):
self.beats = beats
self.notationClass = notationClass
self.markedBeats = []
self.markedPhrases = []
for beat in beats:
for i in range(len(beat.phrases.keys())):
lst = list(beat.phrases.keys())
if(beat.markers[i] == 1):
self.markedBeats.append(beat)
self.markedPhrases.append(lst[i])
def play(self):
for beat in self.beats:
beat.play()
def write(self, filename:str, notationClass:Union[Type[Notation], None]):
if notationClass is not None:
notationClass.display(self, filename)
elif self.notationClass is not None:
self.notationClass.display(self, filename)
else:
raise ValueError("No Notation object found to use.")
</code></pre><br>
</div>
<!-- Further documentation for the remaining classes -->
<!-- Beat Class -->
<div>
<br><h4 class="title is-5">Beat</h4>
<p>A class representing a collection of phrases in a beat.</p>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>number (int):</b> The number of the beat.</li>
<li><b>taaliKhaliOrNone (Literal[-1,0,1]):</b> Indicator for clapped (1), waved (0), or neutral (-1).</li>
<li><b>saam (bool):</b> Indicates if this is a starting beat.</li>
<li><b>phrases (OrderedDict[Phrase, int]):</b> Phrases present in this beat with associated syllables.</li>
<li><b>speed (int):</b> The speed of the beat in BPM.</li>
<li><b>markers (list[Literal[0,1]]):</b> Markers indicating noteworthy phrases within the beat.</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(...):</b>
<p>Initializes a Beat object with the specified parameters.</p>
</li>
<li>
<b>play(self):</b>
<p>Plays the sound files corresponding to the phrases in the beat.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>class Beat():
'''
A class representing a collection of phrases
'''
def __init__(self, number:int, taaliKhaliOrNone:Literal[-1,0,1], saam:bool, phrases:OrderedDict[Phrase, int], speed:int, markers:list[Literal[0,1]]):
self.number = number
assert len(markers) == len(phrases.keys()), "Invalid length for marker array"
self.markers = markers
self.clap = taaliKhaliOrNone
self.saam = saam
self.speed = speed
duration = 60.0/speed #In seconds (this is the duration of the entire beat)
jati = 0
for _,val in phrases.items():
jati += val
syllableDuration = duration/jati #This is duration of a specific segment of the beat
self.multipliers = []
self.soundFiles = []
for phrase, syllables in phrases.items():
self.multipliers.append((syllables*1.0)/phrase.syllables) * (syllableDuration/0.25)) #Since, in the original recording, one syllable = 0.25 seconds
self.soundFiles.append(phrase.soundBite.recording)
self.phrases = phrases
def play(self):
for index in range(len(self.soundFiles)):
s = AudioSegment(self.soundFiles[i])
if self.multipliers[i] >= 1:
s = s.speedup(self.multipliers[i])
else:
s = ae.speed_down(s, self.multipliers[i])
pydubplay(s)
</code></pre><br>
</div>
<!-- Fetcher Class -->
<div>
<br><h4 class="title is-5">Fetcher</h4>
<p>A class containing static methods to fetch sounds and Variables (Selected if too many to write).</p>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>fetch(cls, id, specifier=None, componentIDs=None) -> Sound:</b>
<p>Fetches a sound object given its identifier, or synthesizes it from components if necessary.</p>
</li>
<li>
<b>addRecording(cls, file):</b>
<p>Adds an audio file recording to the recordings folder.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>class Fetcher:
#Class that contains several static methods involving fetching sounds and Variables (Selected if too many to write)
@classmethod
def fetch(cls, id, specifier = None, componentIDs = None) -> Sound:
'''
Fetch the Sound object given a phrase identifier, or synthesize it from componentIDs given a specifier
Parameters:
id(string): The identifier for the sound
specifier(string or None): If the sound does not exist and needs to be specified, whether the phrase is a composite or sequential phrase
componentIDs(list[string] or None): A list of the identifiers making up a composite or sequential phrases
Returns:
newSound (Sound) OR oldSound (Sound): The Sound instance representing the given id
'''
oldSound = Sound.sounds.get(id)
if oldSound:
return oldSound
elif not specifier:
raise ValueError("Did not find soundbite. Have you preregistered the id? Otherwise, you should just pass the soundBite when initializing the phrase.")
elif specifier == "composite":
assert componentIDs, "Need to specify component ids for composite phrases."
newSound = Sound(id, Sound.merge(Sound.sounds.get(c) for c in componentIDs))
return newSound
elif specifier == "sequential":
newSound = Sound(id, Sound.join(Sound.sounds.get(c) for c in componentIDs))
return newSound
@classmethod
def addRecording(cls, file):
'''
A method to add an audio file recording to the recordings folder.
file(str): Path to MIDI file
'''
os.rename(file, "recordings/" + os.path.basename(file))
</code></pre><br>
</div>
<!-- Continue documenting remaining classes similarly -->
<!-- Sound Class -->
<div>
<br><h4 class="title is-5">Sound</h4>
<p>A class representing a soundbite associated with a phrase on the tabla.</p>
<br><h5 class="title is-6">Class Variables (Selected if too many to write)</h5>
<ul>
<li><b>sounds (dict):</b> Stores all instantiated sounds.</li>
</ul>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>id (string):</b> The unique identifier of the soundbite.</li>
<li><b>recording (string):</b> The file name of an audio file in the recordings folder.</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, id, recording):</b>
<p>Initializes a Sound object with the specified ID and recording file.</p>
</li>
<li>
<b>play(self):</b>
<p>Plays the sound represented by this Sound object.</p>
</li>
<li>
<b>merge(cls, sounds) -> str:</b>
<p>For composite sounds, plays all sounds simultaneously and returns the new file name.</p>
</li>
<li>
<b>join(cls, sounds) -> str:</b>
<p>For sequential sounds, plays all sounds one after the other and returns the new file name.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>class Sound():
sounds = {}
'''
Class that represents the soundbite associated with a particular phrase
Class Variables (Selected if too many to write):
sounds(dict): stores all instantiated sounds
Parameters:
id(string): The unique identifier of the soundbite, typically the name of the associated phrase
recording(string): The file name of a audio file in the recordings/ folder. The reocrding must be 0.25 second per syllable, i.e. equivalent to playing Chatusra Jati at 60 bpm
'''
def __init__(self, id, recording):
self.id = id
self.recording = "recordings/" + recording
Sound.sounds.update({id: self})
def play(self):
'''
Plays the sound represented by this Sound object
'''
playsound(self.recording)
@classmethod
def merge(cls, sounds) -> str:
'''
For composite sounds, play all the sounds simultaneously
'''
assert len(sounds) > 1, "More than 1 sound must be provided to merge"
mergedSound = AudioSegment.from_file(sounds[0].recording)
fileName = sounds[0].id
for i in range(1, len(sounds)):
mergedSound = mergedSound.overlay(AudioSegment.from_file(sounds[i].recording), position = 0)
fileName += "+" + sounds[i].id
fileName = "recordings/" + fileName + ".m4a"
mergedSound.export(fileName, format = "ipod")
return fileName
@classmethod
def join(cls, sounds) -> str:
'''
For sequential sounds, play all the sounds one after the other, in the order given
Parameters:
sounds(list[Sound]): the individual component sounds to play
Returns:
newRecording(string): An audio file containing the combination requested
'''
assert len(sounds) > 1, "More than 1 sound must be provided to join"
mergedSound = AudioSegment.from_file(sounds[0].recording)
fileName = sounds[0].id
for i in range(1, len(sounds)):
mergedSound = mergedSound + AudioSegment.from_file(sounds[i].recording)
fileName += sounds[i].id
fileName = "recordings/" + fileName + ".m4a"
mergedSound.export(fileName, format = "ipod")
return fileName
</code></pre><br>
</div>
<!-- Phrase Class -->
<div>
<br><h4 class="title is-5">Phrase</h4>
<p>A class representing a phrase on the tabla.</p>
<br><h5 class="title is-6">Class Variables (Selected if too many to write)</h5>
<ul>
<li><b>registeredPhrases:</b> The phrases that have been registered so far.</li>
</ul>
<br><h5 class="title is-6">Parameters</h5>
<ul>
<li><b>mainID (string):</b> The name of the phrase.</li>
<li><b>syllables (int):</b> Number of syllables in this phrase.</li>
<li><b>position (string):</b> Position where this phrase is played (baiyan, daiyan, or both).</li>
<li><b>info (string):</b> Information about how to play this phrase.</li>
<li><b>aliases (Optional[List[str]]):</b> Other names for this phrase in compositions.</li>
<li><b>soundBite (Union[str, Sound]):</b> Either "Fetch" if sound is preregistered or a path/Sound object.</li>
<li><b>register (bool):</b> Whether this phrase should be registered.</li>
</ul>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>__init__(self, mainID, syllables=1, position='baiyan', info='No info provided', aliases=None, soundBite='Fetch', register=True):</b>
<p>Initializes a Phrase object with the specified parameters and registers it if specified.</p>
</li>
<li>
<b>play(self):</b>
<p>Plays the sound associated with this Phrase object.</p>
</li>
<li>
<b>createCompositePhrase(cls, mainID, componentIDs, aliases=None, soundBite='Fetch', register=True):</b>
<p>Creates a composite Phrase given component phrases.</p>
</li>
<li>
<b>createSequentialPhrase(cls, mainID, componentIDs, position, aliases=None, soundBite='Fetch', register=True):</b>
<p>Creates a sequential Phrase given component phrases.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>class Phrase():
registeredPhrases = {} # The phrases that have been registered so far
'''
Class that represents a phrase on the tabla
'''
def __init__(self, mainID, syllables=1, position='baiyan', info='No info provided', aliases=None, soundBite='Fetch', register=True):
if not isinstance(soundBite, Sound) and soundBite != "Fetch":
soundBite = Sound(mainID, soundBite)
mainID = mainID.lower()
self.ids = [mainID]
if aliases:
self.ids += aliases
self.description = f"Phrase: {self.ids}\nPlayed on {position}.\n{info}\nNo. of syllables: {syllables}"
self.syllables = syllables
self.position = position
self.info = info
self.soundBite = soundBite if soundBite != "Fetch" else Fetcher.fetch(mainID)
if register:
for id in self.ids:
Phrase.registeredPhrases.update({id: self})
def __repr__(self):
return str(self.ids[0])
def play(self):
self.soundBite.play()
@classmethod
def createCompositePhrase(cls, mainID, componentIDs, aliases=None, soundBite='Fetch', register=True):
'''
Creates a composite phrase
Parameters:
componentIDs(list): IDs of the component phrases that make up this composite phrase
'''
assert len(componentIDs) == 2, "A composite phrase must have exactly 2 component phrases"
component1, component2 = map(Phrase.registeredPhrases.get, componentIDs)
assert component1.position != component2.position and component1.position in ["baiyan", "daiyan"] and component2.position in ["baiyan", "daiyan"], "Components must be played on different drums"
x = cls(mainID=mainID,
syllables=max(component1.syllables, component2.syllables),
position="both drums",
info=f"Play the following two phrases simultaneously: \n1) {component1.info}\n2) {component2.info}",
aliases=aliases,
soundBite=soundBite if soundBite else Fetcher.fetch(mainID, "composite", componentIDs),
register=register
)
return x
@classmethod
def createSequentialPhrase(cls, mainID, componentIDs, position, aliases=None, soundBite='Fetch', register=True):
'''
Creates a sequential phrase
Parameters:
componentIDs(list): IDs of the sequential phrases that make up this composite phrase
'''
assert all(id in Phrase.registeredPhrases for id in componentIDs), "Must register component phrases first."
syllables = sum(Phrase.registeredPhrases[id].syllables for id in componentIDs)
info = "Play the following phrases in succession:" + "".join(
f"\n{i}) {Phrase.registeredPhrases[componentIDs[i]].info}" for i in range(len(componentIDs)))
x = cls(mainID=mainID,
syllables=syllables,
position=position,
info=info,
aliases=aliases,
soundBite=soundBite if soundBite else Fetcher.fetch(mainID, "sequential", componentIDs),
register=register
)
return x
</code></pre><br>
</div>
<!-- Continue documenting remaining classes similarly -->
<!-- CompositionGenerator Class -->
<div>
<br><h4 class="title is-5">CompositionGenerator</h4>
<p>A class that provides static methods for generating compositions using the Llama model.</p>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>generate(cls, type:str, taal:Union[str, int], speedClass:str, jati:Union[str, int], school:str, token:str):</b>
<p>Generates a composition given parameters using the Llama model available on HuggingFace.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>class CompositionGenerator():
# Class that provides a static method to generate a composition
@classmethod
def generate(cls, type:str, taal:Union[str, int], speedClass: str, jati: Union[str, int], school: str, token: str):
'''
A method that generates a composition given parameters using the Llama model available on HuggingFace
'''
warnings.warn("This is an experimental feature that may provide incorrect or incomplete results.")
warnings.warn("Execution time might be excessive depending on your hardware.")
phraseInfo = "The following phrases are defined by the user on the tabla, along with a description of how to play them: \n" + "\n".join([key + "." + val.description for key, val in Phrase.registeredPhrases.items()])
mainPrompt = "Using the above phrases only, compose a " + type + " in taal with name/beats " + taal + " and speed class " + speedClass + ". The composition should be in jati with name/syllables per beat " + jati + " and in the " + school + " style of playing. Components of the composition should be marked appropriately."
symbolPrompt = "Each beat should be separated with the character '|'. An example of the expected output if the user requests a Kayda of Ektaal, with Chatusra Jati, in the Lucknow Gharana is: \n" + open("template.tabla, "r").read() + "\n A phrase cannot span more than one beat. A phrase can also span exactly one syllable even if it usually spans more than one. In that case, enclose the phrase with parentheses."
end = "Finally, in addition to following the above rules, the composition should be as authentic and aesthetically pleasing as possible."
prompt = phraseInfo + mainPrompt + symbolPrompt + end
messages = [
{"role": "user", "content": prompt},
]
pipe = pipeline("text-generation", model="meta-llama/Meta-Llama-3-70B-Instruct", token = token, torch_dtype=torch.float16, device_map="auto")
return pipe(messages, do_sample = True, num_return_sequences = 1, eos_token_id = pipe.tokenizer.eos_token_id, return_full_text = False)[0]['generated_text']
</code></pre><br>
</div>
<!-- AudioToBolConvertor Class -->
<div>
<br><h4 class="title is-5">AudioToBolConvertor</h4>
<p>A class providing static methods to transcribe a bol from an audio recording.</p>
<br><h5 class="title is-6">Functions (Selected if too many to write)</h5>
<ul>
<li>
<b>convert(cls, recording:str, speed:int, jati:int) -> str:</b>
<p>Generates the bol transcription from an audio recording.</p>
</li>
<li>
<b>getMostSimilarSound(cls, snippet, from:Dict[str, str]) -> str:</b>
<p>Gets the most similar sounding phrase to a given audio snippet.</p>
</li>
</ul>
<br><h5 class="title is-6">Full Code</h5>
<pre><code>class AudioToBolConvertor():
# Class that provides a static method to transcribe a bol given the recording of a composition
@classmethod
def convert(cls, recording:str, speed:int, jati:int) -> str:
'''
A method that generates the bol given an audio recording
'''
warnings.warn("This is an experimental feature that may provide incorrect or incomplete results.")
currentSyllableDuration = 60.0/(speed*jati)
desiredSyllableDuration = 0.25
sound = AudioSegment.from_file(recording)
if currentSyllableDuration > desiredSyllableDuration:
sound = sound.speedup(currentSyllableDuration / desiredSyllableDuration)
elif currentSyllableDuration < desiredSyllableDuration:
sound = ae.speed_down(sound, currentSyllableDuration / desiredSyllableDuration)
# Parse the audio for every 0.25 second snippet, comparing it to known recordings
recordings = {val.soundBite.recording: key for key, val in Phrase.registeredPhrases.items()}
bolString = ""
marker = 0
while marker < sound.duration_seconds * 1000:
add = cls.getMostSimilarSound(snippet=sound[marker: marker + 250], from=recordings)
marker += Phrase.registeredPhrases[add].syllables * 250
bolString += add
return bolString
@classmethod
def getMostSimilarSound(cls, snippet, from:Dict[str, str]) -> str:
'''
Gets the most similar sounding bol to a given audio snippet
'''
snippet.export("snippetTemp", format = "m4a")
_, encoded = acoustid.fingerprint_file("snippetTemp")
fingerprint, _ = chromaprint.decode_fingerprint(encoded)
references = {}
for key, val in from.items():
_, e = acoustid.fingerprint_file(key)
f, _ = chromaprint.decode_fingerprint(e)
references[val] = f
from operator import xor