Permalink
Cannot retrieve contributors at this time
| -- oooooo v1.3.0 | |
| -- 6 x digital tape loops | |
| -- | |
| -- llllllll.co/t/oooooo | |
| -- | |
| -- | |
| -- | |
| -- ▼ instructions below ▼ | |
| -- | |
| -- E1 selects loops | |
| -- E2 changes mode/parameter | |
| -- | |
| -- in tape mode: | |
| -- K2 stops | |
| -- K2 again resets | |
| -- K3 plays | |
| -- K1+K2 clears | |
| -- K1+K2 again resets | |
| -- K1+K3 primes recording | |
| -- K1+K3 again records | |
| -- | |
| -- in other modes: | |
| -- K2 or K3 activates or lfos | |
| -- E3 adjusts parameter | |
| local Formatters=require 'formatters' | |
| -- user parameters | |
| uP={ | |
| -- initialized in init | |
| } | |
| -- user state | |
| uS={ | |
| recording={0,0,0,0,0,0},-- 0 = not recording, 1 = armed, 2 = recording | |
| recordingTime={0,0,0,0,0,0}, | |
| recordingLoopNum={0,0,0,0,0,0}, | |
| updateUI=false, | |
| updateParams=0, | |
| updateUserParam=0, | |
| updateTape=false, | |
| shift=false, | |
| loopNum=1,-- 7 = all loops | |
| selectedPar=0, | |
| flagClearing=false, | |
| flagSpecial=0, | |
| message="", | |
| currentBeat=0, | |
| currentTime=0, | |
| } | |
| -- user constants | |
| uC={ | |
| bufferMinMax={ | |
| {1,1,80}, | |
| {1,82,161}, | |
| {1,163,243}, | |
| {2,1,80}, | |
| {2,82,161}, | |
| {2,163,243}, | |
| }, | |
| loopMinMax={0.2,78}, | |
| radiiMinMax={3,160}, | |
| widthMinMax={8,124}, | |
| heightMinMax={0,64}, | |
| centerOffsets={ | |
| {0,0}, | |
| {0,0}, | |
| {0,0}, | |
| {0,0}, | |
| {0,0}, | |
| {0,0}, | |
| }, | |
| updateTimerInterval=0.05, | |
| recArmThreshold=0.03, | |
| backupNumber=1, | |
| lfoTime=1, | |
| discreteRates={-400,-200*1.498,-200,-100*1.498,-100,-50*1.498,-50,-25*1.498,-25,0,25,1.498*25,50,1.498*50,100,1.498*100,200,1.498*200,400}, | |
| discreteBeats={1/4,1/2,1,2}, | |
| } | |
| DATA_DIR=_path.data.."oooooo/" | |
| PATH=_path.audio..'oooooo/' | |
| function init() | |
| setup_sharing("oooooo") | |
| params:add_separator("oooooo") | |
| -- add variables into main menu | |
| params:add_group("startup",4) | |
| params:add_option("load on start","load on start",{"no","yes"},1) | |
| params:set_action("load on start",update_parameters) | |
| params:add_option("play on start","play on start",{"no","yes"},1) | |
| params:set_action("play on start",update_parameters) | |
| params:add_option("start lfos random","start lfos random",{"no","yes"},1) | |
| params:set_action("start lfos random",update_parameters) | |
| params:add_control("start length","start length",controlspec.new(0,64,'lin',1,0,'beats')) | |
| params:set_action("start length",update_parameters) | |
| params:add_group("recording",8) | |
| params:add_control("pre level","pre level",controlspec.new(0,1,"lin",0.01,1,"",0.01)) | |
| params:set_action("pre level",update_parameters) | |
| params:add_control("rec level","rec level",controlspec.new(0,1,"lin",0.01,1,"",0.01)) | |
| params:set_action("rec level",update_parameters) | |
| params:add_control("rec thresh","rec thresh",controlspec.new(1,100,'exp',1,10,'amp/1k')) | |
| params:set_action("rec thresh",update_parameters) | |
| params:add_control("vol pinch","vol pinch",controlspec.new(0,1000,'lin',1,500,'ms')) | |
| params:set_action("vol pinch",update_parameters) | |
| params:add_option("rec thru loops","rec thru loops",{"no","yes"},1) | |
| params:set_action("rec thru loops",update_parameters) | |
| params:add_control("stop rec after","stop rec after",controlspec.new(1,64,"lin",1,1,"loops")) | |
| params:set_action("stop rec after",update_parameters) | |
| params:add_option("input type","input type",{"line-in 1","line-in 2","tape","line-in (1+2)+tape"},4) | |
| params:set_action("input type",function(x) | |
| update_softcut_input() | |
| update_parameters() | |
| end) | |
| params:add_option("sync lengths to first","sync lengths to first",{"no","yes"},1) | |
| params:set_action("sync lengths to first",update_parameters) | |
| params:add_group("save/load",3) | |
| params:add_text('save_name',"save as...","") | |
| params:set_action("save_name",function(y) | |
| -- prevent banging | |
| local x=y | |
| params:set("save_name","") | |
| if x=="" then | |
| do return end | |
| end | |
| -- save | |
| print(x) | |
| backup_save(x) | |
| params:set("save_message","saved as "..x) | |
| end) | |
| print("DATA_DIR "..DATA_DIR) | |
| local name_folder=DATA_DIR.."names/" | |
| print("name_folder: "..name_folder) | |
| params:add_file("load_name","load",name_folder) | |
| params:set_action("load_name",function(y) | |
| -- prevent banging | |
| local x=y | |
| params:set("load_name",name_folder) | |
| if #x<=#name_folder then | |
| do return end | |
| end | |
| -- load | |
| print("load_name: "..x) | |
| pathname,filename,ext=string.match(x,"(.-)([^\\/]-%.?([^%.\\/]*))$") | |
| print("loading "..filename) | |
| backup_load(filename) | |
| params:set("save_message","loaded "..filename..".") | |
| end) | |
| params:add_text('save_message',">","") | |
| params:add_group("all loops",8) | |
| params:add_option("pause lfos","pause lfos",{"no","yes"},1) | |
| params:add_control("destroy loops","destroy loops",controlspec.new(0,100,'lin',1,0,'% prob')) | |
| params:add_control("vol ramp","vol ramp",controlspec.new(-1,1,'lin',0,0,'',0.01/2)) | |
| params:add_option("randomize all on reset","randomize on reset",{"no","params","loops","both"},1) | |
| params:set_action("randomize all on reset",function(x) | |
| for i=1,6 do | |
| params:set(i.."randomize on reset",x) | |
| end | |
| end) | |
| params:add_control("reset all every","reset all every",controlspec.new(0,64,"lin",1,0,"beats")) | |
| params:set_action("reset all every",function(x) | |
| for i=1,6 do | |
| params:set(i.."reset every beat",x) | |
| end | |
| end) | |
| params:add_option("continous rate","continous rate",{"no","yes"},2) | |
| params:set_action("continous rate",update_parameters) | |
| params:add_control("slew rate","slew rate",controlspec.new(0,30,'lin',0.1,(60/clock.get_tempo())*4,"s",0.1/30)) | |
| params:set_action("slew rate",function(x) | |
| for i=1,6 do | |
| softcut.level_slew_time(i,x) | |
| softcut.rate_slew_time(i,x) | |
| end | |
| end) | |
| params:add_option("expert mode","expert mode",{"no","yes"},1) | |
| params:set_action("expert mode",update_parameters) | |
| -- reset defaults | |
| params:set("slew rate",(60/clock.get_tempo())*4) | |
| -- add parameters | |
| filter_resonance=controlspec.new(0.05,1,'lin',0,0.25,'') | |
| filter_freq=controlspec.new(20,20000,'exp',0,20000,'Hz') | |
| for i=1,6 do | |
| params:add_group("loop "..i,31) | |
| -- id name min max default k units | |
| params:add_control(i.."start","start",controlspec.new(0,uC.loopMinMax[2],"lin",0.01,0,"s",0.01/uC.loopMinMax[2])) | |
| params:add_control(i.."start lfo amp","start lfo amp",controlspec.new(0,1,"lin",0.01,0.2,"",0.01)) | |
| params:add_control(i.."start lfo period","start lfo period",controlspec.new(0,60,"lin",0.1,0,"s",0.1/60)) | |
| params:add_control(i.."start lfo offset","start lfo offset",controlspec.new(0,60,"lin",0.1,0,"s",0.1/60)) | |
| params:add_control(i.."length","length",controlspec.new(uC.loopMinMax[1],uC.loopMinMax[2],"lin",0.01,(60/clock.get_tempo())*i*4,"s",0.01/uC.loopMinMax[2])) | |
| params:add_control(i.."length lfo amp","length lfo amp",controlspec.new(0,1,"lin",0.01,0.2,"",0.01)) | |
| params:add_control(i.."length lfo period","length lfo period",controlspec.new(0,60,"lin",0,0,"s",0.1/60)) | |
| params:add_control(i.."length lfo offset","length lfo offset",controlspec.new(0,60,"lin",0,0,"s",0.1/60)) | |
| params:add_control(i.."vol","vol",controlspec.new(0,1,"lin",0.01,0.1,"",0.01)) | |
| params:add_control(i.."vol lfo amp","vol lfo amp",controlspec.new(0,1,"lin",0.01,0.25,"",0.01)) | |
| params:add_control(i.."vol lfo period","vol lfo period",controlspec.new(0,60,"lin",0,0,"s",0.1/60)) | |
| params:add_control(i.."vol lfo offset","vol lfo offset",controlspec.new(0,60,"lin",0,0,"s",0.1/60)) | |
| params:add_option(i.."rate","rate (%)",uC.discreteRates,#uC.discreteRates) | |
| params:add_control(i.."rate adjust","rate adjust (%)",controlspec.new(-400,400,"lin",0.1,0,"%",0.1/800)) | |
| params:add_option(i.."rate reverse","reverse rate",{"on","off"},2) | |
| params:add_option(i.."rate lfo center","rate lfo center (%)",uC.discreteRates,#uC.discreteRates) | |
| params:add_control(i.."rate lfo amp","rate lfo amp",controlspec.new(0,1,"lin",0.01,0.25,"",0.01)) | |
| params:add_control(i.."rate lfo period","rate lfo period",controlspec.new(0,60,"lin",0,0,"s",0.1/60)) | |
| params:add_control(i.."rate lfo offset","rate lfo offset",controlspec.new(0,60,"lin",0,0,"s",0.1/60)) | |
| params:add_control(i.."pan","pan",controlspec.new(-1,1,"lin",0.01,0,"",0.01/2)) | |
| params:add_control(i.."pan lfo amp","pan lfo amp",controlspec.new(0,1,"lin",0.01,0.2,"",0.01)) | |
| params:add_control(i.."pan lfo period","pan lfo period",controlspec.new(0,60,"lin",0,0,"s",0.1/60)) | |
| params:add_control(i.."pan lfo offset","pan lfo offset",controlspec.new(0,60,"lin",0,0,"s",0.1/60)) | |
| params:add_control(i.."reset every beat","reset every",controlspec.new(0,64,"lin",1,0,"beats")) | |
| params:add_option(i.."randomize on reset","randomize on reset",{"no","params","loops","both"},1) | |
| params:add { | |
| type='control', | |
| id=i..'filter_frequency', | |
| name='filter cutoff', | |
| controlspec=filter_freq, | |
| formatter=Formatters.format_freq, | |
| action=function(value) | |
| softcut.post_filter_fc(i,value) | |
| end | |
| } | |
| params:add { | |
| type='control', | |
| id=i..'filter_reso', | |
| name='filter resonance', | |
| controlspec=filter_resonance, | |
| action=function(value) | |
| softcut.post_filter_rq(i,value) | |
| end | |
| } | |
| params:add{type='binary',name="recording trig",id=i..'recording trig',behavior='momentary', | |
| action=function(v) | |
| if v==1 then | |
| if uS.recording[i]>0 then | |
| tape_stop_rec(i) | |
| else | |
| tape_rec(i) | |
| end | |
| end | |
| end | |
| } | |
| params:add{type='binary',name="arming trig",id=i..'arming trig',behavior='momentary', | |
| action=function(v) | |
| if v==1 then | |
| if uS.recording[i]>0 then | |
| tape_stop_rec(i) | |
| else | |
| tape_arm_rec(i) | |
| end | |
| end | |
| end | |
| } | |
| params:add_file(i.."load_file","load audio",_path.audio) | |
| params:set_action(i.."load_file",function(x) | |
| if #x<=#_path.audio then | |
| do return end | |
| end | |
| print("load_file",i,x) | |
| loop_load_wav(i,x) | |
| tape_play(i) | |
| end) | |
| params:add_option(i.."isempty","is empty",{"false","true"},2) | |
| params:hide(i.."isempty") | |
| end | |
| params_read_silent(DATA_DIR.."oooooo.pset") | |
| params:set('save_message',"") | |
| params:set('load_name',name_folder) | |
| init_loops(7) | |
| -- make data directory | |
| if not util.file_exists(PATH) then util.make_dir(PATH) end | |
| -- initialize timer for updating screen | |
| timer=metro.init() | |
| timer.time=uC.updateTimerInterval | |
| timer.count=-1 | |
| timer.event=update_timer | |
| timer:start() | |
| -- position poll | |
| softcut.event_phase(update_positions) | |
| softcut.poll_start_phase() | |
| -- call update_softcut_input to set polling input | |
| p_amp_in = update_softcut_input() | |
| -- set period low when primed, default 1 second | |
| p_amp_in.time=1 | |
| p_amp_in.callback=function(val) | |
| for i=1,6 do | |
| if uS.recording[i]==1 then | |
| -- print("incoming signal = "..val) | |
| if val>params:get("rec thresh")/1000 then | |
| tape_rec(i) | |
| end | |
| end | |
| end | |
| end | |
| p_amp_in:start() | |
| for i=1,6 do | |
| params:set_action(i.."vol",function(x) uP[i].volUpdate=true end) | |
| params:set_action(i.."length",function(x) uP[i].loopUpdate=true end) | |
| params:set_action(i.."start",function(x) uP[i].loopUpdate=true end) | |
| params:set_action(i.."pan",function(x) uP[i].panUpdate=true end) | |
| params:set_action(i.."rate",function(x) uP[i].rateUpdate=true end) | |
| params:set_action(i.."rate reverse",function(x) uP[i].rateUpdate=true end) | |
| params:set_action(i.."rate adjust",function(x) uP[i].rateUpdate=true end) | |
| end | |
| redraw() | |
| if params:get("start lfos random")==2 then | |
| randomize_lfos() | |
| end | |
| -- end of init | |
| if params:get("load on start")==2 then | |
| -- backup_load() | |
| if params:get("play on start")==2 then | |
| tape_play(7) | |
| end | |
| else | |
| tape_stop(1) | |
| tape_reset(1) | |
| end | |
| end | |
| function init_loops(j) | |
| audio.level_adc(1) -- input volume 1 | |
| audio.level_cut(1) -- Softcut master level (same as in LEVELS screen) | |
| i1=j | |
| i2=j | |
| if j==7 then | |
| i1=1 | |
| i2=7 | |
| end | |
| for i=i1,i2 do | |
| print("initializing "..i) | |
| -- TODO: if using save file, then load the last save | |
| uP[i]={} | |
| uP[i].loopStart=0 | |
| uP[i].loopLength=(60/clock.get_tempo())*i*4 | |
| if params:get("start length")>0 then | |
| uP[i].loopLength=(60/clock.get_tempo())*params:get("start length") | |
| end | |
| uP[i].loopUpdate=false | |
| uP[i].position=uP[i].loopStart | |
| uP[i].recordedLength=0 | |
| uP[i].isStopped=true | |
| uP[i].vol=0.5 | |
| uP[i].volUpdate=false | |
| uP[i].rate=1 | |
| uP[i].rateUpdate=false | |
| uP[i].pan=0 | |
| uP[i].panUpdate=false | |
| uP[i].lfoWarble={} | |
| uP[i].destroying=false | |
| if i<7 then | |
| params:set(i.."start",0) | |
| params:set(i.."start lfo amp",0.2) | |
| params:set(i.."start lfo period",0) | |
| params:set(i.."start lfo offset",0) | |
| params:set(i.."length",uP[i].loopLength) | |
| params:set(i.."length lfo amp",0.2) | |
| params:set(i.."length lfo period",0) | |
| params:set(i.."length lfo offset",0) | |
| params:set(i.."vol",0.5) | |
| params:set(i.."vol lfo amp",0.3) | |
| params:set(i.."vol lfo period",0) | |
| params:set(i.."vol lfo offset",0) | |
| params:set(i.."rate",15) | |
| params:set(i.."rate adjust",0) | |
| params:set(i.."rate reverse",2) | |
| params:set(i.."rate lfo center",10) | |
| params:set(i.."rate lfo amp",0.2) | |
| params:set(i.."rate lfo period",0) | |
| params:set(i.."rate lfo offset",0) | |
| params:set(i.."pan",0) | |
| params:set(i.."pan lfo amp",0.5) | |
| params:set(i.."pan lfo period",0) | |
| params:set(i.."pan lfo offset",0) | |
| params:set(i.."reset every beat",0) | |
| params:set(i.."isempty",2) | |
| end | |
| for j=1,3 do | |
| uP[i].lfoWarble[j]=math.random(1,60) | |
| end | |
| if i<7 then | |
| -- update softcut | |
| softcut.level(i,0.5) | |
| softcut.pan(i,0) | |
| softcut.play(i,0) | |
| softcut.rate(i,1) | |
| softcut.loop_start(i,uC.bufferMinMax[i][2]) | |
| softcut.loop_end(i,uC.bufferMinMax[i][2]+uP[i].loopLength) | |
| softcut.loop(i,1) | |
| softcut.rec(i,0) | |
| softcut.fade_time(i,0.2) | |
| softcut.level_slew_time(i,params:get("slew rate")) | |
| softcut.rate_slew_time(i,params:get("slew rate")) | |
| softcut.rec_level(i,params:get("rec level")) | |
| softcut.pre_level(i,params:get("pre level")) | |
| softcut.buffer(i,uC.bufferMinMax[i][1]) | |
| softcut.position(i,uC.bufferMinMax[i][2]) | |
| softcut.enable(i,1) | |
| softcut.phase_quant(i,0.025) | |
| softcut.post_filter_dry(i,0.0) | |
| softcut.post_filter_lp(i,1.0) | |
| softcut.post_filter_rq(i,0.3) | |
| softcut.post_filter_fc(i,44100) | |
| softcut.pre_filter_dry(i,1.0) | |
| softcut.pre_filter_lp(i,1.0) | |
| softcut.pre_filter_rq(i,0.3) | |
| softcut.pre_filter_fc(i,44100) | |
| end | |
| end | |
| end | |
| function randomize_parameters(j) | |
| i1=j | |
| i2=j | |
| if j==7 then | |
| i1=1 | |
| i2=6 | |
| end | |
| for i=i1,i2 do | |
| params:set(i.."rate adjust",0) | |
| -- randomize rates between 25%, 50%, 100%, 200%, 400% | |
| -- params:set(i.."rate",math.random(#uC.discreteRates)) | |
| -- randomize rates between 25%, 50%, 100% | |
| params:set(i.."rate",math.random(3)+5) | |
| params:set(i.."rate reverse",math.floor(math.random()*2)+1) | |
| uP[i].rateUpdate=true | |
| params:set(i.."vol",math.random()*0.6+0.2) | |
| uP[i].volUpdate=true | |
| params:set(i.."pan",math.random()*2-1) | |
| uP[i].panUpdate=true | |
| end | |
| end | |
| function randomize_loops(j) | |
| i1=j | |
| i2=j | |
| if j==7 then | |
| i1=1 | |
| i2=6 | |
| end | |
| for i=i1,i2 do | |
| params:set(i.."length",util.clamp(params:get(i.."length")+math.random()*2-1,uC.loopMinMax[1],uC.loopMinMax[2])) | |
| uP[i].loopUpdate=true | |
| end | |
| end | |
| function randomize_lfos() | |
| for i=1,6 do | |
| -- params:set(i.."length lfo period",math.random()*30+5) | |
| -- params:set(i.."length lfo offset",math.random()*60) | |
| params:set(i.."vol lfo period",round_time_to_nearest_beat(math.random()*20+2)) | |
| params:set(i.."vol lfo offset",round_time_to_nearest_beat(math.random()*60)) | |
| params:set(i.."vol lfo amp",math.random()*0.25+0.1) | |
| params:set(i.."pan lfo amp",math.random()*0.6+0.2) | |
| params:set(i.."pan lfo period",round_time_to_nearest_beat(math.random()*20+2)) | |
| params:set(i.."pan lfo offset",round_time_to_nearest_beat(math.random()*60)) | |
| end | |
| end | |
| -- | |
| -- updaters | |
| -- | |
| function update_softcut_input() | |
| if params:get("input type")==1 then | |
| p_amp_in=poll.set("amp_in_l") | |
| print("listening on input 1") | |
| for i=1,6 do | |
| print("input L only channel "..i) | |
| softcut.level_input_cut(1,i,1) | |
| softcut.level_input_cut(2,i,0) | |
| audio.level_adc_cut(1) | |
| audio.level_tape_cut(0) | |
| end | |
| elseif params:get("input type")==2 then | |
| p_amp_in=poll.set("amp_in_r") | |
| print("listening on input 2") | |
| for i=1,6 do | |
| print("input R only channel "..i) | |
| softcut.level_input_cut(1,i,0) | |
| softcut.level_input_cut(2,i,1) | |
| audio.level_adc_cut(1) | |
| audio.level_tape_cut(0) | |
| end | |
| elseif params:get("input type "..i)==3 then | |
| print("tape only") | |
| audio.level_tape_cut(1) | |
| audio.level_adc_cut(0) | |
| else | |
| print("tape+input L+R "..i) | |
| softcut.level_input_cut(1,i,1) | |
| softcut.level_input_cut(2,i,1) | |
| audio.level_adc_cut(1) | |
| audio.level_tape_cut(1) | |
| end | |
| return p_amp_in | |
| end | |
| function update_parameters(x) | |
| params:write(DATA_DIR.."oooooo.pset") | |
| end | |
| function update_positions(i,x) | |
| -- adjust position so it is relative to loop start | |
| uP[i].position=x-uC.bufferMinMax[i][2] | |
| uS.updateUI=true | |
| end | |
| function update_timer() | |
| if params:get("pause lfos")==1 then | |
| uS.currentTime=uS.currentTime+uC.updateTimerInterval | |
| end | |
| -- -- update the count for the lfos | |
| -- uC.lfoTime=uC.lfoTime+uC.updateTimerInterval | |
| -- if uC.lfoTime>376.99 then -- 60 * 2 * pi | |
| -- uC.lfoTime=0 | |
| -- end | |
| -- tape_warble() | |
| if uS.updateUI then | |
| redraw() | |
| end | |
| for i=1,6 do | |
| if uS.recording[i]==2 then | |
| uS.recordingTime[i]=uS.recordingTime[i]+uC.updateTimerInterval | |
| if uS.recordingTime[i]>=uP[i].loopLength then | |
| uS.recordingLoopNum[i]=uS.recordingLoopNum[i]+1 | |
| print("uS.recordingLoopNum[i]: "..uS.recordingLoopNum[i]) | |
| if uS.recordingLoopNum[i]>=params:get("stop rec after") and uS.recordingLoopNum[i]<64 then | |
| -- stop recording when reached a full loop | |
| tape_stop_rec(i,false) | |
| else | |
| uS.recordingTime[i]=0 | |
| end | |
| end | |
| end | |
| end | |
| if math.floor(clock.get_beats())~=uS.currentBeat then | |
| -- a beat has been hit | |
| if params:get("vol ramp")~=0 then | |
| for i=1,6 do | |
| params:set(i.."vol",util.clamp(params:get(i.."vol")+params:get("vol ramp")/10,0,1)) | |
| end | |
| end | |
| if params:get("destroy loops")>0 and math.random()*100<params:get("destroy loops") then | |
| -- cause destruction to moving non empty loops | |
| nonEmptyLoops={} | |
| for i=1,6 do | |
| if params:get(i.."isempty")==1 and uP[i].isStopped==false and uP[i].destroying==false then | |
| table.insert(nonEmptyLoops,i) | |
| end | |
| end | |
| if #nonEmptyLoops>0 then | |
| -- select a loop at random | |
| loopDestroy=nonEmptyLoops[math.random(#nonEmptyLoops)] | |
| clock.run(function() | |
| numBeats=uC.discreteBeats[math.random(#uC.discreteBeats)] | |
| preLevel=math.random() | |
| print("destroying "..loopDestroy.." for "..numBeats.." at "..preLevel.." pre level") | |
| uP[loopDestroy].destroying=true | |
| softcut.rec_level(loopDestroy,0) | |
| softcut.pre_level(loopDestroy,preLevel) | |
| softcut.rec(loopDestroy,1) | |
| clock.sync(numBeats) | |
| softcut.rec_level(loopDestroy,1) | |
| softcut.pre_level(loopDestroy,1) | |
| softcut.rec(loopDestroy,0) | |
| uP[loopDestroy].destroying=false | |
| end) | |
| end | |
| end | |
| uS.currentBeat=math.floor(clock.get_beats()) | |
| for i=1,6 do | |
| if params:get(i.."reset every beat")>0 then | |
| if uS.currentBeat%params:get(i.."reset every beat")==0 then | |
| tape_reset(i) | |
| end | |
| end | |
| end | |
| end | |
| for i=1,6 do | |
| if uP[i].volUpdate or (params:get(i.."vol lfo period")>0 and params:get("pause lfos")==1 and params:get(i.."vol lfo amp")>0) then | |
| uS.updateUI=true | |
| uP[i].volUpdate=false | |
| uP[i].vol=params:get(i.."vol") | |
| if params:get(i.."vol lfo period")>0 and params:get("pause lfos")==1 then | |
| uP[i].vol=uP[i].vol+params:get(i.."vol lfo amp")*calculate_lfo(uS.currentTime,params:get(i.."vol lfo period"),params:get(i.."vol lfo offset")) | |
| uP[i].vol=util.clamp(uP[i].vol,0,1) | |
| end | |
| softcut.level(i,uP[i].vol) | |
| end | |
| if uP[i].rateUpdate or (params:get(i.."rate lfo period")>0 and params:get("pause lfos")==1 and params:get(i.."rate lfo amp")>0) or | |
| uP[i].rate~=uC.discreteRates[params:get(i.."rate")]+params:get(i.."rate adjust") then | |
| uS.updateUI=true | |
| uP[i].rateUpdate=false | |
| local currentRateIndex=params:get(i.."rate") | |
| if params:get(i.."rate lfo period")>0 and params:get(i.."rate lfo amp")>0 and params:get("pause lfos")==1 then | |
| currentRateIndex=util.clamp(params:get(i.."rate lfo center")+round(util.linlin(-1,1,-#uC.discreteRates,#uC.discreteRates,params:get(i.."rate lfo amp")*calculate_lfo(uS.currentTime,params:get(i.."rate lfo period"),params:get(i.."rate lfo offset")))),1,#uC.discreteRates) | |
| -- currentRateIndex=util.clamp(round(util.linlin(-1,1,0,1+#uC.discreteRates,params:get(i.."rate lfo amp")*calculate_lfo(uS.currentTime,params:get(i.."rate lfo period"),params:get(i.."rate lfo offset")))),1,#uC.discreteRates) | |
| end | |
| uP[i].rate=uC.discreteRates[currentRateIndex]+params:get(i.."rate adjust") | |
| uP[i].rate=uP[i].rate*(params:get(i.."rate reverse")*2-3)/100.0 | |
| softcut.rate(i,uP[i].rate) | |
| end | |
| if uP[i].panUpdate or (params:get(i.."pan lfo period")>0 and params:get("pause lfos")==1 and params:get(i.."pan lfo amp")>0) then | |
| uS.updateUI=true | |
| uP[i].panUpdate=false | |
| uP[i].pan=params:get(i.."pan") | |
| if params:get(i.."pan lfo period")>0 and params:get("pause lfos")==1 then | |
| uP[i].pan=uP[i].pan+params:get(i.."pan lfo amp")*calculate_lfo(uS.currentTime,params:get(i.."pan lfo period"),params:get(i.."pan lfo offset")) | |
| end | |
| uP[i].pan=util.clamp(uP[i].pan,-1,1) | |
| softcut.pan(i,uP[i].pan) | |
| end | |
| if uP[i].loopUpdate or (params:get(i.."length lfo period")>0 and params:get("pause lfos")==1 and params:get(i.."length lfo amp")>0) or | |
| (params:get(i.."start lfo period")>0 and params:get("pause lfos")==1 and params:get(i.."start lfo amp")>0) then | |
| uS.updateUI=true | |
| uP[i].loopUpdate=false | |
| uP[i].loopStart=params:get(i.."start") | |
| if params:get(i.."start lfo period")>0 and params:get("pause lfos")==1 then | |
| uP[i].loopStart=params:get(i.."start")+uP[i].loopLength*params:get(i.."start lfo amp")*((1+calculate_lfo(uS.currentTime,params:get(i.."start lfo period"),params:get(i.."start lfo offset")))/2) | |
| uP[i].loopStart=util.clamp(uP[i].loopStart,params:get(i.."start"),uP[i].loopLength+params:get(i.."start")) | |
| end | |
| uP[i].loopLength=params:get(i.."length") | |
| if params:get(i.."length lfo period")>0 and params:get("pause lfos")==1 then | |
| uP[i].loopLength=uP[i].loopLength*(1+params:get(i.."length lfo amp")*calculate_lfo(uS.currentTime,params:get(i.."length lfo period"),params:get(i.."length lfo offset")))/2 | |
| end | |
| if uP[i].loopLength+uP[i].loopStart>uC.loopMinMax[2] then | |
| -- loop length is too long, shorten it | |
| uP[i].loopLength=uC.loopMinMax[2]-uP[i].loopStart | |
| end | |
| -- move to start of loop if position is outside of loop | |
| if uP[i].position<uP[i].loopStart or uP[i].position>uP[i].loopStart+uP[i].loopLength then | |
| uP[i].position=uP[i].loopStart | |
| softcut.position(i,uP[i].position+uC.bufferMinMax[i][2]) | |
| end | |
| softcut.loop_start(i,uP[i].loopStart+uC.bufferMinMax[i][2]) | |
| softcut.loop_end(i,uP[i].loopStart+uC.bufferMinMax[i][2]+uP[i].loopLength) | |
| end | |
| end | |
| end | |
| -- | |
| -- saving and loading | |
| -- | |
| function loop_load_wav(i,fname) | |
| -- loads fname into loop i | |
| local ch,samples,samplerate=audio.file_info(fname) | |
| local duration=samples/48000.0 | |
| softcut.buffer_read_mono(fname,0,uC.bufferMinMax[i][2],uC.loopMinMax[2],1,uC.bufferMinMax[i][1]) | |
| params:set(i.."rate adjust",100*samplerate/48000.0-100,true) | |
| params:set(i.."start",0,true) | |
| params:set(i.."length",duration,true) | |
| params:set(i.."isempty",1,true) | |
| uP[i].loopUpdate=true | |
| end | |
| function loop_save_wav(i,savename) | |
| buffernum=uC.bufferMinMax[i][1] | |
| pos_start=uC.bufferMinMax[i][2]+params:get(i.."start") | |
| softcut.buffer_write_mono(savename,pos_start,params:get(i.."length"),buffernum) | |
| end | |
| function backup_save(savename) | |
| -- create if doesn't exist | |
| os.execute("mkdir -p "..DATA_DIR.."names") | |
| os.execute("mkdir -p "..DATA_DIR..savename) | |
| os.execute("echo "..savename.." > "..DATA_DIR.."names/"..savename) | |
| -- save the parameter set | |
| params:write(DATA_DIR..savename.."/parameters.pset") | |
| -- save the user parameters | |
| tab.save(uP,DATA_DIR..savename.."/uP.txt") | |
| -- | |
| -- iterate over each loop | |
| -- if not "isempty" then save it | |
| for i=1,6 do | |
| if params:get(i.."isempty")==1 then -- not empty | |
| loop_save_wav(i,DATA_DIR..savename.."/loop"..i..".wav") | |
| end | |
| end | |
| end | |
| function backup_load(savename) | |
| for i=1,6 do | |
| if util.file_exists(DATA_DIR..savename.."/loop"..i..".wav") then | |
| print("loading loop"..i) | |
| loop_load_wav(i,DATA_DIR..savename.."/loop"..i..".wav") | |
| end | |
| end | |
| params_read_silent(DATA_DIR..savename.."/parameters.pset") | |
| uP=tab.load(DATA_DIR..savename.."/uP.txt") | |
| for i=1,6 do | |
| if params:get(i.."isempty")==1 then | |
| tape_stop(i) | |
| tape_reset(i) | |
| tape_play(i) | |
| end | |
| uP[i].loopUpdate=true | |
| uP[i].panUpdate=true | |
| uP[i].rateUpdate=true | |
| uP[i].volUpdate=true | |
| end | |
| end | |
| -- | |
| -- tape effects | |
| -- | |
| function tape_warble() | |
| for i=1,6 do | |
| if uP[i].isStopped then | |
| -- do nothing | |
| else | |
| warblePercent=0 | |
| for j=1,3 do | |
| warblePercent=warblePercent+math.sin(2*math.pi*uC.lfoTime/uP[i].lfoWarble[j]) | |
| end | |
| softcut.rate(i,uP[i].rate*(1+warblePercent/200)) | |
| end | |
| end | |
| end | |
| -- | |
| -- tape functions | |
| -- | |
| function tape_stop_reset(j) | |
| -- if uS.loopNum == 7 then stop all | |
| i1=j | |
| i2=j | |
| if j==7 then | |
| i1=1 | |
| i2=6 | |
| end | |
| for i=i1,i2 do | |
| if uP[i].isStopped and uS.recording[i]==0 then | |
| tape_reset(i) | |
| else | |
| tape_stop(i) | |
| end | |
| end | |
| end | |
| function tape_reset(i) | |
| if uP[i].position==0 then | |
| do return end | |
| end | |
| print("tape_reset "..i) | |
| uP[i].position=0 | |
| softcut.position(i,uC.bufferMinMax[i][2]+uP[i].loopStart) | |
| if params:get(i.."randomize on reset")>1 then | |
| if params:get(i.."randomize on reset")==2 then | |
| randomize_parameters(i) | |
| elseif params:get(i.."randomize on reset")==3 then | |
| randomize_loops(i) | |
| elseif params:get(i.."randomize on reset")==4then | |
| randomize_parameters(i) | |
| randomize_loops(i) | |
| end | |
| end | |
| end | |
| function tape_stop(i) | |
| if uP[i].isStopped==true and uS.recording[i]==0 then | |
| do return end | |
| end | |
| print("tape_stop "..i) | |
| if uS.recording[i]>0 then | |
| tape_stop_rec(i,true) | |
| end | |
| -- ????? | |
| -- if this runs as softcut.rate(i,0) though, then overdubbing stops working | |
| softcut.play(i,0) | |
| uP[i].isStopped=true | |
| end | |
| function tape_stop_rec(i,change_loop) | |
| if uS.recording[i]==0 then | |
| do return end | |
| end | |
| print("tape_stop_rec "..i) | |
| p_amp_in.time=1 | |
| still_armed=(uS.recording[i]==1) | |
| uS.recording[i]=0 | |
| uS.recordingLoopNum[i]=0 | |
| if uS.recordingTime[i]<params:get(i.."length") then | |
| uP[i].recordedLength=uS.recordingTime[i] | |
| else | |
| uP[i].recordedLength=params:get(i.."length") | |
| end | |
| uS.recordingTime[i]=0 | |
| -- slowly stop | |
| clock.run(function() | |
| if params:get("vol pinch")>0 then | |
| for j=1,10 do | |
| softcut.rec(i,(10-j)*0.1) | |
| clock.sleep(params:get("vol pinch")/10/1000) | |
| end | |
| end | |
| softcut.rec(i,0) | |
| end) | |
| -- change the loop size if specified | |
| print('params:get("rec thru loops") '..params:get("rec thru loops")) | |
| if not still_armed then | |
| if change_loop then | |
| params:set(i.."length",uP[i].recordedLength) | |
| uP[i].loopUpdate=true | |
| -- sync all the loops here if this is first loop and enabled | |
| if i==1 and params:get("sync lengths to first")==2 then | |
| for j=2,6 do | |
| uP[j].recordedLength=uP[1].recordedLength | |
| params:set(j.."length",uP[j].recordedLength) | |
| uP[j].loopUpdate=true | |
| end | |
| end | |
| elseif params:get("rec thru loops")==2 then | |
| -- keep recording onto the next loop | |
| nextLoop=0 | |
| for j=1,6 do | |
| if params:get(j.."isempty")==2 then | |
| nextLoop=j | |
| break | |
| end | |
| end | |
| -- goto the next loop and record | |
| if nextLoop>0 then | |
| uS.loopNum=nextLoop | |
| tape_rec(uS.loopNum) | |
| end | |
| end | |
| end | |
| end | |
| function tape_clear(i) | |
| print("tape_clear "..i) | |
| -- prevent double clear | |
| if uS.flagClearing then | |
| do return end | |
| end | |
| -- signal clearing to prevent double clear | |
| clock.run(function() | |
| uS.flagClearing=true | |
| uS.message="clearing" | |
| redraw() | |
| clock.sleep(0.5) | |
| uS.flagClearing=false | |
| uS.message="" | |
| redraw() | |
| end) | |
| redraw() | |
| if i==7 then | |
| -- clear everything | |
| softcut.buffer_clear() | |
| for j=1,6 do | |
| if params:get(j.."isempty")==2 then | |
| init_loops(j) | |
| uS.message="resetting" | |
| redraw() | |
| end | |
| params:set(j.."isempty",2) | |
| uP[j].recordedLength=0 | |
| tape_stop(j) | |
| tape_reset(j) | |
| end | |
| else | |
| -- clear a specific section of buffer | |
| if params:get(i.."isempty")==2 then | |
| init_loops(i) | |
| uS.message="resetting" | |
| redraw() | |
| end | |
| params:set(i.."isempty",2) | |
| uP[i].recordedLength=0 | |
| softcut.buffer_clear_region_channel( | |
| uC.bufferMinMax[i][1], | |
| uC.bufferMinMax[i][2], | |
| uC.bufferMinMax[i][3]-uC.bufferMinMax[i][2]) | |
| tape_stop(i) | |
| tape_reset(i) | |
| end | |
| -- reinitialize? | |
| -- init_loops(i) | |
| end | |
| function tape_play(j) | |
| print("tape_play "..j) | |
| if j<7 and uP[j].isStopped==false and uS.recording[j]==0 then | |
| do return end | |
| end | |
| if j<7 and params:get(j.."isempty")==2 then | |
| do return end | |
| end | |
| i1=j | |
| i2=j | |
| if j==7 then | |
| i1=1 | |
| i2=6 | |
| end | |
| for i=i1,i2 do | |
| if uS.recording[i]>0 then | |
| tape_stop_rec(i,true) | |
| end | |
| softcut.play(i,1) | |
| uP[i].rateUpdate=true | |
| uP[i].volUpdate=true | |
| uP[i].isStopped=false | |
| end | |
| end | |
| function tape_arm_rec(i) | |
| if uS.recording[i]==1 then | |
| do return end | |
| end | |
| print("tape_arm_rec "..i) | |
| -- arm recording | |
| uS.recording[i]=1 | |
| uS.recordingLoopNum[i]=0 | |
| -- monitor input | |
| p_amp_in.time=0.025 | |
| end | |
| function tape_rec(i) | |
| if uS.recording[i]==2 then | |
| do return end | |
| end | |
| print("tape_rec "..i) | |
| if uP[i].isStopped then | |
| softcut.play(i,1) | |
| -- print("setting rate to "..uP[i].rate) | |
| softcut.rate(i,uP[i].rate) | |
| uP[i].volUpdate=true | |
| uP[i].isStopped=false | |
| end | |
| p_amp_in.time=1 | |
| uS.recordingTime[i]=0 | |
| uS.recording[i]=2 -- recording is live | |
| softcut.rec_level(i,params:get("rec level")) | |
| softcut.pre_level(i,params:get("pre level")) | |
| params:set(i.."isempty",1) | |
| redraw() | |
| -- slowly start recording | |
| -- ease in recording signal to avoid clicks near loop points | |
| clock.run(function() | |
| if params:get("vol pinch")>0 then | |
| for j=1,10 do | |
| softcut.rec(i,j*0.1) | |
| clock.sleep(params:get("vol pinch")/10/1000) | |
| end | |
| end | |
| softcut.rec(i,1) | |
| end) | |
| end | |
| -- | |
| -- encoders | |
| -- | |
| function enc(n,d) | |
| if n==1 then | |
| d=sign(d) | |
| uS.loopNum=util.clamp(uS.loopNum+d,1,7) | |
| elseif n==2 then | |
| d=sign(d) | |
| if uS.loopNum~=7 then | |
| -- toggle between loop parameters | |
| uS.selectedPar=util.clamp(uS.selectedPar+d,0,7) | |
| else | |
| -- toggle between special parameters | |
| uS.flagSpecial=util.clamp(uS.flagSpecial+d,0,4) | |
| end | |
| elseif n==3 then | |
| if uS.loopNum~=7 then | |
| if uS.selectedPar==1 then | |
| params:set(uS.loopNum.."start",util.clamp(params:get(uS.loopNum.."start")+d/10,0,uC.loopMinMax[2])) | |
| uP[uS.loopNum].loopUpdate=true | |
| elseif uS.selectedPar==2 then | |
| params:set(uS.loopNum.."length",util.clamp(params:get(uS.loopNum.."length")+d/10,uC.loopMinMax[1],uC.loopMinMax[2])) | |
| uP[uS.loopNum].loopUpdate=true | |
| elseif uS.selectedPar==3 then | |
| -- uP[uS.loopNum].vol=util.clamp(uP[uS.loopNum].vol+d/100,0,1) | |
| params:set(uS.loopNum.."vol",util.clamp(params:get(uS.loopNum.."vol")+d/100,0,1)) | |
| uP[uS.loopNum].volUpdate=true | |
| elseif uS.selectedPar==4 then | |
| if params:get("continous rate")==2 then | |
| params:set(uS.loopNum.."rate adjust",util.clamp(params:get(uS.loopNum.."rate adjust")+d,-400,400)) | |
| else | |
| d=sign(d) | |
| params:set(uS.loopNum.."rate adjust",0) | |
| params:set(uS.loopNum.."rate",util.clamp(params:get(uS.loopNum.."rate")+d,1,#uC.discreteRates)) | |
| end | |
| uP[uS.loopNum].rateUpdate=true | |
| elseif uS.selectedPar==5 then | |
| params:set(uS.loopNum.."pan",util.clamp(params:get(uS.loopNum.."pan")+d/100,-1,1)) | |
| uP[uS.loopNum].panUpdate=true | |
| elseif uS.selectedPar==6 then | |
| d=sign(d) | |
| params:set(uS.loopNum.."reset every beat",util.clamp(params:get(uS.loopNum.."reset every beat")+d,0,64)) | |
| elseif uS.selectedPar==7 then | |
| -- add temporary warble | |
| clock.run(function() | |
| local newChange=(1+d/100) | |
| uP[uS.loopNum].rate=uP[uS.loopNum].rate*newChange | |
| softcut.rate(uS.loopNum,uP[uS.loopNum].rate) | |
| clock.sync(1) | |
| uP[uS.loopNum].rate=uP[uS.loopNum].rate/newChange | |
| softcut.rate(uS.loopNum,uP[uS.loopNum].rate) | |
| end) | |
| end | |
| end | |
| end | |
| uS.updateUI=true | |
| end | |
| function key(n,z) | |
| if n==1 then | |
| uS.shift=not uS.shift | |
| elseif z==0 then | |
| do return end | |
| elseif (uS.flagSpecial==0 and uS.loopNum==7) or (uS.selectedPar==0 and uS.loopNum<7) then | |
| -- shift+K2 clears, shift+K3 records only when on tape | |
| if uS.shift==false and n==2 then | |
| -- stop tape | |
| -- if stopped, then reset to 0 | |
| tape_stop_reset(uS.loopNum) | |
| elseif uS.shift==false and n==3 then | |
| -- play tape | |
| tape_play(uS.loopNum) | |
| elseif n==2 then | |
| -- clear | |
| tape_clear(uS.loopNum) | |
| elseif n==3 then | |
| if uS.loopNum==7 then | |
| -- start recording on all | |
| for i=1,6 do | |
| tape_rec(i) | |
| end | |
| else | |
| if uS.recording[uS.loopNum]==0 then | |
| tape_arm_rec(uS.loopNum) | |
| elseif uS.recording[uS.loopNum]==1 then | |
| tape_rec(uS.loopNum) | |
| end | |
| end | |
| end | |
| elseif uS.flagSpecial>0 and uS.loopNum==7 then | |
| -- shit+K2 or shift+K3 activates parameters | |
| if uS.flagSpecial==1 then | |
| -- pause/start lfos | |
| if params:get("pause lfos")==1 then | |
| show_message("pausing lfos") | |
| else | |
| show_message("unpausing lfos") | |
| end | |
| params:set("pause lfos",3-params:get("pause lfos")) | |
| elseif uS.flagSpecial==2 then | |
| -- randomize! | |
| show_message("randomizing") | |
| randomize_parameters(7) | |
| elseif uS.flagSpecial==3 then | |
| -- randomize loops! | |
| show_message("randomizing loops") | |
| randomize_loops(7) | |
| elseif uS.flagSpecial==4 then | |
| -- randomize lfos! | |
| show_message("randomizing lfos") | |
| randomize_lfos() | |
| end | |
| elseif uS.selectedPar>0 and uS.loopNum<7 then | |
| -- shit+K2 or shift+K3 activates parameters when parameter is selected | |
| if (uS.selectedPar==1 or uS.selectedPar==2) then | |
| -- toggle lfo for loops | |
| if params:get(uS.loopNum.."length lfo period")==0 then | |
| show_message("loop "..uS.loopNum.." lfo on") | |
| params:set(uS.loopNum.."length lfo offset",math.random()*60) | |
| params:set(uS.loopNum.."length lfo period",math.random()*60) | |
| params:set(uS.loopNum.."start lfo offset",math.random()*60) | |
| params:set(uS.loopNum.."start lfo period",math.random()*60) | |
| else | |
| show_message("loop "..uS.loopNum.." lfo off") | |
| params:set(uS.loopNum.."length lfo period",0) | |
| params:set(uS.loopNum.."start lfo period",0) | |
| end | |
| elseif uS.selectedPar==3 then | |
| -- toggle lfo for loops | |
| if params:get(uS.loopNum.."vol lfo period")==0 and uS.loopNum~=7 then | |
| show_message("vol "..uS.loopNum.." lfo on") | |
| params:set(uS.loopNum.."vol lfo offset",math.random()*60) | |
| params:set(uS.loopNum.."vol lfo period",math.random()*60) | |
| else | |
| show_message("vol "..uS.loopNum.." lfo off") | |
| params:set(uS.loopNum.."vol lfo period",0) | |
| end | |
| elseif uS.selectedPar==4 then | |
| if uS.shift then | |
| -- toggle rate lfo | |
| if params:get(uS.loopNum.."rate lfo period")==0 then | |
| params:set(uS.loopNum.."rate lfo period",0.4) | |
| end | |
| if params:get(uS.loopNum.."rate lfo amp")==0 then | |
| params:set(uS.loopNum.."rate lfo amp",0.6) | |
| else | |
| params:set(uS.loopNum.."rate lfo amp",0) | |
| end | |
| else | |
| -- toggle reverse | |
| params:set(uS.loopNum.."rate reverse",3-params:get(uS.loopNum.."rate reverse")) | |
| end | |
| elseif uS.selectedPar==5 then | |
| -- toggle lfo for pan | |
| if params:get(uS.loopNum.."pan lfo period")==0 and uS.loopNum~=7 then | |
| show_message("pan "..uS.loopNum.." lfo on") | |
| params:set(uS.loopNum.."pan lfo offset",math.random()*60) | |
| params:set(uS.loopNum.."pan lfo period",math.random()*60) | |
| else | |
| show_message("pan "..uS.loopNum.." lfo off") | |
| params:set(uS.loopNum.."pan lfo period",0) | |
| end | |
| end | |
| end | |
| uS.updateUI=true | |
| end | |
| -- | |
| -- screen | |
| -- | |
| function redraw() | |
| uS.updateUI=false | |
| screen.clear() | |
| -- check shift | |
| shift_amount=0 | |
| if uS.shift then | |
| shift_amount=4 | |
| end | |
| -- show header | |
| screen.level(15) | |
| -- show state symbol | |
| anyRecording=false | |
| anyPrimed=false | |
| for i=1,6 do | |
| if uS.recording[i]==1 then | |
| anyPrimed=true | |
| -- screen.level(1) | |
| -- screen.move(111,i*8+12) | |
| -- screen.text(i) | |
| elseif uS.recording[i]==2 then | |
| anyRecording=true | |
| -- screen.level(15) | |
| -- screen.move(111,i*8+12) | |
| -- screen.text(i) | |
| end | |
| end | |
| screen.level(15) | |
| if params:get("expert mode")==1 then | |
| if anyRecording then | |
| screen.rect(108,1,20,10) | |
| screen.move(111,8) | |
| screen.text("REC") | |
| elseif anyPrimed then | |
| screen.rect(108,1,20,10) | |
| screen.level(1) | |
| screen.move(111,8) | |
| screen.text("REC") | |
| screen.level(15) | |
| elseif uP[uS.loopNum].isStopped then | |
| screen.rect(118,1,10,10) | |
| screen.move(121,8) | |
| screen.text("||") | |
| else | |
| screen.rect(118,1,10,10) | |
| screen.move(121,8) | |
| screen.text(">") | |
| screen.move(122,4) | |
| screen.line(122,8) | |
| end | |
| end | |
| -- show loop info | |
| x=4+shift_amount | |
| y=8+shift_amount | |
| if params:get("expert mode")==1 then | |
| screen.move(x,y) | |
| if uS.loopNum==7 then | |
| screen.text("A") | |
| else | |
| screen.text(uS.loopNum) | |
| end | |
| screen.move(x,y) | |
| screen.rect(x-3,y-7,10,10) | |
| screen.stroke() | |
| end | |
| x=-7 | |
| y=60 | |
| if uS.loopNum==7 then | |
| screen.move(x+10,y) | |
| if uS.flagSpecial==0 and params:get("expert mode")==1 then | |
| screen.move(x+10,y) | |
| tape_icon(x+10,y) | |
| elseif uS.flagSpecial==1 then | |
| if params:get("pause lfos")==1 then | |
| screen.text("pause lfos") | |
| else | |
| screen.text("unpause lfos") | |
| end | |
| elseif uS.flagSpecial==2 then | |
| screen.text("rand pars") | |
| elseif uS.flagSpecial==3 then | |
| screen.text("rand loop") | |
| elseif uS.flagSpecial==4 then | |
| screen.text("rand lfo") | |
| end | |
| elseif uS.selectedPar==0 and params:get("expert mode")==1 then | |
| screen.move(x+10,y) | |
| tape_icon(x+10,y) | |
| elseif uS.selectedPar==1 or uS.selectedPar==2 then | |
| screen.move(x+10,y) | |
| if uS.selectedPar==1 then | |
| screen.level(15) | |
| else | |
| screen.level(1) | |
| end | |
| screen.text(string.format("%1.1f",uP[uS.loopNum].loopStart)) | |
| screen.move(x+24,y) | |
| screen.level(1) | |
| screen.text("-") | |
| screen.move(x+30,y) | |
| if uS.selectedPar==2 then | |
| screen.level(15) | |
| else | |
| screen.level(1) | |
| end | |
| screen.text(string.format("%1.1fs",uP[uS.loopNum].loopStart+uP[uS.loopNum].loopLength)) | |
| elseif uS.selectedPar==3 then | |
| screen.move(x+10,y) | |
| screen.text(string.format("vol %1.1f",uP[uS.loopNum].vol)) | |
| elseif uS.selectedPar==4 then | |
| screen.move(x+10,y) | |
| screen.text(string.format("rate %1.1f%%",uP[uS.loopNum].rate*100)) | |
| elseif uS.selectedPar==5 then | |
| screen.move(x+10,y) | |
| screen.text(string.format("pan %1.1f",uP[uS.loopNum].pan)) | |
| elseif uS.selectedPar==6 then | |
| screen.move(x+10,y) | |
| screen.text("reset every "..params:get(uS.loopNum.."reset every beat").." beat") | |
| elseif uS.selectedPar==7 then | |
| screen.move(x+10,y) | |
| screen.text("warble") | |
| end | |
| -- draw representation of current loop states | |
| for i=1,6 do | |
| if uS.loopNum==i then goto continue end | |
| -- draw circles | |
| r=(uC.radiiMinMax[2]-uC.radiiMinMax[1])*uP[i].loopLength/(uC.bufferMinMax[i][3]-uC.bufferMinMax[i][2])+uC.radiiMinMax[1] | |
| if r>45 then | |
| r=45 | |
| end | |
| x=uC.centerOffsets[i][1]+(uC.widthMinMax[2]-uC.widthMinMax[1])*(uP[i].pan+1)/2+uC.widthMinMax[1] | |
| y=uC.centerOffsets[i][2]+(uC.heightMinMax[2]-uC.heightMinMax[1])*(1-uP[i].vol)+uC.heightMinMax[1] | |
| if uS.loopNum==i then | |
| screen.line_width(1) | |
| screen.level(15) | |
| else | |
| screen.line_width(1) | |
| screen.level(1) | |
| end | |
| screen.move(x+r,y) | |
| screen.circle(x,y,r) | |
| if uS.recording[i]>0 then | |
| screen.circle(x,y,r+1) | |
| end | |
| screen.stroke() | |
| -- draw pixels at position if it has data or | |
| -- its being recorded/primed | |
| angle=360*(uP[i].loopLength-uP[i].position)/(uP[i].loopLength)+90 | |
| if params:get(i.."isempty")==1 or i==uS.loopNum or uS.recording[i]>0 then | |
| for j=-1,1 do | |
| screen.pixel(x+(r-j)*math.sin(math.rad(angle)),y+(r-j)*math.cos(math.rad(angle))) | |
| screen.stroke() | |
| end | |
| end | |
| ::continue:: | |
| end | |
| for i=1,6 do | |
| if uS.loopNum~=i then goto continue end | |
| -- draw circles | |
| r=(uC.radiiMinMax[2]-uC.radiiMinMax[1])*uP[i].loopLength/(uC.bufferMinMax[i][3]-uC.bufferMinMax[i][2])+uC.radiiMinMax[1] | |
| if r>45 then | |
| r=45 | |
| end | |
| x=uC.centerOffsets[i][1]+(uC.widthMinMax[2]-uC.widthMinMax[1])*(uP[i].pan+1)/2+uC.widthMinMax[1] | |
| y=uC.centerOffsets[i][2]+(uC.heightMinMax[2]-uC.heightMinMax[1])*(1-uP[i].vol)+uC.heightMinMax[1] | |
| if uS.loopNum==i then | |
| screen.line_width(1) | |
| screen.level(15) | |
| else | |
| screen.line_width(1) | |
| screen.level(1) | |
| end | |
| screen.move(x+r,y) | |
| screen.circle(x,y,r) | |
| if uS.recording[i]>0 then | |
| screen.circle(x,y,r+1) | |
| end | |
| screen.stroke() | |
| -- draw pixels at position if it has data or | |
| -- its being recorded/primed | |
| angle=360*(uP[i].loopLength-uP[i].position)/(uP[i].loopLength)+90 | |
| if params:get(i.."isempty")==1 or i==uS.loopNum or uS.recording[i]>0 then | |
| for j=-1,1 do | |
| screen.pixel(x+(r-j)*math.sin(math.rad(angle)),y+(r-j)*math.cos(math.rad(angle))) | |
| screen.stroke() | |
| end | |
| end | |
| ::continue:: | |
| end | |
| if uS.message~="" then | |
| screen.level(0) | |
| x=64 | |
| y=28 | |
| w=string.len(uS.message)*6 | |
| screen.rect(x-w/2,y,w,10) | |
| screen.fill() | |
| screen.level(15) | |
| screen.rect(x-w/2,y,w,10) | |
| screen.stroke() | |
| screen.move(x,y+7) | |
| screen.text_center(uS.message) | |
| end | |
| screen.update() | |
| end | |
| --- Creates tape icon. | |
| -- @tparam x {number} X-coordinate of element | |
| -- @tparam y {number} Y-coordinate of element | |
| -- from https://github.com/frederickk/b-b-b-b-beat | |
| function tape_icon(x,y) | |
| local r=2 | |
| screen.move(math.floor(x),math.floor(y)-4) | |
| screen.line_rel(1,0) | |
| screen.line_rel((r*5),0) | |
| for i=0,6,2 do | |
| screen.move(math.floor(x)+(r*i),math.floor(y)-4) | |
| screen.line_rel(0,1) | |
| screen.line_rel(0,r) | |
| end | |
| screen.move(math.floor(x),math.floor(y)+(r*2)-4) | |
| screen.line_rel(1,0) | |
| screen.line_rel(r,0) | |
| screen.move(math.floor(x)+(r*4),math.floor(y)+(r*2)-4) | |
| screen.line_rel(1,0) | |
| screen.line_rel(r,0) | |
| screen.stroke() | |
| end | |
| -- | |
| -- utils | |
| -- | |
| function show_message(message) | |
| clock.run(function() | |
| uS.message=message | |
| redraw() | |
| clock.sleep(0.5) | |
| uS.message="" | |
| redraw() | |
| end) | |
| end | |
| function readAll(file) | |
| local f=assert(io.open(file,"rb")) | |
| local content=f:read("*all") | |
| f:close() | |
| return content | |
| end | |
| function calculate_lfo(current_time,period,offset) | |
| if period==0 then | |
| return 1 | |
| else | |
| return math.sin(2*math.pi*current_time/period+offset) | |
| end | |
| end | |
| function round(x) | |
| return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) | |
| end | |
| function sign(x) | |
| if x>0 then | |
| return 1 | |
| elseif x<0 then | |
| return-1 | |
| else | |
| return 0 | |
| end | |
| end | |
| function round_time_to_nearest_beat(t) | |
| seconds_per_qn=60/clock.get_tempo() | |
| remainder=t%seconds_per_qn | |
| if remainder==0 then | |
| return t | |
| end | |
| return t+seconds_per_qn-remainder | |
| end | |
| local function unquote(s) | |
| return s:gsub('^"',''):gsub('"$',''):gsub('\\"','"') | |
| end | |
| function rerun() | |
| norns.script.load(norns.state.script) | |
| end | |
| params_read_silent=function(fname) | |
| fh,err=io.open(fname) | |
| if err then print("no file");return;end | |
| while true do | |
| line=fh:read() | |
| if line==nil then break end | |
| local par_name,par_value=string.match(line,"(\".-\")%s*:%s*(.*)") | |
| if par_name and par_value then | |
| par_name=unquote(par_name) | |
| if type(tonumber(par_value))=="number" then | |
| par_value=tonumber(par_value) | |
| elseif par_value=="-inf" then | |
| par_value=-1*math.huge | |
| elseif par_value=="inf" then | |
| par_value=math.huge | |
| end | |
| pcall(function() params:set(par_name,par_value,true) end) | |
| else | |
| print(par_name,par_value) | |
| end | |
| end | |
| fh:close() | |
| end | |
| function setup_sharing(script_name) | |
| if not util.file_exists(_path.code.."norns.online") then | |
| print("need to donwload norns.online") | |
| do return end | |
| end | |
| local share=include("norns.online/lib/share") | |
| -- start uploader with name of your script | |
| local uploader=share:new{script_name=script_name} | |
| if uploader==nil then | |
| print("uploader failed, no username?") | |
| do return end | |
| end | |
| -- add parameters | |
| params:add_group("SHARE",4) | |
| -- uploader (CHANGE THIS TO FIT WHAT YOU NEED) | |
| -- select a save | |
| local names_dir=DATA_DIR.."names/" | |
| params:add_file("share_upload","upload",names_dir) | |
| params:set_action("share_upload",function(y) | |
| -- prevent banging | |
| local x=y | |
| params:set("share_download",names_dir) | |
| if #x<=#names_dir then | |
| do return end | |
| end | |
| -- choose data name | |
| -- (here dataname is from the selector) | |
| local dataname=share.trim_prefix(x,DATA_DIR.."names/") | |
| params:set("share_message","uploading...") | |
| _menu.redraw() | |
| print("uploading "..x.." as "..dataname) | |
| -- upload each loop | |
| local pathtofile = "" | |
| local target = "" | |
| for i=1,6 do | |
| pathtofile=DATA_DIR..dataname.."/loop"..i..".wav" | |
| target=DATA_DIR..uploader.upload_username.."-"..dataname.."/loop"..i..".wav" | |
| if util.file_exists(pathtofile) then | |
| msg = uploader:upload{dataname=dataname,pathtofile=pathtofile,target=target} | |
| if not string.match(msg,"OK") then | |
| params:set("share_message",msg) | |
| do return end | |
| end | |
| end | |
| end | |
| -- upload paramset | |
| pathtofile=DATA_DIR..dataname.."/parameters.pset" | |
| target=DATA_DIR..uploader.upload_username.."-"..dataname.."/parameters.pset" | |
| uploader:upload{dataname=dataname,pathtofile=pathtofile,target=target} | |
| -- upload uP | |
| pathtofile=DATA_DIR..dataname.."/uP.txt" | |
| target=DATA_DIR..uploader.upload_username.."-"..dataname.."/uP.txt" | |
| uploader:upload{dataname=dataname,pathtofile=pathtofile,target=target} | |
| -- upload name file | |
| pathtofile=DATA_DIR.."names/"..dataname | |
| target=DATA_DIR.."names/"..uploader.upload_username.."-"..dataname | |
| uploader:upload{dataname=dataname,pathtofile=pathtofile,target=target} | |
| -- goodbye | |
| params:set("share_message","uploaded.") | |
| end) | |
| -- downloader | |
| download_dir=share.get_virtual_directory(script_name) | |
| params:add_file("share_download","download",download_dir) | |
| params:set_action("share_download",function(y) | |
| -- prevent banging | |
| local x=y | |
| params:set("share_download",download_dir) | |
| if #x<=#download_dir then | |
| do return end | |
| end | |
| -- download | |
| print("downloading!") | |
| params:set("share_message","downloading...") | |
| _menu.redraw() | |
| local msg=share.download_from_virtual_directory(x) | |
| params:set("share_message",msg) | |
| end) | |
| params:add{type='binary',name='refresh directory',id='share_refresh',behavior='momentary',action=function(v) | |
| print("updating directory") | |
| params:set("share_message","refreshing directory.") | |
| _menu.redraw() | |
| share.make_virtual_directory() | |
| params:set("share_message","directory updated.") | |
| end | |
| } | |
| params:add_text('share_message',">","") | |
| end | |