├── .gitignore ├── classes ├── mllusc_monome_button_presses.sc ├── mllusc_monome_patterns_view.sc ├── mllusc_storable_attribute.sc ├── mllusc_monome_led_grid.sc ├── mllusc_event.sc ├── mllusc_radio_button_view.sc ├── mllusc_monome_mute_group_view.sc ├── mllusc_mute_group.sc ├── mllusc_monome_blinking_light.sc ├── mllusc_monome_row_view.sc ├── mllusc_mute_groupable_decorator.sc ├── mllusc_waveplayer_row_controller.sc ├── mllusc_toggle_button_view.sc ├── mllusc_storable_object_proxy.sc ├── mllusc_preferences.sc ├── mllusc_metronome_view.sc ├── mllusc_pattern_unit.sc ├── sc_monome │ ├── MonoMIDI.sc │ ├── Monome.sc │ └── MonomEm.sc ├── mllusc_metronome.sc ├── mllusc_event_generator.sc ├── mllusc_preset_butler.sc ├── mllusc_pattern_recorder.sc ├── mllusc_pattern_player.sc ├── mllusc_muter.sc ├── mllusc_resampler.sc ├── mllusc_waveplayer_row_view.sc └── mllusc_wave_player.sc ├── README └── mllusc.rtf /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /classes/mllusc_monome_button_presses.sc: -------------------------------------------------------------------------------- 1 | MlluscMonomeButtonPresses { 2 | var <>callback_function; 3 | *new{|m| 4 | ^super.new.init(m); 5 | } 6 | 7 | init {|m| 8 | m.do({|monome| 9 | monome.action = { |x, y, on| 10 | if(on==1, 11 | {this.changed(this,[\monome_button_press,x,y])}, 12 | {this.changed(this,[\monome_button_release,x,y])} 13 | 14 | ); 15 | callback_function.value(x,y,on); 16 | }; 17 | }); 18 | } 19 | } -------------------------------------------------------------------------------- /classes/mllusc_monome_patterns_view.sc: -------------------------------------------------------------------------------- 1 | MlluscMonomePatternsView { 2 | var monomes; 3 | var blink_routine; 4 | *new{|m| 5 | ^super.new.init(m); 6 | } 7 | 8 | init {|m| 9 | monomes=m; 10 | this.all_lights_off(); 11 | } 12 | 13 | update { arg theChanged, theChanger, more ; 14 | // From controller 15 | case 16 | { (more[0] == \group_deactivated) && (more[1].notNil) } { 17 | this.light_off(more[1]); 18 | }; 19 | // From button presses 20 | case 21 | { (more[0] == \monome_button_press)} { 22 | if(more[2]==0, 23 | {this.changed(this,[\mute_request,more[1]])}) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /classes/mllusc_storable_attribute.sc: -------------------------------------------------------------------------------- 1 | MlluscStorableAttribute{ 2 | var =e.priority); // replace rival event if this priority is greater or equal to the other event's priority 22 | } 23 | 24 | set_low_priority{ 25 | priority=1; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Installation 2 | 3 | For ease of use, place the entire mllusc directry in a place where SuperCollider will check for classes to include when compiling. On OSX this could be: /Applications/SuperCollider/SCClassLibrary/self_contained_projects/mllusc (the 'self_contained_projects' directory will not exist by default, and is one that you might want to create). 4 | 5 | Disclaimer 6 | 7 | mllusc is provided 'as is'. It's published primarily as a 'proof of concept' and not intended to be used in a performance setting (in it's current state). It's published as an example of the kind of thing that can be done in SuperCollider, and to give others the chance to expand or improve on what's there so far. -------------------------------------------------------------------------------- /classes/mllusc_radio_button_view.sc: -------------------------------------------------------------------------------- 1 | // display radio button group for selecting which mute group a thing belongs to 2 | MlluscRadioButtonView { 3 | var window; 4 | var buttons; 5 | var id; 6 | var callback_function; 7 | *new{|i,w,x,y,n,c| 8 | ^super.new.init(i,w,x,y,n,c); 9 | } 10 | 11 | init {|i,w,x,y,n,c| 12 | id=i; 13 | window=w; 14 | callback_function=c; 15 | // draw the buttons 16 | n.do({|num,i| 17 | var new_butt=MlluscToggleButtonView.new(window,num.asString,40@40,8, 18 | e { 19 | callback_function.value(num); 20 | }); 21 | buttons=buttons.add(new_butt); 22 | }); 23 | this.deactivate_all(); 24 | } 25 | 26 | 27 | activate {|index| 28 | this.deactivate_all(); 29 | buttons[index].activate(false); 30 | window.refresh; 31 | } 32 | 33 | deactivate_all { 34 | buttons.do({|b,i| 35 | b.deactivate(false); 36 | }); 37 | window.refresh; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /classes/mllusc_monome_mute_group_view.sc: -------------------------------------------------------------------------------- 1 | MlluscMonomeMuteGroupView { 2 | var monomes; 3 | *new{|m| 4 | ^super.new.init(m); 5 | } 6 | 7 | init {|m| 8 | monomes=m; 9 | this.all_lights_off(); 10 | } 11 | 12 | update { arg theChanged, theChanger, more ; 13 | // From controller 14 | case 15 | { (more[0] == \group_deactivated) && (more[1].notNil) } { 16 | this.light_off(more[1]); 17 | }; 18 | // From button presses 19 | case 20 | { (more[0] == \monome_button_press)} { 21 | if(more[2]==0, 22 | {this.changed(this,[\mute_request,more[1]])}) 23 | } 24 | } 25 | 26 | light_on{|num| 27 | monomes.do({|m| 28 | {m.led(num,0,1)}.defer; 29 | }) 30 | } 31 | 32 | light_off{|num| 33 | monomes.do({|m| 34 | {m.led(num,0,0)}.defer; 35 | }) 36 | } 37 | 38 | all_lights_off{ 39 | (0..5).do({|num| 40 | monomes.do({|m| 41 | {m.led(num,0,0)}.defer; 42 | }) 43 | }); 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /classes/mllusc_mute_group.sc: -------------------------------------------------------------------------------- 1 | MlluscMuteGroup{ 2 | var index; 3 | var player; 5 | var metronome; 6 | var <>recorder; 7 | var armed_callback_function; 9 | var <>started_recording_callback_function; 10 | var <>play_callback_function; 11 | var <>stop_callback_function; 12 | *new{|i,m| 13 | ^super.new.init(i,m); 14 | } 15 | 16 | init {|i,m| 17 | index=i; 18 | metronome=m; 19 | player=MlluscPatternPlayer.new(metronome); 20 | recorder=MlluscPatternRecorder.new(metronome); 21 | 22 | recorder.addDependant(this); 23 | status=\stopped; 24 | } 25 | 26 | // the button associated with this unit has been pressed, now figure out what to do about that 27 | do_it{ 28 | status.switch( 29 | \stopped, {recorder.arm_for_recording();status=\armed;}, 30 | \armed, {recorder.stop();status=\stopped;}, 31 | \recording, {recorder.arm_for_recording();status=\armed;}, 32 | \playing, {player.stop();status=\stopped;} 33 | ); 34 | } 35 | 36 | stop{ 37 | recorder.stop(); 38 | player.stop(); 39 | status=\stopped; 40 | } 41 | 42 | update { arg theChanged, theChanger, more ; 43 | case 44 | { (more[0] == \started_recording)} { 45 | status=\recording; 46 | started_recording_callback_function.value(); 47 | } 48 | { (more[0] == \finished_recording)} { 49 | status=\playing; 50 | play_callback_function.value(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /classes/sc_monome/MonoMIDI.sc: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------- 2 | // 3 | // Bridge between Monome OSC and MIDI messaging. 4 | // __ Daniel Jones 5 | // http://www.erase.net 6 | // 7 | // Run serialio with: 8 | // ./serialio localhost:57120 9 | // 10 | // In Audio MIDI Setup, configure the MIDI IAC device with 11 | // two ports named "A" and "B", each with one input and output. 12 | // MonoMIDI writes to port A and reads from port B, and so your 13 | // MIDI host program must read from A and write to B. 14 | // 15 | // To create a bridge, simply execute: 16 | // m = MonoMIDI.new; 17 | // 18 | // 19 | //----------------------------------------------------------------- 20 | 21 | MonoMIDI { 22 | var <>monome; 23 | var <>midiOut; 24 | var <>midiPort = 0; 25 | 26 | *new { |monome| 27 | ^super.newCopyArgs(monome).init; 28 | } 29 | 30 | init { 31 | monome = monome ?? Monome.new; 32 | MIDIClient.init; 33 | midiOut = MIDIClient.destinations.detect({ |x| x.name == "A" }).uid; 34 | midiOut = MIDIOut(0, midiOut); 35 | MIDIIn.connect(0, MIDIClient.sources.detect({ |x| x.name == "B" })); 36 | MIDIIn.noteOn = { |src, chan, num, value| 37 | var x, y; 38 | [ src, chan, num, value ].postln; 39 | x = num % 8; 40 | y = div(num, 8); 41 | if ((x < 8) && (y < 8)) 42 | { 43 | monome.led(x, y, (value > 0).binaryValue); 44 | }; 45 | }; 46 | MIDIIn.noteOff = { |src, chan, num, value| 47 | var x, y; 48 | x = num % 8; 49 | y = div(num, 8); 50 | if ((x < 8) && (y < 8)) 51 | { 52 | monome.led(x, y, 0); 53 | }; 54 | }; 55 | monome.clear; 56 | monome.action = { |x, y, value| 57 | if (value == 1) { 58 | midiOut.noteOn(0, (y * 8) + x, 127); 59 | }; 60 | if (value == 0) { 61 | midiOut.noteOff(0, (y * 8) + x, 127); 62 | }; 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /classes/mllusc_metronome.sc: -------------------------------------------------------------------------------- 1 | MlluscMetronome { 2 | var ticktask; 3 | var <>metronome_bus; 4 | var \pitch_drop); 12 | player_modifier_dict.add(1 -> \time_stretch); 13 | player_modifier_dict.add(2 -> \scratch); 14 | } 15 | 16 | update { arg theChanged, theChanger, more; 17 | case 18 | {this.is_a_player_modifier_button(more)} { 19 | if(more[0] == \monome_button_press, 20 | {current_player_modifier=more[1]}, 21 | {if (current_player_modifier==more[1], 22 | {current_player_modifier=nil;}) 23 | ;} 24 | ); 25 | } 26 | {this.is_a_row_button(more)} { 27 | if(more[0] == \monome_button_press, 28 | { 29 | if (this.get_current_player_modifier()==\pitch_drop, 30 | { 31 | this.generate_event(more,\pitch_drop_reverse,false)},{this.generate_event(more)} 32 | ); 33 | }, 34 | {// its a release, do we need to send an event? 35 | "its a release".postln; 36 | ("current_player_modifier="++current_player_modifier).postln; 37 | if(this.get_current_player_modifier()==\pitch_drop, 38 | { 39 | "generating reverse thing".postln;this.generate_event(more,\pitch_drop,false) 40 | }); 41 | } 42 | ); 43 | } 44 | } 45 | 46 | generate_event{|more,mod,q=true| 47 | var e,the_mod; 48 | if(mod.isNil,{the_mod=this.get_current_player_modifier()},{the_mod=mod}); 49 | e=MlluscEvent.new(more[2]-1,more[1],the_mod,2,q);//wave_player_id,step,synth 50 | this.changed(this,[\event,e]) 51 | } 52 | 53 | is_a_player_modifier_button{|more| 54 | ^((more[2])==6); 55 | } 56 | is_a_row_button{|more| 57 | ^(((more[2])>=1)&&((more[2])<=5)); 58 | } 59 | get_current_player_modifier{ 60 | if(current_player_modifier.notNil, 61 | {^(this.get_dict_value(current_player_modifier))}, 62 | {^(\regular)} 63 | ); 64 | } 65 | 66 | get_dict_value{|i| 67 | ^(player_modifier_dict.matchAt(i)); 68 | } 69 | } -------------------------------------------------------------------------------- /classes/mllusc_preset_butler.sc: -------------------------------------------------------------------------------- 1 | MlluscPresetButler{ 2 | var 0,{ 69 | ^(this.get_unique_file_path(name.realNextName)); 70 | },{^(temppath)}); 71 | } 72 | 73 | populate_preset_dictionary{ 74 | var itemsindir = (current_directory++"/"++"*").pathMatch; 75 | itemsindir.do({|it,in| 76 | preset_dictionary.put(PathName(it).fileName.split($.)[0],it); 77 | }); 78 | } 79 | file_exists{|path| 80 | ^(path.pathExists==\file); 81 | } 82 | get_timestamp{ 83 | ^(Date.localtime().stamp); 84 | } 85 | 86 | register_object_proxy{|p| 87 | object_proxies.put(p.identifier_string, p); 88 | } 89 | 90 | get_big_string{ 91 | var string=""; 92 | object_proxies.do({|it,k| 93 | string=string++(it.get_string_representation()); 94 | }); 95 | ^string; 96 | } 97 | } -------------------------------------------------------------------------------- /classes/mllusc_pattern_recorder.sc: -------------------------------------------------------------------------------- 1 | MlluscPatternRecorder{ 2 | var armed_callback_function; 10 | var <>recording_callback_function; 11 | var <>recording_complete_callback_function; 12 | var <>stop_callback_function; 13 | *new{|m| 14 | ^super.new.init(m); 15 | } 16 | 17 | init {|m| 18 | metronome=m; 19 | quantize_ticks_per_beat=4; // 4 ticks p/b == 16ths 20 | status=\stopped; 21 | length_in_beats=8; 22 | length_in_quantized_events=length_in_beats*quantize_ticks_per_beat; 23 | metronome.tempo_clock.sched(metronome.tempo_clock.timeToNextBeat(1), 24 | {this.on_every_quantize_interval();1/quantize_ticks_per_beat}); 25 | } 26 | 27 | arm_for_recording { 28 | // recording will start when the next row event is received 29 | status=\armed; 30 | armed_callback_function.value(); 31 | } 32 | 33 | stop{ 34 | status=\stopped; 35 | stop_callback_function.value(); 36 | } 37 | 38 | update { arg theChanged, theChanger, more ; 39 | case 40 | { (more[0] == \event)} { 41 | if (status==\stopped,{^false}); 42 | if (status==\armed,{this.setup_new_recording()}); 43 | this.store_event(more[1]); 44 | } 45 | } 46 | 47 | on_every_quantize_interval{ 48 | if (status!=\recording,{^false}); // skip if we're not recording 49 | 50 | if(this.list_complete(),{ 51 | recording_complete_callback_function.value(); 52 | status=\stopped; 53 | this.changed(this,[\finished_recording]); 54 | }, 55 | { 56 | this.advance_to_next_slot(); 57 | }); 58 | } 59 | 60 | store_event{|event| 61 | event.set_low_priority(); 62 | list[list.size-1].add(event); 63 | ("*** in store event, list size is now "++list.size).postln; 64 | } 65 | 66 | get_number_of_events_stored{ 67 | var count=0; 68 | ("inget_number_of_events_stored, list size reports as "++list.size).postln; 69 | list.do({|i1| 70 | i1.do({|i2| 71 | count=count+1; 72 | }) 73 | }) 74 | ^count; 75 | } 76 | 77 | setup_new_recording{ 78 | status=\recording; 79 | list=List.new(); // initialise empty 2d list 80 | this.advance_to_next_slot(); 81 | this.changed(this,[\started_recording]); 82 | recording_callback_function.value(); 83 | } 84 | 85 | // create a new slot ready to store events 86 | advance_to_next_slot{ 87 | list.add(List.new()); 88 | ("*** advance to next slot modified list size, it's now "++list.size).postln; 89 | } 90 | 91 | list_complete{ 92 | ^(list.size==length_in_quantized_events); 93 | } 94 | } -------------------------------------------------------------------------------- /classes/mllusc_pattern_player.sc: -------------------------------------------------------------------------------- 1 | MlluscPatternPlayer{ 2 | var play_callback_function; 9 | var <>stop_callback_function; 10 | var <>looping_to_the_beginning_callback_function; 11 | var on_every_quantize_interval_func; 12 | *new{|m| 13 | ^super.new.init(m); 14 | } 15 | 16 | init {|m| 17 | metronome=m; 18 | quantize_ticks_per_beat=4; // 4 ticks p/b == 16ths 19 | status=\stopped; 20 | on_every_quantize_interval_func={}; 21 | // start on half beats, 280degrees out of phase with event command evaluation 22 | metronome.tempo_clock.sched(metronome.tempo_clock.timeToNextBeat(1), 23 | { 24 | this.on_every_quantize_interval_func_wrapper(); 25 | 1/quantize_ticks_per_beat; 26 | }); 27 | } 28 | 29 | play {|a| 30 | "got play command".postln; 31 | if(a.notNil,{this.set_event_list(a)}); 32 | step_index=nil; // nil value will get set to 0 on the first iteration of on_every_quantize_interval 33 | status=\playing; 34 | play_callback_function.value(); 35 | this.set_on_every_quantize_interval_func(); 36 | } 37 | 38 | stop { 39 | "got stop command".postln; 40 | status=\stopped; 41 | on_every_quantize_interval_func={}; 42 | stop_callback_function.value(); 43 | } 44 | 45 | set_event_list{|l| 46 | list=l; 47 | } 48 | 49 | get_status{ 50 | ^status; 51 | } 52 | 53 | merge_list_with{|other_player| 54 | var tobeadded; 55 | var new_list=this.get_synched_list_from_other_player(other_player); 56 | list.do({|it,in| 57 | if(new_list[in].isNil, 58 | {tobeadded=[]}, 59 | {tobeadded=new_list[in]}); 60 | it.addAll(tobeadded); 61 | });list 62 | } 63 | 64 | get_synched_list_from_other_player{|other_player| 65 | var diff=step_index-other_player.step_index; 66 | ^other_player.list.rotate(diff); 67 | } 68 | 69 | on_every_quantize_interval_func_wrapper{ 70 | on_every_quantize_interval_func.value(); 71 | } 72 | set_on_every_quantize_interval_func{ 73 | on_every_quantize_interval_func={ 74 | if (status==\playing,{ 75 | 76 | // queue the next slot 77 | if (step_index.isNil, 78 | {this.reset_pointer()}, 79 | { 80 | if(this.should_loop_back(), 81 | { 82 | this.reset_pointer(); 83 | looping_to_the_beginning_callback_function.value(); 84 | }, 85 | { 86 | this.advance_to_next_slot(); 87 | } 88 | ); 89 | } 90 | ); 91 | 92 | this.send_events_from_current_slot(); 93 | 94 | }); 95 | } 96 | 97 | } 98 | 99 | should_loop_back{ 100 | ^(step_index==((list.size)-1)); 101 | } 102 | 103 | advance_to_next_slot{ 104 | step_index=step_index+1; 105 | } 106 | 107 | reset_pointer{ 108 | step_index=0; 109 | } 110 | 111 | send_events_from_current_slot{ 112 | list[step_index].do({|e| 113 | this.changed(this,[\event,e]) 114 | }) 115 | } 116 | 117 | get_number_of_events_stored{ 118 | ^(list.flat.size); 119 | } 120 | } -------------------------------------------------------------------------------- /classes/mllusc_muter.sc: -------------------------------------------------------------------------------- 1 | // Responsibilities: 2 | // Decorates objectes that should be part of mute groups 3 | // Creates group objects behind the scenes 4 | // listens and reacts to events that should change the state of groups and their members 5 | MlluscMuter { 6 | var registered_mutables; 7 | var mute_groups; 8 | var <>mutable_added_to_group_callback_function; 9 | var <>mute_group_became_inactive_callback_function; 10 | *new{|c| 11 | ^super.new.init(c); 12 | } 13 | 14 | init {|c| 15 | registered_mutables=Array.new(); 16 | mute_groups=Array.new(); 17 | } 18 | 19 | mute_all{ 20 | mute_groups.do({|m| 21 | m.mute(); 22 | }) 23 | } 24 | 25 | mute_this_group{|index| 26 | this.get_group_by_index(index).mute(); 27 | } 28 | 29 | make_mutables_group_inactive{|mutable| 30 | var m=this.find_decorated_version_of_mutable(mutable); 31 | m.set_playing_status(0); 32 | mute_group_became_inactive_callback_function.value(m.get_mute_group_index()); 33 | } 34 | 35 | add_to_group{|mutable,group_index| 36 | var group=this.create_or_return_group(group_index); 37 | mutable=this.register_mutable(mutable); // register and add decorator if necessary 38 | mutable.mute(); 39 | mutable.set_mute_group(group); 40 | mutable_added_to_group_callback_function.value(mutable,group_index); 41 | ^(mutable); // returns the decorated object 42 | } 43 | 44 | get_preset_mutables{ 45 | var string=""; 46 | // the position of each mute group in the resultant string is significant 47 | registered_mutables.do({|m,i| 48 | string=string++m.get_mute_group_index()++"x"; 49 | }); 50 | ^(string); 51 | } 52 | 53 | set_preset_mutables{|s| 54 | s.split($x).do({|it,in| 55 | if(it!="", 56 | {this.add_to_group(registered_mutables[in],it.asInteger)} 57 | ); 58 | }); 59 | } 60 | 61 | // Private methods --------------------------------------------------------------------- 62 | 63 | register_mutable{|mutable| 64 | mutable=this.find_decorated_version_of_mutable(mutable); 65 | if(this.mutable_already_registered(mutable), 66 | {^(mutable)}, 67 | { 68 | registered_mutables=registered_mutables.add(mutable); // add to array 69 | ^(mutable); 70 | }); 71 | } 72 | 73 | find_decorated_version_of_mutable{|m| 74 | if (this.mutable_is_already_decorated(m), 75 | {^m}); 76 | registered_mutables.do({|it,in| 77 | if(it===m, 78 | {^(it)}); // returns group object if found 79 | // returns nil if group doesn't exist 80 | }); 81 | ^(this.decorate_with_mute_groupable(m)); 82 | } 83 | 84 | mutable_already_registered{|mutable| 85 | registered_mutables.do({|m| 86 | if (mutable===m,{^true}); 87 | }) 88 | ^(false); 89 | } 90 | 91 | decorate_with_mute_groupable{|m| 92 | if (this.mutable_is_already_decorated(m), 93 | {^m}, 94 | {^(MlluscMuteGroupableDecorator.new(m))}); 95 | } 96 | 97 | 98 | 99 | mutable_is_already_decorated{|m| 100 | ^(m.respondsTo('mute_my_siblings')); 101 | } 102 | 103 | create_or_return_group{|i| 104 | var grp=this.get_group_by_index(i); 105 | if(grp.notNil, 106 | {grp=grp}, 107 | {var ng=MlluscMuteGroup.new(i); 108 | ng.addDependant(this); // listen to events from this group 109 | mute_groups=mute_groups.add(ng); 110 | grp=ng}); 111 | ^grp; 112 | } 113 | 114 | get_group_by_index{|i| 115 | mute_groups.do({|it,in| 116 | if(it.get_index()==i, 117 | {^(it)}); // returns group object if found 118 | }); 119 | ^nil; // returns nil if group doesn't exist 120 | } 121 | 122 | get_group_index_by_mutable{|m| 123 | ^(this.find_decorated_version_of_mutable(m).get_mute_group_index()); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /classes/sc_monome/MonomEm.sc: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------- 2 | // 3 | // Visual emulator for the Monome 40h. 4 | // Sends OSC messages to the specified host/port in the same 5 | // format as the 40h, and responds to LED actions. 6 | // __ Daniel Jones 7 | // http://www.erase.net 8 | // 9 | // Usage: 10 | // e = MonomEm.new(host, port); 11 | // m = Monome.new(host, port); 12 | // m.action = { |x, y, on| [x, y, on].postln }; 13 | // m.led(5, 6, 1); 14 | // 15 | // or: 16 | // m = Monome.emu; 17 | // m.led(6, 7, 1); 18 | // 19 | //----------------------------------------------------------------- 20 | 21 | 22 | MonomEm 23 | { 24 | 25 | var 0).binaryValue 119 | }); 120 | }); 121 | } 122 | 123 | led_col { |x, on| 124 | defer({ 125 | 8.do({ |y| 126 | var pow = (2 ** y).asInteger; 127 | matrix[y][x].value = ((pow & on) > 0).binaryValue 128 | }); 129 | }); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /classes/mllusc_resampler.sc: -------------------------------------------------------------------------------- 1 | MlluscResampler{ 2 | var ', phasor, 0); 10 | var started_recording_latch=Latch.ar(started_recording,started_recording); 11 | var phasor_is_zero=BinaryOpUGen('==', phasor, 0); 12 | var recording_done=if(started_recording_latch>0, phasor_is_zero>0, 0); 13 | SendTrig.ar(recording_done,0,666); // Send a message when the pointer reached the end of the buffer 14 | FreeSelf.kr(recording_done); // free self when the recording is done 15 | BufWr.ar(In.ar(bus_to_record_from,2), record_to_buffer, phasor, 0); // Record from bus_to_record_from 16 | })]; 17 | } 18 | 19 | *init { |server| 20 | server = server ? Server.default; 21 | synth_defs.do(_.send(server)); 22 | server.sync; 23 | } 24 | 25 | 26 | *new{|btrf,out,m| 27 | ^super.new.init(btrf,out,m); 28 | } 29 | 30 | init {|btrf,out,m| 31 | var quantize_ticks_per_beat, response; 32 | bus_to_record_from=btrf; out_bus=out; metronome=m; status=\stopped; 33 | quantize_ticks_per_beat=4; // 4 ticks p/b == 16ths 34 | metronome.tempo_clock.sched( 35 | metronome.tempo_clock.timeToNextBeat(1), 36 | { 37 | this.on_every_quantize_interval(); 38 | 1/quantize_ticks_per_beat 39 | } 40 | ); 41 | metronome.addDependant(this); 42 | } 43 | 44 | get_buffer_copy{ 45 | var new_buf=this.set_up_buffer(); 46 | buffer.copyData(new_buf); 47 | ^(new_buf); 48 | } 49 | 50 | set_resampling_done_callback_function{|f| 51 | resampling_done_callback_function=f; 52 | } 53 | 54 | start_resampling{ 55 | "** resampler start_resampling".postln; 56 | buffer=this.set_up_buffer(); 57 | on_next_quantize_interval_function= { 58 | var nodeID, triggerID, commandpath, osc_path_responder; 59 | "** resampler executing on_next_quantize_interval_function".postln; 60 | status=\resampling; 61 | resampler_synth=Synth.tail( 62 | Server.default, 63 | "MlluscResampler", 64 | [\record_to_buffer,buffer,\bus_to_record_from,bus_to_record_from] 65 | ); 66 | response_func = { arg time, responder, message; 67 | status=\stopped;("time="++time++" responder="++responder++" message="++message).postln; 68 | this.resampling_done() 69 | }; 70 | // listen to only to the resampler_synth synth's messages 71 | osc_path_responder = OSCpathResponder( 72 | Server.default.addr, 73 | ['/tr', resampler_synth.nodeID, 0], 74 | response_func 75 | ); 76 | osc_path_responder.add; 77 | } 78 | } 79 | 80 | get_buffer_duration{ 81 | ^(buffer.duration); 82 | } 83 | 84 | // Private ------------------------------- 85 | 86 | on_every_quantize_interval{ 87 | if(on_next_quantize_interval_function.notNil, 88 | { 89 | Server.default.makeBundle(0.03, 90 | on_next_quantize_interval_function.value(); 91 | on_next_quantize_interval_function=nil; 92 | ); 93 | }); 94 | } 95 | 96 | // TODO: resampled audio plays back more quietly than 'direct' audio, why? 97 | resampling_done{ 98 | var copy=this.get_buffer_copy(); 99 | resampling_done_callback_function.value(buffer,length_in_beats); 100 | resampler_synth.free; 101 | } 102 | 103 | set_up_buffer{|len=8| 104 | var target_length; 105 | length_in_beats=len; 106 | target_length=length_in_beats*metronome.get_spb(); 107 | ^(Buffer.alloc(Server.default, Server.default.sampleRate * target_length, 2)); 108 | } 109 | 110 | update { arg theChanged, theChanger, more; 111 | case 112 | {more[0] == \tempo_change} {buffer=this.set_up_buffer()}; 113 | } 114 | } -------------------------------------------------------------------------------- /classes/mllusc_waveplayer_row_view.sc: -------------------------------------------------------------------------------- 1 | // Responsibilities: 2 | // gets messages from row controller, and updates interface elements 3 | // gets messages from interface elements and notifies controller 4 | MlluscWavePlayerRowView { 5 | var window; 6 | var id; 7 | var y_pos; 8 | var Run\ 24 | */\cf0 \ 25 | \ 26 | (\cf3 \ 27 | \cf4 var\cf3 monomes, muter,monome_mute_group_view;\ 28 | \cf4 var\cf3 preference_file_path=\cf4 Document\cf3 .current.path.dirname++\cf5 "/mllusc_preferences"\cf3 ;\ 29 | \cf4 var\cf3 preset_directory_path=\cf4 Document\cf3 .current.path.dirname++\cf5 "/mllusc_presets/"\cf3 ;\ 30 | preset_directory_path.postln;\ 31 | \pard\pardeftab560\ql\qnatural 32 | \cf4 Server\cf3 .default = \cf4 Server\cf3 .internal;\ 33 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 34 | \cf3 s = \cf4 Server\cf3 .default;\ 35 | ~bg_colour=\cf4 Color\cf3 .new255(30,30,30);\ 36 | ~button_bg_colour=\cf4 Color\cf3 .new255(70,70,70);\ 37 | ~statebutton_bg_colour=\cf4 Color\cf3 .new255(10,10,20);\ 38 | ~button_text_colour=\cf4 Color\cf3 .new255(220,220,220);\ 39 | ~button_border_colour=\cf4 Color\cf3 .white;\ 40 | ~highlight_1_colour=\cf4 Color\cf3 .new255(150,0,0);\ 41 | ~highlight_1_colour_dark=\cf4 Color\cf3 .new255(50,0,0);\ 42 | ~highlight_2_colour=\cf4 Color\cf3 .new255(0,50,0);\ 43 | ~preferences=\cf4 MlluscPreferences\cf3 .new();\ 44 | ~preferences.set_preference_file_path(preference_file_path);\ 45 | ~preferences.register_item(\cf5 "preset_directory"\cf3 ,preset_directory_path);\ 46 | \ 47 | ~format_button=\{\cf4 |button|\cf3 \ 48 | button.radius=8;\ 49 | button.border=2;\ 50 | button.inverse=\cf4 true\cf3 ;\ 51 | button.extrude=\cf4 false\cf3 ;\ 52 | \ 53 | \};\ 54 | ~format_rounded_button=\{\cf4 |button|\cf3 \ 55 | button.border=2;\ 56 | button.inverse=\cf4 true\cf3 ;\ 57 | button.extrude=\cf4 false\cf3 ;\ 58 | \};\ 59 | \ 60 | s.waitForBoot(\{\ 61 | \cf4 MlluscMetronome\cf3 .init;\ 62 | \cf4 MlluscWavePlayer\cf3 .init;\ 63 | \cf4 MlluscResampler\cf3 .init;\ 64 | \cf4 SystemClock\cf3 .sched(0.0,\{\ 65 | \{\ 66 | \cf4 var\cf3 muter, metronome, mainwin, presses, metronome_view, r, event_generator, monome_led_grid, pattern_units, resampler, summing_bus_index, win, presetwin, save_as_button,set_folder_button;\ 67 | \ 68 | summing_bus_index=50; \cf6 // where waveplayers will send their output\cf3 \ 69 | \ 70 | monomes=[\cf4 MonomEm\cf3 .new(\cf5 "127.0.0.1"\cf3 , 8080),\cf4 Monome\cf3 .new(\cf5 "127.0.0.1"\cf3 , 8080)];\ 71 | \ 72 | win = \cf4 GUI\cf3 .window.new(\cf5 "Mllusc"\cf3 ,\cf4 Rect\cf3 (200,0,950,600)).front.decorate;\ 73 | win.view.background = ~bg_colour;\ 74 | mainwin = \cf4 FlowView\cf3 .new(win, \cf4 Rect\cf3 (0,0,950,300));\ 75 | presetwin=\cf4 FlowView\cf3 .new(win,\cf4 Rect\cf3 (980,0,1200,600));\ 76 | ~metronome=\cf4 MlluscMetronome\cf3 .new(2);\ 77 | \pard\pardeftab560\ql\qnatural 78 | \cf3 monome_led_grid=\cf4 MlluscMonomeLedGrid\cf3 (monomes,8,8);\ 79 | resampler=\cf4 MlluscResampler\cf3 .new(summing_bus_index,0,~metronome);\ 80 | resampler.set_resampling_done_callback_function(e \{\cf4 |buf,beats|\cf3 \ 81 | \cf4 var\cf3 wp=~r[0].at(\cf7 'wave_player'\cf3 );\ 82 | wp.set_resampled_audio_flag(\cf4 true\cf3 );\ 83 | wp.use_this_buffer(buf);\ 84 | wp.set_length_in_beats(beats);\ 85 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 86 | \cf3 muter.mute_all();\ 87 | wp.set_level(1);\ 88 | \{~r[0].at(\cf7 'row_view'\cf3 ).move_level_slider(1)\}.defer;\ 89 | wp.play_this_event(\cf4 MlluscEvent\cf3 .new(0,0,\cf7 \\regular\cf3 ,2));\ 90 | \pard\pardeftab560\ql\qnatural 91 | \cf3 \});\ 92 | \ 93 | ~preset_butler=\cf4 MlluscPresetButler\cf3 .new();\ 94 | ~preset_butler.set_directory(preset_directory_path);\ 95 | \cf6 // Overwrite preset dir if preference directive exists\cf3 \ 96 | ~preset_butler.set_directory(~preferences.get_value(\cf5 "preset_directory"\cf3 ));\ 97 | ~preset_butler.register_object(~metronome,\cf5 "metronome"\cf3 ,[\cf7 \\preset_bps\cf3 ]);\ 98 | \ 99 | \ 100 | muter=\cf4 MlluscMuter\cf3 .new();\ 101 | \ 102 | save_as_button=\cf4 RoundButton\cf3 (presetwin, 80@40);\ 103 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 104 | \cf3 ~format_rounded_button.value(save_as_button);\ 105 | save_as_button.states=[[\cf5 "Save as"\cf3 ,~button_text_colour,~button_bg_colour]];\ 106 | \cf6 // Limitation of CocoaDialog, can't predefine starting path\cf3 \ 107 | save_as_button.action=\{\ 108 | \cf4 CocoaDialog\cf3 .savePanel(\{ \cf4 arg\cf3 path;\ 109 | \pard\pardeftab560\ql\qnatural 110 | \cf3 ~preset_butler.save_preset_at_path(path);\ 111 | ~preset_view.refresh();\ 112 | \},\{\ 113 | \cf5 "Save was cancelled"\cf3 .postln;\ 114 | \});\ 115 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 116 | \cf3 \};\ 117 | \ 118 | set_folder_button=\cf4 RoundButton\cf3 (presetwin, 80@40);\ 119 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab560\ql\qnatural\pardirnatural 120 | \cf3 ~format_rounded_button.value(set_folder_button);\ 121 | set_folder_button.states=[[\cf5 "Set Folder"\cf3 ,~button_text_colour,~button_bg_colour]];\ 122 | set_folder_button.action=\{\ 123 | \cf4 SCRequestString\cf3 ( ~preferences.get_value(\cf5 "preset_directory"\cf3 ),\ 124 | \pard\pardeftab560\ql\qnatural 125 | \cf3 \cf5 "Enter the full path to your preset folder here"\cf3 , \{ \cf4 |path|\cf3 \ 126 | ~preferences.set_value(\cf5 "preset_directory"\cf3 ,path);\ 127 | ~preset_butler.set_directory(~preferences.get_value(\cf5 "preset_directory"\cf3 ));\ 128 | ~preset_view.path=~preferences.get_value(\cf5 "preset_directory"\cf3 );\ 129 | ~preset_view.refresh();\ 130 | \},\{\ 131 | \cf5 "Set preferences path was cancelled"\cf3 .postln;\ 132 | \},1);\ 133 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab560\ql\qnatural\pardirnatural 134 | \cf3 \};\ 135 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 136 | \cf3 \ 137 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 138 | \cf3 ~preset_view = \cf4 FileListView\cf3 (\ 139 | presetwin,\ 140 | \cf4 Rect\cf3 (420,0,230,180), \ 141 | preset_directory_path,\ 142 | action: \{ \cf4 arg\cf3 v; postf(\cf5 "action: %\\n"\cf3 , v.filename ) \}, \ 143 | dblClickAction: \{ \cf4 arg\cf3 v; postf(\cf5 "dblClickAction: %\\n"\cf3 , v.fullname ); ~preset_butler.load_preset_from_path(v.fullname)\}\ 144 | );\ 145 | ~preset_view.listbox.background=~state_button_bg_colour;\ 146 | ~preset_view.listbox.hiliteColor=~highlight_1_colour;\ 147 | ~preset_view.listbox.stringColor=~button_text_colour; \ 148 | \pard\pardeftab560\ql\qnatural 149 | \cf3 presses=\cf4 MlluscMonomeButtonPresses\cf3 .new(monomes[1]); \cf6 // listen for presses from the real monome only\cf3 \ 150 | event_generator=\cf4 MlluscEventGenerator\cf3 .new();\ 151 | \ 152 | muter.mutable_added_to_group_callback_function=e \{\cf4 |mutable,group_index|\cf3 \ 153 | ~r[mutable.get_id()].at(\cf7 'row_view'\cf3 ).radio_button_view.activate(group_index);\ 154 | \};\ 155 | muter.mute_group_became_inactive_callback_function=e \{\cf4 |mute_group_index|\cf3 \ 156 | monome_led_grid.off(mute_group_index,0);\ 157 | \};\ 158 | ~preset_butler.register_object(muter,\cf5 "muter"\cf3 ,[\cf7 \\preset_mutables\cf3 ]);\ 159 | presses.addDependant(event_generator);\ 160 | \ 161 | metronome_view=\cf4 MlluscMetronomeView\cf3 .new(mainwin,20,200,~metronome);\ 162 | ~metronome.addDependant(metronome_view);\ 163 | ~metronome.set_bpm(120);\ 164 | mainwin.hr;\ 165 | \ 166 | pattern_units=\cf4 Array\cf3 .new();\ 167 | \ 168 | \cf6 // Create pattern units\cf3 \ 169 | (0..1).do(\{\cf4 |index|\cf3 \ 170 | \cf4 var\cf3 pattern_unit=\cf4 MlluscPatternUnit\cf3 .new(index,~metronome);\ 171 | \cf4 var\cf3 x=index+6;\ 172 | \cf4 var\cf3 y=0;\ 173 | event_generator.addDependant(pattern_unit.recorder);\ 174 | pattern_unit.recorder.armed_callback_function=\{monome_led_grid.blink(x,y,0.2)\};\ 175 | pattern_unit.recorder.stop_callback_function=\{monome_led_grid.off(x,y)\};\ 176 | pattern_unit.recorder.recording_complete_callback_function=\{monome_led_grid.on(x,y);pattern_unit.player.play(pattern_unit.recorder.list)\};\ 177 | pattern_unit.play_callback_function=\{monome_led_grid.on(x,y)\};\ 178 | pattern_unit.player.stop_callback_function=\{monome_led_grid.off(x,y)\};\ 179 | pattern_unit.started_recording_callback_function=\{monome_led_grid.blink(x,y,0.02)\};\ 180 | pattern_units=pattern_units.add(pattern_unit); \ 181 | \});\ 182 | \ 183 | \cf6 // Assign behaviours to monome button presses by coordinate pairs\cf3 \ 184 | presses.callback_function=e \{\cf4 |x,y,on|\cf3 \ 185 | case\ 186 | \cf6 // Only respond to presses not releases\cf3 \ 187 | \{on==1\} \{\ 188 | case\ 189 | \cf6 // Top row\cf3 \ 190 | \{y==0\} \{\ 191 | case\ 192 | \{x.inclusivelyBetween(0,4)\} \{muter.mute_this_group(x)\}\ 193 | \cf6 // 0,5: Bounce down patterns\cf3 \ 194 | \{x==5\} \{\ 195 | pattern_units[0].player.merge_list_with(pattern_units[1].player);\ 196 | pattern_units[1].stop();\ 197 | \}\ 198 | \cf6 // 0,6 0,7: Advance the state of pattern units \cf3 \ 199 | \{x.inclusivelyBetween(6,7)\} \{pattern_units[x-6].do_it()\}\ 200 | \}\ 201 | \cf6 // Bottom Row\cf3 \ 202 | \{y==7\} \{\ 203 | case\ 204 | \cf6 // 7,0: Start resampling (to first wave player row)\cf3 \ 205 | \{x==0\} \{resampler.start_resampling()\}\ 206 | \cf6 // 7,6: load previous preset\cf3 \ 207 | \{x==6\} \{\ 208 | \{\ 209 | ~preset_view.value=~preset_view.value-1;\ 210 | ~preset_butler.load_preset_from_path(~preset_view.fullname);\ 211 | \}.defer;\ 212 | \}\ 213 | \cf6 // 7,6: load next preset\cf3 \ 214 | \{x==7\} \{\ 215 | \{\ 216 | ~preset_view.value=~preset_view.value+1;\ 217 | ~preset_butler.load_preset_from_path(~preset_view.fullname);\ 218 | \}.defer;\ 219 | \}\ 220 | \}\ 221 | \}\ 222 | \};\ 223 | \ 224 | \cf6 // Set up rows array for access to all row elements\cf3 \ 225 | ~r = \cf4 Array\cf3 .new(0);\ 226 | \ 227 | \cf6 // For each wave player row\cf3 \ 228 | (0..4).do(\{\cf4 arg\cf3 num;\ 229 | \cf4 var\cf3 wave_player, row_controller, row_view, monome_row_view,test;\ 230 | \cf4 var\cf3 dict=\cf4 Dictionary\cf3 .new;\ 231 | \ 232 | \cf6 // models\cf3 \ 233 | wave_player=\cf4 MlluscWavePlayer\cf3 .new(num,~metronome,8,summing_bus_index);\ 234 | \ 235 | \cf6 // views\cf3 \ 236 | row_view=\cf4 MlluscWavePlayerRowView\cf3 .new(num,mainwin,\ 237 | e \{\cf4 |group_index|\cf3 \ 238 | muter.add_to_group(wave_player,group_index);\ 239 | \},\ 240 | wave_player\ 241 | );\ 242 | monome_row_view=\cf4 MlluscMonomeRowView\cf3 .new(num+1,monomes); \cf6 // offset (+1 to skip first row which is already assigned), monome_array\cf3 \ 243 | \ 244 | \cf6 // controllers\cf3 \ 245 | row_controller=\cf4 MlluscWavePlayerRowController\cf3 .new(num,wave_player,~metronome,muter,monomes,row_view);\ 246 | \ 247 | \cf6 // set up listeners\cf3 \ 248 | ~metronome.addDependant(wave_player);\ 249 | wave_player.addDependant(row_controller);\ 250 | \ 251 | row_controller.addDependant(row_view);\ 252 | row_controller.addDependant(monome_row_view);\ 253 | \ 254 | row_view.addDependant(row_controller);\ 255 | muter.addDependant(row_controller);\ 256 | event_generator.addDependant(row_controller);\ 257 | pattern_units[0].player.addDependant(row_controller);\ 258 | pattern_units[1].player.addDependant(row_controller);\ 259 | row_controller.addDependant(monome_row_view);\ 260 | \ 261 | \cf6 // Add named items to a dictionary, and store it in row array (~r)\cf3 \ 262 | dict.add(\cf7 'wave_player'\cf3 -> wave_player);\ 263 | dict.add(\cf7 'row_view'\cf3 -> row_view);\ 264 | dict.add(\cf7 'monome_row_view'\cf3 -> monome_row_view);\ 265 | dict.add(\cf7 'row_controller'\cf3 -> row_controller);\ 266 | ~r=~r.add(dict);\ 267 | \ 268 | wave_player=muter.add_to_group(wave_player,num); \cf6 // radio buttons should get highlighted here\cf3 \ 269 | \ 270 | wave_player.set_play_event_callback_function(\ 271 | e \{\ 272 | monome_led_grid.on(muter.get_group_index_by_mutable(wave_player),0);\ 273 | wave_player.mute_my_siblings();\ 274 | \}\ 275 | );\ 276 | wave_player.set_step_change_callback_function(\ 277 | e \{\cf4 |s|\cf3 \ 278 | \{monome_row_view.single_light_on(s)\}.defer;\ 279 | \}\ 280 | );\ 281 | wave_player.set_stop_callback_function(\ 282 | e \{\cf4 |s|\cf3 \ 283 | monome_row_view.all_lights_off();\ 284 | if(wave_player.mute_group.all_mutables_have_stopped(),\ 285 | \{muter.make_mutables_group_inactive(wave_player)\},\ 286 | \{\}\ 287 | );\ 288 | \}\ 289 | );\ 290 | \ 291 | wave_player.set_waveform_changed_callback_function(\ 292 | e \{\cf4 |a|\cf3 \ 293 | ~r[wave_player.get_id()].at(\cf7 'row_view'\cf3 ).redraw_waveform(a);\ 294 | \}\ 295 | );\ 296 | \ 297 | wave_player.set_storable_attributes_loaded_callback_function(\ 298 | e \{\cf4 |w|\cf3 \ 299 | ~r[w.get_id()].at(\cf7 'row_view'\cf3 ).set_inherit_button(w.get_inherit_wave());\ 300 | \};\ 301 | );\ 302 | \ 303 | wave_player.set_level_set_callback_function(\ 304 | e \{\cf4 |w|\cf3 \ 305 | ~r[w.get_id()].at(\cf7 'row_view'\cf3 ).move_level_slider(w.get_level());\ 306 | \};\ 307 | );\ 308 | \ 309 | ~preset_butler.register_object(wave_player,\cf5 "wave_player"\cf3 ++num,[\cf7 \\preset_wave\cf3 ,\cf7 \\preset_rate_scale_multiplier\cf3 ,\cf7 \\preset_inherit_wave\cf3 ,\cf7 \\preset_level\cf3 ]);\ 310 | wave_player.set_level(0.7);\ 311 | row_view.move_level_slider(0.7);\ 312 | mainwin.startRow;\ 313 | \});\cf6 // eo 0..4 loop\cf3 \ 314 | \}.defer; \cf6 // eo scheduled func\cf3 \ 315 | \}); \cf6 // eo wait for boot\cf3 \ 316 | \});\ 317 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 318 | \cf0 )} --------------------------------------------------------------------------------