-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathYummyFillings.rb
5566 lines (4785 loc) · 201 KB
/
YummyFillings.rb
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
##| YummyFillings.rb
##| Useful methods for sonic pi
##| Harry LeBlanc 2024, gnu public license
##| HarryLeBlancLPCC@gmail.com
##| version 1.0.0, 12/12/24
##| 1.0.1, fixed 2 bugs in convertdrumnotation
##| 1.0.2, fixed bug in samplebpm
##| 1.0.3, added asdr & levels args to env
##| Use eval_file to load these method definitions, not load!
##| useful constants for specifying time intervals in notes
##| assumes one beat is a quarter note
##| allows combinations like dotted * eighth or quarter * triplet
whole = 4.0
half =2.0
quarter =1.0
eighth =0.5
sixteenth =0.25
dotted = 1.5
triplet =2.0 / 3
debugmode = true
#scrub
#small utility function to clean nils with the specified value (defaults to "")
define :scrub do |value, cleanvalue = ""|
(value == nil ? cleanvalue : value )
end
##| ##debugprint
##| a utility function to optionally print out debugging messages,
##| controlled by the debugmode variable. If not set, defaults to false and prints nothing.
##| label: a text string to explain what the value means.
##| value: the value being displayed for debugging purposes. If nil, just displays the label.
##| expandlist: if either arg is a list or ring, print them individually
##| indents: how many levels of recursion, which will print n copies of indenttext
##| indenttext: the text to use for nested indentations
##| logtofile: set to true if you wish to log to a text file
##| filename: the name of the file to log to. Will append if it exists, create it if it does not.
define :debugprint do |label, value="tricksy hobbit!", expandlist=false, indents=0, indenttext=" ", logtofile=false, filename="e:/yummy/debuglog.txt", **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters) #if boolish kwargs
fakenil = "tricksy hobbit!"
#puts "got to ##debugprint"
if current_debug
# puts "debugmode is active" + "\n"
# puts "label: " + label.to_s + "\n"
# puts "value: " + scrub(value).to_s + "\n"
# puts "label class: " + label.class.to_s + "\n"
# puts "value class: " + scrub(value).class.to_s + "\n"
#stringify nils for pretty printing
if value == "tricksy hobbit!" && label == nil
label = "nil "
value = nil
elsif label == nil && value == nil
label = "nil "
elsif value == nil
# puts "pretty nil value"
value = "nil "
# puts value
elsif value == "tricksy hobbit!"
value = nil
end #if label == nil
# puts "label: "
# puts label
# puts "value: "
# puts value
if expandlist
# puts "in expandlist mode" + "\n"
if label.is_a? Hash
# puts "label is a hash" + "\n"
##debugprint "hash: ", nil, expandlist, indents, indenttext, logtofile, filename
indents += 1
label.each do |hkey, hval|
##debugprint "key: ", fakenil, expandlist, indents, indenttext, logtofile, filename
##debugprint hkey, fakenil, expandlist, indents + 1, indenttext, logtofile, filename
##debugprint "value: ", fakenil, expandlist, indents, indenttext, logtofile, filename
##debugprint hval, fakenil, expandlist, indents + 1, indenttext, logtofile, filename
end
# puts "after hash label"
##debugprint value, fakenil, expandlist, indents, indenttext, logtofile, filename
elsif ringorlist label
# puts "ringorlist label" + "\n"
if label.is_a? Array
##debugprint "array: "
else
##debugprint "ring: "
end #if array or ring
# puts "after conditional print"
label.each do |nestedlabel|
##debugprint nestedlabel, fakenil, expandlist, indents + 1, indenttext, logtofile, filename
end #each label item
elsif value.is_a? Hash
# puts "value is a hash" + "\n"
##debugprint label if label != nil
##debugprint "hash: "
indents += 1
value.each do |hkey, hval|
##debugprint "key: ", fakenil, expandlist, indents, indenttext, logtofile, filename
##debugprint hkey, fakenil, expandlist, indents + 1, indenttext, logtofile, filename
##debugprint "value: ", fakenil, expandlist, indents, indenttext, logtofile, filename
##debugprint hval, fakenil, expandlist, indents + 1, indenttext, logtofile, filename
end
elsif ringorlist value
# puts "ringorlist value" + "\n"
if label != nil
##debugprint label, fakenil, expandlist, indents, indenttext, logtofile, filename
end #if label not nil
if value.is_a? Array
##debugprint "array: "
else
##debugprint "ring: "
end #if array or ring
value.each do |nestedvalue|
##debugprint nestedvalue, fakenil, expandlist, indents + 1, indenttext, logtofile, filename
end #each label item
else
# puts "bottom of recursion" + "\n"
#bottom of recursion, ready to actually print
outputstring = (indenttext * indents) + symbolwithcolon(label) + symbolwithcolon(value) + "\n"
puts outputstring
if logtofile
# puts "logging to file"
File.write(filename, outputstring, mode: "a")
end #if logging
end #if got lists
else
# puts "no expandlist" + "\n"
# puts "label: "
# puts label
# puts "value: "
# puts value
outputstring = (indenttext * indents) + symbolwithcolon(label) + symbolwithcolon(value) + "\n"
puts outputstring
if logtofile
# puts "logging to file"
File.write(filename, outputstring, mode: "a")
end #if logging
end #if expandlist
else
# puts "not debug mode"
end #if debug
# puts "end of ##debugprint"
end #define
# symbolcolon
# provides conditional colon for pretty symbol printing.
# args:
# thisvalue: the value to test.
# returns: ":" if a symbol, "" if not.
define :symbolcolon do |thisvalue, **kwargs|
if thisvalue == nil
return ""
elsif thisvalue.is_a? Symbol
return ":"
else
return ""
end #if
end #define symbolcolon
# symbolwithcolon
# wrapper to simplify.
define :symbolwithcolon do |thisvalue|
thisvalue ||= ""
thisvalue = symbolcolon(thisvalue) + thisvalue.to_s
thisvalue #return value
end #define symbolwithcolon
# clonekeys
# duplicate key/val pairs in a hash to match a master key.
# args:
# thishash: the hash to add cloned key/val pairs to.
# keystoclone: an array of keys. The first key that has a non-nil, non-false value paired to it
# will be treated as the master key.
# Can also be an array of arrays. If so, each array will be processed separately.
# falseflag: boolean flag (defaulting to true), indicating whether to purge keys whose paired value is false.
# This supports not cloning certain values by pre-loading the hash with key/false pairs. Such keys will not be cloned.
# In turn, this supports layering synth voices in arrange.
# Example code:
# clonekeys {1 => false, 2=> :foo 5=> :bar}, [1, 2, 3, 4]
# returns: {2=> :foo, 3 => :foo, 4=> :foo, 5 => :bar}
# clonekeys {1 => false, 2=> :foo}, [1, 2, 3, 4], false
# returns: {1=> false, 2=> :foo, 3 => :foo, 4=> :foo, 5=> :bar}
# clonekeys {1 => false, 2=> :foo, 5=> :bar}, [[1, 2, 3, 4], [5, 6, 7]]
# returns: {2 => :foo, 3=> :foo, 4 => :foo, 5=> :bar, 6=> :bar, 7=> :bar}
define :clonekeys do |thishash, keystoclone, falseflag=true, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters)
cleanargs = stripparams kwargs, method(__method__).parameters
##debugprint "top of clonekeys"
##debugprint "thishash: ", thishash
##debugprint "keystoclone: ", keystoclone
##debugprint "falseflag: ", falseflag
if !keystoclone[0].is_a? Array #no nested array
##debugprint "no nested array, wrapping keystoclone"
keystoclone = [keystoclone]
end #if need array wrapper
keystoclone.each do |thislist|
##debugprint "thislist: ", thislist
mainkey = nil
secondarykeys = []
thislist.each do |thiskey|
if !mainkey
##debugprint "looking for main key"
##debugprint "thiskey: ", thiskey
##debugprint "thishash.keys: ", thishash.keys
##debugprint "thishash[thiskey]: ", thishash[thiskey]
##debugprint "thishash.keys.include? thiskey: ", (thishash.keys.include? thiskey)
##debugprint "thishash[thiskey] != false: ", (thishash[thiskey] != false)
if (thishash.keys.include? thiskey) && (thishash[thiskey] != false)
##debugprint "found mainkey: ", thiskey
mainkey = thiskey
elsif thishash[thiskey]
##debugprint "not a main key, adding to secondary keys: ", thiskey
secondarykeys << thiskey
else
##debugprint "thishash[thiskey] is false or nil"
end #if got content for thiskey
else
##debugprint "got a main key, looking for secondary keys"
if !thishash.keys.include? thiskey
##debugprint "adding to secondary keys: ", thiskey
secondarykeys << thiskey
else
##debugprint "already in thishash, skipping key ", thiskey
end #if don't already have key in hash
end # if no mainkey
end
if mainkey
##debugprint "got a mainkey: ", mainkey
secondarykeys.each do |thiskey|
##debugprint "cloning thiskey: ", thiskey
thishash[thiskey] = thishash[mainkey]
end #each secondary key
else
##debugprint "no mainkey, doing nothing"
end #if got a main key
end #each thislist
if falseflag
##debugprint "got false flag, purging false keys"
thishash.delete_if {|key, val| val == false}
end #if falseflag
##debugprint "thishash: ", thishash
thishash #return value
end #define clonekeys
# portamentomelody
# play a melody on a synth portamento, by manipulating the pitch over time.
# args:
# thisarrangement: the melody in mini-notation (see arrange for details); e.g.: "qd :c5, qd :ds5, q :g5"
# thissynth: the name of the synth to play the note; defaults to :sine.
# note_slide: the portamento time; if set to longer than the shortest note, will be length of shortest note.
define :portamentomelody do |thisarrangement, thissynth = :sine, note_slide = 0.0625, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters)
cleanargs = stripparams kwargs, method(__method__).parameters
##debugprint "top of portamentomelody"
##debugprint "thissynth: ", thissynth
##debugprint "thisarrangement: ", thisarrangement
##debugprint "note_slide: ", note_slide
twoarrays = rowstocolumns (stringtonestedarray thisarrangement)
##debugprint "twoarrays: ", twoarrays
timearray = rowstocolumns(cooktimes twoarrays[0].join(","))[0]
notearray = []
twoarrays[1].each do |thisnote|
##debugprint "thisnote: ", thisnote
if thisnote.downcase =~ /[:abcdefg]/
##debugprint "note is a symbol"
notearray << thisnote.delete_prefix(":").to_sym.to_i
elsif thisnote =~ /^[\d\.\s]*$/ #digits and dots and whitespace
##debugprint "note is a number"
notearray << thisnote.to_f
else
##debugprint "note is garbage, skipping"
end #if note is symbol or number
end #each note
##debugprint "notearray: ", notearray
totaltime = timearray.sum
##debugprint "totaltime: ", totaltime
if note_slide > timearray.min
##debugprint "note_slide too long, adjusting"
note_slide = timearray.min
end #if slide too long
##debugprint "note_slide: ", note_slide
##debugprint "timearray: ", timearray
##debugprint "notearray: ", notearray
with_synth thissynth do
##debugprint "playing note ", notearray[0]
handle = play notearray.delete_at(0), sustain: totaltime, note_slide: note_slide, **cleanargs
##debugprint "sleeping:: ", timearray[0]
sleep timearray.delete_at(0)
##debugprint "notearray: ", notearray
##debugprint "timearray: ", timearray
notearray.length.times do
##debugprint "playing note ", notearray[0]
control handle, note: notearray.delete_at(0)
##debugprint "sleeping:: ", timearray[0]
sleep timearray.delete_at(0)
##debugprint "notearray: ", notearray
##debugprint "timearray: ", timearray
end #traversing notearray
end #with thissynth
nil #return value
end #define portamentomelody
# slidemelody: alias for portamentomelody
define :slidemelody do |thisarrangement, thissynth = :sine, note_slide = 0.0625, **kwargs|
portamentomelody thisarrangement, thissynth, note_slide, **kwargs
end
# vibrato
# quickly oscillate pitch up & down
# args:
# handle: node controlling the samples/synths; could be a chord node
# range: how much to move the volume up & down; defaults to 0.5
# rate: the rate of tremolo, in beats; defaults to sixteenth.
# duration: how long the tremolo lasts; defaults to whole.
define :vibrato do |handle, rate=sixteenth, depth=0.25, duration=whole, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters)
cleanargs = stripparams kwargs, method(__method__).parameters
##debugprint "top of vibrato"
##debugprint "handle: ", handle
##debugprint "rate: ", rate
##debugprint "depth: ", depth
##debugprint "duration: ", duration
halfrate = rate / 2.0
elapsedtime = 0
##debugprint "about to thread"
direction = 1
pitch = handle.args["note"] unless handle.is_a? SonicPi::ChordGroup
##debugprint "pitch: ", pitch
in_thread do
while elapsedtime < duration do
vtargs = ""
##debugprint "elapsedtime: ", elapsedtime
if handle.is_a? SonicPi::ChordGroup
##debugprint "got a chord group"
pitch = pitch.to_a
##debugprint "pitch: ", pitch
handle.sub_nodes.each do |subhandle|
##debugprint "subhandle: ", subhandle
##debugprint "subhandle methods: ", subhandle.class.instance_methods
newpitch = subhandle.args["note"] + (depth * direction)
##debugprint "newpitch: ", newpitch
control subhandle, note: newpitch, note_slide: halfrate
end #each subhandle
else
##debugprint "got a single note"
newpitch = pitch.to_i + (depth * direction)
##debugprint "newpitch: ", newpitch
control handle , note: newpitch , note_slide: halfrate
end #if chordgroup or single note
sleep halfrate
direction *= -1
elapsedtime += halfrate
end #while elapsedtime < duration
##debugprint "bottom of time loop"
##debugprint "elapsedtime: ", elapsedtime
if handle.is_a? SonicPi::ChordGroup
##debugprint "got a chord group"
pitch = pitch.to_a
##debugprint "pitch: ", pitch
handle.sub_nodes.each do |subhandle|
##debugprint "subhandle: ", subhandle
newpitch = subhandle.args["note"]
##debugprint "newpitch: ", newpitch
control subhandle, note: newpitch, note_slide: halfrate
end #each subhandle
else
##debugprint "got a single note"
control handle, note: pitch, note_slide: halfrate
##debugprint "after control"
end #if chordgroup or single note
##debugprint "after resetting tone and amp"
stop
end #in_thread
handle #return value
end #define tremolo
# tremolo
# quickly oscillate volume up & down
# args:
# handle: node controlling the samples/synths; could be a chord node
# amp: the base volume of the sound; defaults to 1.
# depth: how much to move the volume up & down; defaults to 0.5
# rate: the rate of tremolo, in beats; defaults to sixteenth.
# duration: how long the tremolo lasts; defaults to whole.
define :tremolo do |handle, amp=1, depth=1, rate=sixteenth, duration=whole, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters)
cleanargs = stripparams kwargs, method(__method__).parameters
##debugprint "top of tremolo"
##debugprint "handle: ", handle
##debugprint "amp: ", amp
##debugprint "depth: ", depth
##debugprint "rate: ", rate
##debugprint "duration: ", duration
halfrate = rate / 2.0
elapsedtime = 0
##debugprint "about to thread"
direction = 1
in_thread do
while elapsedtime < duration do
##debugprint "elapsedtime: ", elapsedtime
if handle.is_a? SonicPi::ChordGroup
##debugprint "got a chord group"
subamp = amp / handle.sub_nodes.count.to_f
subdepth = depth / handle.sub_nodes.count.to_f
##debugprint "subamp: ", subamp
pitch = pitch.to_a
##debugprint "pitch: ", pitch
handle.sub_nodes.each do |subhandle|
##debugprint "subhandle: ", subhandle
control subhandle, amp: (subamp + (subdepth * direction)), amp_slide: halfrate
end #each subhandle
else
##debugprint "got a single note"
control handle, amp: (amp + (depth * direction)), amp_slide: halfrate
end #if chordgroup or single note
sleep halfrate
direction *= -1
elapsedtime += halfrate
end #while elapsedtime < duration
##debugprint "bottom of time loop"
if handle.is_a? SonicPi::ChordGroup
##debugprint "got a chord group"
subamp = amp / handle.sub_nodes.count.to_f
##debugprint "subamp: ", subamp
pitch = pitch.to_a
##debugprint "pitch: ", pitch
handle.sub_nodes.each do |subhandle|
##debugprint "subhandle: ", subhandle
control subhandle, amp: subamp, amp_slide: halfrate
end #each subhandle
else
##debugprint "got a single note"
control handle, amp: amp, amp_slide: halfrate
end #if chordgroup or single note
stop
end #in_thread
handle #return value
end #define tremolo
##| ringorlist -- simple utility function to test whether an item is a ring or a list.
##| arg: thisitem
##| true if either, false if anything else.
##| Added a couple of synonyms, listorring and tickable.
define :ringorlist do |thisitem|
thisitem.is_a? Enumerable or thisitem.is_a? SonicPi::Core::RingVector
end
define :listorring do |thisitem|
ringorlist thisitem
end
define :tickable do |thisitem|
ringorlist thisitem
end
##| overridekwargs
##| a useful method to help emulate the native ruby ability to specify params by either position or name.
##| kwargs: the hash of named params specified by the user.
##| params: the parameters defined by the calling method. Get via introspection (see example code)
##| arglistname: the name of the variable holding the list in the first name, defaults to "kwargs".
##| a useful little method to make user-defined methods act more like native ruby methods,
##| so you can specify params either by position or by name.
##| simply add **kwargs as the last param of your method, and put this line of code at the top of the method body:
##| eval overridekwargs(kwargs, method(__method__).parameters)
##| See also stripparams, which is useful for stripping out method-related params,
##| leaving only params which are suitable to pass to play or sample.
define :overridekwargs do |kwargs, params, ignorenewargs=true, arglistname="kwargs"|
# ##debugprint "kwargs before defaulting: " + kwargs.to_s
kwargs ||= {}
# ##debugprint "kwargs after defaulting: " + kwargs.to_s
params.collect! {|x| x=x[1]} #params is an array of arrays, with the name as the 2nd item in each nested array. This strips & flattens
# ##debugprint "params: ", params
kwargcmdlist = ""
kwargs.each do |argname|
# ##debugprint "argname: " + argname[0].to_s
# ##debugprint "params include argname? " + (params.include? argname[0]).to_s
# ##debugprint "ignore new? " + ignorenewargs.to_s
cleanname = argname[0]
if argname[0] == nil
cleanname = "garbage"
end #if nil argname[0]
if params.include? cleanname or params.include? cleanname.to_s or params.include? cleanname.to_sym or !ignorenewargs
kwargcmdlist += cleanname.to_s + " = " + arglistname.to_s + "[:" + cleanname.to_s + "]\n"
else
# ##debugprint argname[0].to_s + " is not a valid param -- did you make a typo?"
end
end
# ##debugprint "kwargcmdlist", kwargcmdlist
kwargcmdlist
end
##| stripparams: a utility function to delete params from kwargs.
##| Useful for passing args to nested method calls.
##| kwargs: a hash of key word args
##| params: an array/ring of params to strip
##| sample code:
##| cleanargs = stripparams kwargs, method(__method__).parameters
define :stripparams do |kwargs, params|
# ##debugprint "kwargs: ", kwargs
# ##debugprint "params ", params
tempargs = kwargs.clone
params.each do |thisparam|
# ##debugprint "about to delete thisparam[1]: ", thisparam[1]
tempargs.delete(thisparam[1])
end
# ##debugprint "kwargs after stripping: ", kwargs
tempargs #return value
end #define
# funkyrandom
# randomly generates a funky rhythm,
# returned as a string of notations (bwhqestdr) suitable for feeding into cooknotes or arrange.
# Args:
# totaltime: the whole length of the pattern. Defaults to 16 (4 bars).
# shortestbeat: the shortest beat used in the pattern. Defaults to 0.25 (sixteenth).
# shortestbeat must be one of: sixteenth, eighth, quarter, half, whole (0.25, 0.5, 1, 2, 4)
# restodds: the odds of a rest, using one_in. Defaults to 8.
define :funkyrandom do |totaltime=16, shortestbeat=0.25, restodds=8, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters) if boolish kwargs
cleanargs = stripparams kwargs, method(__method__).parameters
##debugprint "top of funkyrandom"
randomfunk = []
funkpatterns = {}
thisbeat = ""
funkpatterns[sixteenth] = ["s", "e", "es"]
funkpatterns[eighth] = ["e", "q", "qe"]
funkpatterns[quarter] = ["q", "h", "hq"]
funkpatterns[half] = ["h", "w", "wh"]
funkpatterns[whole] = ["w", "ww", "www"]
thispattern = funkpatterns[shortestbeat]
##debugprint "thispattern: ", thispattern
while totaltime > 0
thisbeat = thispattern.choose
##debugprint "thisbeat: ", thisbeat
thistime = cooktime(thisbeat)[0]
##debugprint "thistime: ", thistime
if thistime > totaltime
##debugprint "thistime > totaltime"
thistime = totaltime
##debugprint "thistime: ", thistime
thisbeat = funkpatterns[thistime][0]
##debugprint "thisbeat: ", thisbeat
end #if thistime > totaltime
if one_in restodds
##debugprint "got a rest"
thisbeat = "r" + thisbeat
##debugprint "thisbeat: ", thisbeat
end #if got a rest
randomfunk << thisbeat
##debugprint "randomfunk: ", randomfunk
totaltime -= thistime
end #while totaltime > 0
randomfunk = randomfunk.join(",") #convert to comma-delimited string
##debugprint "randomfunk: ", randomfunk
randomfunk #return value
end #define funkyrandom
##| funkify
##| A method to play a sound in a funky, random manner for a specified period of time.
##| thissound: a synth or sample. Can also be an array or list of synths or samples.
##| totaltime: the number of beats for the entire pattern. Defaults to 16 (4 bars).
##| shortestbeat: the smallest subdivision. Will sleep 1, 2, or 3 times that each time a sound plays.
##| thesenotes: a list or ring of notes, used with synths only. Defaults to [:c4].
##| densities: a list or ring of densities, applied per note/sleep.
##| tickorchoose: tick or choose. Used to define how to traverse densities and thesenotes .
define :funkify do |thissound, totaltime=16, shortestbeat=sixteenth, thesenotes=[:c4], densities=[1], tickorchoose="tick", **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters) if boolish kwargs
cleanargs = stripparams kwargs, method(__method__).parameters
##debugprint "top of funkify"
##debugprint "thissound: ", thissound
##debugprint "totaltime: ", totaltime
##debugprint "shortestbeat: ", shortestbeat
##debugprint "thesenotes: ", thesenotes
##debugprint "densities: ", densities
##debugprint "tickorchoose: ", tickorchoose
##debugprint "kwargs: ", kwargs
sleepytime = 0
thisdensity = 1
thisnote = 0
cooktimes(funkyrandom totaltime, shortestbeat).each do |timeandrest|
sleepytime = timeandrest[0]
##debugprint "sleepytime: ", sleepytime
isarest = timeandrest[1]
##debugprint "isarest: ", isarest
thisdensity = ( tickorchoose = "tick" ? densities.tick : densities.choose )
##debugprint "thisdensity: ", thisdensity
thisnote = (tickorchoose == "tick" ? thesenotes.tick : thesenotes.choose )
##debugprint "thisnote: ", thisnote
density thisdensity do
if !isarest
if synth_names.to_a.include? thissound
with_synth thissound do
##debugprint "it's a synth: ", thissound
play thisnote, **cleanargs
end #with_synth
else
##debugprint "it's a sample: ", thissound
sample thissound, **cleanargs
end #if synth or sample
end #if not a rest
sleep sleepytime
end #each timeandrest
end #with random seed
end #define funkify
##| samplebpm -- utility to return the bpm of any sample loop.
##| thissample: the sample to extract the bpm from.
##| num_beats: the number of beats used to calculate bpm. Defaults to 4
##| example:
##| puts samplebpm :loop_amen
##| puts samplebpm :loop_amen_full, 16
define :samplebpm do |thissample, beats=4.0, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters, false) if boolish kwargs if boolish kwargs
with_sample_bpm thissample, num_beats: beats do
current_bpm
end #with_sample_bpm
end #define :samplebpm
# formattedsamplename
# Utility that provides either a leading colon or embedded quotes, depending on if it's a symbol or string (path).
# Used for command string evaluation.
define :formattedsamplename do |thissample|
# ##debugprint "top of formattedsamplename"
# ##debugprint "thissample: ", thissample
cleanname = thissample.to_s
cleanname = cleanname.to_s.sub(":", "") if cleanname.to_s[0] != "["
# ##debugprint "cleanname before: ", cleanname
singlequote = '"'
# ##debugprint "singlequote: ", singlequote
if all_sample_names.to_a.include? cleanname.to_sym
# ##debugprint "got a sample from the list"
cleanname.prepend( ":")
elsif cleanname[0] == "["
# ##debugprint "got a list, leaving bare: ", cleanname
else
# ##debugprint "not a sample in the list"
cleanname = singlequote + thissample.to_s + singlequote
cleanname = cleanname.gsub('""', '"') #to clean up repeated quotes
end
# ##debugprint "cleanname after: ", cleanname
return cleanname
end #define formattedsamplename
##| transposesample
##| transposes a sample up or down by specified rpitch, while pitch_stretching to keep tempo.
##| args:
##| thissample: the sample to transpose.
##| autostretch: whether to apply any pitch_stretch to the sample. Defaults to true.
##| It is your responsibility to provide options for pitch_stretch, time_dis, etc.
##| You may need to fiddle with time_dis, window_size and pitch_dis to tweak the sound.
##| example:
##| mysample = :bass_hit_c
##| [0, 3, 7, -2].each do |thispitch|
##| use_bpm thisbpm
##| handle = transposesample mysample, thispitch, autostretch=false
##| sleep 16
##| end
##| sleep 2
##| end
##| Code returns a handle (node) for further manipulation, e.g. lfos, envelopes, trancegates.
define :transposesample do |thissample, rpitch=0, autostretch=true, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters, false)
cleanargs = stripparams kwargs, method(__method__).parameters
autostretch = boolish autostretch #force boolean
handle = nil
# ##debugprint "top of transposesample"
# ##debugprint "thissample: ", thissample
# ##debugprint "rpitch: ", rpitch
# ##debugprint "autostretch: ", autostretch
# ##debugprint "kwargs: ", kwargs
# ##debugprint "cleanargs: ", cleanargs
ratio = midi_to_hz(60 + rpitch) / midi_to_hz(60)
# ##debugprint "ratio: ", ratio
cmd = "handle = sample " + formattedsamplename(thissample)
# ##debugprint "cmd: ", cmd
if rpitch != 0
# ##debugprint "got a nonzero rpitch"
cmd += ", rpitch: " + rpitch.to_s
# ##debugprint "cmd: ", cmd
end #if got an rpitch
if autostretch
# ##debugprint "autostretch mode, defaulting pitch stretch"
cleanargs[:pitch_stretch] ||= (sample_duration thissample) * ratio.to_f
cleanargs[:window_size] ||= 0.2
cleanargs[:pitch_dis] ||= 0.001
cleanargs[:time_dis] ||= 0.001
else
# ##debugprint "not in autostretch mode, deleting stretch-related settings"
cleanargs.delete(:pitch_stretch)
cleanargs.delete(:window_size)
cleanargs.delete(:pitch_dis)
cleanargs.delete(:time_dis)
end #if not autostretch
cmd += tickargs cleanargs
# ##debugprint "cmd: ", cmd
eval cmd
# ##debugprint "handle: ", handle
handle #return handle
end #transposesample
##| arrayhashtohasharray
##| A utility function that converts a hash of arrays to an array of hashes.
##| The array length will be the length of the longest array in the hash,
##| and values from shorter arrays will be looped
##| (e.g., for a 2-element array, the 3rd element will equal the first element)
##| Args:
##| arrayhash: the hash of arrays (e.g. { amp: [1, 2, 3], duration: [1, 2]})
##| makering: if true, forces the return value to a ring, not an array. Defaults to true.
define :arrayhashtohasharray do |arrayhash, makering=true|
hasharray = []
maxlength = 0
arrayhash.each do |key, value|
if !ringorlist value
value = [value].ring
else
value = value.ring
end
maxlength = value.length if value.length > maxlength
arrayhash[key] = value
end #first iteration through hash
arrayhash.each do |key, value|
(0..maxlength-1).each do |i|
if hasharray[i] == nil
hasharray[i] = {}
end
hasharray[i][key] = arrayhash[key][i]
end #each value item
end #each hash element
if makering
hasharray = hasharray.ring
end
hasharray #return value
end #define
# cleanchordorscale
# turns a chord or scale into a plain array.
define :cleanchordorscale do |myitem|
# ##debugprint "top of cleanchordorscale"
# ##debugprint "myitem: ", myitem
# ##debugprint "myitem.class: ", myitem.class
cleanitem = nil
if myitem.is_a? SonicPi::Chord or myitem.is_a? SonicPi::Scale or ringorlist myitem
# ##debugprint "got a scale or chord or ring"
cleanitem = []
myitem.each do |x| cleanitem << x end
else
# ##debugprint "no scale or chord or ring"
cleanitem = myitem
end
cleanitem ##return value
end
# closestnote
# finds the note closest to the given note in the given scale.
# args:
# thisnote: the note to start with.
# thisscale: the scale to search.
# Example code:
# use_random_seed Time.now.to_i
# thisnote = rand_i(89) #full piano range
# ##debugprint "thisnote: ", thisnote
# ##debugprint "closestnote: ", closestnote(thisnote, thisscale)
define :closestnote do |thisnote, thisscale, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters) if boolish kwargs
thisscale = cleanchordorscale thisscale
###debugprint thisscale
foundnote = nil
thisstep = 0
thisneg = true
while !foundnote
thissign = (thisneg ? -1 : 1)
###debugprint "test value: ", thisnote + (thisstep * thissign)
if thisscale.include? thisnote + (thisstep * thissign)
###debugprint "got a match!"
foundnote = thisnote + (thisstep * thissign)
elsif thisneg
###debugprint "got a negative, incrementing step"
thisstep += 1
###debugprint "thisstep: :", thisstep
end #if foundnote or thisneg
thisneg = !thisneg
###debugprint "thisneg: ", thisneg
end #while
return foundnote
end #define closestnote
##| arpeggiate
##| A method to sequentially play the chord or scale or array/ring of notes passed in.
##| Args:
##| thesenotes: the ring/list of notes to play, maybe a chord or scale, or just a user-defined list.
##| thesedelays: either a single value, or an array of values, to sleep after playing each note; defaults to [quarter].
##| vibrato: either, false, true (which uses defaults for vibrato),
##| a hash of args to pass to vibrato, or a string containing args for vibrato.
##| tremolo: either false, true (which uses defaults for tremolo) ,
##| a hash of args to pass to tremolo, or a string containing args for tremolo.
##| synthdefaults: any additional args are assumed to be synth defaults, and will be used to change defaults per note.
##| Again, if a single value on each item, will be used on all notes. If a ring/list, will be ticked through for each note.
##| Example:
##| arpeggiate (chord :c4, "m7"), [0.1, 0.1, 0.1, 0.7], amp: 1.5, duration: [0.1, 0.1, 0.1, 0.7]
define :arpeggiate do |thesenotes, thesedelays=[quarter], vibrato=false, tremolo=false, **kwargs|
eval overridekwargs(kwargs, method(__method__).parameters) if boolish kwargs
cleanargs = stripparams kwargs, method(__method__).parameters
##debugprint "top of arpeggiate"
##debugprint "thesenotes: ", thesenotes
##debugprint "thesedelays: ", thesedelays
##debugprint "vibrato: ", vibrato
##debugprint "tremolo: ", tremolo
##debugprint "remapping synth defaults"
if !boolish cleanargs
##debugprint "no cleanargs"
synthdefaultarray = [nil].ring
else
##debugprint "got cleanargs: ", cleanargs
synthdefaultarray = arrayhashtohasharray cleanargs
end
##debugprint "testing for singleton delays"
if !ringorlist thesedelays
thesedelays = [thesedelays].ring
end
handle = nil
##debugprint "main loop"
thesenotes.length.times do |i|
##debugprint "synthdefaultarray[i]: ", synthdefaultarray[i]
##debugprint "thesedelays.ring[i]: ", thesedelays.ring[i]
if synthdefaultarray[i] == nil
##debugprint "playing note raw"
handle = play thesenotes.ring[i]
else
##debugprint "playing note with synth defaults"
with_synth_defaults synthdefaultarray[i] do
##debugprint "playing note"
handle = play thesenotes.ring[i]
end #synth defaults
end #if got synth defaults
if vibrato
##debugprint "vibrato"
if !vibrato.is_a? Hash
##debugprint "setting vibrato empty hash"
vibrato = {}
else
##debugprint "vibrato already a hash"
end #if vibrato not a hash
##debugprint "about to call vibrato"
vibrato handle, **vibrato
else
##debugprint "no vibrato"
end #if vibrato
if tremolo
##debugprint "tremolo"
if !tremolo.is_a? Hash
##debugprint "setting tremolo empty hash"
tremolo = {}
else
##debugprint "tremolo already a hash"
end #if tremolo not a hash
##debugprint "about to call tremolo"
tremolo handle, **tremolo
else
##debugprint "no tremolo"
end #if tremolo