├── README.md ├── lib ├── doc.png ├── _utils.lua ├── linn.lua ├── browser.lua ├── sampler.lua ├── timber_takt.lua ├── ui.lua └── Engine_Timber_Takt.sc ├── .github └── FUNDING.yml └── takt.lua /README.md: -------------------------------------------------------------------------------- 1 | ![docs](lib/doc.png) 2 | -------------------------------------------------------------------------------- /lib/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsyourbedtime/takt/HEAD/lib/doc.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: its_your_bedtime 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /lib/_utils.lua: -------------------------------------------------------------------------------- 1 | local utils = {} 2 | 3 | 4 | local function default_engine_params(i) 5 | local default = { 6 | lock = 0, offset = 0, rule = 0, retrig = 0, div = 5, 7 | --- 8 | sample = i, note = 60, play_mode = 3, 9 | quality = 5, amp = 0, pan = 0, detune_cents = 0, 10 | start_frame = 0, loop_start_frame = 0, end_frame = 2000000000, loop_end_frame = 2000000000, 11 | --- 12 | amp_env_attack = 0, amp_env_decay = 1, 13 | amp_env_sustain = 1, amp_env_release = 0, 14 | -- 15 | filter_type = 1, filter_freq = 20000, filter_resonance = 0, 16 | -- 17 | freq_mod_lfo_1 = 0, freq_mod_lfo_2 = 0, 18 | amp_mod_lfo_1 = 0, filter_freq_mod_lfo_2 = 0, 19 | -- 20 | reverb_send = -48, delay_send = -48, sidechain_send = -99 21 | } 22 | return default 23 | end 24 | 25 | local function default_midi_params(i) 26 | local default = { 27 | lock = 0, offset = 0, rule = 0, retrig = 0, div = 5, 28 | --- 29 | device = 1, note = 74 - i , length = 1, 30 | channel = 1, velocity = 100, program_change = -1, 31 | --- 32 | cc_1 = 1, cc_1_val = -1, cc_2 = 2, cc_2_val = -1, 33 | cc_3 = 3, cc_3_val = -1, cc_4 = 4, cc_4_val = -1, 34 | cc_5 = 5, cc_5_val = -1, cc_6 = 6, cc_6_val = -1, 35 | } 36 | return default 37 | end 38 | 39 | function utils.make_default_pattern() 40 | 41 | local default = { 42 | bpm = 120, 43 | sync_div = 0, 44 | track = { 45 | mute = {}, 46 | pos = {}, 47 | start = {}, 48 | len = {}, 49 | div = {}, 50 | cycle = {}, 51 | }, 52 | } 53 | 54 | for i = 1, 14 do 55 | default.track.mute[i] = false 56 | default.track.pos[i] = 0 57 | default.track.start[i] = 1 58 | default.track.len[i] = 256 59 | default.track.div[i] = 5 60 | default.track.cycle[i] = 1 61 | end 62 | 63 | 64 | for l = 1, 7 do 65 | local m = l + 7 66 | 67 | default[l] = {} 68 | default[m] = {} 69 | default[l].params = {} 70 | default[m].params = {} 71 | default[l].params[tostring(l)] = default_engine_params(l) 72 | default[m].params[tostring(m)] = default_midi_params(m) 73 | 74 | for i=0,256 do 75 | default[l][i] = 0 76 | default[m][i] = 0 77 | default[l].params[i] = {} 78 | default[m].params[i] = {} 79 | setmetatable(default[l].params[i], {__index = default[l].params[tostring(l)]}) 80 | setmetatable(default[m].params[i], {__index = default[m].params[tostring(m)]}) 81 | 82 | end 83 | end 84 | 85 | return default 86 | 87 | end 88 | 89 | return utils -------------------------------------------------------------------------------- /lib/linn.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ LINN 4 | -- ////\\\\ BY NEAUOIRE 5 | -- \\\\//// 6 | -- \\\\//// LINN LAYOUT 7 | -- \\\\//// 8 | -- 9 | -- lib 10 | 11 | local linn = {} 12 | 13 | 14 | local focus = { x = 0, y = 0 } 15 | local keys = { 'C','C#','D','D#','E','F','F#','G','G#','A','A#','B' } 16 | 17 | local notes = { 18 | 'F4', 'F4#', 'G4', 'G4#', 'A4', 'A4#', 'B4', 'C5', 'C5#', 'D5', 'D5#', 'E5', 'F5', 'F5#', 'G5', 'G5#', 19 | 'C4', 'C4#', 'D4', 'D4#', 'E4', 'F4', 'F4#', 'G4', 'G4#', 'A4', 'A4#', 'B4', 'C5', 'C5#', 'D5', 'D5#', 20 | 'G3', 'G3#', 'A3', 'A3#', 'B3', 'C4', 'C4#', 'D4', 'D4#', 'E4', 'F4', 'F4#', 'G4', 'G4#', 'A4', 'A4#', 21 | 'D3', 'D3#', 'E3', 'F3', 'F3#', 'G3', 'G3#', 'A3', 'A3#', 'B3', 'C4', 'C4#', 'D4', 'D4#', 'E4', 'F4', 22 | 'A2', 'A2#', 'B2', 'C4', 'C3#', 'D3', 'D3#', 'E3', 'F3', 'F3#', 'G3', 'G3#', 'A3', 'A3#', 'B3', 'C4', 23 | 'E2', 'F2', 'F2#', 'G2', 'G2#', 'A2', 'A2#', 'B2', 'C3', 'C3#', 'D3', 'D3#', 'E3', 'F3', 'F3#', 'G3', 24 | 'B1', 'C2', 'C2#', 'D2', 'D2#', 'E2', 'F2', 'F2#', 'G2', 'G2#', 'A2', 'A2#', 'B2', 'C3', 'C3#', 'D3', 25 | 'F1#', 'G1', 'G1#', 'A1', 'A1#', 'B1', 'C2', 'C2#', 'D2', 'D2#', 'E2', 'F2', 'F2#', 'G2', 'G2#', 'A2', 26 | } 27 | 28 | 29 | local index_of = function(list,value) 30 | for i=1,#list do 31 | if list[i] == value then return i end 32 | end 33 | return -1 34 | end 35 | 36 | -- Main 37 | 38 | function linn.note_at(i) 39 | local n = notes[i] 40 | local k = n:sub(1, 1) 41 | local o = tonumber(n:sub(2, 2)) 42 | local s = n:match('#') 43 | local p = n:gsub(o,'') 44 | local v = index_of(keys, p) + (12 * (o + 2)) - 1 45 | local l = 0 46 | 47 | if p == 'C' then 48 | l = 15 49 | elseif s then 50 | l = 2 51 | else 52 | l = 6 53 | end 54 | 55 | return { i = i, k = k, o = o, s = s, v = v, l = l, p = p } 56 | end 57 | 58 | function linn.pos_at(id) 59 | return { x = ((id-1) % 16) + 1, y = math.floor(id / 16) + 1 } 60 | end 61 | 62 | function linn.id_at(x,y) 63 | return ((y-1) * 16) + x 64 | end 65 | 66 | 67 | function linn.on_grid_key_down(x,y, m) 68 | focus.x = x 69 | focus.y = y 70 | if m then m:note_on(linn.note_at(linn.id_at(x,y)).v,127) end 71 | end 72 | 73 | function linn.on_grid_key_up(x,y, m) 74 | focus.x = 0 75 | focus.y = 0 76 | if m then m:note_off(linn.note_at(linn.id_at(x,y)).v,127) end 77 | end 78 | 79 | 80 | function linn.grid_key(x, y, z, m)--, tr, sample) 81 | if y < 8 then 82 | if z == 1 then 83 | linn.on_grid_key_down(x, y, m) 84 | else 85 | linn.on_grid_key_up(x, y, m) 86 | return false 87 | end 88 | return linn.note_at(linn.id_at(x,y)).v 89 | end 90 | end 91 | 92 | function linn.grid_redraw(g) 93 | for i=1, 111 do 94 | pos = linn.pos_at(i) 95 | note = linn.note_at(i) 96 | g:led(pos.x,pos.y, note.l) 97 | end 98 | g:led(focus.x,focus.y, 10) 99 | g:led(16,1,3) 100 | end 101 | 102 | 103 | return linn -------------------------------------------------------------------------------- /lib/browser.lua: -------------------------------------------------------------------------------- 1 | local fs = {} 2 | -- fileselect mod 3 | 4 | 5 | function fs.enter(folder, callback, id) 6 | fs.s_offset = 0 7 | fs.bounds_y = 6 8 | fs.open = true 9 | fs.folders = {} 10 | fs.list = {} 11 | fs.display_list = {} 12 | fs.lengths = {} 13 | fs.pos = 0 14 | fs.depth = 0 15 | fs.folder = folder 16 | fs.callback = callback 17 | fs.done = false 18 | fs.path = nil 19 | fs.sample_id = id 20 | 21 | 22 | if fs.folder:sub(-1,-1) ~= "/" then 23 | fs.folder = fs.folder .. "/" 24 | end 25 | 26 | fs.getlist() 27 | 28 | end 29 | 30 | function fs.exit() 31 | if (fs.path and fs.callback) then fs.callback(fs.sample_id, fs.path) params:set('play_mode_' .. fs.sample_id, 2) end 32 | fs.open = false 33 | engine.noteOff(100) 34 | end 35 | 36 | 37 | fs.getdir = function() 38 | local path = fs.folder 39 | for k,v in pairs(fs.folders) do 40 | path = path .. v 41 | end 42 | return path 43 | end 44 | 45 | fs.getlist = function() 46 | local dir = fs.getdir() 47 | fs.list = util.scandir(dir) 48 | fs.display_list = {} 49 | fs.lengths = {} 50 | fs.len = #fs.list 51 | fs.pos = 0 52 | fs.s_offset = 0 53 | 54 | -- Generate display list and lengths 55 | for k, v in ipairs(fs.list) do 56 | local line = v 57 | local max_line_length = 76 58 | line = util.trim_string_to_width(line, max_line_length) 59 | fs.display_list[k] = line 60 | end 61 | end 62 | 63 | fs.preview_sample = function(z) 64 | if z == 1 then 65 | fs.file = fs.list[fs.pos+1] 66 | if string.find(fs.file,'/') then 67 | print('cant play folder') 68 | else 69 | local path = fs.folder 70 | for k,v in pairs(fs.folders) do 71 | path = path .. v 72 | end 73 | fs.path = path .. fs.file 74 | fs.preview = true 75 | params:set("sample_100", fs.path) 76 | -- small delay to get it all working 77 | local m = metro.init() 78 | m.time = 0.3 79 | m.count = 1 80 | m.event = function() 81 | engine.playMode(100, 2) 82 | engine.noteOn(100, 261.6, 0.7, 100 ) 83 | end 84 | m:start() 85 | end 86 | elseif z == 0 then 87 | engine.noteOff(100) 88 | fs.preview = false 89 | end 90 | end 91 | 92 | fs.key = function(n,z) 93 | if n == 1 then 94 | fs.preview_sample(z) 95 | elseif n==2 and z==1 then 96 | if fs.depth > 0 then 97 | fs.folders[fs.depth] = nil 98 | fs.depth = fs.depth - 1 99 | fs.getlist() 100 | else 101 | fs.path = nil 102 | fs.exit() 103 | end 104 | -- select 105 | elseif n==3 and z==1 then 106 | if #fs.list > 0 then 107 | fs.file = fs.list[fs.pos+1] 108 | if string.find(fs.file,'/') then 109 | fs.depth = fs.depth + 1 110 | fs.folders[fs.depth] = fs.file 111 | fs.getlist() 112 | else 113 | local path = fs.folder 114 | for k,v in pairs(fs.folders) do 115 | path = path .. v 116 | end 117 | fs.path = path .. fs.file 118 | fs.done = true 119 | end 120 | end 121 | 122 | elseif z == 0 and fs.done == true then 123 | fs.exit() 124 | end 125 | end 126 | 127 | fs.update_offset = function(val, y, length, bounds, offset) 128 | if y > val + (8 - offset) then 129 | val = util.clamp( val + 1, 0, length - bounds) 130 | elseif y < bounds + ( val - (7 - offset)) then 131 | val = util.clamp( val - 1, 0, length - bounds) 132 | end 133 | return val 134 | end 135 | 136 | 137 | 138 | fs.enc = function(n,d) 139 | if n==2 then 140 | fs.pos = util.clamp(fs.pos + d, 0, fs.len - 1) 141 | fs.s_offset = fs.update_offset(fs.s_offset, fs.pos+1, fs.len, fs.bounds_y, 2) 142 | end 143 | end 144 | 145 | 146 | fs.redraw = function() 147 | 148 | screen.level(0) 149 | screen.rect(43, 8, 83, 53) 150 | screen.fill() 151 | screen.level(2) 152 | screen.rect(44, 9, 82, 52) 153 | screen.stroke() 154 | 155 | 156 | if #fs.list == 0 then 157 | screen.level(4) 158 | screen.move(45, 15) 159 | screen.text("(no files)") 160 | else 161 | for i= 1, fs.bounds_y do 162 | local list_index = i + (fs.s_offset) 163 | 164 | screen.level(4) 165 | screen.move(46, 8 + 8 * i ) 166 | screen.level(fs.pos + 1 == list_index and 9 or 2) 167 | screen.text(fs.display_list[list_index] or '') 168 | screen.stroke() 169 | end 170 | if #fs.list > 6 then 171 | screen.level(1) 172 | screen.rect(123, 10 + (fs.pos), 1, 50 - #fs.list) 173 | screen.fill() 174 | end 175 | end 176 | end 177 | 178 | 179 | return fs -------------------------------------------------------------------------------- /lib/sampler.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- sampler 3 | -- @its_your_bedtime 4 | -- 5 | 6 | local sampler = { 7 | play = false, 8 | rec = false, 9 | pos = 0, 10 | start = 0, 11 | length = 60, 12 | max_length = 60, 13 | rec_length = 0, 14 | mode = 1, 15 | source = 1, 16 | slot = 1, 17 | 18 | } 19 | 20 | local load_sample = include("lib/timber_takt").load_sample 21 | local wait_metro 22 | 23 | sampler.phase = function(t, x) 24 | sampler.pos = x 25 | 26 | if sampler.pos >= sampler.length then 27 | sampler.pos = sampler.start 28 | 29 | for i = 1, 2 do 30 | softcut.position(i, sampler.start) 31 | softcut.play(i, 0) 32 | softcut.rec(i, 0) 33 | end 34 | end 35 | end 36 | 37 | 38 | function sampler.get_pos() 39 | return sampler.pos 40 | end 41 | 42 | function sampler.get_len() 43 | return sampler.length 44 | end 45 | 46 | function sampler.get_state() 47 | return recording or playing and true or false 48 | end 49 | 50 | 51 | function sampler.init() 52 | -- softcut 53 | audio.level_cut(0.5) 54 | audio.level_adc_cut(1) 55 | audio.level_eng_cut(0) 56 | softcut.level_input_cut(1, 1, 1) 57 | softcut.level_input_cut(2, 1, 0) 58 | softcut.level_input_cut(1, 2, 0) 59 | softcut.level_input_cut(2, 2, 1) 60 | softcut.pan(1,-1) 61 | softcut.pan(2,1) 62 | 63 | for i=1, 2 do 64 | softcut.level(i,1) 65 | softcut.level_slew_time(i,0.1) 66 | softcut.play(i, 0) 67 | softcut.rate(i, 1) 68 | softcut.rate_slew_time(i,0.5) 69 | softcut.loop_start(i, 0) 70 | softcut.loop_end(i, 60) 71 | softcut.loop(i, 0) 72 | softcut.fade_time(i, 0.1) 73 | softcut.rec(i, 0) 74 | softcut.rec_level(i, 0.7) 75 | softcut.pre_level(i, 0) 76 | softcut.position(i, 0) 77 | softcut.buffer(i, i) 78 | softcut.enable(i, 1) 79 | softcut.filter_dry(i, 1) 80 | end 81 | 82 | softcut.phase_quant(1, .01) 83 | softcut.event_phase(sampler.phase) 84 | 85 | wait_metro = metro.init() 86 | wait_metro.time = 0.01 87 | wait_metro.count = -1 88 | sampler.rec = false 89 | end 90 | 91 | 92 | function sampler.set_mode() 93 | local mode = sampler.mode 94 | if mode == 1 then -- stereo 95 | -- set softcut to stereo inputs 96 | softcut.level_input_cut(1, 1, 1) 97 | softcut.level_input_cut(2, 1, 0) 98 | softcut.level_input_cut(1, 2, 0) 99 | softcut.level_input_cut(2, 2, 1) 100 | elseif mode == 2 then -- mono L + R 101 | softcut.level_input_cut(1, 1, 1) 102 | softcut.level_input_cut(2, 1, 1) 103 | softcut.level_input_cut(1, 2, 1) 104 | softcut.level_input_cut(2, 2, 1) 105 | elseif mode == 3 then -- mono - L 106 | softcut.level_input_cut(1, 1, 1) 107 | softcut.level_input_cut(2, 1, 0) 108 | softcut.level_input_cut(1, 2, 1) 109 | softcut.level_input_cut(2, 2, 0) 110 | elseif mode == 4 then -- mono - R 111 | softcut.level_input_cut(1, 1, 0) 112 | softcut.level_input_cut(2, 1, 1) 113 | softcut.level_input_cut(1, 2, 0) 114 | softcut.level_input_cut(2, 2, 1) 115 | end 116 | end 117 | 118 | function sampler.set_source() 119 | local src = sampler.source 120 | if src == 1 then -- ext 121 | audio.level_adc_cut(1) 122 | audio.level_eng_cut(0) 123 | elseif src == 2 then -- int 124 | audio.level_adc_cut(0) 125 | audio.level_eng_cut(1) 126 | end 127 | end 128 | 129 | 130 | function sampler.rec(state) 131 | if state == 1 then 132 | sampler.rec = not sampler.rec 133 | end 134 | 135 | for i = 1, 2 do 136 | if sampler.rec then 137 | sampler.clear(1) 138 | softcut.loop_start(i, 0) 139 | softcut.loop_end(i, 60) 140 | softcut.poll_start_phase() 141 | softcut.position(i, 0) 142 | softcut.rec(i, 1) 143 | else 144 | sampler.length = sampler.pos 145 | sampler.rec_length = sampler.pos 146 | softcut.poll_stop_phase() 147 | softcut.rec(i, 0) 148 | sampler.start = 0 149 | softcut.position(i, 0) 150 | end 151 | end 152 | 153 | end 154 | 155 | 156 | function sampler.play(state) 157 | sampler.play = state == 1 and true or false 158 | for i = 1, 2 do 159 | if sampler.play then 160 | softcut.poll_start_phase() 161 | softcut.position(i, sampler.start) 162 | softcut.play(i, 1) 163 | else 164 | sampler.pos = 0 165 | softcut.poll_stop_phase() 166 | softcut.play(i, 0) 167 | end 168 | end 169 | end 170 | 171 | 172 | function sampler.set_start(x) 173 | sampler.start = x 174 | for i = 1, 2 do 175 | softcut.position(i, x) 176 | softcut.loop_start(i, x) 177 | end 178 | end 179 | 180 | 181 | function sampler.set_length(x) 182 | sampler.length = x 183 | for i = 1, 2 do 184 | if sampler.pos > sampler.length then 185 | softcut.position(i, sampler.start) 186 | end 187 | softcut.loop_end(i, sampler.length) 188 | end 189 | end 190 | 191 | 192 | function sampler.clear(state) 193 | if state ~= 1 then return false end 194 | sampler.start = 0 195 | sampler.length = sampler.max_length 196 | sampler.rec_length = 0 197 | sampler.pos = 0 198 | softcut.buffer_clear() 199 | for i = 1, 2 do 200 | softcut.position(i, 0) 201 | end 202 | 203 | end 204 | 205 | 206 | function sampler.save_and_load(state) 207 | if state == 1 then 208 | local PATH = _path.audio .. 'takt/' 209 | if not util.file_exists(PATH) then util.make_dir(PATH) end 210 | local name = 'sample_' .. #util.scandir(PATH) .. '.wav' 211 | local start = sampler.start 212 | local length = sampler.length 213 | local mode = sampler.mode 214 | local slot = sampler.slot 215 | 216 | --local name = os.date('%m%d%H%M') ..'_'.. slot 217 | 218 | print('saving', start, length) 219 | print(PATH..name) 220 | if mode == 1 then 221 | softcut.buffer_write_stereo (PATH .. name, start, length) 222 | elseif mode == 2 or mode == 3 then 223 | softcut.buffer_write_mono (PATH .. name, start, length, 1) 224 | elseif mode == 4 then 225 | softcut.buffer_write_mono (PATH .. name, start, length, 2) 226 | end 227 | 228 | wait_metro.event = function(stage) 229 | local ch, len = audio.file_info(PATH .. name) 230 | local ready = util.round(len / 48000, 0.1) == util.round(length, 0.1) and true or false 231 | 232 | if ready then 233 | load_sample(slot, PATH .. name) 234 | params:set('play_mode_' .. slot, 2) 235 | wait_metro:stop() 236 | end 237 | end 238 | 239 | wait_metro:start() 240 | end 241 | end 242 | 243 | return sampler -------------------------------------------------------------------------------- /lib/timber_takt.lua: -------------------------------------------------------------------------------- 1 | --- Timber Engine lib 2 | -- Engine params, functions and UI views. 3 | -- 4 | -- @module TimberEngine 5 | -- @release v1.0.0 Beta 6 6 | -- @author Mark Eats 7 | -- minor tweaks for takt 8 | engine.name = "Timber_Takt" 9 | 10 | 11 | local ControlSpec = require "controlspec" 12 | local Formatters = require "formatters" 13 | 14 | local Timber = {} 15 | 16 | Timber.FileSelect = require "fileselect" 17 | 18 | 19 | Timber.sample_changed_callback = function() end 20 | Timber.meta_changed_callback = function() end 21 | Timber.waveform_changed_callback = function() end 22 | Timber.play_positions_changed_callback = function() end 23 | 24 | local samples_meta = {} 25 | local specs = {} 26 | local options = {} 27 | 28 | local NUM_SAMPLES = 100 29 | local STREAMING_BUFFER_SIZE = 65536 30 | local MAX_FRAMES = 2000000000 31 | 32 | Timber.specs = specs 33 | Timber.options = options 34 | Timber.samples_meta = samples_meta 35 | Timber.num_sample_params = 0 36 | 37 | local param_ids = { 38 | "sample", "quality", "transpose", "detune_cents", "play_mode", "start_frame", "end_frame", "loop_start_frame", "loop_end_frame", 39 | "freq_mod_lfo_1", "freq_mod_lfo_2", "freq_mod_env", 40 | "filter_type", "filter_freq", "filter_resonance", "filter_freq_mod_lfo_1", "filter_freq_mod_lfo_2", "filter_freq_mod_env", "filter_freq_mod_vel", "filter_freq_mod_pressure", "filter_tracking", 41 | "pan", "pan_mod_lfo_1", "pan_mod_lfo_2", "pan_mod_env", "amp", "amp_mod_lfo_1", "amp_mod_lfo_2", 42 | "amp_env_attack", "amp_env_decay", "amp_env_sustain", "amp_env_release", 43 | "mod_env_attack", "mod_env_decay", "mod_env_sustain", "mod_env_release", 44 | "lfo_1_fade", "lfo_2_fade", "delay_send", "reverb_send", "sidechain_send" 45 | } 46 | 47 | local extra_param_ids = {} 48 | local beat_params = false 49 | 50 | options.PLAY_MODE_BUFFER = {"Loop", "Inf. Loop", "Gated", "1-Shot"} 51 | options.PLAY_MODE_BUFFER_DEFAULT = 2 52 | options.PLAY_MODE_STREAMING = {"Loop", "Gated", "1-Shot"} 53 | options.PLAY_MODE_STREAMING_DEFAULT = 2 54 | options.PLAY_MODE_IDS = {{0, 1, 2, 3}, {1, 2, 3}} 55 | 56 | 57 | specs.LFO_1_FREQ = ControlSpec.new(0.05, 20, "exp", 0, 2, "Hz") 58 | specs.LFO_2_FREQ = ControlSpec.new(0.05, 20, "exp", 0, 4, "Hz") 59 | options.LFO_WAVE_SHAPE = {"Sine", "Triangle", "Saw", "Square", "Random"} 60 | specs.LFO_FADE = ControlSpec.new(-10, 10, "lin", 0, 0, "s") 61 | 62 | options.FILTER_TYPE = {"Low Pass", "High Pass"} 63 | specs.FILTER_FREQ = ControlSpec.new(20, 20000, "exp", 0, 20000, "Hz") 64 | specs.FILTER_RESONANCE = ControlSpec.new(0, 1, "lin", 0, 0, "") 65 | specs.FILTER_TRACKING = ControlSpec.new(0, 2, "lin", 0, 1, ":1") 66 | specs.AMP_ENV_ATTACK = ControlSpec.new(0, 5, "lin", 0, 0, "s") 67 | specs.AMP_ENV_DECAY = ControlSpec.new(0.003, 5, "lin", 0, 1, "s") 68 | specs.AMP_ENV_SUSTAIN = ControlSpec.new(0, 1, "lin", 0, 1, "") 69 | specs.AMP_ENV_RELEASE = ControlSpec.new(0.003, 10, "lin", 0, 0.003, "s") 70 | specs.MOD_ENV_ATTACK = ControlSpec.new(0.003, 5, "lin", 0, 1, "s") 71 | specs.MOD_ENV_DECAY = ControlSpec.new(0.003, 5, "lin", 0, 2, "s") 72 | specs.MOD_ENV_SUSTAIN = ControlSpec.new(0, 1, "lin", 0, 0.65, "") 73 | specs.MOD_ENV_RELEASE = ControlSpec.new(0.003, 10, "lin", 0, 1, "s") 74 | options.QUALITY = {"Nasty", "Low", "SP-1200", "Medium", "High"} 75 | specs.AMP = ControlSpec.new(-48, 16, 'db', 0, 0, "dB") 76 | 77 | specs.DELAY_SEND = ControlSpec.new(-99, 0, 'db', 0, -99, "dB") 78 | specs.DELAY_TIME = ControlSpec.new(0.0001, 3, 'exp', 0, 0.1, 's') 79 | specs.DELAY_FEEDBACK = ControlSpec.new(0, 1, 'lin', 0, 0.5, '') 80 | specs.DELAY_LEVEL = ControlSpec.DB:copy() 81 | specs.DELAY_LEVEL.default = -10 82 | 83 | specs.REVERB_SEND = ControlSpec.new(-99, 0, 'db', 0, -99, "dB") 84 | specs.REVERB_TIME = controlspec.new(0.1, 60.00, "lin", 0.01, 10, "s") 85 | specs.REVERB_DAMP = controlspec.new(0.0, 1.0, "lin", 0.01, 0.1, "") 86 | specs.REVERB_SIZE = controlspec.new(0, 5.0, "lin", 0.01, 3.00, "") 87 | specs.REVERB_DIFF = controlspec.new(0.0, 1.0, "lin", 0.01, 0.707, "") 88 | specs.REVERB_MOD_DEPTH = controlspec.new(0.0, 1.0, "lin", 0.01, 0.1, "") 89 | specs.REVERB_MOD_FREQ = controlspec.new(0.0, 10.0, "lin", 0.01, 2, "hz") 90 | specs.REVERB_MULT = controlspec.new(0.0, 1.0, "lin", 0.01, 1, "") 91 | specs.REVERB_LOWCUT = controlspec.new(100, 6000, "lin", 0.01, 500, "hz") 92 | specs.REVERB_HIGHCUT = controlspec.new(1000, 10000, "lin", 0.01, 2000, "hz") 93 | specs.COMP_SEND = ControlSpec.new(-99, 0, 'db', 0, -99, "dB") 94 | specs.COMP_LEVEL = ControlSpec.new(-99, 6, 'db', 0, 0, "dB") 95 | specs.COMP_MIX = ControlSpec.new(-1, 1, "lin", 0.01, -1, "") 96 | specs.COMP_THRESHOLD = ControlSpec.new(0.005, 1, "exp", 0.001, 0.1, "") 97 | specs.COMP_SLOPEBELOW = ControlSpec.new(0.7, 1.2, "lin", 0.01, 1, "") 98 | specs.COMP_SLOPEABOVE = ControlSpec.new(0, 1, "lin", 0.001, 0.1, "") 99 | specs.COMP_CLAMPTIME = ControlSpec.new(0.0001, 1, 'exp', 0.001, 0.01, 's') 100 | specs.COMP_RELAXTIME = ControlSpec.new(0.0001, 1, 'exp', 0.001, 0.1, 's') 101 | 102 | 103 | 104 | -- 27khz - 8 bit, 105 | QUALITY_SAMPLE_RATES = { 8000, 16000, 26040, 32000, 48000 } 106 | QUALITY_BIT_DEPTHS = { 8, 12, 12, 16, 24 } 107 | 108 | local function default_sample() 109 | local sample = { 110 | manual_load = false, 111 | streaming = 0, 112 | num_frames = 0, 113 | num_channels = 0, 114 | sample_rate = 0, 115 | freq_multiplier = 1, 116 | playing = false, 117 | positions = {}, 118 | waveform = {} 119 | } 120 | return sample 121 | end 122 | 123 | -- Meta data 124 | -- These are index zero to align with SC and MIDI note numbers 125 | for i = 1, 100 do 126 | samples_meta[i] = default_sample() 127 | end 128 | 129 | -- Functions 130 | 131 | local function copy_table(obj) 132 | if type(obj) ~= "table" then return obj end 133 | local result = setmetatable({}, getmetatable(obj)) 134 | for k, v in pairs(obj) do result[copy_table(k)] = copy_table(v) end 135 | return result 136 | end 137 | 138 | local function lookup_play_mode(sample_id) 139 | return options.PLAY_MODE_IDS[samples_meta[sample_id].streaming + 1][params:get("play_mode_" .. sample_id)] 140 | end 141 | 142 | 143 | local function set_play_mode(id, play_mode) 144 | engine.playMode(id, play_mode) 145 | if samples_meta[id].streaming == 1 then 146 | local start_frame = params:get("start_frame_" .. id) 147 | params:set("start_frame_" .. id, start_frame - 1) 148 | params:set("start_frame_" .. id, start_frame) 149 | end 150 | end 151 | 152 | function Timber.load_sample(id, file) 153 | samples_meta[id].manual_load = true 154 | params:set("sample_" .. id, file) 155 | end 156 | 157 | local function sample_loaded(id, streaming, num_frames, num_channels, sample_rate) 158 | 159 | samples_meta[id].streaming = streaming 160 | samples_meta[id].num_frames = num_frames 161 | samples_meta[id].num_channels = num_channels 162 | samples_meta[id].sample_rate = sample_rate 163 | samples_meta[id].freq_multiplier = 1 164 | samples_meta[id].playing = false 165 | samples_meta[id].positions = {} 166 | samples_meta[id].waveform = {} 167 | 168 | local start_frame = params:get("start_frame_" .. id) 169 | local end_frame = params:get("end_frame_" .. id) 170 | --local by_length = params:get("by_length_" .. id) 171 | 172 | local start_frame_max = num_frames 173 | if streaming == 1 then 174 | start_frame_max = start_frame_max - STREAMING_BUFFER_SIZE 175 | end 176 | params:lookup_param("start_frame_" .. id).controlspec.maxval = start_frame_max 177 | params:lookup_param("end_frame_" .. id).controlspec.maxval = num_frames 178 | 179 | local play_mode_param = params:lookup_param("play_mode_" .. id) 180 | if streaming == 0 then 181 | play_mode_param.options = options.PLAY_MODE_BUFFER 182 | play_mode_param.count = #options.PLAY_MODE_BUFFER 183 | else 184 | play_mode_param.options = options.PLAY_MODE_STREAMING 185 | play_mode_param.count = #options.PLAY_MODE_STREAMING 186 | end 187 | 188 | local duration = num_frames / sample_rate 189 | 190 | -- Set defaults 191 | if samples_meta[id].manual_load then 192 | if streaming == 0 then 193 | params:set("play_mode_" .. id, options.PLAY_MODE_BUFFER_DEFAULT) 194 | else 195 | params:set("play_mode_" .. id, options.PLAY_MODE_STREAMING_DEFAULT) 196 | end 197 | 198 | params:set("start_frame_" .. id, 1) -- Odd little hack to make sure it actually gets set 199 | params:set("start_frame_" .. id, 0) 200 | params:set("end_frame_" .. id, 1) 201 | params:set("end_frame_" .. id, num_frames) 202 | params:set("loop_start_frame_" .. id, 1) 203 | params:set("loop_start_frame_" .. id, 0) 204 | params:set("loop_end_frame_" .. id, 1) 205 | params:set("loop_end_frame_" .. id, num_frames) 206 | 207 | params:set("transpose_" .. id, 0) 208 | params:set("detune_cents_" .. id, 0) 209 | 210 | else 211 | -- These need resetting after having their ControlSpecs altered 212 | params:set("start_frame_" .. id, start_frame) 213 | params:set("end_frame_" .. id, end_frame) 214 | --params:set("by_length_" .. id, by_length) 215 | 216 | set_play_mode(id, lookup_play_mode(id)) 217 | end 218 | 219 | samples_meta[id].manual_load = false 220 | end 221 | 222 | local function sample_load_failed(id, error_status) 223 | 224 | samples_meta[id] = default_sample() 225 | samples_meta[id].error_status = error_status 226 | 227 | samples_meta[id].manual_load = false 228 | end 229 | 230 | function Timber.clear_samples(first, last) 231 | first = first or 1 232 | last = last or first 233 | if last < first then last = first end 234 | 235 | engine.clearSamples(first, last) 236 | 237 | local extended_params = {} 238 | for _, v in pairs(param_ids) do table.insert(extended_params, v) end 239 | 240 | for i = first, last do 241 | 242 | samples_meta[i] = default_sample() 243 | 244 | -- Set all params to default without firing actions 245 | for k, v in pairs(extended_params) do 246 | local param = params:lookup_param(v .. "_" .. i) 247 | local param_action = param.action 248 | param.action = function(value) end 249 | if param.t == 3 then -- Control 250 | params:set(v .. "_" .. i, param.controlspec.default) 251 | elseif param.t == 4 then -- File 252 | params:set(v .. "_" .. i, "-") 253 | elseif param.t ~= 6 then -- Not trigger 254 | params:set(v .. "_" .. i, param.default) 255 | end 256 | param.action = param_action 257 | end 258 | 259 | end 260 | 261 | end 262 | 263 | 264 | 265 | local function store_waveform(id, offset, padding, waveform_blob) 266 | 267 | for i = 1, string.len(waveform_blob) - padding do 268 | 269 | local value = string.byte(string.sub(waveform_blob, i, i + 1)) 270 | value = util.linlin(0, 126, -1, 1, value) 271 | 272 | local frame_index = math.ceil(i / 2) + offset 273 | if i % 2 > 0 then 274 | samples_meta[id].waveform[frame_index] = {} 275 | samples_meta[id].waveform[frame_index][1] = value -- Min 276 | else 277 | samples_meta[id].waveform[frame_index][2] = value -- Max 278 | end 279 | end 280 | 281 | Timber.waveform_changed_callback(id) 282 | end 283 | 284 | local function play_position(id, voice_id, position) 285 | 286 | samples_meta[id].positions[voice_id] = position 287 | Timber.play_positions_changed_callback(id) 288 | 289 | if not samples_meta[id].playing then 290 | samples_meta[id].playing = true 291 | Timber.meta_changed_callback(id) 292 | end 293 | end 294 | 295 | local function voice_freed(id, voice_id) 296 | samples_meta[id].positions[voice_id] = nil 297 | samples_meta[id].playing = false 298 | for _, _ in pairs(samples_meta[id].positions) do 299 | samples_meta[id].playing = true 300 | break 301 | end 302 | Timber.meta_changed_callback(id) 303 | Timber.play_positions_changed_callback(id) 304 | end 305 | 306 | local function set_marker(id, param_prefix) 307 | 308 | -- Updates start frame, end frame, loop start frame, loop end frame all at once to make sure everything is valid 309 | 310 | local start_frame = params:get("start_frame_" .. id) 311 | local end_frame = params:get("end_frame_" .. id) 312 | 313 | if samples_meta[id].streaming == 0 then -- Buffer 314 | 315 | local loop_start_frame = params:get("loop_start_frame_" .. id) 316 | local loop_end_frame = params:get("loop_end_frame_" .. id) 317 | 318 | local first_frame = math.min(start_frame, end_frame) 319 | local last_frame = math.max(start_frame, end_frame) 320 | 321 | -- Set loop min and max 322 | params:lookup_param("loop_start_frame_" .. id).controlspec.minval = first_frame 323 | params:lookup_param("loop_start_frame_" .. id).controlspec.maxval = last_frame 324 | params:lookup_param("loop_end_frame_" .. id).controlspec.minval = first_frame 325 | params:lookup_param("loop_end_frame_" .. id).controlspec.maxval = last_frame 326 | 327 | local SHORTEST_LOOP = 100 328 | if loop_start_frame > loop_end_frame - SHORTEST_LOOP then 329 | if param_prefix == "loop_start_frame_" then 330 | loop_end_frame = loop_start_frame + SHORTEST_LOOP 331 | elseif param_prefix == "loop_end_frame_" then 332 | loop_start_frame = loop_end_frame - SHORTEST_LOOP 333 | end 334 | end 335 | 336 | -- Set loop start and end 337 | params:set("loop_start_frame_" .. id, loop_start_frame - 1, true) -- Hack to make sure it gets set 338 | params:set("loop_start_frame_" .. id, loop_start_frame, true) 339 | params:set("loop_end_frame_" .. id, loop_end_frame + 1, true) 340 | params:set("loop_end_frame_" .. id, loop_end_frame, true) 341 | 342 | if param_prefix == "loop_start_frame_" or loop_start_frame ~= params:get("loop_start_frame_" .. id) then 343 | engine.loopStartFrame(id, params:get("loop_start_frame_" .. id)) 344 | end 345 | if param_prefix == "loop_end_frame_" or loop_end_frame ~= params:get("loop_end_frame_" .. id) then 346 | engine.loopEndFrame(id, params:get("loop_end_frame_" .. id)) 347 | end 348 | 349 | 350 | else -- Streaming 351 | 352 | if param_prefix == "start_frame_" then 353 | params:lookup_param("end_frame_" .. id).controlspec.minval = params:get("start_frame_" .. id) 354 | end 355 | 356 | if lookup_play_mode(id) < 2 then 357 | params:lookup_param("start_frame_" .. id).controlspec.maxval = samples_meta[id].num_frames - STREAMING_BUFFER_SIZE 358 | else 359 | params:lookup_param("start_frame_" .. id).controlspec.maxval = params:get("end_frame_" .. id) 360 | end 361 | 362 | end 363 | 364 | -- Set start and end 365 | params:set("start_frame_" .. id, start_frame - 1, true) 366 | params:set("start_frame_" .. id, start_frame, true) 367 | params:set("end_frame_" .. id, end_frame + 1, true) 368 | params:set("end_frame_" .. id, end_frame, true) 369 | 370 | if param_prefix == "start_frame_" or start_frame ~= params:get("start_frame_" .. id) then 371 | engine.startFrame(id, params:get("start_frame_" .. id)) 372 | end 373 | if param_prefix == "end_frame_" or end_frame ~= params:get("end_frame_" .. id) then 374 | engine.endFrame(id, params:get("end_frame_" .. id)) 375 | end 376 | 377 | waveform_last_edited = {id = id, param = param_prefix .. id} 378 | end 379 | 380 | function Timber.osc_event(path, args, from) 381 | 382 | if path == "/engineSampleLoaded" then 383 | sample_loaded(args[1], args[2], args[3], args[4], args[5]) 384 | 385 | elseif path == "/engineSampleLoadFailed" then 386 | sample_load_failed(args[1], args[2]) 387 | 388 | elseif path == "/engineWaveform" then 389 | store_waveform(args[1], args[2], args[3], args[4]) 390 | 391 | elseif path == "/enginePlayPosition" then 392 | play_position(args[1], args[2], args[3]) 393 | 394 | elseif path == "/engineVoiceFreed" then 395 | voice_freed(args[1], args[2]) 396 | 397 | end 398 | end 399 | 400 | osc.event = Timber.osc_event 401 | -- NOTE: If you need the OSC callback in your script then Timber.osc_event(path, args, from) 402 | -- must be called from the end of that function to pass the data down to this lib 403 | 404 | -- Formatters 405 | 406 | local function format_st(param) 407 | local formatted = param:get() .. " ST" 408 | if param:get() > 0 then formatted = "+" .. formatted end 409 | return formatted 410 | end 411 | 412 | local function format_cents(param) 413 | local formatted = param:get() .. " cents" 414 | if param:get() > 0 then formatted = "+" .. formatted end 415 | return formatted 416 | end 417 | 418 | local function format_frame_number(sample_id) 419 | return function(param) 420 | local sample_rate = samples_meta[sample_id].sample_rate 421 | if sample_rate <= 0 then 422 | return "-" 423 | else 424 | return Formatters.format_secs_raw(param:get() / sample_rate) 425 | end 426 | end 427 | end 428 | 429 | local function format_fade(param) 430 | local secs = param:get() 431 | local suffix = " in" 432 | if secs < 0 then 433 | secs = secs - specs.LFO_FADE.minval 434 | suffix = " out" 435 | end 436 | secs = util.round(secs, 0.01) 437 | return math.abs(secs) .. " s" .. suffix 438 | end 439 | 440 | local function format_ratio_to_one(param) 441 | return util.round(param:get(), 0.01) .. ":1" 442 | end 443 | 444 | 445 | local function format_hide_for_stream(sample_id, param_name, formatter) 446 | return function(param) 447 | if Timber.samples_meta[sample_id].streaming == 1 then 448 | return "N/A" 449 | else 450 | if formatter then 451 | return formatter(param) 452 | else 453 | return util.round(param:get(), 0.01) .. " " .. param.controlspec.units 454 | end 455 | end 456 | end 457 | end 458 | 459 | -- Params 460 | 461 | function Timber.add_params() 462 | 463 | params:add{type = "trigger", id = "clear_all", name = "Clear All", action = function(value) 464 | Timber.clear_samples(1, #samples_meta) 465 | end} 466 | params:add{type = "control", id = "lfo_1_freq", name = "LFO1 Freq", controlspec = specs.LFO_1_FREQ, formatter = Formatters.format_freq, action = function(value) 467 | engine.lfo1Freq(value) 468 | end} 469 | params:add{type = "option", id = "lfo_1_wave_shape", name = "LFO1 Shape", options = options.LFO_WAVE_SHAPE, default = 1, action = function(value) 470 | engine.lfo1WaveShape(value - 1) 471 | end} 472 | params:add{type = "control", id = "lfo_2_freq", name = "LFO2 Freq", controlspec = specs.LFO_2_FREQ, formatter = Formatters.format_freq, action = function(value) 473 | engine.lfo2Freq(value) 474 | end} 475 | params:add{type = "option", id = "lfo_2_wave_shape", name = "LFO2 Shape", options = options.LFO_WAVE_SHAPE, default = 4, action = function(value) 476 | engine.lfo2WaveShape(value - 1) 477 | end} 478 | 479 | 480 | params:add_separator() 481 | params:add_control("delay_time", "Delay: time", specs.DELAY_TIME, Formatters.secs_as_ms) 482 | params:set_action("delay_time", engine.delayTime) 483 | 484 | params:add_control("delay_feedback", "Delay: feedback", specs.DELAY_FEEDBACK, Formatters.unipolar_as_percentage) 485 | params:set_action("delay_feedback", engine.feedbackAmount) 486 | 487 | params:add_control("delay_level", "Delay: level", specs.DELAY_LEVEL, Formatters.default) 488 | params:set_action("delay_level", engine.delayLevel) 489 | params:add_separator() 490 | -- reverb time 491 | params:add_control("reverb_time", "Reverb: time", specs.REVERB_TIME) 492 | params:set_action("reverb_time", function(value) engine.reverbTime(value) end) 493 | -- dampening 494 | params:add_control("reverb_damp", "Reverb: damp", specs.REVERB_DAMP) 495 | params:set_action("reverb_damp", function(value) engine.reverbDamp(value) end) 496 | -- reverb size 497 | params:add_control("reverb_size", "Reverb: size", specs.REVERB_SIZE) 498 | params:set_action("reverb_size", function(value) engine.reverbSize(value) end) 499 | -- diffusion 500 | params:add_control("reverb_diff", "Reverb: diff", specs.REVERB_DIFF) 501 | params:set_action("reverb_diff", function(value) engine.reverbDiff(value) end) 502 | -- mod depth 503 | params:add_control("reverb_mod_depth", "Reverb: mod depth", specs.REVERB_MOD_DEPTH) 504 | params:set_action("reverb_mod_depth", function(value) engine.reverbModDepth(value) end) 505 | -- mod rate 506 | params:add_control("reverb_mod_freq", "Reverb: mod freq", specs.REVERB_MOD_FREQ) 507 | params:set_action("reverb_mod_freq", function(value) engine.reverbModFreq(value) end) 508 | -- low, mid, high, lowcut, highcut 509 | params:add_control("reverb_low", "Reverb: low mult", specs.REVERB_MULT) 510 | params:set_action("reverb_low", function(value) engine.reverbLow(value) end) 511 | 512 | params:add_control("reverb_mid", "Reverb: mid mult", specs.REVERB_MULT) 513 | params:set_action("reverb_mid", function(value) engine.reverbMid(value) end) 514 | 515 | params:add_control("reverb_high", "Reverb: high mult", specs.REVERB_MULT) 516 | params:set_action("reverb_high", function(value) engine.reverbHigh(value) end) 517 | 518 | params:add_control("reverb_lowcut", "Reverb: low cut", specs.REVERB_LOWCUT) 519 | params:set_action("reverb_lowcut", function(value) engine.reverbLowcut(value) end) 520 | 521 | params:add_control("reverb_highcut", "Reverb: high cut", specs.REVERB_HIGHCUT) 522 | params:set_action("reverb_highcut", function(value) engine.reverbHighcut(value) end) 523 | 524 | params:add_separator() 525 | 526 | params:add_control("takt_comp_level", "Comp. level", specs.COMP_LEVEL) 527 | params:set_action("takt_comp_level", function(value) engine.compLevel(value) end) 528 | 529 | 530 | params:add_control("takt_comp_mix", "Comp. dry/wet", specs.COMP_MIX, Formatters.percentage) 531 | params:set_action("takt_comp_mix", function(value) engine.compMix(value) end) 532 | 533 | params:add_control("takt_comp_threshold", "Comp. threshold", specs.COMP_THRESHOLD) 534 | params:set_action("takt_comp_threshold", function(value) engine.compThreshold(value) end) 535 | 536 | params:add_control("comp_slopebelow", "Comp. slope below", specs.COMP_SLOPEBELOW) 537 | params:set_action("comp_slopebelow", function(value) engine.compSlopeBelow(value) end) 538 | 539 | params:add_control("comp_slopeabove", "Comp. slope above", specs.COMP_SLOPEABOVE) 540 | params:set_action("comp_slopeabove", function(value) engine.compSlopeAbove(value) end) 541 | 542 | params:add_control("comp_clamptime", "Comp. attack", specs.COMP_CLAMPTIME, Formatters.secs_as_ms) 543 | params:set_action("comp_clamptime", function(value) engine.compClampTime(value) end) 544 | 545 | params:add_control("comp_relaxtime", "Comp. release", specs.COMP_RELAXTIME, Formatters.secs_as_ms) 546 | params:set_action("comp_relaxtime", function(value) engine.compRelaxTime(value) end) 547 | 548 | 549 | end 550 | 551 | function Timber.add_sample_params(id) 552 | 553 | local name_prefix = "" 554 | if id then name_prefix = id .. " " end 555 | id = id or 0 556 | 557 | params:add{type = "file", id = "sample_" .. id, name = name_prefix .. "Sample", action = function(value) 558 | if samples_meta[id].num_frames > 0 or value ~= "-" then 559 | 560 | -- Set some large defaults in case a pset load is about to try and set all these 561 | params:lookup_param("start_frame_" .. id).controlspec.maxval = MAX_FRAMES 562 | params:lookup_param("end_frame_" .. id).controlspec.maxval = MAX_FRAMES 563 | params:lookup_param("loop_start_frame_" .. id).controlspec.maxval = MAX_FRAMES 564 | params:set("loop_start_frame_" .. id, 0) 565 | params:lookup_param("loop_end_frame_" .. id).controlspec.maxval = MAX_FRAMES 566 | params:set("loop_end_frame_" .. id, MAX_FRAMES) 567 | local play_mode_param = params:lookup_param("play_mode_" .. id) 568 | play_mode_param.options = options.PLAY_MODE_BUFFER 569 | play_mode_param.count = #options.PLAY_MODE_BUFFER 570 | 571 | engine.loadSample(id, value) 572 | else 573 | samples_meta[id].manual_load = false 574 | end 575 | end } 576 | params:add{type = "trigger", id = "clear_" .. id, name = "Clear", action = function(value) 577 | Timber.clear_samples(id) 578 | end} 579 | 580 | params:add{type = "option", id = "quality_" .. id, name = "Quality", options = options.QUALITY, default = #options.QUALITY, action = function(value) 581 | engine.downSampleTo(id, QUALITY_SAMPLE_RATES[value]) 582 | engine.bitDepth(id, QUALITY_BIT_DEPTHS[value]) 583 | end} 584 | params:add{type = "number", id = "transpose_" .. id, name = "Transpose", min = -48, max = 48, default = 0, formatter = format_st, action = function(value) 585 | engine.transpose(id, value) 586 | end} 587 | params:add{type = "number", id = "detune_cents_" .. id, name = "Detune", min = -100, max = 100, default = 0, formatter = format_cents, action = function(value) 588 | engine.detuneCents(id, value) 589 | end} 590 | 591 | params:add_separator() 592 | 593 | 594 | params:add{type = "option", id = "play_mode_" .. id, name = "Play Mode", options = options.PLAY_MODE_BUFFER, default = options.PLAY_MODE_BUFFER_DEFAULT, action = function(value) 595 | set_play_mode(id, lookup_play_mode(id)) 596 | end} 597 | params:add{type = "control", id = "start_frame_" .. id, name = "Start", controlspec = ControlSpec.new(0, MAX_FRAMES, "lin", 1, 0), formatter = format_frame_number(id), action = function(value) 598 | set_marker(id, "start_frame_") 599 | end} 600 | params:add{type = "control", id = "end_frame_" .. id, name = "End", controlspec = ControlSpec.new(0, MAX_FRAMES, "lin", 1, MAX_FRAMES), formatter = format_frame_number(id), action = function(value) 601 | set_marker(id, "end_frame_") 602 | end} 603 | params:add{type = "control", id = "loop_start_frame_" .. id, name = "Loop Start", controlspec = ControlSpec.new(0, MAX_FRAMES, "lin", 1, 0), 604 | formatter = format_hide_for_stream(id, "loop_start_frame_" .. id, format_frame_number(id)), action = function(value) 605 | set_marker(id, "loop_start_frame_") 606 | end} 607 | params:add{type = "control", id = "loop_end_frame_" .. id, name = "Loop End", controlspec = ControlSpec.new(0, MAX_FRAMES, "lin", 1, MAX_FRAMES), 608 | formatter = format_hide_for_stream(id, "loop_end_frame_" .. id, format_frame_number(id)), action = function(value) 609 | set_marker(id, "loop_end_frame_") 610 | end} 611 | 612 | params:add_separator() 613 | 614 | params:add{type = "control", id = "freq_mod_lfo_1_" .. id, name = "Freq Mod (LFO1)", controlspec = ControlSpec.UNIPOLAR, action = function(value) 615 | engine.freqModLfo1(id, value) 616 | end} 617 | params:add{type = "control", id = "freq_mod_lfo_2_" .. id, name = "Freq Mod (LFO2)", controlspec = ControlSpec.UNIPOLAR, action = function(value) 618 | engine.freqModLfo2(id, value) 619 | end} 620 | params:add{type = "control", id = "freq_mod_env_" .. id, name = "Freq Mod (Env)", controlspec = ControlSpec.BIPOLAR, action = function(value) 621 | engine.freqModEnv(id, value) 622 | end} 623 | 624 | params:add_separator() 625 | 626 | params:add{type = "option", id = "filter_type_" .. id, name = "Filter Type", options = options.FILTER_TYPE, default = 1, action = function(value) 627 | engine.filterType(id, value - 1) 628 | end} 629 | params:add{type = "control", id = "filter_freq_" .. id, name = "Filter Cutoff", controlspec = specs.FILTER_FREQ, formatter = Formatters.format_freq, action = function(value) 630 | engine.filterFreq(id, value) 631 | end} 632 | params:add{type = "control", id = "filter_resonance_" .. id, name = "Filter Resonance", controlspec = specs.FILTER_RESONANCE, action = function(value) 633 | engine.filterReso(id, value) 634 | end} 635 | 636 | params:add{type = "control", id = "filter_freq_mod_lfo_1_" .. id, name = "Filter Cutoff Mod (LFO1)", controlspec = ControlSpec.UNIPOLAR, action = function(value) 637 | engine.filterFreqModLfo1(id, value) 638 | end} 639 | params:add{type = "control", id = "filter_freq_mod_lfo_2_" .. id, name = "Filter Cutoff Mod (LFO2)", controlspec = ControlSpec.UNIPOLAR, action = function(value) 640 | engine.filterFreqModLfo2(id, value) 641 | end} 642 | params:add{type = "control", id = "filter_freq_mod_env_" .. id, name = "Filter Cutoff Mod (Env)", controlspec = ControlSpec.BIPOLAR, action = function(value) 643 | engine.filterFreqModEnv(id, value) 644 | end} 645 | params:add{type = "control", id = "filter_freq_mod_vel_" .. id, name = "Filter Cutoff Mod (Vel)", controlspec = ControlSpec.BIPOLAR, action = function(value) 646 | engine.filterFreqModVel(id, value) 647 | end} 648 | params:add{type = "control", id = "filter_freq_mod_pressure_" .. id, name = "Filter Cutoff Mod (Pres)", controlspec = ControlSpec.BIPOLAR, action = function(value) 649 | engine.filterFreqModPressure(id, value) 650 | end} 651 | params:add{type = "control", id = "filter_tracking_" .. id, name = "Filter Tracking", controlspec = specs.FILTER_TRACKING, formatter = format_ratio_to_one, action = function(value) 652 | engine.filterTracking(id, value) 653 | end} 654 | 655 | params:add_separator() 656 | 657 | params:add{type = "control", id = "pan_" .. id, name = "Pan", controlspec = ControlSpec.PAN, formatter = Formatters.bipolar_as_pan_widget, action = function(value) 658 | engine.pan(id, value) 659 | end} 660 | params:add{type = "control", id = "pan_mod_lfo_1_" .. id, name = "Pan Mod (LFO1)", controlspec = ControlSpec.UNIPOLAR, action = function(value) 661 | engine.panModLfo1(id, value) 662 | end} 663 | params:add{type = "control", id = "pan_mod_lfo_2_" .. id, name = "Pan Mod (LFO2)", controlspec = ControlSpec.UNIPOLAR, action = function(value) 664 | engine.panModLfo2(id, value) 665 | end} 666 | params:add{type = "control", id = "pan_mod_env_" .. id, name = "Pan Mod (Env)", controlspec = ControlSpec.BIPOLAR, action = function(value) 667 | engine.panModEnv(id, value) 668 | end} 669 | 670 | params:add{type = "control", id = "amp_" .. id, name = "Amp", controlspec = specs.AMP, action = function(value) 671 | engine.amp(id, value) 672 | end} 673 | params:add{type = "control", id = "amp_mod_lfo_1_" .. id, name = "Amp Mod (LFO1)", controlspec = ControlSpec.UNIPOLAR, action = function(value) 674 | engine.ampModLfo1(id, value) 675 | end} 676 | params:add{type = "control", id = "amp_mod_lfo_2_" .. id, name = "Amp Mod (LFO2)", controlspec = ControlSpec.UNIPOLAR, action = function(value) 677 | engine.ampModLfo2(id, value) 678 | end} 679 | 680 | params:add_separator() 681 | 682 | params:add{type = "control", id = "amp_env_attack_" .. id, name = "Amp Env Attack", controlspec = specs.AMP_ENV_ATTACK, formatter = Formatters.format_secs, action = function(value) 683 | engine.ampAttack(id, value) 684 | end} 685 | params:add{type = "control", id = "amp_env_decay_" .. id, name = "Amp Env Decay", controlspec = specs.AMP_ENV_DECAY, formatter = Formatters.format_secs, action = function(value) 686 | engine.ampDecay(id, value) 687 | end} 688 | params:add{type = "control", id = "amp_env_sustain_" .. id, name = "Amp Env Sustain", controlspec = specs.AMP_ENV_SUSTAIN, action = function(value) 689 | engine.ampSustain(id, value) 690 | end} 691 | params:add{type = "control", id = "amp_env_release_" .. id, name = "Amp Env Release", controlspec = specs.AMP_ENV_RELEASE, formatter = Formatters.format_secs, action = function(value) 692 | engine.ampRelease(id, value) 693 | end} 694 | 695 | params:add_separator() 696 | 697 | params:add{type = "control", id = "mod_env_attack_" .. id, name = "Mod Env Attack", controlspec = specs.MOD_ENV_ATTACK, formatter = Formatters.format_secs, action = function(value) 698 | engine.modAttack(id, value) 699 | end} 700 | params:add{type = "control", id = "mod_env_decay_" .. id, name = "Mod Env Decay", controlspec = specs.MOD_ENV_DECAY, formatter = Formatters.format_secs, action = function(value) 701 | engine.modDecay(id, value) 702 | end} 703 | params:add{type = "control", id = "mod_env_sustain_" .. id, name = "Mod Env Sustain", controlspec = specs.MOD_ENV_SUSTAIN, action = function(value) 704 | engine.modSustain(id, value) 705 | end} 706 | params:add{type = "control", id = "mod_env_release_" .. id, name = "Mod Env Release", controlspec = specs.MOD_ENV_RELEASE, formatter = Formatters.format_secs, action = function(value) 707 | engine.modRelease(id, value) 708 | end} 709 | 710 | params:add_separator() 711 | 712 | 713 | params:add_control("sidechain_send_" .. id, "Sidechain send", specs.COMP_SEND) 714 | params:set_action("sidechain_send_" .. id, function(value) engine.sidechainSend(id, value) end) 715 | params:add_control("delay_send_" .. id, "Delay send", specs.DELAY_SEND) 716 | params:set_action("delay_send_" .. id, function(value) engine.delaySend(id, value) end) 717 | params:add_control("reverb_send_" .. id, "Reverb send", specs.REVERB_SEND) 718 | params:set_action("reverb_send_" .. id, function(value) engine.reverbSend(id, value) end) 719 | 720 | params:add_separator() 721 | 722 | 723 | params:add{type = "control", id = "lfo_1_fade_" .. id, name = "LFO1 Fade", controlspec = specs.LFO_FADE, formatter = format_fade, action = function(value) 724 | if value < 0 then value = specs.LFO_FADE.minval - 0.00001 + math.abs(value) end 725 | engine.lfo1Fade(id, value) 726 | end} 727 | params:add{type = "control", id = "lfo_2_fade_" .. id, name = "LFO2 Fade", controlspec = specs.LFO_FADE, formatter = format_fade, action = function(value) 728 | if value < 0 then value = specs.LFO_FADE.minval - 0.00001 + math.abs(value) end 729 | engine.lfo2Fade(id, value) 730 | end} 731 | 732 | Timber.num_sample_params = Timber.num_sample_params + 1 733 | end 734 | 735 | function Timber.load_folder(file, add) 736 | 737 | local sample_id = 1 738 | if add then 739 | for i = NUM_SAMPLES - 1, 0, -1 do 740 | --print(i) 741 | if Timber.samples_meta[i].num_frames > 0 then 742 | sample_id = i + 1 743 | break 744 | end 745 | end 746 | end 747 | 748 | Timber.clear_samples(sample_id, NUM_SAMPLES) 749 | 750 | local split_at = string.match(file, "^.*()/") 751 | local folder = string.sub(file, 1, split_at) 752 | file = string.sub(file, split_at + 1) 753 | 754 | local found = false 755 | 756 | for k, v in ipairs(Timber.FileSelect.list) do 757 | if v == file then found = true end 758 | if found then 759 | if sample_id > 100 then 760 | print("Max files loaded") 761 | break 762 | end 763 | -- Check file type 764 | local lower_v = v:lower() 765 | if string.find(lower_v, ".wav") or string.find(lower_v, ".aif") or string.find(lower_v, ".aiff") then 766 | --print(sample_id,folder .. v ) 767 | Timber.load_sample(sample_id, folder .. v) 768 | params:set('play_mode_'..sample_id, 4) 769 | sample_id = sample_id + 1 770 | else 771 | print("Skipped", v) 772 | end 773 | 774 | end 775 | 776 | end 777 | end 778 | 779 | 780 | function Timber.init() 781 | 782 | params:add_trigger('load_f','+ Load Folder') 783 | params:set_action('load_f', function() Timber.FileSelect.enter(_path.audio, function(file) 784 | if file ~= "cancel" then Timber.load_folder(file, add) end end) end) 785 | 786 | Timber.options.PLAY_MODE_BUFFER_DEFAULT = 3 787 | Timber.options.PLAY_MODE_STREAMING_DEFAULT = 3 788 | params:add_separator() 789 | Timber.add_params() 790 | 791 | for i = 1, NUM_SAMPLES do 792 | params:add_separator() 793 | Timber.add_sample_params(i) 794 | end 795 | 796 | end 797 | 798 | function Timber.get_meta(id) 799 | return Timber.samples_meta[id] 800 | end 801 | 802 | return Timber 803 | -------------------------------------------------------------------------------- /lib/ui.lua: -------------------------------------------------------------------------------- 1 | -- takt ui @its_your_bedtime 2 | 3 | local ui = { waveform = {}, in_l = 0, in_r = 0, out_l = 0, out_r = 0, vu_l, vu_r, vu_o_l, vu_o_r } 4 | local note_num_to_name = require ('musicutil').note_num_to_name 5 | local name_lookup = { 6 | ['SMP'] = 'sample', ['MODE'] = 'play_mode', ['NOTE'] = 'note', ['STRT'] = 'start_frame', ['END'] = 'end_frame', 7 | ['FM1'] = 'freq_mod_lfo_1', ['FM2'] = 'freq_mod_lfo_2', ['VOL'] = 'amp', ['PAN'] = 'pan', ['ENV'] = 'env', 8 | ['AMP'] = 'amp_mod_lfo_1', ['AM2'] = 'amp_mod_lfo_2', ['SR'] = 'quality', ['TYPE'] = 'filter_type', ['CFM'] = 'filter_freq_mod_lfo_2', 9 | ['RVRB'] = 'reverb_send', ['DEL'] = 'delay_send', 10 | 11 | } 12 | local midi_name_lookup = { 13 | [1] = 'note', [2] = 'velocity', [3] = 'length', [4] = 'channel', [5] = 'device', [6] = 'program_change', 14 | [7] = 'cc_1_val', [8] = 'cc_2_val', [9] = 'cc_3_val', [10] = 'cc_3_val', [11] = 'cc_4_val', [12] = 'cc_4_val' 15 | } 16 | local dividers = {[0] = 'OFF', '1/8', '1/4', '1/2', '3/4', '--', '3/2', '2x' } 17 | 18 | local filter = controlspec.WIDEFREQ 19 | 20 | function ui.init() 21 | ui.vu_l, ui.vu_r, ui.vu_o_l, ui.vu_o_r = poll.set("amp_in_l"), poll.set("amp_in_r"), poll.set("amp_out_l"), poll.set("amp_out_r") 22 | ui.vu_l.time, ui.vu_r.time, ui.vu_o_l.time, ui.vu_o_r.time = 1 / 24, 1 / 24, 1 / 24, 1 / 24 23 | 24 | ui.vu_l.callback = function(val) ui.in_l = util.linlin(0,1, 0, 30, val) end 25 | ui.vu_r.callback = function(val) ui.in_r = util.linlin(0,1, 0, 30, val) end 26 | ui.vu_o_l.callback = function(val) ui.out_l = util.linlin(0,1, 0, 30, val) end 27 | ui.vu_o_r.callback = function(val) ui.out_r = util.linlin(0,1, 0, 30, val)end 28 | for i = 1, 10 do 29 | ui.waveform[i] = {0,0} 30 | end 31 | ui.reverb = {} 32 | ui.reverb.orbit = math.fmod(1,2)~=0 and 6 or 15 33 | ui.reverb.position = 1 <= 2 and 0 or 1 <= 4 and 2 or 4 34 | ui.reverb.velocity = util.linlin(0, 1, 0.01, 1, 1) 35 | 36 | 37 | end 38 | 39 | function ui.start_polls() 40 | ui.vu_l:start() 41 | ui.vu_r:start() 42 | ui.vu_o_l:start() 43 | ui.vu_o_r:start() 44 | end 45 | 46 | function ui.stop_polls() 47 | ui.vu_l:stop() 48 | ui.vu_r:stop() 49 | ui.vu_o_l:stop() 50 | ui.vu_o_r:stop() 51 | end 52 | 53 | local function get_step(x) return (x * 16) - 15 end 54 | 55 | local function set_brightness(n, i) screen.level(i == n and 6 or 2) end 56 | 57 | local function metro_icon(x, y, pos) 58 | screen.level(0) 59 | screen.move(x + 2, y + 5) 60 | screen.line(x + 7, y) 61 | screen.line(x + 12, y + 5) 62 | screen.line(x + 3, y + 5) 63 | screen.stroke() 64 | screen.move(x + 7, y + 3) 65 | screen.line(pos % 4 > 1 and (x + 4) or (x + 10), y ) 66 | screen.stroke() 67 | 68 | end 69 | 70 | 71 | function ui.head(params_data, data, view, k1, rules, PATTERN_REC, preview) 72 | local tr = data.selected[1] 73 | local s = data.selected[2] 74 | local pos = data[data.pattern].track.pos[tr] 75 | 76 | screen.level((not view.sampling and data.ui_index == -6 ) and 5 or 2) 77 | screen.rect(1, 0, 20, 7) 78 | screen.fill() 79 | screen.level(0) 80 | 81 | screen.font_size(6) 82 | screen.font_face(25) 83 | 84 | 85 | if not s then 86 | screen.move(3,6) 87 | screen.text('P') 88 | if PATTERN_REC then 89 | screen.move(7,6) 90 | screen.level( pos % 8 or 0) 91 | screen.text('!') 92 | end 93 | screen.move(18,6) 94 | screen.level(0) 95 | screen.text_right(data.pattern or nil ) 96 | screen.stroke() 97 | 98 | else 99 | screen.move(11,6) 100 | screen.text_center( tr ..':' .. s ) 101 | end 102 | 103 | screen.level((not view.sampling and data.ui_index == (not s and -5 or -3) ) and 5 or 2) 104 | screen.rect(22, 0, 20, 7) 105 | screen.fill() 106 | screen.level(0) 107 | 108 | if s then 109 | screen.move(31,6) 110 | screen.text_center( dividers[params_data.div] ) 111 | 112 | else 113 | screen.move(24, 6) 114 | screen.text('TR') 115 | screen.move(40, 6) 116 | screen.text_right(tr ) 117 | end 118 | 119 | screen.stroke() 120 | 121 | if s then 122 | local rule_name = rules[params_data.rule][1] 123 | 124 | screen.level((not view.sampling and data.ui_index == -2 ) and 5 or 2) 125 | screen.rect(43, 0, 41, 7) 126 | screen.fill() 127 | screen.level(0) 128 | if string.len(rule_name) < 5 then 129 | screen.move(45, 6) 130 | screen.text('RULE') 131 | screen.move(82, 6) 132 | screen.text_right(rule_name) 133 | else 134 | screen.move(45, 6) 135 | screen.text(rule_name) 136 | end 137 | 138 | else 139 | screen.level((not view.sampling and data.ui_index == -4) and 5 or 2) 140 | screen.rect(43, 0, 25, 7) 141 | screen.fill() 142 | screen.level(0) 143 | metro_icon(42,1, pos) 144 | screen.move(66, 6) 145 | screen.text_right(data[data.pattern].bpm) 146 | 147 | 148 | end 149 | 150 | screen.stroke() 151 | 152 | if not s then 153 | screen.level((not view.sampling and data.ui_index == -3) and 5 or 2) 154 | screen.rect(69, 0, 15, 7) 155 | screen.fill() 156 | screen.level(0) 157 | screen.move(76,6) 158 | screen.text_center(dividers[data[data.pattern].track.div[tr]]) 159 | screen.stroke() 160 | end 161 | 162 | if not k1 then 163 | screen.level((not view.sampling and data.ui_index == -1) and 5 or 2) 164 | screen.rect(85, 0, 9, 7) 165 | screen.fill() 166 | screen.level(0) 167 | screen.move(89,6) 168 | screen.text_center(params_data.retrig) 169 | screen.stroke() 170 | 171 | for i = 1, 16 do 172 | local offset_y = i <= 8 and 0 or 4 173 | local offset_x = i <= 8 and 0 or 8 174 | local st = s and get_step(data.selected[2]) or util.round(data[data.pattern].track.pos[tr], 16) + 1 175 | local step = data[data.pattern][tr][st + (i - 1 )] 176 | 177 | screen.level((not view.sampling and data.ui_index == 0) and 5 or 2) 178 | if step == 1 then 179 | screen.rect(92 + ((i - offset_x) * 4), offset_y + 1, 2, 2) 180 | screen.stroke() 181 | else 182 | screen.rect(91 + ((i - offset_x) * 4), offset_y, 3, 3) 183 | screen.fill() 184 | end 185 | end 186 | else 187 | if preview then 188 | screen.level(2) 189 | screen.rect(85, 0, 41, 7) 190 | screen.fill() 191 | 192 | screen.level(0) 193 | 194 | screen.move(88, 6) 195 | screen.line(88, 1) 196 | screen.stroke() 197 | screen.move(88, 1) 198 | screen.line(91, 4) 199 | screen.stroke() 200 | screen.move(91,3) 201 | screen.line(88, 6) 202 | screen.stroke() 203 | 204 | screen.move(97,6) 205 | screen.text('PREVIEW') 206 | screen.stroke() 207 | 208 | 209 | else 210 | screen.level(data.ui_index == -2 and 5 or 2) 211 | screen.rect(85, 0, 20, 7) 212 | screen.fill() 213 | screen.level(0) 214 | screen.move(95,6) 215 | 216 | screen.text_center(dividers[data[data.pattern].sync_div]) 217 | screen.stroke() 218 | screen.level(data.ui_index == -1 and 5 or 2) 219 | 220 | if tr < 8 then 221 | screen.rect(106, 0, 20, 7) 222 | screen.fill() 223 | screen.level(0) 224 | screen.rect(109 , 3, 15, 2) 225 | screen.stroke() 226 | screen.level(data.ui_index == -1 and 5 or 2) 227 | screen.pixel(108, 2) 228 | screen.pixel(108, 4) 229 | screen.pixel(123, 2) 230 | screen.pixel(123, 4) 231 | screen.fill() 232 | screen.level(1) 233 | 234 | screen.rect(109, 3, util.linlin(-99, 0, 0, 14, data[data.pattern][tr].params[tostring(tr)].sidechain_send), 1) 235 | 236 | screen.fill() 237 | else 238 | screen.rect(106, 0, 20, 7) 239 | screen.fill() 240 | 241 | end 242 | end 243 | end 244 | end 245 | 246 | function ui.draw_env(x, y, t, params_data, ui_index) 247 | local atk, dec, sus, rel 248 | atk = params_data.amp_env_attack 249 | dec = params_data.amp_env_decay 250 | sus = params_data.amp_env_sustain 251 | rel = params_data.amp_env_release 252 | 253 | local sy = util.clamp(y - (sus * 10) + 2, 0, y ) 254 | local attack_peak = x + atk * 2 255 | 256 | screen.level(2) 257 | screen.rect(x - 1, y - 15, 40, 16) 258 | screen.stroke() 259 | 260 | 261 | screen.level(ui_index == 9 and 15 or 1) 262 | screen.move(x,y) 263 | screen.line(attack_peak, y - 14) 264 | screen.stroke() 265 | screen.level(ui_index == 10 and 15 or 1) 266 | screen.move(attack_peak, y - 14) 267 | screen.line(x + ((atk / 2) + dec) * 3 + 3, sy) 268 | screen.stroke() 269 | screen.level(ui_index == 11 and 15 or 1 ) 270 | screen.move(x + ((atk / 2) + dec) * 3 + 2, sy) 271 | screen.line(util.clamp(x + (rel) * 3 + 24, 0, x+38), sy ) 272 | screen.stroke() 273 | screen.level(ui_index == 12 and 15 or 1) 274 | screen.move(util.clamp(x + ( rel) * 3 + 24, 2, x+38), sy) 275 | screen.line(util.clamp(x + ( rel) * 2 + 38, 0, x+38), y) 276 | screen.stroke() 277 | 278 | end 279 | 280 | function ui.draw_filter(x, y, params_data, ui_index) 281 | 282 | screen.level(2) 283 | screen.rect(x - 1, y - 15, 40, 16) 284 | screen.stroke() 285 | 286 | local sample = params_data.sample 287 | local cut_m = filter:unmap(params_data.filter_freq) 288 | local cut = util.linexp(0,1,1,34, cut_m )--/ 1200 289 | local res = util.linlin(0,1,0,5, params_data.filter_resonance) 290 | 291 | 292 | screen.level(1) 293 | 294 | if params_data.filter_type == 1 then 295 | local t = x + cut 296 | screen.level(ui_index == 17 and 15 or 1) 297 | screen.move(x - 1, y - 9) 298 | screen.curve (t, y - 9, t, y - 9 - (res / 2), t + 1, y - 9 - res) 299 | screen.stroke() 300 | screen.level(ui_index == 18 and 15 or 1) 301 | screen.move(t + 1, y - 9 - res ) 302 | screen.line(t + 4, y) 303 | screen.stroke() 304 | 305 | 306 | elseif params_data.filter_type == 2 then 307 | cut = 34 - cut 308 | local t = x + cut 309 | screen.level( ui_index == 18 and 15 or 1) 310 | screen.move(t - 1, y) 311 | screen.line(t , y - 9 - res) 312 | screen.stroke() 313 | 314 | screen.move(x + 38 , y - 9) 315 | screen.level(ui_index == 17 and 15 or 1) 316 | screen.curve (t, y - 9, t, y - 9 - (res / 2), t, y - 9 - res) 317 | 318 | screen.stroke() 319 | screen.close() 320 | 321 | 322 | 323 | end 324 | 325 | end 326 | 327 | function ui.draw_mode(x, y, mode, index, lock) 328 | set_brightness(16, index) 329 | screen.rect(x - 3, y - 15, 20, 17) 330 | screen.fill() 331 | screen.level(0) 332 | screen.move(x, y - 8) 333 | screen.text('MODE') 334 | screen.stroke() 335 | --screen.level(0) 336 | 337 | local lvl = lock == true and 15 or 0 -- 338 | screen.level(lvl) 339 | if mode == 1 or mode == 2 then -- loop 340 | 341 | screen.move(x + 3, y) 342 | screen.line(x + 5, y) 343 | screen.move(x + 5, y + 1) 344 | screen.line(x + 8, y + 1) 345 | screen.move(x + 8, y) 346 | screen.line(x + 10, y) 347 | screen.move(x + 11, y - 1) 348 | screen.line(x + 11, y - 3) 349 | screen.move(x + 8, y - 3) 350 | screen.line(x + 10, y - 3) 351 | screen.move(x + 5, y - 4) 352 | screen.line(x + 8, y - 4) 353 | screen.move(x + 3, y - 3) 354 | screen.line(x + 5, y - 3) 355 | screen.move(x + 4, y - 2) 356 | screen.line(x + 4, y - 6) 357 | screen.move(x + 4, y - 2) 358 | screen.line(x + 7, y - 2) 359 | 360 | elseif mode == 3 or mode == 4 then -- oneshot 361 | 362 | screen.move(x + 1, y - 2) 363 | screen.line(x + 11, y - 2) 364 | screen.move(x + 9, y - 5) 365 | screen.line(x + 12, y - 2) 366 | screen.move(x + 9, y ) 367 | screen.line(x + 12, y - 3) 368 | 369 | end 370 | screen.stroke() 371 | end 372 | 373 | local function draw_start_end_markers(x, y, w, h, ui_index, params_data, meta, lock) 374 | local num_frames = meta.num_frames 375 | local start_x = x + 0.5 + util.round((params_data.start_frame / num_frames) * (w - 1)) 376 | local end_x = x + 0.5 + util.round((params_data.end_frame / num_frames) * (w - 1)) 377 | 378 | set_brightness(3, ui_index) 379 | screen.move(start_x, y) 380 | screen.line(start_x, y + h ) 381 | screen.stroke() 382 | 383 | local arrow_direction = 1 384 | if start_x > end_x then arrow_direction = -1 end 385 | local clamp = util.clamp(start_x + 0.5 * arrow_direction, 0, (x + w) - 1) 386 | screen.move(clamp, y + 1) 387 | screen.line_rel(2 * arrow_direction, 2) 388 | screen.stroke() 389 | 390 | screen.pixel(clamp , y + 3) 391 | 392 | screen.stroke() 393 | 394 | set_brightness(4, ui_index) 395 | screen.move(end_x, y ) 396 | screen.line(end_x, y + h) 397 | screen.stroke() 398 | 399 | 400 | end 401 | 402 | 403 | function ui.draw_waveform(x, y, params_data, ui_index, meta, lock) 404 | screen.level(2) 405 | screen.rect(x + 1 , y + 1, 40, 16) 406 | screen.stroke() 407 | if meta.waveform[1] then 408 | screen.level(1) 409 | 410 | for i = 1, 39 do 411 | local w = meta.waveform[i] or {0,0} 412 | screen.move(x + 1 + i, y + 16) 413 | screen.line(x + 1 + i, y + (16 - w[2]*15)) 414 | screen.stroke() 415 | end 416 | 417 | screen.level(2) 418 | for _, v in pairs(meta.positions) do 419 | local position_x = (x + 1 + util.linlin(0, 1, 0, 39, v)) 420 | screen.move(position_x, y + 16) 421 | screen.line(position_x, y + 1 ) 422 | end 423 | screen.stroke() 424 | draw_start_end_markers(x + 1, y + 1, 39, 15, ui_index, params_data, meta, lock) 425 | else 426 | if ui_index == 3 or ui_index == 4 then 427 | set_brightness(ui_index, ui_index) 428 | end 429 | screen.move(x + 6, y + 11) 430 | screen.text('+ LOAD') 431 | screen.stroke() 432 | end 433 | end 434 | 435 | function ui.draw_note(x, y, params_data, index, ui_index, lock) 436 | set_brightness(index, ui_index) 437 | screen.rect(x, y, 20, 17) 438 | screen.fill() 439 | local offset = params_data.detune_cents and util.linlin(-100,100,-3,3, params_data.detune_cents) or 0 440 | screen.level(0) 441 | screen.rect(x + 7 + offset, y + 6, 3, 2) 442 | screen.rect(x + 8 + offset, y + 6, 3, 1) 443 | screen.rect(x + 10 + offset, y +2, 1, 4) 444 | screen.rect(x + 11 + offset, y + 3, 1, 1) 445 | screen.rect(x + 12 + offset, y + 4, 1, 1) 446 | screen.fill() 447 | 448 | local note_name = params_data.note 449 | local oct = math.floor(note_name / 12 - 2) == 0 and '' or math.floor(note_name / 12 - 2) 450 | screen.level(0) 451 | 452 | local lvl = lock == true and 15 or 0 -- 453 | 454 | screen.level(lvl) 455 | screen.move(x + 9, y + 15) 456 | screen.text_center(oct .. note_num_to_name(note_name):gsub('♯', '#')) 457 | screen.stroke() 458 | 459 | end 460 | 461 | function ui.draw_pan(x, y, params_data, ui_index, menu_index, lock) 462 | set_brightness(menu_index, ui_index) 463 | screen.rect(x, y, 20, 17) 464 | screen.fill() 465 | screen.level(0) 466 | screen.move(x + 9, y + 7) 467 | screen.text_center('PAN') 468 | 469 | local pan = params_data.pan * 5 470 | 471 | 472 | local lvl = lock == true and 15 or 0 -- 473 | 474 | screen.move(x + 4, y + 13) 475 | screen.line(x + 15, y + 13) 476 | screen.stroke() 477 | screen.level(lvl) 478 | local pan_abs = math.abs(pan) 479 | 480 | if util.round(pan_abs,0.5) > 0.5 then screen.rect(x + 9, y + 11, 1, 3) end 481 | screen.rect(x + 9 + pan, y + 11 - (pan_abs/4), 1, 3 + (pan_abs/2)) 482 | 483 | screen.fill() 484 | 485 | end 486 | 487 | 488 | function ui.tile(index, name, value, ui_index, lock, custom) 489 | 490 | local x = index > 14 and (21 * index) - 314 491 | or (index == 13 or index == 14) and (21 * index) - 188 492 | or index > 6 and (21 * index) - 146 493 | or (21 * index) - 20 494 | 495 | local y = index > 14 and 44 or index > 6 and 26 or 8 496 | local x_ext = index == 4 and 6 or index == 3 and 2 or 0 497 | 498 | 499 | set_brightness(index, ui_index) 500 | screen.rect(x , y, 20, 17) 501 | screen.fill() 502 | 503 | screen.level(custom and custom == ui_index and 15 or 0) 504 | screen.move( x + 10, y + 7) 505 | screen.text_center(name) 506 | screen.move( x + 10,y + 15) 507 | 508 | local lvl = lock == true and 15 or 0 -- 509 | screen.level(lvl) 510 | 511 | 512 | 513 | if (type(value) == 'number'and value ~= math.floor(value)) and index ~= 7 then 514 | local value = string.sub(value, 2) 515 | end 516 | 517 | if string.len(tostring(value)) > 4 then local value = util.round(value, 0.01) end 518 | 519 | screen.text_center(value) 520 | screen.stroke() 521 | 522 | end 523 | 524 | 525 | local count = { 1, 1} 526 | 527 | function lfo(x, y, n, f, s, amp, width) 528 | local left = x + 5 529 | local top = y + 5 530 | 531 | local lowamp = 0.5 532 | local highamp = 1 + amp 533 | 534 | --screen.level(0) 535 | local width = width and width or 10 536 | 537 | count[n] = (count[n] + 0.3) + util.expexp(0.05, 20, 0.11, 0.13, f) 538 | 539 | local i = 2 540 | for j = 1, width do 541 | local amp = math.sin((count[n] * (i == 1 and 1 or 2) / 0.3 + j / width) * (i == 1 and 2 or 4) * math.pi) 542 | * util.linlin(1, width / 2, lowamp, highamp, j < (width / 2) and j or width - j) - 0.75 543 | * util.linlin(1, width / 2, lowamp, highamp, j < (width / 2) and j or width - j)-(util.linexp(0, 1, 0.5, 6, j/width) * 0) 544 | or 0 545 | 546 | screen.pixel(left - 1 + j, top + amp) 547 | end 548 | screen.fill() 549 | end 550 | 551 | 552 | 553 | function ui.draw_level_meter(x, y, minv, maxv, value, index, ui_index, lock, n) 554 | if n then 555 | set_brightness(index, ui_index) 556 | screen.rect(x, y, 20, 17) 557 | screen.fill() 558 | 559 | screen.level(0) 560 | screen.move(x + 10, y + 7) 561 | screen.text_center(n) 562 | screen.stroke() 563 | 564 | local lvl = lock == true and 15 or 0 565 | screen.level(lvl) 566 | end 567 | local h = maxv == 0 and 20 or 0 568 | screen.rect(x + 4, y + 12, 13, 3) 569 | screen.stroke() 570 | 571 | screen.level(1) 572 | screen.rect(x + 4, y + 12, util.linlin(minv, maxv, 0, 12, value) , 1) 573 | screen.rect(x + 4, y + 13, util.linlin(minv, maxv-((maxv+h)/10), 0, 12, value) , 1) 574 | screen.fill() 575 | 576 | set_brightness(index, ui_index) 577 | screen.pixel(x + 3, y+ 11) 578 | screen.pixel(x + 3, y+ 14) 579 | screen.pixel(x + 16, y+ 11) 580 | screen.pixel(x + 16, y+ 14) 581 | screen.fill() 582 | end 583 | 584 | function ui.draw_volume(x, y, minv, maxv, value, index, ui_index, lock, n) 585 | 586 | set_brightness(index, ui_index) 587 | screen.rect(x, y, 20, 17) 588 | screen.fill() 589 | screen.level(0) 590 | 591 | screen.rect(x + 6, y + 4, 1,3) 592 | screen.rect(x + 7, y + 3, 1,5) 593 | screen.rect(x + 8, y + 2, 1,7) 594 | screen.fill() 595 | local vol = util.linlin(minv,maxv,0,3, value) 596 | if vol < 0.4 then 597 | screen.move(x + 10, y + 4) 598 | screen.line(x + 13, y + 7) 599 | screen.stroke() 600 | screen.move(x + 10, y + 7) 601 | screen.line(x + 13, y + 4) 602 | screen.stroke() 603 | end 604 | for i = 0, vol do 605 | local x = x + 8 + (i * 2) 606 | local y = y + 6 - i 607 | if i > 1 then 608 | screen.pixel(x - 1, y - 1) 609 | screen.pixel(x - 1, y + (i *1.7)) 610 | end 611 | screen.rect(x, y, 1, i > 1 and (i*1.7) or i ) 612 | end 613 | screen.fill() 614 | ui.draw_level_meter(x, y, minv, maxv, value, index, ui_index, lock) 615 | end 616 | 617 | 618 | function ui.draw_lfo(x, y, lfo_num, params_data, index, ui_index, lock, n) 619 | set_brightness(index, ui_index) 620 | screen.rect(x, y, 20, 17) 621 | screen.fill() 622 | 623 | local value = params_data[name_lookup[n]] 624 | 625 | if index == ui_index then 626 | local shape = params:get('lfo_'.. lfo_num .. '_wave_shape') 627 | local freq = params:get('lfo_'.. lfo_num ..'_freq') 628 | screen.level(0) 629 | lfo(x, y, lfo_num, freq, shape, 0) 630 | 631 | else 632 | screen.level(0) 633 | screen.move(x + 5, y + 7) 634 | screen.text(n) 635 | screen.stroke() 636 | end 637 | local lvl = lock == true and 15 or 0 -- 638 | 639 | screen.level(lvl) 640 | ui.draw_level_meter(x, y, 0, 1, value, index, ui_index, lock) 641 | end 642 | 643 | local sr_types = { '8k', '16k', '26k', '32k', '48k' } 644 | local f_types = { 'LPF', 'HPF' } 645 | 646 | 647 | function ui.is_lock(t, params_data) 648 | local lock = false 649 | if params_data.default then 650 | local tile_name = t[2] 651 | local tile_value = t[3] 652 | local param_name = name_lookup[t[2]] 653 | 654 | local default = params_data.default[param_name] 655 | 656 | if tile_name == 'SR' then 657 | lock = sr_types[default] ~= tile_value and true or false 658 | elseif tile_name == 'TYPE' then 659 | lock = f_types[default] ~= tile_value and true or false 660 | else 661 | lock = (lock == false and default ~= params_data[param_name]) and true or false 662 | end 663 | end 664 | 665 | return lock 666 | end 667 | 668 | 669 | ---- fx 670 | 671 | 672 | function ui.draw_delay(x, y, index ) 673 | screen.level((index > 11 and index < 15) and 6 or 2) 674 | screen.rect(x, y, 20, 17) 675 | screen.fill() 676 | screen.level(0) 677 | 678 | 679 | screen.level(0) 680 | screen.move(x + 10, y + 7) 681 | if index > 11 and index < 15 then 682 | local n = {[12] = "VOL", [13] = "TIME",[14] = 'FBK'} 683 | screen.text_center(n[index]) 684 | else 685 | screen.text_center('DEL') 686 | end 687 | local v = { [12] = { params:get('delay_level'), -99, 0} , [13] = {params:get('delay_time'), 0, 3 } ,[14] = {params:get('delay_feedback'), 0, 1},} 688 | 689 | local i = (index > 11 and index < 15) and index or 12 690 | ui.draw_level_meter(x, y, v[i][2], v[i][3], v[i][1] or v[i][1], i, index) 691 | 692 | local vol = util.linlin(-99, 0, 1, 5, params:get('delay_level')) 693 | local feedback = util.linlin(0, 1, 0, 4, params:get('delay_feedback')) 694 | local time = util.round(util.linlin(0.0001, 3, 0.5, 8 - feedback, params:get('delay_time'))) 695 | screen.level(2) 696 | screen.rect(x + 22, y + 1, 40, 16) 697 | screen.stroke() 698 | for i = 1, 2 + feedback do 699 | local lvl = (index == 13 and i == 2+feedback) and 6 or (index == 14 and i == 1) and 6 or 1 700 | 701 | screen.level(0) 702 | screen.circle(x + (43+((time + feedback)*2)) - (i*(1 + time)) , y + 9, 1 + vol ) 703 | screen.fill() 704 | screen.level(lvl) 705 | screen.circle(x + (43+ ((time + feedback)*2))- (i*(1 + time)) , y + 9, 1 + vol) 706 | screen.stroke() 707 | end 708 | end 709 | 710 | 711 | function ui.midi_screen(params_data, ui_index, tracks, steps) 712 | 713 | local tile = { 714 | {1, 'NOTE', function(i, _, lock) ui.draw_note(1, 8, params_data,i, ui_index, lock) end }, 715 | {2, 'VEL', params_data.velocity }, 716 | {3, 'LEN', params_data.length}, 717 | {4, 'CH', params_data.channel }, 718 | {5, 'DEV', params_data.device }, 719 | {6, 'PGM', params_data.program_change }, 720 | {7, 'CC' .. params_data.cc_1, params_data.cc_1_val }, 721 | {8, 'CC' .. params_data.cc_2, params_data.cc_2_val }, 722 | {9, 'CC' .. params_data.cc_3, params_data.cc_3_val }, 723 | {10,'CC' .. params_data.cc_4, params_data.cc_4_val }, 724 | {11,'CC' .. params_data.cc_5, params_data.cc_5_val }, 725 | {12,'CC' .. params_data.cc_6, params_data.cc_6_val }, 726 | } 727 | 728 | 729 | for k, v in pairs(tile) do 730 | 731 | local lock = false 732 | if params_data.default then 733 | lock = (lock == false and params_data.default[midi_name_lookup[k]] ~= (k == 1 and params_data.note or v[3]) ) and true or false 734 | end 735 | 736 | if v[3] and type(v[3]) == 'function' then 737 | v[3](v[1], v[2], lock) 738 | elseif v[3] then 739 | if v[1] > 3 and v[3] < 0 then 740 | v[3] = '--' 741 | elseif v[1] == 3 then 742 | v[3] = util.round(util.linlin(1, 256, 1, 16,v[3]),0.01) 743 | -- elseif v[1] > 6 then 744 | -- local cc = string.sub(v[2], 3) 745 | --if tonumber(cc) > 100 then v[2] = 'CC.'.. string.sub(v[2], 4) end 746 | end 747 | ui.tile(v[1], v[2], v[3], ui_index, lock , v[1] > 6 and v[1] + 6 or false) 748 | end 749 | end 750 | 751 | screen.level(2) 752 | screen.rect(2, 45, 124, 19) 753 | screen.stroke() 754 | 755 | for i = 8, 14 do 756 | local pos = tracks.pos[i] 757 | screen.level(1) 758 | screen.move(3 + (pos / 2.1), 32 + (i+i) - 2) 759 | screen.line(3 + (pos / 2.1), 32 + (i+i) ) 760 | screen.stroke() 761 | 762 | for k,v in pairs(steps[i]) do 763 | if v == 1 then 764 | screen.level(5) 765 | screen.pixel(2 + (k / 2.1), 32 + (i + (i - 1)) ) 766 | screen.fill() 767 | 768 | if util.round(k,16) == util.round(pos , 16) then 769 | screen.level(15) 770 | screen.pixel(2 + (k / 2.1), 32 + (i + (i - 1)) ) 771 | screen.stroke() 772 | end 773 | end 774 | 775 | 776 | end 777 | 778 | end 779 | 780 | end 781 | 782 | 783 | function ui.main_screen(params_data, ui_index, meta) 784 | 785 | local tile = { 786 | {1, 'SMP', params_data.sample}, 787 | {2, 'NOTE', function(i,n,lock) ui.draw_note(22, 8, params_data,i, ui_index, lock) end }, 788 | {3, 'S-E', function(i,n,lock) ui.draw_waveform(43, 8, params_data, ui_index, meta, lock) end }, 789 | {5, 'FM1', function(i,n,lock) ui.draw_lfo(85, 8, 1, params_data, i, ui_index, lock, n) end }, 790 | {6, 'FM2', function(i,n,lock) ui.draw_lfo(106, 8, 2, params_data, i, ui_index, lock, n) end }, 791 | {7, 'VOL', function(i,n,lock) ui.draw_volume(1, 26, -48, 16, params_data[name_lookup[n]], i , ui_index, lock, n) end}, 792 | {8, 'PAN', function(i,n,lock) ui.draw_pan(22, 26, params_data, ui_index, 8, lock) end }, 793 | {9, 'ENV', function(i,n,lock) ui.draw_env(45, 42, 'AMP', params_data, ui_index) end }, 794 | {13, 'AMP', function(i,n,lock) ui.draw_lfo(85, 26, 1, params_data, i, ui_index, lock, n) end }, 795 | {14, 'CFM', function(i,n,lock) ui.draw_lfo(106, 26, 1, params_data, i, ui_index, lock, n) end }, 796 | {15, 'SR', sr_types[params_data.quality] }, 797 | {16, 'MODE', function(i,n,lock) ui.draw_mode(25, 59, params_data.play_mode, ui_index, lock) end }, 798 | {17, 'FILTER', function(lock) ui.draw_filter(45, 60, params_data, ui_index) end }, 799 | {19, 'DEL', function(i,n,lock) ui.draw_level_meter(85, 44, -48, 0, params_data[name_lookup[n]], i, ui_index, lock, n) end }, 800 | {20, 'RVRB', function(i,n,lock) ui.draw_level_meter(106, 44, -48, 0, params_data[name_lookup[n]], i , ui_index, lock, n) end }, 801 | 802 | } 803 | 804 | for k, v in pairs(tile) do 805 | local lock = ui.is_lock(v, params_data ) 806 | if v[3] and type(v[3]) == 'function' then 807 | v[3](v[1], v[2], lock) 808 | elseif v[3] then 809 | ui.tile(v[1], v[2], v[3], ui_index, lock ) 810 | end 811 | end 812 | end 813 | 814 | 815 | local function tile_x(x) 816 | return 21 * (x) + 1 817 | end 818 | 819 | local function wpos(x) return util.linlin(0, #ui.waveform, 0, 121, x) end 820 | 821 | function ui.draw_save_icon(x, y, index, ui_index, slot) 822 | screen.rect(x + 5, y + 3, 12, 12) 823 | screen.stroke() 824 | 825 | set_brightness(index, ui_index) 826 | 827 | 828 | screen.rect( x + 16, y + 2,1,1) 829 | screen.fill() 830 | 831 | screen.level(0) 832 | screen.rect( x + 7, y + 3,8,4) 833 | screen.stroke() 834 | 835 | screen.rect( x + 11, y + 3,2,2) 836 | screen.fill() 837 | 838 | screen.move( x + 10, y + 13) 839 | screen.text_center(slot) 840 | screen.stroke() 841 | end 842 | 843 | 844 | function ui.sampling(sampler, ui_index, pos) 845 | local modes = {'ST', 'L+R', 'L', 'R'} 846 | local sources = {'EXT', 'INT' } 847 | -- vus[sampling.mode][sampling.source] 848 | local vus = { {{ui.in_l, ui.in_r },{ui.out_l, ui.out_r }}, 849 | {{(ui.in_l+ui.in_r)/((ui.in_l > 0 or ui.in_r > 0) and 2 or 1 )},{(ui.out_l+ui.out_r)/((ui.out_l > 0 or ui.out_r > 0) and 2 or 1)}}, 850 | {{ui.in_l},{ui.out_l}}, 851 | {{ui.in_r},{ui.out_r}}, 852 | } 853 | local src = sources[sampler.source] 854 | local mode = modes[sampler.mode] 855 | --local pos = sampler.pos 856 | local rec = sampler.rec 857 | local play = sampler.play 858 | 859 | local len = sampler.length 860 | 861 | set_brightness(-1, ui_index) 862 | 863 | screen.rect(tile_x(0), 8, 20, 17) 864 | screen.fill() 865 | screen.level(0) 866 | if sampler.mode == 1 then 867 | screen.rect(5,11,13, 2) 868 | screen.rect(5,13,13, 2) 869 | screen.stroke() 870 | screen.level(0) 871 | screen.rect(5, 11, vus[sampler.mode][sampler.source][1], 1) 872 | screen.rect(5, 13, vus[sampler.mode][sampler.source][2], 1) 873 | else 874 | screen.rect(5,11,13, 4) 875 | screen.stroke() 876 | screen.rect(5, 11, vus[sampler.mode][sampler.source][1], 4) 877 | screen.fill() 878 | end 879 | 880 | screen.fill() 881 | screen.level(0) 882 | screen.move( tile_x(0) + 10, 8 + 15) 883 | screen.text_center(mode) 884 | 885 | set_brightness(0, ui_index) 886 | screen.rect(tile_x(1) , 8, 20, 17) 887 | screen.fill() 888 | screen.level(0) 889 | screen.move( tile_x(1) + 10, 8 + 7) 890 | screen.text_center('SRC') 891 | screen.move( tile_x(1) + 10, 8 + 15) 892 | screen.text_center(src) 893 | 894 | 895 | set_brightness(1, ui_index) 896 | if rec then screen.level(15) end 897 | screen.rect( tile_x(2) , 8, 20, 17) 898 | screen.fill() 899 | 900 | 901 | screen.level(0) 902 | screen.circle( tile_x(2) + 10, 8 + 9, 4.5) 903 | 904 | if rec then 905 | screen.circle( tile_x(2) + 10, 8 + 9, 5) 906 | screen.fill() 907 | else 908 | screen.circle( tile_x(2) + 10, 8 + 9, 4.5) 909 | screen.stroke() 910 | end 911 | 912 | set_brightness(2, ui_index) 913 | screen.rect( tile_x(3) , 8, 20, 17) 914 | screen.fill() 915 | screen.level(0) 916 | 917 | screen.move(tile_x(3) + 7, 8 + 5) 918 | screen.line(tile_x(3) + 7 + 8, 8 + 5 + (8 * 0.5)) 919 | screen.line(tile_x(3) + 7, 8 + 13) 920 | screen.close() 921 | if rec then screen.fill() 922 | else screen.stroke() end 923 | 924 | screen.line_width(1) 925 | set_brightness(3, ui_index) 926 | screen.rect( tile_x(4) , 8, 20, 17) 927 | screen.fill() 928 | screen.level(0) 929 | 930 | 931 | ui.draw_save_icon(tile_x(4), 8, 3, ui_index, sampler.slot) 932 | 933 | set_brightness(4, ui_index) 934 | screen.rect( tile_x(5) , 8, 20, 17) 935 | screen.fill() 936 | screen.level(0) 937 | 938 | screen.move( tile_x(5) + 10, 8 + 15) 939 | screen.rect(tile_x(5) + 7, 8 + 7, 8, 8) 940 | screen.rect(tile_x(5) + 6, 8 + 5, 10, 2) 941 | 942 | screen.stroke() 943 | 944 | screen.rect(tile_x(5) + 9, 8 + 3, 1, 1) 945 | screen.rect(tile_x(5) + 10, 8 + 2, 1, 1) 946 | screen.rect(tile_x(5) + 11, 8 + 3, 1, 1) 947 | 948 | screen.rect(tile_x(5) + 8, 8 + 8, 1, 5) 949 | screen.rect(tile_x(5) + 10, 8 + 8, 1, 5) 950 | screen.rect(tile_x(5) + 12, 8 + 8, 1, 5) 951 | screen.fill() 952 | 953 | screen.level(2) 954 | screen.rect(2, 27, 124 , 34 ) 955 | screen.stroke() 956 | 957 | screen.level(2) 958 | screen.stroke() 959 | 960 | 961 | if rec then 962 | local p = math.floor(pos * 10) 963 | ui.waveform[p] = sampler.source == 1 and { ui.in_l, ui.in_r } or { ui.out_l, ui.out_r } 964 | end 965 | 966 | screen.level(1) 967 | 968 | 969 | for k,v in pairs(ui.waveform) do 970 | 971 | local l = sampler.mode == 1 and 1 or sampler.mode == 4 and 2 or 1 972 | local r = sampler.mode == 1 and 2 or sampler.mode == 3 and 1 or 2 973 | 974 | screen.move(3 + wpos(k) , 44) 975 | screen.line(3 + wpos(k) , 43 - util.clamp((ui.waveform[k][l]),0, 15)) 976 | screen.stroke() 977 | 978 | screen.move(3 + wpos(k) , 43) 979 | screen.line(3 +wpos(k), 44 + util.clamp((ui.waveform[k][r]),0, 15)) 980 | screen.stroke() 981 | end 982 | 983 | if play then 984 | screen.level(2) 985 | local pos_ = util.linlin(0,sampler.rec_length, 3, 123, pos) 986 | screen.move( pos_, 60) 987 | screen.line( pos_, 27) 988 | screen.stroke() 989 | end 990 | 991 | set_brightness(5, ui_index) 992 | local start_ = util.linlin( 0,sampler.rec_length, 0, 122, sampler.start) 993 | screen.move(3 + start_, 60) 994 | screen.line(3 + start_, 27) 995 | 996 | screen.stroke() 997 | 998 | set_brightness(6, ui_index) 999 | local end_ = util.linlin(0, sampler.rec_length, 0, 122, sampler.length) 1000 | screen.move( 3 + end_, 60) 1001 | screen.line( 3 + end_, 27) 1002 | 1003 | screen.stroke() 1004 | end 1005 | 1006 | 1007 | function ui.draw_reverb(x, y, index, stage) 1008 | local w, h = 40, 34 1009 | 1010 | screen.level(2) 1011 | screen.rect(x, y + 8, w, h - 8) 1012 | screen.stroke() 1013 | 1014 | screen.level((index == 10 or index == 11) and 6 or 2) 1015 | screen.rect(x - 1, y - 1, w + 1, 7) 1016 | screen.fill() 1017 | 1018 | local time = util.round(util.linlin(0.1, 60, 0, 2, params:get('reverb_time'))) 1019 | local size = util.round(util.linlin(0, 5, 2, 15, params:get('reverb_size'))) 1020 | local damp = util.round(util.linlin(0, 1, 1, util.round(size - 1), params:get('reverb_damp'))) 1021 | local diff = util.round(util.linlin(0, 1, 1, 3, params:get('reverb_diff'))) 1022 | --local mod_depth = util.linlin(0, 1, 10, 40, params:get('reverb_moddepth')) 1023 | --local mod_freq = util.linlin(0, 1, 20, 34, params:get('reverb_modfreq')) 1024 | --local lowcut = util.round(util.linlin(100, 6000, 6, util.round(size), params:get('reverb_lowcut'))) - size 1025 | --local highcut = util.round( util.linlin(1000, 10000, 0, util.round(size - 6 ) , params:get('reverb_highcut'))) 1026 | 1027 | screen.level(0) 1028 | screen.move(x + 19, y + 5) 1029 | if index > 9 and index < 12 then 1030 | local n = {[10] = "DAMP", [11] = "DIFF"} -- , [12] = 'LOWCUT', [13] = 'HIGHCUT',} --[[[11] = 'MOD DEPTH', [12] = 'MOD FREQ',]] 1031 | screen.text_center(n[index]) 1032 | else 1033 | screen.text_center('REVERB') 1034 | end 1035 | 1036 | ui.reverb.velocity = util.linlin(0, 1, 0.01, ( time / 5) / (1 / 2), 0.15) 1037 | ui.reverb.position = (ui.reverb.position - ui.reverb.velocity) % (math.pi * (2 / (diff/2))) 1038 | ui.reverb.x = ((ui.reverb.orbit) * (size/(16))) * math.sin(ui.reverb.position / (size - damp)/2) 1039 | ui.reverb.y = ((ui.reverb.orbit) * (size/16)) * math.sin(ui.reverb.position / (size - damp)/2) 1040 | 1041 | screen.level(1) 1042 | local s_x = util.round(size, 1) 1043 | local s_y = s_x 1044 | local h_x = util.round(s_x / 2,1) 1045 | 1046 | local cube_x = util.round((x + 17) - h_x) --+ util.round(ui.reverb.x / 4) 1047 | local cube_y = util.round((y + 25) - h_x) 1048 | 1049 | screen.rect(cube_x + h_x , cube_y - h_x , s_x , s_y ) 1050 | screen.stroke() 1051 | 1052 | screen.level(1) 1053 | screen.move(cube_x,cube_y - 1 + s_y) 1054 | screen.line(cube_x+h_x, cube_y - h_x - 1 + s_y) 1055 | screen.stroke() 1056 | 1057 | screen.move(cube_x + s_x,cube_y - 1 + s_y) 1058 | screen.line(cube_x+h_x + s_x, cube_y - h_x - 1 + s_y) 1059 | screen.stroke() 1060 | 1061 | screen.level(index == 11 and 15 or 2) -- index > 9 and index < 14 and i or i % 4 ) 1062 | screen.circle(x + (w/2) + (ui.reverb.x / (16 - size)) , y + (h/1.5) - (ui.reverb.y / (16 - size)), 2 - (ui.reverb.x/10) + diff ) 1063 | screen.fill() 1064 | 1065 | if index == 10 then 1066 | 1067 | screen.level(15) 1068 | screen.move(cube_x + s_x - 1, cube_y - damp + s_x) 1069 | screen.line(cube_x - 1, cube_y - damp + s_x) 1070 | 1071 | screen.line(cube_x - 1, cube_y - damp + s_x) 1072 | 1073 | screen.line(cube_x + h_x - 1, cube_y - damp + s_x - h_x) 1074 | screen.line(cube_x + h_x + s_x - 1, cube_y - h_x - damp + s_x) 1075 | 1076 | screen.close() 1077 | screen.stroke() 1078 | end 1079 | --[[ elseif index == 11 then 1080 | 1081 | screen.level(15) 1082 | screen.move(cube_x + s_x - 1, cube_y - diff + s_x) 1083 | screen.line(cube_x - 1, cube_y - diff + s_x) 1084 | 1085 | screen.line(cube_x - 1, cube_y - diff + s_x) 1086 | 1087 | screen.line(cube_x + h_x - 1, cube_y - diff + s_x - h_x) 1088 | screen.line(cube_x + h_x + s_x - 1, cube_y - h_x - diff + s_x) 1089 | 1090 | screen.close() 1091 | screen.stroke() 1092 | ]] 1093 | 1094 | 1095 | 1096 | screen.level(1) 1097 | screen.move(cube_x,cube_y - 1) 1098 | screen.line(cube_x+s_x/2, cube_y - s_y/2 - 1) 1099 | screen.stroke() 1100 | 1101 | screen.move(cube_x + s_x,cube_y - 1) 1102 | screen.line(cube_x+s_x/2 + s_x, cube_y - s_y/2 - 1) 1103 | screen.stroke() 1104 | 1105 | screen.level(1) 1106 | screen.rect(cube_x, cube_y, s_x, s_y ) 1107 | screen.stroke() 1108 | 1109 | screen.level(0) 1110 | screen.rect(x,y + h - 2, w - 2, 1) 1111 | screen.fill() 1112 | 1113 | --screen.level(2) 1114 | --screen.rect(x, y, w, h) 1115 | --screen.stroke() 1116 | 1117 | 1118 | end 1119 | 1120 | function ui.draw_comp(x, y, index) 1121 | local w, h = 40, 34 1122 | 1123 | screen.level(2) 1124 | screen.rect(x, y + 8, w, h - 8) 1125 | screen.stroke() 1126 | screen.level(index > 2 and index < 8 and 6 or 2) 1127 | screen.rect(x - 1, y - 1, w + 1, 7) 1128 | screen.fill() 1129 | 1130 | local level = util.linlin(-99, 6, 0, 34, params:get('takt_comp_level')) 1131 | local threshold = util.linexp(0, 1, 5, 15, params:get('takt_comp_threshold')) 1132 | local slope_b = util.linlin(0, 1, 0, 10, params:get('comp_slopebelow')) 1133 | local slope_a = util.linlin(0, 1, 0, 10, params:get('comp_slopeabove')) 1134 | local attack = util.linlin(0, 1, 3, 20, params:get('comp_clamptime')) 1135 | local release = util.linlin(0, 1, 3, 39, params:get('comp_relaxtime')) 1136 | 1137 | screen.level(0) 1138 | screen.move(x + 19, y + 5) 1139 | if (index > 0 and index < 8) then 1140 | local n = {[1] = "COMP VOL", [2] = "DRY/WET", [3] = 'THRESH', [4] = 'SLOPE B', [5] = 'SLOPE A', [6] = 'ATTACK', [7] = 'RELEASE'} 1141 | screen.text_center(n[index]) 1142 | else 1143 | screen.text_center('DYNAMICS') 1144 | end 1145 | 1146 | screen.stroke() 1147 | screen.level(index == 6 and 15 or 1) 1148 | screen.move(x, y + h - slope_a) 1149 | screen.line(x + attack, y + h - threshold - slope_a) 1150 | screen.stroke() 1151 | screen.level(index == 4 and 15 or 1) 1152 | screen.move(x + attack, y + h - threshold - slope_a) 1153 | screen.line(x + w - 1 , y + h - threshold - slope_b) 1154 | screen.stroke() 1155 | if index == 5 then 1156 | screen.level(index == 5 and 15 or 1) 1157 | screen.rect( x + attack - 1, y + h - threshold - slope_a -1, 3, 3) 1158 | screen.fill() 1159 | end 1160 | 1161 | screen.level(index == 7 and 15 or 1) 1162 | screen.move(x + release, y + h - threshold - 10) 1163 | screen.line(x + release, y + h - 1) 1164 | screen.stroke() 1165 | --thr 1166 | 1167 | screen.level(index == 3 and 15 or 1) 1168 | screen.move(x + release , y + h - threshold - 10) 1169 | screen.line(x , y + h - threshold - 10) 1170 | screen.stroke() 1171 | screen.level(1) 1172 | screen.rect(x + w - 3, y + h - 1 , 1, - ui.out_l) 1173 | screen.rect(x + w - 2, y + h - 1, 1, - ui.out_r) 1174 | screen.fill() 1175 | end 1176 | 1177 | function ui.draw_lfo_tile(x,y, index) 1178 | 1179 | local shapes = {'SIN', 'TRI', 'SAW', 'SQR', 'RND'} 1180 | local shape1 = params:get('lfo_1_wave_shape') 1181 | local freq1 = params:get('lfo_1_freq') 1182 | local shape2 = params:get('lfo_2_wave_shape') 1183 | local freq2 = params:get('lfo_2_freq') 1184 | -- print(freq1,freq2,shape1,shape2) 1185 | -- 2.0 4.0 1 4 1186 | 1187 | 1188 | local lvl = (index > 14 and index < 19) and 6 or 2 1189 | screen.level(lvl) 1190 | screen.rect(x, y, 20, 17) 1191 | screen.fill() 1192 | screen.level(2) 1193 | screen.rect(x + 22, y + 1, 40, 16) 1194 | screen.stroke() 1195 | 1196 | screen.level(0) 1197 | screen.move(x + 10, y + 7) 1198 | 1199 | if (index > 14 and index < 19) then 1200 | local n = {[15] = "FREQ", [16] = "SHP", [17] = "FREQ", [18] = "SHP",} 1201 | screen.text_center(n[index]) 1202 | else 1203 | screen.text_center('LFO') 1204 | end 1205 | 1206 | 1207 | 1208 | local lvl2 = (index > 14 and index < 19) and index or 15 1209 | 1210 | -- local n = index == 16 and 'shape1' or 'shape2' 1211 | local t = index == 17 and freq1 or freq2 1212 | 1213 | --if (index == 16 or index == 18 )then 1214 | --screen.move(x + 9, y + 8) 1215 | --screen.text_center(shapes[n] ) 1216 | --screen.stroke() 1217 | if index == 15 then 1218 | ui.draw_level_meter(x, y, 0.05, 20, freq1, 15, index) 1219 | elseif index == 16 then 1220 | screen.move(x + 10, y + 15) 1221 | screen.text_center(shapes[shape1] ) 1222 | screen.stroke() 1223 | elseif index == 17 then 1224 | ui.draw_level_meter(x, y, 0.05, 20, freq2, 17, index) 1225 | elseif index == 18 then 1226 | screen.move(x + 10, y + 15) 1227 | screen.text_center(shapes[shape2] ) 1228 | screen.stroke() 1229 | else 1230 | ui.draw_level_meter(x, y, 0.05, 20, freq1, 15, index) 1231 | end 1232 | 1233 | screen.level((index == 15 or index == 16) and 6 or 1) 1234 | lfo(x + 17, y, 1, util.clamp(freq1,0.2,20), shape1, 1, 38) 1235 | screen.level((index == 17 or index == 18) and 6 or 1) 1236 | lfo(x + 17, y + 8, 2, util.clamp(freq2,0.2, 20), shape2, 1, 38) 1237 | end 1238 | 1239 | function ui.patterns(pattern, metaseq, ui_index, stage) 1240 | local tile = { 1241 | 1242 | {1, 'VOL', function(i,n) ui.draw_level_meter(1, 8, -99, 6, params:get('takt_comp_level'), i, ui_index, false, n) end}, 1243 | {2, 'MIX', function(i,n) ui.draw_level_meter(1, 26, -1, 1, params:get('takt_comp_mix'), 2, ui_index, false, n) end}, 1244 | {4, 'TIME', function(i,n) ui.draw_level_meter(64, 8, 0.1, 60, params:get('reverb_time'), 8, ui_index, false, n) end}, 1245 | {10,'SIZE', function(i,n) ui.draw_level_meter(64, 26, 0, 5, params:get('reverb_size'), 9, ui_index, false, n) end}, 1246 | 1247 | } 1248 | 1249 | for k, v in pairs(tile) do 1250 | 1251 | 1252 | if v[3] and type(v[3]) == 'function' then 1253 | v[3](v[1], v[2]) 1254 | elseif v[3] then 1255 | ui.tile(v[1], v[2], v[3], ui_index, lock ) 1256 | end 1257 | end 1258 | 1259 | ui.draw_comp(23, 9, ui_index) 1260 | ui.draw_reverb(86, 9, ui_index, stage) 1261 | ui.draw_delay(1, 44, ui_index) 1262 | ui.draw_lfo_tile(64, 44, ui_index) 1263 | 1264 | end 1265 | 1266 | return ui -------------------------------------------------------------------------------- /lib/Engine_Timber_Takt.sc: -------------------------------------------------------------------------------- 1 | // CroneEngine_Timber 2 | // 3 | // v1.0.0 Beta 6 Mark Eats 4 | 5 | Engine_Timber_Takt : CroneEngine { 6 | 7 | var maxVoices = 7; 8 | var maxSamples = 101; 9 | var killDuration = 0.003; 10 | var waveformDisplayRes = 40; 11 | 12 | var voiceGroup; 13 | var voiceList; 14 | var samples; 15 | var replyFunc; 16 | 17 | var players; 18 | var synthNames; 19 | var lfos; 20 | var mixer; 21 | var reverb; 22 | var delay; 23 | 24 | var lfoBus; 25 | var fxBus; 26 | var reverbBus; 27 | var mixerBus; 28 | var sidechainBus; 29 | 30 | var loadQueue; 31 | var loadingSample = -1; 32 | 33 | var generateWaveformsOnLoad = true; 34 | var scriptAddress; 35 | var waveformQueue; 36 | var waveformRoutine; 37 | var generatingWaveform = -1; 38 | var abandonCurrentWaveform = false; 39 | 40 | var pitchBendAllRatio = 1; 41 | var pressureAll = 0; 42 | 43 | var defaultSample; 44 | 45 | // var debugBuffer; 46 | 47 | *new { arg context, doneCallback; 48 | ^super.new(context, doneCallback); 49 | } 50 | 51 | 52 | alloc { 53 | 54 | // debugBuffer = Buffer.alloc(context.server, context.server.sampleRate * 4, 5); 55 | 56 | defaultSample = ( 57 | 58 | streaming: 0, 59 | buffer: nil, 60 | path: nil, 61 | 62 | channels: 0, 63 | sampleRate: 0, 64 | reverbSend: -99, 65 | delaySend: -99, 66 | sidechainSend: -99, 67 | numFrames: 0, 68 | 69 | transpose: 0, 70 | detuneCents: 0, 71 | pitchBendRatio: 1, 72 | pressure: 0, 73 | 74 | lfo1Fade: 0, 75 | lfo2Fade: 0, 76 | 77 | startFrame: 0, 78 | endFrame: 0, 79 | playMode: 0, 80 | loopStartFrame: 0, 81 | loopEndFrame: 0, 82 | 83 | freqModLfo1: 0, 84 | freqModLfo2: 0, 85 | freqModEnv: 0, 86 | 87 | ampAttack: 0, 88 | ampDecay: 1, 89 | ampSustain: 1, 90 | ampRelease: 0.003, 91 | 92 | modAttack: 1, 93 | modDecay: 2, 94 | modSustain: 0.65, 95 | modRelease: 1, 96 | 97 | downSampleTo: 48000, 98 | bitDepth: 24, 99 | 100 | filterFreq: 20000, 101 | filterReso: 0, 102 | filterType: 0, 103 | filterTracking: 1, 104 | filterFreqModLfo1: 0, 105 | filterFreqModLfo2: 0, 106 | filterFreqModEnv: 0, 107 | filterFreqModVel: 0, 108 | filterFreqModPressure: 0, 109 | 110 | pan: 0, 111 | panModLfo1: 0, 112 | panModLfo2: 0, 113 | panModEnv: 0, 114 | amp: 0, 115 | ampModLfo1: 0, 116 | ampModLfo2: 0, 117 | ); 118 | 119 | voiceGroup = Group.new(context.xg); 120 | voiceList = List.new(); 121 | 122 | lfoBus = Bus.control(context.server, 2); 123 | mixerBus = Bus.audio(context.server, 2); 124 | sidechainBus = Bus.audio(context.server, 2); 125 | fxBus = Bus.audio(context.server, 2); 126 | reverbBus = Bus.audio(context.server, 2); 127 | players = Array.newClear(4); 128 | 129 | loadQueue = Array.new(maxSamples); 130 | scriptAddress = NetAddr("localhost", 10111); 131 | waveformQueue = Array.new(maxSamples); 132 | 133 | // Receive messages from server 134 | replyFunc = OSCFunc({ 135 | arg msg; 136 | var id = msg[2]; 137 | scriptAddress.sendBundle(0, ['/enginePlayPosition', msg[3].asInt, msg[4].asInt, msg[5]]); 138 | }, path: '/replyPlayPosition', srcID: context.server.addr); 139 | 140 | // Sample defaults 141 | samples = Array.fill(maxSamples, { defaultSample.deepCopy; }); 142 | 143 | // Buffer players 144 | 2.do({ 145 | arg i; 146 | players[i] = { 147 | arg freqRatio = 1, sampleRate, gate, playMode, voiceId, sampleId, bufnum, numFrames, startFrame, i_lockedStartFrame, endFrame, loopStartFrame, loopEndFrame; 148 | 149 | var signal, progress, phase, offsetPhase, direction, rate, phaseStart, phaseEnd, 150 | firstFrame, lastFrame, shouldLoop, inLoop, loopEnabled, loopInf, duckDuration, duckNumFrames, duckNumFramesShortened, duckGate, duckControl; 151 | 152 | firstFrame = startFrame.min(endFrame); 153 | lastFrame = startFrame.max(endFrame); 154 | 155 | loopEnabled = InRange.kr(playMode, 0, 1); 156 | loopInf = InRange.kr(playMode, 1, 1); 157 | 158 | direction = (endFrame - startFrame).sign; 159 | rate = freqRatio * BufRateScale.ir(bufnum) * direction; 160 | 161 | progress = (Sweep.ar(1, SampleRate.ir * rate) + i_lockedStartFrame).clip(firstFrame, lastFrame); 162 | 163 | shouldLoop = loopEnabled * gate.max(loopInf); 164 | 165 | inLoop = Select.ar(direction > 0, [ 166 | progress < (loopEndFrame - 40), // NOTE: This tiny offset seems odd but avoids some clicks when phasor start changes 167 | progress > (loopStartFrame + 40) 168 | ]); 169 | inLoop = PulseCount.ar(inLoop).clip * shouldLoop; 170 | 171 | phaseStart = Select.ar(inLoop, [ 172 | K2A.ar(i_lockedStartFrame), 173 | K2A.ar(loopStartFrame) 174 | ]); 175 | // Let phase run over end so it is caught by FreeSelf below. 150 is chosen to work even with drastic re-pitching. 176 | phaseEnd = Select.ar(inLoop, [ 177 | K2A.ar(endFrame + (BlockSize.ir * 150 * direction)), 178 | K2A.ar(loopEndFrame) 179 | ]); 180 | 181 | phase = Phasor.ar(trig: 0, rate: rate, start: phaseStart, end: phaseEnd, resetPos: 0); 182 | 183 | // Free if reached end of sample 184 | FreeSelf.kr(Select.kr(direction > 0, [ 185 | phase < firstFrame, 186 | phase > lastFrame 187 | ])); 188 | 189 | SendReply.kr(trig: Impulse.kr(15), cmdName: '/replyPlayPosition', values: [sampleId, voiceId, (phase / numFrames).clip]); 190 | 191 | signal = BufRd.ar(numChannels: i + 1, bufnum: bufnum, phase: phase, interpolation: 2); 192 | 193 | // Duck across loop points and near start/end to avoid clicks (3ms * 2, playback time) 194 | duckDuration = 0.003; 195 | duckNumFrames = duckDuration * BufSampleRate.ir(bufnum) * freqRatio * BufRateScale.ir(bufnum); 196 | 197 | // Start (these also mute one-shots) 198 | duckControl = Select.ar(firstFrame > 0, [ 199 | phase.linlin(firstFrame, firstFrame + 1, 0, 1), 200 | phase.linlin(firstFrame, firstFrame + duckNumFrames, 0, 1) 201 | ]); 202 | 203 | // End 204 | duckControl = duckControl * Select.ar(lastFrame < numFrames, [ 205 | phase.linlin(lastFrame - 1, lastFrame, 1, 0), 206 | phase.linlin(lastFrame - duckNumFrames, lastFrame, 1, 0) 207 | ]); 208 | 209 | duckControl = duckControl.max(inLoop); 210 | 211 | duckNumFramesShortened = duckNumFrames.min((loopEndFrame - loopStartFrame) * 0.45); 212 | duckDuration = (duckNumFramesShortened / duckNumFrames) * duckDuration; 213 | duckNumFrames = duckNumFramesShortened; 214 | 215 | duckGate = Select.ar(direction > 0, [ 216 | InRange.ar(phase, loopStartFrame, loopStartFrame + duckNumFrames), 217 | InRange.ar(phase, loopEndFrame - duckNumFrames, loopEndFrame) 218 | ]) * inLoop; 219 | 220 | duckControl = duckControl * EnvGen.ar(Env.new([1, 0, 1], [A2K.kr(duckDuration)], \linear, nil, nil), duckGate); 221 | 222 | signal = signal * duckControl; 223 | }; 224 | }); 225 | 226 | // Streaming players 227 | 2.do({ 228 | arg i; 229 | players[i + 2] = { 230 | arg freqRatio = 1, sampleRate, gate, playMode, voiceId, sampleId, bufnum, numFrames, i_lockedStartFrame, endFrame, loopStartFrame, loopEndFrame; 231 | var signal, rate, progress, loopEnabled, oneShotActive, duckDuration, duckControl; 232 | 233 | loopEnabled = InRange.kr(playMode, 0, 1); 234 | 235 | rate = (sampleRate / SampleRate.ir) * freqRatio; 236 | 237 | signal = VDiskIn.ar(numChannels: i + 1, bufnum: bufnum, rate: rate, loop: loopEnabled); 238 | 239 | progress = Sweep.ar(1, SampleRate.ir * rate) + i_lockedStartFrame; 240 | progress = Select.ar(loopEnabled, [progress.clip(0, endFrame), progress.wrap(0, numFrames)]); 241 | 242 | SendReply.kr(trig: Impulse.kr(15), cmdName: '/replyPlayPosition', values: [sampleId, voiceId, progress / numFrames]); 243 | 244 | // Ducking 245 | duckDuration = 0.003 * sampleRate * rate.reciprocal; 246 | 247 | // Start 248 | duckControl = Select.ar(i_lockedStartFrame > 0, [ 249 | K2A.ar(1), 250 | progress.linlin(i_lockedStartFrame, i_lockedStartFrame + duckDuration, 0, 1) + (progress < i_lockedStartFrame) 251 | ]); 252 | 253 | // End 254 | duckControl = duckControl * Select.ar(endFrame < numFrames, [ 255 | progress.linlin(endFrame, endFrame + 1, 1, loopEnabled), 256 | progress.linlin(endFrame - duckDuration, endFrame, 1, loopEnabled) 257 | ]); 258 | 259 | // Duck at end of stream if loop is enabled and startFrame > 0 260 | duckControl = duckControl * Select.ar(loopEnabled * (i_lockedStartFrame > 0), [ 261 | K2A.ar(1), 262 | progress.linlin(numFrames - duckDuration, numFrames, 1, 0) 263 | ]); 264 | 265 | // One shot freer 266 | FreeSelf.kr((progress >= endFrame) * (1 - loopEnabled)); 267 | 268 | signal = signal * duckControl; 269 | }; 270 | }); 271 | 272 | 273 | // SynthDefs 274 | 275 | lfos = SynthDef(\lfos, { 276 | arg out, lfo1Freq = 2, lfo1WaveShape = 0, lfo2Freq = 4, lfo2WaveShape = 3; 277 | var lfos, i_controlLag = 0.005; 278 | 279 | var lfoFreqs = [Lag.kr(lfo1Freq, i_controlLag), Lag.kr(lfo2Freq, i_controlLag)]; 280 | var lfoWaveShapes = [lfo1WaveShape, lfo2WaveShape]; 281 | 282 | lfos = Array.fill(2, { 283 | arg i; 284 | var lfo, lfoOscArray = [ 285 | SinOsc.kr(lfoFreqs[i]), 286 | LFTri.kr(lfoFreqs[i]), 287 | LFSaw.kr(lfoFreqs[i]), 288 | LFPulse.kr(lfoFreqs[i], mul: 2, add: -1), 289 | LFNoise0.kr(lfoFreqs[i]) 290 | ]; 291 | lfo = Select.kr(lfoWaveShapes[i], lfoOscArray); 292 | lfo = Lag.kr(lfo, 0.005); 293 | }); 294 | 295 | Out.kr(out, lfos); 296 | 297 | }).play(target:context.xg, args: [\out, lfoBus], addAction: \addToHead); 298 | 299 | 300 | synthNames = Array.with(\monoBufferVoice, \stereoBufferVoice, \monoStreamingVoice, \stereoStreamingVoice); 301 | synthNames.do({ 302 | 303 | arg name, i; 304 | 305 | SynthDef(name, { 306 | 307 | arg out, reverbSendBus, delaySendBus, sidechainSendBus, sampleRate, freq, transposeRatio, detuneRatio = 1, pitchBendRatio = 1, pitchBendSampleRatio = 1, playMode = 0, gate = 0, killGate = 1, vel = 1, pressure = 0, pressureSample = 0, amp = 1, 308 | lfos, lfo1Fade, lfo2Fade, freqModLfo1, freqModLfo2, freqModEnv, 309 | ampAttack, ampDecay, ampSustain, ampRelease, modAttack, modDecay, modSustain, modRelease, 310 | downSampleTo, bitDepth, reverbSend, delaySend, sidechainSend, 311 | filterFreq, filterReso, filterType, filterTracking, filterFreqModLfo1, filterFreqModLfo2, filterFreqModEnv, filterFreqModVel, filterFreqModPressure, 312 | pan, panModLfo1, panModLfo2, panModEnv, ampModLfo1, ampModLfo2; 313 | 314 | var i_nyquist = SampleRate.ir * 0.5, i_cFreq = 48.midicps, i_origFreq = 60.midicps, signal, freqRatio, freqModRatio, filterFreqRatio, 315 | killEnvelope, ampEnvelope, modEnvelope, lfo1, lfo2, i_controlLag = 0.005; 316 | 317 | // Lag inputs 318 | detuneRatio = Lag.kr(detuneRatio * pitchBendRatio * pitchBendSampleRatio, i_controlLag); 319 | pressure = Lag.kr(pressure + pressureSample, i_controlLag); 320 | amp = Lag.kr(amp, i_controlLag); 321 | filterFreq = Lag.kr(filterFreq, i_controlLag); 322 | filterReso = Lag.kr(filterReso, i_controlLag); 323 | pan = Lag.kr(pan, i_controlLag); 324 | 325 | // LFOs 326 | lfo1 = Line.kr(start: (lfo1Fade < 0), end: (lfo1Fade >= 0), dur: lfo1Fade.abs, mul: In.kr(lfos, 1)); 327 | lfo2 = Line.kr(start: (lfo2Fade < 0), end: (lfo2Fade >= 0), dur: lfo2Fade.abs, mul: In.kr(lfos, 2)[1]); 328 | 329 | // Envelopes 330 | gate = gate.max(InRange.kr(playMode, 3, 3)); // Ignore gate for one shots 331 | killGate = killGate + Impulse.kr(0); // Make sure doneAction fires 332 | killEnvelope = EnvGen.ar(envelope: Env.asr(0, 1, killDuration), gate: killGate, doneAction: Done.freeSelf); 333 | ampEnvelope = EnvGen.ar(envelope: Env.adsr(ampAttack, ampDecay, ampSustain, ampRelease), gate: gate, doneAction: Done.freeSelf); 334 | modEnvelope = EnvGen.ar(envelope: Env.adsr(modAttack, modDecay, modSustain, modRelease), gate: gate); 335 | 336 | // gate.poll(8, "gate"); 337 | // killGate.poll(8, "killGate"); 338 | // ampEnvelope.poll(8, "ampEnvelope"); 339 | 340 | // Freq modulation 341 | freqModRatio = 2.pow((lfo1 * freqModLfo1) + (lfo2 * freqModLfo2) + (modEnvelope * freqModEnv)); 342 | freq = freq * transposeRatio * detuneRatio; 343 | freq = (freq * freqModRatio).clip(20, i_nyquist); 344 | freqRatio = (freq / i_origFreq) * 1; 345 | 346 | // Player 347 | signal = SynthDef.wrap(players[i], [\kr, \kr, \kr, \kr], [freqRatio, sampleRate, gate, playMode]); 348 | 349 | // Downsample and bit reduction 350 | if(i > 1, { // Streaming 351 | downSampleTo = downSampleTo.min(sampleRate); 352 | }, { 353 | downSampleTo = Select.kr(downSampleTo >= sampleRate, [ 354 | downSampleTo, 355 | downSampleTo = context.server.sampleRate 356 | ]); 357 | }); 358 | signal = Decimator.ar(signal, downSampleTo, bitDepth); 359 | 360 | // 12dB LP/HP filter 361 | filterFreqRatio = Select.kr((freq < i_cFreq), [ 362 | i_cFreq + ((freq - i_cFreq) * filterTracking), 363 | i_cFreq - ((i_cFreq - freq) * filterTracking) 364 | ]); 365 | filterFreqRatio = filterFreqRatio / i_cFreq; 366 | filterFreq = filterFreq * filterFreqRatio; 367 | filterFreq = filterFreq * ((48 * lfo1 * filterFreqModLfo1) + (48 * lfo2 * filterFreqModLfo2) + (96 * modEnvelope * filterFreqModEnv) + (48 * vel * filterFreqModVel) + (48 * pressure * filterFreqModPressure)).midiratio; 368 | filterFreq = filterFreq.clip(20, 20000); 369 | filterReso = filterReso.linlin(0, 1, 1, 0.02); 370 | signal = Select.ar(filterType, [ 371 | RLPF.ar(signal, filterFreq, filterReso), 372 | RHPF.ar(signal, filterFreq, filterReso) 373 | ]); 374 | 375 | // Panning 376 | pan = (pan + (lfo1 * panModLfo1) + (lfo2 * panModLfo2) + (modEnvelope * panModEnv)).clip(-1, 1); 377 | signal = Splay.ar(inArray: signal, spread: 1 - pan.abs, center: pan); 378 | 379 | // Amp 380 | signal = signal * lfo1.range(1 - ampModLfo1, 1) * lfo2.range(1 - ampModLfo2, 1) * ampEnvelope * killEnvelope * vel.linlin(0, 1, 0.1, 1); 381 | signal = tanh(signal * amp.dbamp * (1 + pressure)).softclip; 382 | Out.ar(out, signal); 383 | Out.ar(sidechainSendBus, signal * sidechainSend.dbamp); 384 | Out.ar(delaySendBus, signal * delaySend.dbamp); 385 | Out.ar(reverbSendBus, signal * reverbSend.dbamp); 386 | }).add; 387 | }); 388 | 389 | 390 | // delay 391 | delay = SynthDef(\delay, { 392 | 393 | arg in, out, delayTime=0.3, feedbackAmount =0.5, level = -10; 394 | var signal = In.ar(in, 2); 395 | var feedback = LocalIn.ar(2); 396 | signal = DelayC.ar(signal + feedback, maxdelaytime: 4, delaytime: delayTime); 397 | 398 | LocalOut.ar(signal * feedbackAmount); 399 | Out.ar(out, signal * level.dbamp); 400 | 401 | 402 | }).play(target:context.xg, args: [\in, fxBus, \out, mixerBus], addAction: \addToTail); 403 | 404 | 405 | 406 | // reverb 407 | reverb = SynthDef(\reverb, { 408 | 409 | arg in, out, reverbTime=10, damp=0.1, size=3.0, diff=0.7, modDepth=0.1, modFreq=2, low=1, mid=1, high=1, lowcut=500, highcut=200; 410 | 411 | var signal = In.ar(in, 2); 412 | signal = JPverb.ar(signal, reverbTime, damp, size, diff, modDepth, modFreq, low, mid, high, lowcut, highcut); 413 | Out.ar(out, signal); 414 | 415 | 416 | }).play(target:context.xg, args: [\in, reverbBus, \out, mixerBus,], addAction: \addToTail); 417 | 418 | 419 | 420 | // Mixer and FX 421 | mixer = SynthDef(\mixer, { 422 | 423 | 424 | arg in, out, sidechain, compMix = -1, compLevel = 0, thresh=0.1, slopeBelow=1, slopeAbove=0.1, clampTime=0.01, relaxTime=0.2; //, mul=1, add=0; 425 | var sidechain_sig = In.ar(sidechain, 2); 426 | var signal = In.ar(in, 2); 427 | 428 | var wet = Compander.ar(signal * compLevel.dbamp, XFade2.ar(signal, sidechain_sig, compMix), thresh, slopeBelow, slopeAbove, clampTime, relaxTime, mul: 1, add: 0); 429 | 430 | 431 | signal = CompanderD.ar( signal, 0.7, 1, 0.4, 0.008, 0.2); 432 | 433 | //signal = Compander.ar(signal, sidechain_sig, thresh, slopeBelow, slopeAbove, clampTime, relaxTime, mul: 1, add: 0); 434 | 435 | signal = tanh(XFade2.ar(signal, wet, compMix).softclip); 436 | 437 | //ReplaceOut.ar(out, XFade2.ar(signal, wet, compMix)); 438 | 439 | Out.ar(out, signal); 440 | 441 | }).play(target:context.xg, args: [\in, mixerBus, \sidechain, sidechainBus, \out, context.out_b], addAction: \addToTail); 442 | 443 | 444 | 445 | this.addCommands; 446 | 447 | } 448 | 449 | 450 | 451 | // Functions 452 | 453 | queueLoadSample { 454 | arg sampleId, filePath; 455 | var item = ( 456 | sampleId: sampleId, 457 | filePath: filePath 458 | ); 459 | 460 | loadQueue = loadQueue.addFirst(item); 461 | if(loadingSample == -1, { 462 | this.loadSample() 463 | }); 464 | } 465 | 466 | killVoicesPlaying { 467 | arg sampleId; 468 | var activeVoices; 469 | 470 | // Kill any voices that are currently playing this sampleId 471 | activeVoices = voiceList.select{arg v; v.sampleId == sampleId}; 472 | activeVoices.do({ 473 | arg v; 474 | if(v.startRoutine.notNil, { 475 | v.startRoutine.stop; 476 | v.startRoutine.free; 477 | }, { 478 | v.theSynth.set(\killGate, -1); 479 | }); 480 | voiceList.remove(v); 481 | }); 482 | } 483 | 484 | clearBuffer { 485 | arg sampleId; 486 | 487 | this.killVoicesPlaying(sampleId); 488 | 489 | if(samples[sampleId].buffer.notNil, { 490 | samples[sampleId].buffer.close; 491 | samples[sampleId].buffer.free; 492 | samples[sampleId].buffer = nil; 493 | }); 494 | 495 | samples[sampleId].numFrames = 0; 496 | } 497 | 498 | moveSample { 499 | arg fromId, toId; 500 | var fromSample = samples[fromId]; 501 | 502 | if(fromId != toId, { 503 | this.killVoicesPlaying(fromId); 504 | this.killVoicesPlaying(toId); 505 | samples[fromId] = samples[toId]; 506 | samples[toId] = fromSample; 507 | }); 508 | } 509 | 510 | copySample { 511 | arg fromId, toFirstId, toLastId; 512 | 513 | for(toFirstId, toLastId, { 514 | arg i; 515 | if(fromId != i, { 516 | this.killVoicesPlaying(fromId); 517 | this.killVoicesPlaying(i); 518 | samples[i] = samples[fromId].deepCopy; 519 | }); 520 | }); 521 | } 522 | 523 | copyParams { 524 | arg fromId, toFirstId, toLastId; 525 | 526 | for(toFirstId, toLastId, { 527 | arg i; 528 | var newSample; 529 | 530 | if((fromId != i).and(samples[i].numFrames > 0), { 531 | this.killVoicesPlaying(fromId); 532 | this.killVoicesPlaying(i); 533 | 534 | // Copies all except play mode and marker positions 535 | newSample = samples[fromId].deepCopy; 536 | 537 | newSample.streaming = samples[i].streaming; 538 | newSample.buffer = samples[i].buffer; 539 | newSample.path = samples[i].path; 540 | 541 | newSample.channels = samples[i].channels; 542 | newSample.sampleRate = samples[i].sampleRate; 543 | newSample.numFrames = samples[i].numFrames; 544 | 545 | newSample.startFrame = samples[i].startFrame; 546 | newSample.endFrame = samples[i].endFrame; 547 | newSample.playMode = samples[i].playMode; 548 | newSample.loopStartFrame = samples[i].loopStartFrame; 549 | newSample.loopEndFrame = samples[i].loopEndFrame; 550 | 551 | samples[i] = newSample; 552 | }); 553 | }); 554 | } 555 | 556 | loadFailed { 557 | arg sampleId, message; 558 | if(message.notNil, { 559 | (sampleId.asString ++ ":" + message).postln; 560 | }); 561 | scriptAddress.sendBundle(0, ['/engineSampleLoadFailed', sampleId, message]); 562 | } 563 | 564 | loadSample { 565 | var timeoutRoutine, item, sampleId, filePath, file, buffer, sample = (); 566 | 567 | if(loadQueue.notEmpty, { 568 | 569 | item = loadQueue.pop; 570 | sampleId = item.sampleId; 571 | filePath = item.filePath; 572 | 573 | loadingSample = sampleId; 574 | // ("Load" + sampleId + filePath).postln; 575 | 576 | this.clearBuffer(sampleId); 577 | 578 | if((sampleId < 0).or(sampleId >= samples.size), { 579 | ("Invalid sample ID:" + sampleId + "(must be 0-" ++ (samples.size - 1) ++ ").").postln; 580 | this.loadSample(); 581 | 582 | }, { 583 | 584 | if(filePath.compare("-") != 0, { 585 | 586 | file = SoundFile.openRead(filePath); 587 | if(file.isNil, { 588 | this.loadFailed(sampleId, "Could not open file"); 589 | this.loadSample(); 590 | }, { 591 | 592 | // 2 sec then timeout and move to next one 593 | timeoutRoutine = Routine.new({ 594 | 2.yield; 595 | this.loadFailed(sampleId, "Loading timed out"); 596 | this.loadSample(); 597 | }).play; 598 | 599 | sample = samples[sampleId]; 600 | 601 | sample.channels = file.numChannels.min(2); 602 | sample.sampleRate = file.sampleRate; 603 | sample.startFrame = 0; 604 | sample.endFrame = file.numFrames; 605 | sample.loopStartFrame = 0; 606 | sample.loopEndFrame = file.numFrames; 607 | 608 | // If file is over the buffer-addressable number of frames (~5.8mins at 48kHz) then prepare it for streaming instead. 609 | // Streaming has fairly limited options for playback (no looping etc). 610 | 611 | if(file.numFrames < 16777216, { 612 | // if(file.duration < 10, { 613 | 614 | // Load into memory 615 | if(file.numChannels == 1, { 616 | buffer = Buffer.read(server: context.server, path: filePath, action: { 617 | arg buf; 618 | sample.numFrames = file.numFrames; 619 | scriptAddress.sendBundle(0, ['/engineSampleLoaded', sampleId, 0, file.numFrames, file.numChannels, file.sampleRate]); 620 | // ("Buffer" + sampleId + "loaded:" + buf.numFrames + "frames." + buf.duration.round(0.01) + "secs." + buf.numChannels + "channel.").postln; 621 | this.queueWaveformGeneration(sampleId, filePath); 622 | timeoutRoutine.stop(); 623 | this.loadSample(); 624 | }); 625 | }, { 626 | buffer = Buffer.readChannel(server: context.server, path: filePath, channels: [0, 1], action: { 627 | arg buf; 628 | sample.numFrames = file.numFrames; 629 | scriptAddress.sendBundle(0, ['/engineSampleLoaded', sampleId, 0, file.numFrames, file.numChannels, file.sampleRate]); 630 | // ("Buffer" + sampleId + "loaded:" + buf.numFrames + "frames." + buf.duration.round(0.01) + "secs." + buf.numChannels + "channels.").postln; 631 | this.queueWaveformGeneration(sampleId, filePath); 632 | timeoutRoutine.stop(); 633 | this.loadSample(); 634 | }); 635 | }); 636 | sample.buffer = buffer; 637 | sample.streaming = 0; 638 | 639 | }, { 640 | if(file.numChannels > 2, { 641 | this.loadFailed(sampleId, "Too many chans (" ++ file.numChannels ++ ")"); 642 | timeoutRoutine.stop(); 643 | this.loadSample(); 644 | }, { 645 | // Prepare for streaming from disk 646 | sample.path = filePath; 647 | sample.streaming = 1; 648 | sample.numFrames = file.numFrames; 649 | scriptAddress.sendBundle(0, ['/engineSampleLoaded', sampleId, 1, file.numFrames, file.numChannels, file.sampleRate]); 650 | // ("Stream buffer" + sampleId + "prepared:" + file.numFrames + "frames." + file.duration.round(0.01) + "secs." + file.numChannels + "channels.").postln; 651 | this.queueWaveformGeneration(sampleId, filePath); 652 | timeoutRoutine.stop(); 653 | this.loadSample(); 654 | }); 655 | }); 656 | 657 | file.close; 658 | samples[sampleId] = sample; 659 | 660 | }); 661 | }, { 662 | this.loadFailed(sampleId); 663 | this.loadSample(); 664 | }); 665 | }); 666 | }, { 667 | // Done 668 | loadingSample = -1; 669 | }); 670 | } 671 | 672 | clearSamples { 673 | arg firstId, lastId = firstId; 674 | 675 | this.stopWaveformGeneration(firstId, lastId); 676 | 677 | firstId.for(lastId, { 678 | arg i; 679 | var removeQueueIndex; 680 | 681 | if(samples[i].notNil, { 682 | 683 | // Remove from load queue 684 | removeQueueIndex = loadQueue.detectIndex({ 685 | arg item; 686 | item.sampleId == i; 687 | }); 688 | if(removeQueueIndex.notNil, { 689 | loadQueue.removeAt(removeQueueIndex); 690 | }); 691 | 692 | this.clearBuffer(i); 693 | 694 | samples[i] = defaultSample.deepCopy; 695 | }); 696 | 697 | }); 698 | } 699 | 700 | queueWaveformGeneration { 701 | arg sampleId, filePath; 702 | var item; 703 | 704 | this.stopWaveformGeneration(sampleId); 705 | 706 | if(generateWaveformsOnLoad, { 707 | 708 | item = ( 709 | sampleId: sampleId, 710 | filePath: filePath 711 | ); 712 | 713 | waveformQueue = waveformQueue.addFirst(item); 714 | 715 | if(generatingWaveform == -1, { 716 | this.generateWaveforms() 717 | }); 718 | }); 719 | } 720 | 721 | stopWaveformGeneration { 722 | arg firstId, lastId = firstId; 723 | 724 | // Clear from queue 725 | firstId.for(lastId, { 726 | arg i; 727 | var removeQueueIndex; 728 | 729 | // Remove any existing with same ID 730 | removeQueueIndex = waveformQueue.detectIndex({ 731 | arg item; 732 | item.sampleId == i; 733 | }); 734 | if(removeQueueIndex.notNil, { 735 | waveformQueue.removeAt(removeQueueIndex); 736 | }); 737 | }); 738 | 739 | // Stop currently in progress 740 | if((generatingWaveform >= firstId).and(generatingWaveform <= lastId), { 741 | abandonCurrentWaveform = true; 742 | }); 743 | } 744 | 745 | generateWaveforms { 746 | 747 | var sendEvery = 24000; 748 | var sampleId, file, samplesArray, numFrames, numChannels, sampleRate, block, iterations, downsample; 749 | var min, max, offset, i, f; 750 | var waveform, routine; 751 | 752 | "Started generating waveforms".postln; 753 | 754 | waveformRoutine = Routine.new({ 755 | 756 | while({ waveformQueue.notEmpty }, { 757 | var startSecs = Date.getDate.rawSeconds; 758 | var item = waveformQueue.pop; 759 | sampleId = item.sampleId; 760 | generatingWaveform = sampleId; 761 | 762 | file = SoundFile.openRead(item.filePath); 763 | if(file.isNil, { 764 | ("File could not be opened for waveform generation:" + item.filePath).postln; 765 | }, { 766 | 767 | // Load samples into array 768 | numFrames = file.numFrames; 769 | numChannels = file.numChannels; 770 | sampleRate = file.sampleRate; 771 | samplesArray = FloatArray.newClear(numFrames * numChannels); 772 | file.readData(samplesArray); 773 | file.close; 774 | 775 | block = (numFrames / waveformDisplayRes).roundUp; 776 | iterations = waveformDisplayRes.min(numFrames); 777 | downsample = ((10 * (sampleRate / 48000)).min(block / 10).round).max(1); 778 | 779 | offset = 0; 780 | waveform = Int8Array.new((iterations * 2) + (iterations % 4)); 781 | 782 | i = 0; 783 | while({ (i < iterations).and(abandonCurrentWaveform == false) }, { 784 | 785 | if(abandonCurrentWaveform == false, { 786 | 787 | min = 0; 788 | max = 0; 789 | 790 | f = i * block; 791 | while({ (f < (i * block + block).min(numFrames)).and(abandonCurrentWaveform == false) }, { 792 | var sample = 0; 793 | 794 | if(abandonCurrentWaveform == false, { 795 | 796 | for(0, numChannels.min(2) - 1, { 797 | arg c; 798 | sample = sample + samplesArray[f * numChannels + c]; 799 | }); 800 | sample = sample / numChannels; 801 | 802 | min = sample.min(min); 803 | max = sample.max(max); 804 | 805 | // Let other sclang work happen 806 | 0.00004.yield; 807 | }); 808 | f = f + downsample; 809 | }); 810 | 811 | // 0-126, 63 is center (zero) 812 | min = min.linlin(-1, 0, 0, 63).round.asInt; 813 | max = max.linlin(0, 1, 63, 126).round.asInt; 814 | waveform = waveform.add(min); 815 | waveform = waveform.add(max); 816 | 817 | if(((i + 1 - offset) * block * numChannels >= sendEvery).and(abandonCurrentWaveform == false), { 818 | this.sendWaveform(sampleId, offset, waveform); 819 | offset = i + 1; 820 | waveform = Int8Array.new(((iterations - offset) * 2) + (iterations % 4)); 821 | }); 822 | }); 823 | i = i + 1; 824 | }); 825 | 826 | if(abandonCurrentWaveform, { 827 | abandonCurrentWaveform = false; 828 | ("Waveform" + sampleId + "abandoned after" + (Date.getDate.rawSeconds - startSecs).round(0.001) + "s").postln; 829 | }, { 830 | if(waveform.size > 0, { 831 | this.sendWaveform(sampleId, offset, waveform); 832 | }); 833 | ("Waveform" + sampleId + "generated in" + (Date.getDate.rawSeconds - startSecs).round(0.001) + "s").postln; 834 | }); 835 | }); 836 | }); 837 | 838 | "Finished generating waveforms".postln; 839 | generatingWaveform = -1; 840 | 841 | }).play; 842 | } 843 | 844 | sendWaveform { 845 | arg sampleId, offset, waveform; 846 | var padding = 0; 847 | 848 | // Pad to work around https://github.com/supercollider/supercollider/issues/2125 849 | while({ waveform.size % 4 > 0 }, { 850 | waveform = waveform.add(0); 851 | padding = padding + 1; 852 | }); 853 | 854 | // ("Send waveform for" + sampleId + "offset" + offset + "size" + waveform.size).postln; 855 | scriptAddress.sendBundle(0, ['/engineWaveform', sampleId, offset, padding, waveform]); 856 | } 857 | 858 | assignVoice { 859 | arg voiceId, sampleId, freq, pitchBendRatio, vel; 860 | var voiceToRemove; 861 | 862 | // Remove a voice if ID matches or there are too many 863 | voiceToRemove = voiceList.detect{arg v; v.id == voiceId}; 864 | if(voiceToRemove.isNil && (voiceList.size >= maxVoices), { 865 | voiceToRemove = voiceList.detect{arg v; v.gate == 0}; 866 | if(voiceToRemove.isNil, { 867 | voiceToRemove = voiceList.last; 868 | }); 869 | }); 870 | 871 | if(voiceToRemove.notNil, { 872 | if(voiceToRemove.startRoutine.notNil, { 873 | voiceToRemove.startRoutine.stop; 874 | voiceToRemove.startRoutine.free; 875 | voiceList.remove(voiceToRemove); 876 | this.addVoice(voiceId, sampleId, freq, pitchBendAllRatio, vel, false); 877 | }, { 878 | voiceToRemove.theSynth.set(\killGate, 0); 879 | voiceList.remove(voiceToRemove); 880 | this.addVoice(voiceId, sampleId, freq, pitchBendAllRatio, vel, true); 881 | }); 882 | }, { 883 | this.addVoice(voiceId, sampleId, freq, pitchBendAllRatio, vel, false); 884 | }); 885 | } 886 | 887 | addVoice { 888 | arg voiceId, sampleId, freq, pitchBendRatio, vel, delayStart; 889 | var defName, sample = samples[sampleId], streamBuffer, delay = 0, cueSecs; 890 | 891 | if(delayStart, { delay = killDuration; }); 892 | 893 | if(sample.numFrames > 0, { 894 | if(sample.streaming == 0, { 895 | if(sample.buffer.numChannels == 1, { 896 | defName = \monoBufferVoice; 897 | }, { 898 | defName = \stereoBufferVoice; 899 | }); 900 | this.addSynth(defName, voiceId, sampleId, sample.buffer, freq, pitchBendRatio, vel, delay); 901 | 902 | }, { 903 | cueSecs = Date.getDate.rawSeconds; 904 | Buffer.cueSoundFile(server: context.server, path: sample.path, startFrame: sample.startFrame, numChannels: sample.channels, bufferSize: 65536, completionMessage: { 905 | arg streamBuffer; 906 | if(streamBuffer.numChannels == 1, { 907 | defName = \monoStreamingVoice; 908 | }, { 909 | defName = \stereoStreamingVoice; 910 | }); 911 | delay = (delay - (Date.getDate.rawSeconds - cueSecs)).max(0); 912 | this.addSynth(defName, voiceId, sampleId, streamBuffer, freq, pitchBendRatio, vel, delay); 913 | 0; 914 | }); 915 | }); 916 | }); 917 | } 918 | 919 | addSynth { 920 | arg defName, voiceId, sampleId, buffer, freq, pitchBendRatio, vel, delay; 921 | var newVoice, sample = samples[sampleId]; 922 | 923 | newVoice = (id: voiceId, sampleId: sampleId, gate: 1); 924 | 925 | // Delay adding a new synth until after killDuration if need be 926 | newVoice.startRoutine = Routine { 927 | delay.wait; 928 | 929 | newVoice.theSynth = Synth.new(defName: defName, args: [ 930 | \out, mixerBus, 931 | 932 | \reverbSendBus, reverbBus, 933 | \reverbSend, sample.reverbSend, 934 | 935 | \delaySendBus, fxBus, 936 | \delaySend, sample.delaySend, 937 | 938 | \sidechainSendBus, sidechainBus, 939 | \sidechainSend, sample.sidechainSend, 940 | 941 | \bufnum, buffer.bufnum, 942 | 943 | \voiceId, voiceId, 944 | \sampleId, sampleId, 945 | 946 | \sampleRate, sample.sampleRate, 947 | 948 | \numFrames, sample.numFrames, 949 | \freq, freq, 950 | \transposeRatio, sample.transpose.midiratio, 951 | \detuneRatio, (sample.detuneCents / 100).midiratio, 952 | \pitchBendRatio, pitchBendRatio, 953 | \pitchBendSampleRatio, sample.pitchBendRatio, 954 | \gate, 1, 955 | \vel, vel, 956 | \pressure, pressureAll, 957 | \pressureSample, sample.pressure, 958 | 959 | \startFrame, sample.startFrame, 960 | \i_lockedStartFrame, sample.startFrame, 961 | \endFrame, sample.endFrame, 962 | \playMode, sample.playMode, 963 | \loopStartFrame, sample.loopStartFrame, 964 | \loopEndFrame, sample.loopEndFrame, 965 | 966 | \lfos, lfoBus, 967 | \lfo1Fade, sample.lfo1Fade, 968 | \lfo2Fade, sample.lfo2Fade, 969 | 970 | \freqModLfo1, sample.freqModLfo1, 971 | \freqModLfo2, sample.freqModLfo2, 972 | \freqModEnv, sample.freqModEnv, 973 | 974 | \ampAttack, sample.ampAttack, 975 | \ampDecay, sample.ampDecay, 976 | \ampSustain, sample.ampSustain, 977 | \ampRelease, sample.ampRelease, 978 | \modAttack, sample.modAttack, 979 | \modDecay, sample.modDecay, 980 | \modSustain, sample.modSustain, 981 | \modRelease, sample.modRelease, 982 | 983 | \downSampleTo, sample.downSampleTo, 984 | \bitDepth, sample.bitDepth, 985 | 986 | \filterFreq, sample.filterFreq, 987 | \filterReso, sample.filterReso, 988 | \filterType, sample.filterType, 989 | \filterTracking, sample.filterTracking, 990 | \filterFreqModLfo1, sample.filterFreqModLfo1, 991 | \filterFreqModLfo2, sample.filterFreqModLfo2, 992 | \filterFreqModEnv, sample.filterFreqModEnv, 993 | \filterFreqModVel, sample.filterFreqModVel, 994 | \filterFreqModPressure, sample.filterFreqModPressure, 995 | 996 | \pan, sample.pan, 997 | \panModLfo1, sample.panModLfo1, 998 | \panModLfo2, sample.panModLfo2, 999 | \panModEnv, sample.panModEnv, 1000 | 1001 | \amp, sample.amp, 1002 | \ampModLfo1, sample.ampModLfo1, 1003 | \ampModLfo2, sample.ampModLfo2, 1004 | 1005 | ], target: voiceGroup).onFree({ 1006 | 1007 | if(sample.streaming == 1, { 1008 | if(buffer.notNil, { 1009 | buffer.close; 1010 | buffer.free; 1011 | }); 1012 | }); 1013 | voiceList.remove(newVoice); 1014 | 1015 | scriptAddress.sendBundle(0, ['/engineVoiceFreed', sampleId, voiceId]); 1016 | 1017 | }); 1018 | 1019 | scriptAddress.sendBundle(0, ['/enginePlayPosition', sampleId, voiceId, sample.startFrame / sample.numFrames]); 1020 | 1021 | newVoice.startRoutine.free; 1022 | newVoice.startRoutine = nil; 1023 | }.play; 1024 | 1025 | voiceList.addFirst(newVoice); 1026 | } 1027 | 1028 | 1029 | 1030 | // Commands 1031 | 1032 | setArgOnVoice { 1033 | arg voiceId, name, value; 1034 | var voice = voiceList.detect{arg v; v.id == voiceId}; 1035 | if(voice.notNil, { 1036 | voice.theSynth.set(name, value); 1037 | }); 1038 | } 1039 | 1040 | setArgOnSample { 1041 | arg sampleId, name, value; 1042 | if(samples[sampleId].notNil, { 1043 | samples[sampleId][name] = value; 1044 | this.setArgOnVoicesPlayingSample(sampleId, name, value); 1045 | }); 1046 | } 1047 | 1048 | setArgOnVoicesPlayingSample { 1049 | arg sampleId, name, value; 1050 | var voices = voiceList.select{arg v; v.sampleId == sampleId}; 1051 | voices.do({ 1052 | arg v; 1053 | v.theSynth.set(name, value); 1054 | }); 1055 | } 1056 | 1057 | addCommands { 1058 | 1059 | this.addCommand(\generateWaveforms, "i", { 1060 | arg msg; 1061 | generateWaveformsOnLoad = (msg[1] == 1); 1062 | }); 1063 | 1064 | // noteOn(id, freq, vel, sampleId) 1065 | this.addCommand(\noteOn, "iffi", { 1066 | arg msg; 1067 | var id = msg[1], freq = msg[2], vel = msg[3] ?? 1, sampleId = msg[4] ?? 0, 1068 | sample = samples[sampleId]; 1069 | 1070 | // debugBuffer.zero(); 1071 | 1072 | if(sample.notNil, { 1073 | this.assignVoice(id, sampleId, freq, pitchBendAllRatio, vel); 1074 | }); 1075 | }); 1076 | 1077 | // noteOff(id) 1078 | this.addCommand(\noteOff, "i", { 1079 | arg msg; 1080 | var voice = voiceList.detect{arg v; v.id == msg[1]}; 1081 | if(voice.notNil, { 1082 | if(voice.startRoutine.notNil, { 1083 | voice.startRoutine.stop; 1084 | voice.startRoutine.free; 1085 | voiceList.remove(voice); 1086 | }, { 1087 | voice.theSynth.set(\gate, 0); 1088 | voice.gate = 0; 1089 | // Move voice to end so that oldest gate-off voices are found first when stealing 1090 | voiceList.remove(voice); 1091 | voiceList.add(voice); 1092 | }); 1093 | }); 1094 | }); 1095 | 1096 | // noteOffAll() 1097 | this.addCommand(\noteOffAll, "", { 1098 | arg msg; 1099 | voiceList.do({ 1100 | arg v; 1101 | if(v.startRoutine.notNil, { 1102 | v.startRoutine.stop; 1103 | v.startRoutine.free; 1104 | voiceList.remove(v); 1105 | }); 1106 | v.gate = 0; 1107 | }); 1108 | voiceGroup.set(\gate, 0); 1109 | }); 1110 | 1111 | // noteKill(id) 1112 | this.addCommand(\noteKill, "i", { 1113 | arg msg; 1114 | var voice = voiceList.detect{arg v; v.id == msg[1]}; 1115 | if(voice.notNil, { 1116 | if(voice.startRoutine.notNil, { 1117 | voice.startRoutine.stop; 1118 | voice.startRoutine.free; 1119 | }, { 1120 | voice.theSynth.set(\killGate, 0); 1121 | }); 1122 | voiceList.remove(voice); 1123 | }); 1124 | }); 1125 | 1126 | // noteKillAll() 1127 | this.addCommand(\noteKillAll, "", { 1128 | arg msg; 1129 | voiceList.do({ 1130 | arg v; 1131 | if(v.startRoutine.notNil, { 1132 | v.startRoutine.stop; 1133 | v.startRoutine.free; 1134 | }); 1135 | v.gate = 0; 1136 | }); 1137 | voiceGroup.set(\killGate, 0); 1138 | voiceList.clear; 1139 | }); 1140 | 1141 | // pitchBendVoice(id, ratio) 1142 | this.addCommand(\pitchBendVoice, "if", { 1143 | arg msg; 1144 | this.setArgOnVoice(msg[1], \pitchBendRatio, msg[2]); 1145 | }); 1146 | 1147 | // pitchBendSample(id, ratio) 1148 | this.addCommand(\pitchBendSample, "if", { 1149 | arg msg; 1150 | this.setArgOnSample(msg[1], \pitchBendSampleRatio, msg[2]); 1151 | }); 1152 | 1153 | // pitchBendAll(ratio) 1154 | this.addCommand(\pitchBendAll, "f", { 1155 | arg msg; 1156 | pitchBendAllRatio = msg[1]; 1157 | voiceGroup.set(\pitchBendRatio, pitchBendAllRatio); 1158 | }); 1159 | 1160 | // pressureVoice(id, pressure) 1161 | this.addCommand(\pressureVoice, "if", { 1162 | arg msg; 1163 | this.setArgOnVoice(msg[1], \pressure, msg[2]); 1164 | }); 1165 | 1166 | // pressureSample(id, pressure) 1167 | this.addCommand(\pressureSample, "if", { 1168 | arg msg; 1169 | this.setArgOnSample(msg[1], \pressureSample, msg[2]); 1170 | }); 1171 | 1172 | // pressureAll(pressure) 1173 | this.addCommand(\pressureAll, "f", { 1174 | arg msg; 1175 | pressureAll = msg[1]; 1176 | voiceGroup.set(\pressure, pressureAll); 1177 | }); 1178 | 1179 | this.addCommand(\lfo1Freq, "f", { arg msg; 1180 | lfos.set(\lfo1Freq, msg[1]); 1181 | }); 1182 | 1183 | this.addCommand(\lfo1WaveShape, "i", { arg msg; 1184 | lfos.set(\lfo1WaveShape, msg[1]); 1185 | }); 1186 | 1187 | this.addCommand(\lfo2Freq, "f", { arg msg; 1188 | lfos.set(\lfo2Freq, msg[1]); 1189 | }); 1190 | 1191 | this.addCommand(\lfo2WaveShape, "i", { arg msg; 1192 | lfos.set(\lfo2WaveShape, msg[1]); 1193 | }); 1194 | 1195 | // FX commands 1196 | 1197 | this.addCommand(\compLevel, "f", { arg msg; 1198 | mixer.set(\compLevel, msg[1]); 1199 | }); 1200 | 1201 | this.addCommand(\compMix, "f", { arg msg; 1202 | mixer.set(\compMix, msg[1]); 1203 | }); 1204 | 1205 | this.addCommand(\compThreshold, "f", { arg msg; 1206 | mixer.set(\thresh, msg[1]); 1207 | }); 1208 | 1209 | this.addCommand(\compSlopeBelow, "f", { arg msg; 1210 | mixer.set(\slopeBelow, msg[1]); 1211 | }); 1212 | this.addCommand(\compSlopeAbove, "f", { arg msg; 1213 | mixer.set(\slopeAbove, msg[1]); 1214 | }); 1215 | 1216 | this.addCommand(\compClampTime, "f", { arg msg; 1217 | mixer.set(\clampTime, msg[1]); 1218 | }); 1219 | 1220 | this.addCommand(\compRelaxTime, "f", { arg msg; 1221 | mixer.set(\relaxTime, msg[1]); 1222 | }); 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | // Delay 1229 | 1230 | this.addCommand(\delayTime, "f", { arg msg; 1231 | delay.set(\delayTime, msg[1]); 1232 | }); 1233 | 1234 | this.addCommand(\feedbackAmount, "f", { arg msg; 1235 | delay.set(\feedbackAmount, msg[1]); 1236 | }); 1237 | 1238 | this.addCommand(\delayLevel, "f", { arg msg; 1239 | delay.set(\level, msg[1]); 1240 | }); 1241 | 1242 | // Reverb 1243 | 1244 | this.addCommand(\reverbTime, "f", { arg msg; 1245 | reverb.set(\reverbTime, msg[1]); 1246 | }); 1247 | 1248 | this.addCommand(\reverbDamp, "f", { arg msg; 1249 | reverb.set(\damp, msg[1]); 1250 | }); 1251 | 1252 | this.addCommand(\reverbSize, "f", { arg msg; 1253 | reverb.set(\size, msg[1]); 1254 | }); 1255 | 1256 | this.addCommand(\reverbDiff, "f", { arg msg; 1257 | reverb.set(\diff, msg[1]); 1258 | }); 1259 | 1260 | this.addCommand(\reverbModDepth, "f", { arg msg; 1261 | reverb.set(\modDepth, msg[1]); 1262 | }); 1263 | 1264 | this.addCommand(\reverbModFreq, "f", { arg msg; 1265 | reverb.set(\modFreq, msg[1]); 1266 | }); 1267 | 1268 | this.addCommand(\reverbLow, "f", { arg msg; 1269 | reverb.set(\low, msg[1]); 1270 | }); 1271 | this.addCommand(\reverbMid, "f", { arg msg; 1272 | reverb.set(\mid, msg[1]); 1273 | }); 1274 | this.addCommand(\reverbHigh, "f", { arg msg; 1275 | reverb.set(\high, msg[1]); 1276 | }); 1277 | this.addCommand(\reverbLowcut, "f", { arg msg; 1278 | reverb.set(\lowcut, msg[1]); 1279 | }); 1280 | this.addCommand(\reverbHighcut, "f", { arg msg; 1281 | reverb.set(\highcut, msg[1]); 1282 | }); 1283 | 1284 | // Sample commands 1285 | 1286 | // loadSample(id, filePath) 1287 | this.addCommand(\loadSample, "is", { 1288 | arg msg; 1289 | this.queueLoadSample(msg[1], msg[2].asString); 1290 | }); 1291 | 1292 | this.addCommand(\clearSamples, "ii", { 1293 | arg msg; 1294 | this.clearSamples(msg[1], msg[2]); 1295 | }); 1296 | 1297 | this.addCommand(\moveSample, "ii", { 1298 | arg msg; 1299 | this.moveSample(msg[1], msg[2]); 1300 | }); 1301 | 1302 | this.addCommand(\copySample, "iii", { 1303 | arg msg; 1304 | this.copySample(msg[1], msg[2], msg[3]); 1305 | }); 1306 | 1307 | this.addCommand(\copyParams, "iii", { 1308 | arg msg; 1309 | this.copyParams(msg[1], msg[2], msg[3]); 1310 | }); 1311 | 1312 | this.addCommand(\transpose, "if", { 1313 | arg msg; 1314 | var sampleId = msg[1], value = msg[2]; 1315 | if(samples[sampleId].notNil, { 1316 | samples[sampleId][\transpose] = value; 1317 | this.setArgOnVoicesPlayingSample(sampleId, \transposeRatio, value.midiratio); 1318 | }); 1319 | 1320 | // TODO 1321 | // debugBuffer.write('/home/we/dust/code/timber/lib/debug.wav'); 1322 | }); 1323 | 1324 | this.addCommand(\detuneCents, "if", { 1325 | arg msg; 1326 | var sampleId = msg[1], value = msg[2]; 1327 | if(samples[sampleId].notNil, { 1328 | samples[sampleId][\detuneCents] = value; 1329 | this.setArgOnVoicesPlayingSample(sampleId, \detuneRatio, (value / 100).midiratio); 1330 | }); 1331 | }); 1332 | 1333 | this.addCommand(\startFrame, "ii", { 1334 | arg msg; 1335 | this.setArgOnSample(msg[1], \startFrame, msg[2]); 1336 | }); 1337 | 1338 | this.addCommand(\endFrame, "ii", { 1339 | arg msg; 1340 | this.setArgOnSample(msg[1], \endFrame, msg[2]); 1341 | }); 1342 | 1343 | this.addCommand(\playMode, "ii", { 1344 | arg msg; 1345 | this.setArgOnSample(msg[1], \playMode, msg[2]); 1346 | }); 1347 | 1348 | this.addCommand(\loopStartFrame, "ii", { 1349 | arg msg; 1350 | this.setArgOnSample(msg[1], \loopStartFrame, msg[2]); 1351 | }); 1352 | 1353 | this.addCommand(\loopEndFrame, "ii", { 1354 | arg msg; 1355 | this.setArgOnSample(msg[1], \loopEndFrame, msg[2]); 1356 | }); 1357 | 1358 | this.addCommand(\lfo1Fade, "if", { 1359 | arg msg; 1360 | this.setArgOnSample(msg[1], \lfo1Fade, msg[2]); 1361 | }); 1362 | 1363 | this.addCommand(\lfo2Fade, "if", { 1364 | arg msg; 1365 | this.setArgOnSample(msg[1], \lfo2Fade, msg[2]); 1366 | }); 1367 | 1368 | this.addCommand(\freqModLfo1, "if", { 1369 | arg msg; 1370 | this.setArgOnSample(msg[1], \freqModLfo1, msg[2]); 1371 | }); 1372 | 1373 | this.addCommand(\freqModLfo2, "if", { 1374 | arg msg; 1375 | this.setArgOnSample(msg[1], \freqModLfo2, msg[2]); 1376 | }); 1377 | 1378 | this.addCommand(\freqModEnv, "if", { 1379 | arg msg; 1380 | this.setArgOnSample(msg[1], \freqModEnv, msg[2]); 1381 | }); 1382 | 1383 | this.addCommand(\ampAttack, "if", { 1384 | arg msg; 1385 | this.setArgOnSample(msg[1], \ampAttack, msg[2]); 1386 | }); 1387 | 1388 | this.addCommand(\ampDecay, "if", { 1389 | arg msg; 1390 | this.setArgOnSample(msg[1], \ampDecay, msg[2]); 1391 | }); 1392 | 1393 | this.addCommand(\ampSustain, "if", { 1394 | arg msg; 1395 | this.setArgOnSample(msg[1], \ampSustain, msg[2]); 1396 | }); 1397 | 1398 | this.addCommand(\ampRelease, "if", { 1399 | arg msg; 1400 | this.setArgOnSample(msg[1], \ampRelease, msg[2]); 1401 | }); 1402 | 1403 | this.addCommand(\modAttack, "if", { 1404 | arg msg; 1405 | this.setArgOnSample(msg[1], \modAttack, msg[2]); 1406 | }); 1407 | 1408 | this.addCommand(\modDecay, "if", { 1409 | arg msg; 1410 | this.setArgOnSample(msg[1], \modDecay, msg[2]); 1411 | }); 1412 | 1413 | this.addCommand(\modSustain, "if", { 1414 | arg msg; 1415 | this.setArgOnSample(msg[1], \modSustain, msg[2]); 1416 | }); 1417 | 1418 | this.addCommand(\modRelease, "if", { 1419 | arg msg; 1420 | this.setArgOnSample(msg[1], \modRelease, msg[2]); 1421 | }); 1422 | 1423 | this.addCommand(\downSampleTo, "ii", { 1424 | arg msg; 1425 | this.setArgOnSample(msg[1], \downSampleTo, msg[2]); 1426 | }); 1427 | 1428 | this.addCommand(\bitDepth, "ii", { 1429 | arg msg; 1430 | this.setArgOnSample(msg[1], \bitDepth, msg[2]); 1431 | }); 1432 | 1433 | this.addCommand(\reverbSend, "ii", { 1434 | arg msg; 1435 | this.setArgOnSample(msg[1], \reverbSend, msg[2]); 1436 | }); 1437 | 1438 | this.addCommand(\delaySend, "ii", { 1439 | arg msg; 1440 | this.setArgOnSample(msg[1], \delaySend, msg[2]); 1441 | }); 1442 | 1443 | this.addCommand(\sidechainSend, "ii", { 1444 | arg msg; 1445 | this.setArgOnSample(msg[1], \sidechainSend, msg[2]); 1446 | }); 1447 | 1448 | this.addCommand(\filterFreq, "if", { 1449 | arg msg; 1450 | this.setArgOnSample(msg[1], \filterFreq, msg[2]); 1451 | }); 1452 | 1453 | this.addCommand(\filterReso, "if", { 1454 | arg msg; 1455 | this.setArgOnSample(msg[1], \filterReso, msg[2]); 1456 | }); 1457 | 1458 | this.addCommand(\filterType, "ii", { 1459 | arg msg; 1460 | this.setArgOnSample(msg[1], \filterType, msg[2]); 1461 | }); 1462 | 1463 | this.addCommand(\filterTracking, "if", { 1464 | arg msg; 1465 | this.setArgOnSample(msg[1], \filterTracking, msg[2]); 1466 | }); 1467 | 1468 | this.addCommand(\filterFreqModLfo1, "if", { 1469 | arg msg; 1470 | this.setArgOnSample(msg[1], \filterFreqModLfo1, msg[2]); 1471 | }); 1472 | 1473 | this.addCommand(\filterFreqModLfo2, "if", { 1474 | arg msg; 1475 | this.setArgOnSample(msg[1], \filterFreqModLfo2, msg[2]); 1476 | }); 1477 | 1478 | this.addCommand(\filterFreqModEnv, "if", { 1479 | arg msg; 1480 | this.setArgOnSample(msg[1], \filterFreqModEnv, msg[2]); 1481 | }); 1482 | 1483 | this.addCommand(\filterFreqModVel, "if", { 1484 | arg msg; 1485 | this.setArgOnSample(msg[1], \filterFreqModVel, msg[2]); 1486 | }); 1487 | 1488 | this.addCommand(\filterFreqModPressure, "if", { 1489 | arg msg; 1490 | this.setArgOnSample(msg[1], \filterFreqModPressure, msg[2]); 1491 | }); 1492 | 1493 | this.addCommand(\pan, "if", { 1494 | arg msg; 1495 | this.setArgOnSample(msg[1], \pan, msg[2]); 1496 | }); 1497 | 1498 | this.addCommand(\panModLfo1, "if", { 1499 | arg msg; 1500 | this.setArgOnSample(msg[1], \panModLfo1, msg[2]); 1501 | }); 1502 | 1503 | this.addCommand(\panModLfo2, "if", { 1504 | arg msg; 1505 | this.setArgOnSample(msg[1], \panModLfo2, msg[2]); 1506 | }); 1507 | 1508 | this.addCommand(\panModEnv, "if", { 1509 | arg msg; 1510 | this.setArgOnSample(msg[1], \panModEnv, msg[2]); 1511 | }); 1512 | 1513 | this.addCommand(\amp, "if", { 1514 | arg msg; 1515 | this.setArgOnSample(msg[1], \amp, msg[2]); 1516 | }); 1517 | 1518 | this.addCommand(\ampModLfo1, "if", { 1519 | arg msg; 1520 | this.setArgOnSample(msg[1], \ampModLfo1, msg[2]); 1521 | }); 1522 | 1523 | this.addCommand(\ampModLfo2, "if", { 1524 | arg msg; 1525 | this.setArgOnSample(msg[1], \ampModLfo2, msg[2]); 1526 | }); 1527 | 1528 | } 1529 | 1530 | free { 1531 | if(waveformRoutine.notNil, { 1532 | waveformRoutine.stop; 1533 | waveformRoutine.free; 1534 | }); 1535 | samples.do({ 1536 | arg item, i; 1537 | if(item.notNil, { 1538 | if(item.buffer.notNil, { 1539 | item.buffer.free; 1540 | }); 1541 | }); 1542 | }); 1543 | // NOTE: Are these already getting freed elsewhere? 1544 | scriptAddress.free; 1545 | replyFunc.free; 1546 | synthNames.free; 1547 | voiceList.free; 1548 | players.free; 1549 | voiceGroup.free; 1550 | lfos.free; 1551 | fxBus.free; 1552 | reverbBus.free; 1553 | reverb.free; 1554 | delay.free; 1555 | mixer.free; 1556 | } 1557 | } 1558 | -------------------------------------------------------------------------------- /takt.lua: -------------------------------------------------------------------------------- 1 | -- takt v2.2 2 | -- @its_your_bedtime 3 | -- 4 | -- parameter locking sequencer 5 | -- 6 | 7 | local sampler = include('lib/sampler') 8 | local browser = include('lib/browser') 9 | local timber = include('lib/timber_takt') 10 | local takt_utils = include('lib/_utils') 11 | local ui = include('lib/ui') 12 | local linn = include('lib/linn') 13 | local beatclock = require 'beatclock' 14 | local music = require 'musicutil' 15 | local fileselect = require('fileselect') 16 | local textentry = require('textentry') 17 | -- 18 | local midi_clock 19 | local midi_out_devices = {} 20 | local REC_CC = 38 21 | -- 22 | local hold_time, down_time, blink = 0, 0, 1 23 | local ALT, SHIFT, MOD, PATTERN_REC, K1_hold, K3_hold, ptn_copy, ptn_change_pending = false, false, false, false, false, false, false, false 24 | local redraw_params, hold, holdmax, first, second = {}, {}, {}, {}, {} 25 | local copy = { false, false } 26 | local freq_map = controlspec.WIDEFREQ 27 | local amp_map = controlspec.DB 28 | amp_map.maxval = 16 29 | local send_map = controlspec.new(-48, 0, 'db', 0, -48, "dB")--.DB 30 | local sidechain_map = controlspec.new(-99, 0, 'db', 0, -99, "dB")--.DB 31 | local threshold_map = controlspec.new(0.01, 1, "exp", 0.001, 0.1, "") 32 | local time_map = controlspec.new(0.0001, 5, 'exp', 0, 0.1, 's') 33 | 34 | -- 35 | local g = grid.connect() 36 | local data = { pattern = 1, ui_index = 1, selected = { 1, false }, metaseq = { from = 1, to = 1, div = 1}, [1] = takt_utils.make_default_pattern() } 37 | local view = { steps_engine = true, steps_midi = false, notes_input = false, sampling = false, patterns = false } 38 | local choke = { 1, 2, 3, 4, 5, 6, 7, {},{},{},{},{},{},{}, ['8rt'] = {},['9rt'] = {},['10rt'] = {},['11rt'] = {}, ['12rt'] = {}, ['13rt'] = {},['14rt'] = {} } 39 | local dividers = { [1] = 16, [2] = 8, [3] = 4, [4] = 3, [5] = 2, [6] = 1.5, [7] = 1,} 40 | local midi_dividers = { [1] = 16, [2] = 8, [3] = 4, [4] = 3, [5] = 1, [6] = 0.666, [7] = 0.545,} 41 | local sampling_actions = {[-1] = function()end,[0]=function()end, [1] = sampler.rec, [2] = sampler.play, [3] = sampler.save_and_load, [4] = sampler.clear, [5] = sampler.play, [6] = sampler.play } 42 | local lfo_1, lfo_2 = {[5] = true, [13] = true, }, {[6] = true, [14] = true, } 43 | local last_index = 1 44 | 45 | 46 | local param_ids = { 47 | ['quality'] = "quality", ['start_frame'] = "start_frame", ['end_frame'] = "end_frame", ['loop_start_frame'] = "loop_start_frame", ['loop_end_frame'] = "loop_end_frame", 48 | ['freq_mod_lfo_1'] = "freq_mod_lfo_1", ['play_mode'] = 'play_mode', ['detune_cents'] = 'detune_cents', 49 | ['freq_mod_lfo_2'] = "freq_mod_lfo_2", ['filter_type'] = "filter_type", ['filter_freq'] = "filter_freq", ['filter_resonance'] = "filter_resonance", 50 | ['filter_freq_mod_lfo_1'] = "filter_freq_mod_lfo_1", ['filter_freq_mod_lfo_2'] = "filter_freq_mod_lfo_2", ['pan'] = "pan", ['amp'] = "amp", 51 | ['amp_mod_lfo_1'] = "amp_mod_lfo_1", ['amp_mod_lfo_2'] = "amp_mod_lfo_2", ['amp_env_attack'] = "amp_env_attack", ['amp_env_decay'] = "amp_env_decay", 52 | ['amp_env_sustain'] = "amp_env_sustain", ['amp_env_release'] = "amp_env_release", ['reverb_send'] = "reverb_send", ["delay_send"] = 'delay_send', ['sidechain_send'] = 'sidechain_send' 53 | 54 | } 55 | 56 | local rules = { 57 | [0] = { 'OFF', function() return true end }, 58 | [1] = { '10%', function() return 10 >= math.random(100) and true or false end }, 59 | [2] = { '20%', function() return 20 >= math.random(100) and true or false end }, 60 | [3] = { '30%', function() return 30 >= math.random(100) and true or false end }, 61 | [4] = { '50%', function() return 50 >= math.random(100) and true or false end }, 62 | [5] = { '60%', function() return 60 >= math.random(100) and true or false end }, 63 | [6] = { '70%', function() return 70 >= math.random(100) and true or false end }, 64 | [7] = { '90%', function() return 90 >= math.random(100) and true or false end }, 65 | [8] = {'/ 2', function(tr, step) return data[data.pattern].track.cycle[tr] % 2 == 0 and true or false end }, 66 | [9] = {'/ 3', function(tr, step) return data[data.pattern].track.cycle[tr] % 3 == 0 and true or false end }, 67 | [10] = {'/ 4', function(tr, step) return data[data.pattern].track.cycle[tr] % 4 == 0 and true or false end }, 68 | [11] = {'/ 5', function(tr, step) return data[data.pattern].track.cycle[tr] % 5 == 0 and true or false end }, 69 | [12] = {'/ 6', function(tr, step) return data[data.pattern].track.cycle[tr] % 6 == 0 and true or false end }, 70 | [13] = {'/ 7', function(tr, step) return data[data.pattern].track.cycle[tr] % 7 == 0 and true or false end }, 71 | [14] = {'/ 8', function(tr, step) return data[data.pattern].track.cycle[tr] % 8 == 0 and true or false end }, 72 | [15] = {'RND NOTE', function(tr, step) 73 | data[data.pattern][tr].params[step].note = math.random(24,120) return true end }, 74 | [16] = {'+- NOTE', function(tr, step) 75 | data[data.pattern][tr].params[step].note = util.clamp(data[data.pattern][tr].params[step].note + math.random(-20,20),24,120) return true end }, 76 | [17] = {'RND START', function(tr, step) 77 | if tr < 8 then 78 | local max_frame = params:lookup_param("end_frame_" .. data[data.pattern][tr].params[step].sample).controlspec.maxval 79 | data[data.pattern][tr].params[step].start_frame = math.random(0, max_frame) 80 | data[data.pattern][tr].params[step].loop_start = math.random(0, max_frame) 81 | end 82 | return true end }, 83 | [18] = {'RND ST-EN', function(tr, step) 84 | if tr < 8 then 85 | local max_frame = params:lookup_param("end_frame_" .. data[data.pattern][tr].params[step].sample).controlspec.maxval 86 | data[data.pattern][tr].params[step].start_frame = math.random(0, max_frame) 87 | data[data.pattern][tr].params[step].end_frame = math.random(0, max_frame) 88 | data[data.pattern][tr].params[step].loop_start_frame = data[data.pattern][tr].params[step].start_frame 89 | data[data.pattern][tr].params[step].loop_end_frame = data[data.pattern][tr].params[step].end_frame 90 | end 91 | return true end }, 92 | } 93 | 94 | --- utils, load/save 95 | 96 | local function to_id(x, y) 97 | return x + ((y - 1) * 16) 98 | end 99 | 100 | local function pattern_exists(x, y) 101 | return data[x + ((y - 1) * 16)] ~= nil and true or false 102 | end 103 | 104 | local function id_to_x(id) 105 | return (id - 1) % 16 + 1 106 | end 107 | 108 | local function id_to_y(id) 109 | return math.ceil(id / 16) 110 | end 111 | 112 | local function K3_is_hold() 113 | return K3_hold 114 | end 115 | 116 | local function K1_is_hold() 117 | return K1_hold 118 | end 119 | 120 | local function set_enc_res(fine, coarse) 121 | return K3_is_hold() and coarse or fine 122 | end 123 | 124 | local function reset_positions() 125 | for i = 1, 14 do 126 | data[data.pattern].track.pos[i] = 0 127 | end 128 | end 129 | 130 | local prev_mix_val = -1 131 | local prev_level_val = 0 132 | 133 | local function comp_shut(state) 134 | if state then 135 | print('run') 136 | params:set('takt_comp_mix', prev_mix_val) 137 | params:set('takt_comp_level', prev_level_val) 138 | elseif not state then 139 | print('stop') 140 | prev_mix_val = params:get('takt_comp_mix') 141 | prev_level_val = params:get('takt_comp_level') 142 | params:set('takt_comp_mix', -1) 143 | params:set('takt_comp_level', -99) 144 | end 145 | 146 | end 147 | 148 | local function deepcopy(orig) 149 | local orig_type = type(orig) 150 | local copy 151 | if orig_type == 'table' then 152 | copy = {} 153 | for orig_key, orig_value in next, orig, nil do 154 | copy[deepcopy(orig_key)] = deepcopy(orig_value) 155 | end 156 | setmetatable(copy, getmetatable(orig)) 157 | else -- number, string, boolean, etc 158 | copy = orig 159 | end 160 | return copy 161 | end 162 | 163 | local function load_project(pth) 164 | 165 | sequencer_metro:stop() 166 | midi_clock:stop() 167 | engine.noteOffAll() 168 | redraw_metro:stop() 169 | comp_shut(sequencer_metro.is_running) 170 | 171 | if string.find(pth, '.tkt') ~= nil then 172 | local saved = tab.load(pth) 173 | if saved ~= nil then 174 | print("data found") 175 | for k,v in pairs(saved[2]) do 176 | data[k] = v 177 | end 178 | -- re-init metatables 179 | for t = 1, #data do 180 | for l = 1, 7 do 181 | for k = 1, 256 do 182 | data[t][l].params[k] = saved[2][t][l].params[k] 183 | setmetatable(data[t][l].params[k], {__index = data[t][l].params[tostring(l)]}) 184 | end 185 | end 186 | 187 | for l = 8, 14 do 188 | for k = 1, 256 do 189 | data[t][l].params[k] = saved[2][t][l].params[k] 190 | setmetatable(data[t][l].params[k], {__index = data[t][l].params[tostring(l)]}) 191 | end 192 | end 193 | end 194 | 195 | if saved[1] then params:read(norns.state.data .. saved[1] .. ".pset") end 196 | reset_positions() 197 | else 198 | print("no data") 199 | end 200 | end 201 | redraw_metro:start() 202 | end 203 | 204 | local function save_project(txt) 205 | sequencer_metro:stop() 206 | midi_clock:stop() 207 | redraw_metro:stop() 208 | engine.noteOffAll() 209 | comp_shut(sequencer_metro.is_running) 210 | if txt then 211 | tab.save({ txt, data }, norns.state.data .. txt ..".tkt") 212 | params:write( norns.state.data .. txt .. ".pset") 213 | else 214 | print("save cancel") 215 | end 216 | redraw_metro:start() 217 | end 218 | 219 | -- views 220 | 221 | local function set_view(x) 222 | if not sampler.rec then 223 | for k, v in pairs(view) do 224 | view[k] = k == x and true or false 225 | end 226 | end 227 | if view.sampling or view.patterns then last_index = data.ui_index data.ui_index = 1 228 | else 229 | data.ui_index = last_index 230 | end 231 | 232 | end 233 | 234 | --- steps 235 | 236 | local function get_step(x) 237 | return (x * 16) - 15 238 | end 239 | 240 | local function get_substep(tr, step) 241 | for s = (step*16) - 15, (step*16) + 15 do 242 | if data[data.pattern][tr][s] == 1 then 243 | return true 244 | end 245 | end 246 | end 247 | 248 | local function get_params(tr, step, lock) 249 | if not step then 250 | return data[data.pattern][tr].params[tostring(tr)] 251 | else 252 | local res = data[data.pattern][tr].params[step] 253 | if lock then 254 | res.default = data[data.pattern][tr].params[tostring(tr)] 255 | end 256 | return data[data.pattern][tr].params[step] -- res 257 | end 258 | end 259 | 260 | local function set_locks(step_param) 261 | for k, v in pairs(step_param) do 262 | if param_ids[k] ~= nil then 263 | params:set(k .. '_' .. step_param.sample, v) 264 | end 265 | end 266 | end 267 | local function set_cc(step_param) 268 | for i = 1, 6 do 269 | local cc = step_param['cc_' .. i] 270 | local val = step_param['cc_' .. i .. '_val'] 271 | if val > -1 then 272 | midi_out_devices[step_param.device]:cc(cc, val, step_param.channel) 273 | end 274 | end 275 | end 276 | 277 | local function move_params(tr, src, dst ) 278 | local s = data[data.pattern][tr].params[src] 279 | data[data.pattern][tr].params[dst] = s 280 | end 281 | 282 | local function clear_substeps(tr, s ) 283 | for l = s, s + 15 do 284 | data[data.pattern][tr][l] = 0 285 | data[data.pattern][tr].params[l] = {} 286 | setmetatable(data[data.pattern][tr].params[l], {__index = data[data.pattern][tr].params[tostring(tr)]}) 287 | end 288 | end 289 | 290 | local function move_substep(tr, step, t) 291 | for s = step, step + 15 do 292 | data[data.pattern][tr][s] = (s == t) and 1 or 0 293 | move_params(tr, step, (s == t) and s or step) 294 | end 295 | end 296 | 297 | local function make_retrigs(tr, step, t) 298 | local t = 16 - t 299 | local offset = data[data.pattern][tr].params[step].offset 300 | local params = data[data.pattern][tr].params[step] 301 | local st = step + 1 302 | 303 | for s = st + offset, (st + 14) - offset do 304 | if t == 16 then 305 | data[data.pattern][tr][s] = 0 306 | elseif s % t == 1 then 307 | data[data.pattern][tr][s] = s - offset == st and 1 or 0 308 | else 309 | data[data.pattern][tr][s] = ((s + offset) % t == 0) and 1 or 0 310 | data[data.pattern][tr].params[s] = params 311 | end 312 | end 313 | end 314 | 315 | local function have_substeps(tr, step) 316 | local st = get_step(step) 317 | for s = st, st + 15 do 318 | if data[data.pattern][tr][s] == 1 then 319 | return s 320 | end 321 | end 322 | end 323 | 324 | local function place_note(tr, step, note ) 325 | data[data.pattern][tr][step] = 1 326 | data[data.pattern][tr].params[step].lock = 1 327 | data[data.pattern][tr].params[step].note = data[data.pattern][tr].params[step].note 328 | data[data.pattern][tr].params[step].note = note 329 | end 330 | 331 | --- tracks 332 | 333 | local function is_lock() 334 | local src = data.selected 335 | if src[2] == false then 336 | return tostring(src[1]) 337 | else 338 | return src[2] 339 | end 340 | end 341 | 342 | local function tr_change(tr) 343 | data.selected[1] = tr 344 | redraw_params[1] = get_params(tr) 345 | redraw_params[2] = redraw_params[1] 346 | end 347 | 348 | local function get_sample() 349 | return data[data.pattern][data.selected[1]].params[is_lock()].sample 350 | end 351 | 352 | local function sample_not_loaded(n) 353 | return params:get('sample_' .. n) == '-' 354 | end 355 | 356 | local function sync_tracks(tr) 357 | for i=1, 14 do 358 | if data[data.pattern].track.div[i] == data[data.pattern].track.div[tr] then 359 | data[data.pattern].track.pos[i] = data[data.pattern].track.pos[tr] 360 | end 361 | end 362 | end 363 | 364 | local function mute_track(tr) 365 | 366 | data[data.pattern].track.mute[tr] = not data[data.pattern].track.mute[tr] 367 | if data[data.pattern].track.mute[tr] and tr < 8 then 368 | engine.noteOff(choke[tr]) 369 | else 370 | print('midi mute') 371 | end 372 | 373 | end 374 | 375 | local function set_div(tr, div) 376 | data[data.pattern].track.div[tr] = div 377 | data[data.pattern][tr].params[tostring(tr)].div = div 378 | sync_tracks(tr) 379 | end 380 | 381 | local function set_bpm(n) 382 | data[data.pattern].bpm = n 383 | sequencer_metro.time = 60 / (data[data.pattern].bpm * 2) / 16 --[[ppqn]] / 4 384 | midi_clock:bpm_change( util.round(data[data.pattern].bpm / midi_dividers[util.clamp(data[data.pattern].sync_div, 1, 7)])) 385 | end 386 | 387 | local function set_loop(tr, start, len) 388 | data[data.pattern].track.start[tr] = get_step(start) 389 | data[data.pattern].track.len[tr] = get_step(len) + 15 390 | sync_tracks(tr) 391 | end 392 | 393 | local function get_tr_start( tr ) 394 | return math.ceil(data[data.pattern].track.start[tr] / 16) 395 | end 396 | 397 | local function get_tr_len( tr ) 398 | return math.ceil(data[data.pattern].track.len[tr] / 16) 399 | end 400 | 401 | local function get_sample_len(tr, s) 402 | local maxval = params:lookup_param("end_frame_" .. data[data.pattern][tr].params[s].sample).controlspec.maxval 403 | data[data.pattern][tr].params[s].end_frame = maxval 404 | data[data.pattern][tr].params[s].loop_end_frame = maxval 405 | end 406 | 407 | local function get_sample_start(tr, s) 408 | local minval = params:lookup_param("start_frame_" .. data[data.pattern][tr].params[s].sample).controlspec.minval 409 | data[data.pattern][tr].params[s].start_frame = minval 410 | data[data.pattern][tr].params[s].start_end_frame = minval 411 | end 412 | --- copy / settings 413 | 414 | local function copy_step(src, dst) 415 | for i = 0, 15 do 416 | data[data.pattern][dst[1]][get_step(dst[2]) + i] = data[data.pattern][src[1]][get_step(src[2]) + i] 417 | data[data.pattern][dst[1]].params[get_step(dst[2]) + i] = deepcopy(data[data.pattern][src[1]].params[get_step(src[2]) + i]) 418 | end 419 | end 420 | 421 | local function copy_pattern(src, dst) 422 | data[dst] = deepcopy(data[src]) 423 | end 424 | 425 | local function open_sample_settings() 426 | local p = is_lock() 427 | norns.menu.toggle(true) 428 | norns.encoders.set_sens(2,1) 429 | _norns.enc(1, 1000) 430 | _norns.enc(2,-9999999) 431 | _norns.enc(2, 36 +(( data[data.pattern][data.selected[1]].params[p].sample - 1 ) * 51 )) 432 | norns.encoders.set_sens(2,4) 433 | end 434 | 435 | function open_settings(i) 436 | norns.menu.toggle(true) 437 | _norns.enc(1, 1000) 438 | _norns.enc(2,-9999999) 439 | _norns.enc(2, 10 + (i*4)) 440 | end 441 | 442 | local function change_filter_type() 443 | local tr = data.selected[1] 444 | local p = is_lock() 445 | p = type(p) == 'string' and p or get_step(p) 446 | data[data.pattern][tr].params[p].filter_type = data[data.pattern][tr].params[p].filter_type 447 | data[data.pattern][tr].params[p].filter_type = (data[data.pattern][tr].params[p].filter_type % 2 ) + 1 448 | end 449 | 450 | local function choke_group(tr, sample) 451 | if sample == choke[tr] then 452 | engine.noteOff(tr) 453 | end 454 | end 455 | 456 | local function kill_all_midi() 457 | for id = 1, 4 do 458 | for ch = 1, 16 do 459 | for note = 0, 127 do 460 | midi_out_devices[id]:note_off(note, 0, ch) 461 | end 462 | end 463 | end 464 | end 465 | 466 | local function notes_off_midi() 467 | for i = 8, 14 do 468 | if choke[i][6] then 469 | midi_out_devices[choke[i][1]]:note_off(choke[i][2], choke[i][3], choke[i][4]) 470 | end 471 | end 472 | end 473 | 474 | -- seq 475 | local m_div = function(div) return div == 1 and 2 or div^2 end 476 | 477 | local function change_pattern(pt) 478 | if data[pt] == nil then 479 | data[pt] = takt_utils.make_default_pattern() 480 | end 481 | data.pattern = pt 482 | end 483 | 484 | local function metaseq(stage) 485 | if data[data.pattern].track.pos[1] == data[data.pattern].track.len[1] - 1 then 486 | 487 | if ptn_change_pending then 488 | change_pattern(ptn_change_pending) 489 | ptn_change_pending = false 490 | end 491 | 492 | if (data.metaseq.to and data.metaseq.from) then 493 | change_pattern(data.pattern < data.metaseq.to and data.pattern + 1 or data.metaseq.from) 494 | set_bpm(data[data.pattern].bpm) 495 | end 496 | end 497 | end 498 | 499 | local function advance_step(tr, counter) 500 | local start = data[data.pattern].track.start[tr] 501 | local len = data[data.pattern].track.len[tr] 502 | data[data.pattern].track.pos[tr] = util.clamp((data[data.pattern].track.pos[tr] + 1) % (len ), start, len) -- voice pos 503 | data[data.pattern].track.cycle[tr] = counter % 256 == 0 and data[data.pattern].track.cycle[tr] + 1 or data[data.pattern].track.cycle[tr] --data[data.pattern].track.cycle[tr] 504 | end 505 | 506 | local function seqrun(counter) 507 | for tr = 1, 14 do 508 | 509 | local div = data[data.pattern].track.div[tr] 510 | 511 | if (div ~= 6 and counter % dividers[div] == 0) 512 | or (div == 6 and counter % dividers[div] >= 0.5) then 513 | 514 | advance_step(tr, counter) 515 | 516 | local mute = data[data.pattern].track.mute[tr] 517 | local pos = data[data.pattern].track.pos[tr] 518 | local trig = data[data.pattern][tr][pos] 519 | 520 | if tr > 7 and choke[tr][6] then 521 | if pos > choke[tr][5] + choke[tr][6] then 522 | midi_out_devices[choke[tr][1]]:note_off(choke[tr][2], choke[tr][3], choke[tr][4]) 523 | end 524 | end 525 | 526 | if trig == 1 and not mute then 527 | 528 | set_locks(data[data.pattern][tr].params[tostring(tr)]) 529 | 530 | local step_param = get_params(tr, pos, true) 531 | 532 | data[data.pattern].track.div[tr] = step_param.div ~= data[data.pattern].track.div[tr] and step_param.div or data[data.pattern].track.div[tr] 533 | 534 | if rules[step_param.rule][2](tr, pos) then 535 | 536 | step_param = step_param.lock ~= 1 and get_params(tr) or step_param 537 | 538 | if tr == data.selected[1] then 539 | redraw_params[1] = step_param 540 | redraw_params[2] = step_param 541 | end 542 | 543 | if tr < 8 then 544 | 545 | set_locks(step_param) 546 | choke_group(tr, step_param.sample) 547 | engine.noteOn(tr, music.note_num_to_freq(step_param.note), 1, step_param.sample) 548 | choke[tr] = step_param.sample 549 | 550 | else 551 | 552 | set_cc(step_param) 553 | 554 | if step_param.program_change >= 0 then 555 | midi_out_devices[step_param.device]:program_change(step_param.program_change, step_param.channel) 556 | end 557 | 558 | midi_out_devices[step_param.device]:note_on( step_param.note, step_param.velocity, step_param.channel ) 559 | choke[tr] = { step_param.device, step_param.note, step_param.velocity, step_param.channel, pos, step_param.length} 560 | end 561 | end 562 | end 563 | end 564 | end 565 | 566 | end 567 | 568 | local function midi_event(d) 569 | 570 | local msg = midi.to_msg(d) 571 | local tr = data.selected[1] 572 | 573 | local pos = data[data.pattern].track.pos[tr] 574 | 575 | -- REC TOGGLE 576 | if msg.cc == REC_CC and msg.val == 127 then 577 | PATTERN_REC = not PATTERN_REC 578 | -- Note off 579 | elseif msg.type == "note_off" then 580 | --engine.noteOff(tr) 581 | -- Note on 582 | elseif msg.type == "note_on" then 583 | if not view.sampling then 584 | engine.noteOff(tr) 585 | engine.noteOn(tr, music.note_num_to_freq(msg.note), msg.vel / 127, data[data.pattern][tr].params[tostring(tr)].sample) 586 | if sequencer_metro.is_running and PATTERN_REC then 587 | place_note(tr, pos, msg.note) 588 | end 589 | end 590 | end 591 | 592 | end 593 | 594 | --- 595 | 596 | local track_params = { 597 | [-6] = function(tr, s, d) -- ptn 598 | local pt = (util.clamp(data.pattern + d, 1, 64)) 599 | change_pattern(pt) 600 | data.metaseq.from = false --data.pattern 601 | data.metaseq.to = false --data.pattern 602 | end, 603 | [-5] = function(tr, s, d) -- rnd 604 | local offset = view.steps_midi and 7 or 0 605 | data.selected[1] = util.clamp(data.selected[1] + d, 1 + offset, 7 + offset) 606 | tr_change(data.selected[1]) 607 | end, 608 | [-4] = function(tr, s, d) -- global bpm 609 | set_bpm(util.clamp(data[data.pattern].bpm + d, 1, 999)) 610 | end, 611 | [-3] = function(tr, s, d) -- track scale 612 | 613 | local div = data[data.pattern].track.div[tr] 614 | data[data.pattern].track.div[tr] = util.clamp(data[data.pattern].track.div[tr] + d, 1, 7) 615 | data[data.pattern][tr].params[tostring(tr)].div = data[data.pattern].track.div[tr] 616 | if div ~= data[data.pattern].track.div[tr] then sync_tracks(tr) end 617 | 618 | end, 619 | [-2] = function(tr, s, d) -- midi out bpm scale 620 | data[data.pattern].sync_div = util.clamp(data[data.pattern].sync_div + d, 0, 7) 621 | if data[data.pattern].sync_div == 0 then midi_clock.send = false else midi_clock.send = true end 622 | end, 623 | [-1] = function(tr, s, d) -- sidechain 624 | data[data.pattern][tr].params[tostring(tr)].sidechain_send = util.clamp(data[data.pattern][tr].params[tostring(tr)].sidechain_send + d, -99, 0 ) 625 | end, 626 | } 627 | 628 | 629 | local midi_step_params = { 630 | 631 | [1] = function(tr, s, d) -- note 632 | data[data.pattern][tr].params[s].note = util.clamp(data[data.pattern][tr].params[s].note + d, 25, 127) 633 | end, 634 | [2] = function(tr, s, d) -- velocity 635 | data[data.pattern][tr].params[s].velocity = util.clamp(data[data.pattern][tr].params[s].velocity + d, 0, 127) 636 | end, 637 | [3] = function(tr, s, d) -- length 638 | data[data.pattern][tr].params[s].length = util.clamp(data[data.pattern][tr].params[s].length + d, 1, 256) 639 | end, 640 | [4] = function(tr, s, d) -- channel 641 | data[data.pattern][tr].params[s].channel = util.clamp(data[data.pattern][tr].params[s].channel + d, 1, 16) 642 | end, 643 | [5] = function(tr, s, d) -- device 644 | data[data.pattern][tr].params[s].device = util.clamp(data[data.pattern][tr].params[s].device + d, 1, 4) 645 | end, 646 | [6] = function(tr, s, d) -- pgm 647 | data[data.pattern][tr].params[s].program_change = util.clamp(data[data.pattern][tr].params[s].program_change + d, -1, 127) 648 | end, 649 | 650 | [7] = function(tr, s, d) -- 651 | data[data.pattern][tr].params[s].cc_1_val = util.clamp(data[data.pattern][tr].params[s].cc_1_val + d, -1, 127) 652 | end, 653 | [8] = function(tr, s, d) -- 654 | data[data.pattern][tr].params[s].cc_2_val = util.clamp(data[data.pattern][tr].params[s].cc_2_val + d, -1, 127) 655 | end, 656 | [9] = function(tr, s, d) -- 657 | data[data.pattern][tr].params[s].cc_3_val = util.clamp(data[data.pattern][tr].params[s].cc_3_val + d, -1, 127) 658 | end, 659 | [10] = function(tr, s, d) -- 660 | data[data.pattern][tr].params[s].cc_4_val = util.clamp(data[data.pattern][tr].params[s].cc_4_val + d, -1, 127) 661 | end, 662 | [11] = function(tr, s, d) -- 663 | data[data.pattern][tr].params[s].cc_5_val = util.clamp(data[data.pattern][tr].params[s].cc_5_val + d, -1, 127) 664 | end, 665 | [12] = function(tr, s, d) -- 666 | data[data.pattern][tr].params[s].cc_6_val = util.clamp(data[data.pattern][tr].params[s].cc_6_val + d, -1, 127) 667 | end, 668 | 669 | [13] = function(tr, s, d) -- 670 | data[data.pattern][tr].params[s].cc_1 = util.clamp(data[data.pattern][tr].params[s].cc_1 + d, 1, 127) 671 | end, 672 | [14] = function(tr, s, d) -- 673 | data[data.pattern][tr].params[s].cc_2 = util.clamp(data[data.pattern][tr].params[s].cc_2 + d, 1, 127) 674 | end, 675 | [15] = function(tr, s, d) -- 676 | data[data.pattern][tr].params[s].cc_3 = util.clamp(data[data.pattern][tr].params[s].cc_3 + d, 1, 127) 677 | end, 678 | [16] = function(tr, s, d) -- 679 | data[data.pattern][tr].params[s].cc_4 = util.clamp(data[data.pattern][tr].params[s].cc_4 + d, 1, 127) 680 | end, 681 | [17] = function(tr, s, d) -- 682 | data[data.pattern][tr].params[s].cc_5 = util.clamp(data[data.pattern][tr].params[s].cc_5 + d, 1, 127) 683 | end, 684 | [18] = function(tr, s, d) -- 685 | data[data.pattern][tr].params[s].cc_6 = util.clamp(data[data.pattern][tr].params[s].cc_6 + d, 1, 127) 686 | end, 687 | 688 | } 689 | 690 | local step_params = { 691 | [1] = function(tr, s, d) -- sample 692 | data[data.pattern][tr].params[s].sample = util.clamp(data[data.pattern][tr].params[s].sample + d, 1, 99) 693 | end, 694 | [2] = function(tr, s, d) -- note 695 | if K3_is_hold() then 696 | data[data.pattern][tr].params[s].detune_cents = util.clamp(data[data.pattern][tr].params[s].detune_cents + d, -100, 100) 697 | else 698 | data[data.pattern][tr].params[s].note = util.clamp(data[data.pattern][tr].params[s].note + d, 25, 127) 699 | end 700 | end, 701 | [3] = function(tr, s, d) -- start 702 | local sample = data[data.pattern][tr].params[s].sample 703 | local pspec = params:lookup_param("start_frame_" .. sample).controlspec 704 | local start = util.clamp(pspec:unmap( data[data.pattern][tr].params[s].start_frame ) + (d / set_enc_res(200, 1000) ), 0, 1) 705 | data[data.pattern][tr].params[s].start_frame = pspec:map(start) 706 | data[data.pattern][tr].params[s].lool_start_frame = pspec:map(start) 707 | end, 708 | [4] = function(tr, s, d) -- len 709 | local sample = data[data.pattern][tr].params[s].sample 710 | local pspec = params:lookup_param("end_frame_" .. sample).controlspec 711 | local length = util.clamp(pspec:unmap( data[data.pattern][tr].params[s].end_frame ) + (d / set_enc_res(200, 1000)), 0, 1) 712 | data[data.pattern][tr].params[s].end_frame = pspec:map(length) 713 | data[data.pattern][tr].params[s].loop_end_frame = pspec:map(length) 714 | end, 715 | [5] = function(tr, s, d) -- freq mod lfo 1 freq_lfo1 716 | --[[ local pspec = params:lookup_param("lfo_1_freq").controlspec 717 | local freq = util.clamp(pspec:unmap( params:get('lfo_1_freq') ) + (d / 10), 0, 1) 718 | params:set('lfo_1_freq', pspec:map(freq)) 719 | ]] 720 | data[data.pattern][tr].params[s].freq_mod_lfo_1 = util.clamp(data[data.pattern][tr].params[s].freq_mod_lfo_1 + d / 100, 0, 1) 721 | end, 722 | [6] = function(tr, s, d) -- freq mod lfo 2 723 | data[data.pattern][tr].params[s].freq_mod_lfo_2 = util.clamp(data[data.pattern][tr].params[s].freq_mod_lfo_2 + d / 100, 0, 1) 724 | end, 725 | [7] = function(tr, s, d) -- volume 726 | data[data.pattern][tr].params[s].amp = amp_map:map(util.clamp(amp_map:unmap(data[data.pattern][tr].params[s].amp) + d / 200, 0,1 )) 727 | end, 728 | [8] = function(tr, s, d) -- pan 729 | data[data.pattern][tr].params[s].pan = util.clamp(data[data.pattern][tr].params[s].pan + d / 20 , -1, 1) 730 | end, 731 | [9] = function(tr, s, d) -- atk 732 | data[data.pattern][tr].params[s].amp_env_attack = util.clamp(data[data.pattern][tr].params[s].amp_env_attack + d / 50, 0, 5) 733 | end, 734 | [10] = function(tr, s, d) -- dec 735 | data[data.pattern][tr].params[s].amp_env_decay = util.clamp(data[data.pattern][tr].params[s].amp_env_decay + d / 50, 0.01, 5) 736 | end, 737 | [11] = function(tr, s, d) -- sus 738 | data[data.pattern][tr].params[s].amp_env_sustain = util.clamp(data[data.pattern][tr].params[s].amp_env_sustain + d / 50, 0, 1) 739 | end, 740 | [12] = function(tr, s, d) -- rel 741 | data[data.pattern][tr].params[s].amp_env_release = util.clamp(data[data.pattern][tr].params[s].amp_env_release + d / 10, 0, 10) 742 | end, 743 | [13] = function(tr, s, d) 744 | data[data.pattern][tr].params[s].amp_mod_lfo_1 = util.clamp(data[data.pattern][tr].params[s].amp_mod_lfo_1 + d / 100, 0, 1) 745 | end, 746 | [14] = function(tr, s, d) 747 | data[data.pattern][tr].params[s].filter_freq_mod_lfo_2 = util.clamp(data[data.pattern][tr].params[s].filter_freq_mod_lfo_2 + d / 100, 0, 1) 748 | end, 749 | [15] = function(tr, s, d) 750 | data[data.pattern][tr].params[s].quality = util.clamp(data[data.pattern][tr].params[s].quality + d, 1, 5) 751 | end, 752 | [16] = function(tr, s, d) 753 | data[data.pattern][tr].params[s].play_mode = util.clamp(data[data.pattern][tr].params[s].play_mode + d, 1, 4) 754 | end, 755 | [17] = function(tr, s, d) 756 | local fr = freq_map:unmap(data[data.pattern][tr].params[s].filter_freq) 757 | fr = util.clamp(fr + d / 200,0.1,1) 758 | data[data.pattern][tr].params[s].filter_freq = freq_map:map(fr) 759 | end, 760 | [18] = function(tr, s, d) 761 | data[data.pattern][tr].params[s].filter_resonance = util.clamp(data[data.pattern][tr].params[s].filter_resonance + d / 20, 0, 1) 762 | end, 763 | [19] = function(tr, s, d) 764 | data[data.pattern][tr].params[s].delay_send = send_map:map(util.clamp(send_map:unmap(data[data.pattern][tr].params[s].delay_send) + d / 200, 0,1 )) 765 | end, 766 | [20] = function(tr, s, d) 767 | data[data.pattern][tr].params[s].reverb_send = send_map:map(util.clamp(send_map:unmap(data[data.pattern][tr].params[s].reverb_send) + d / 200, 0,1 )) end, 768 | 769 | } 770 | 771 | local sampling_params = { 772 | [-1] = function(d)sampler.mode = util.clamp(sampler.mode + d, 1, 4) sampler.set_mode() end, 773 | [0] = function(d) sampler.source = util.clamp(sampler.source + d, 1, 2) sampler.set_source() end, 774 | [3] = function(d) sampler.slot = util.clamp(sampler.slot + d, 1, 100) end, 775 | [5] = function(d) sampler.start = util.clamp(sampler.start + d / (20), 0, sampler.length) sampler.set_start(sampler.start) end, 776 | [6] = function(d) sampler.length = util.clamp(sampler.length + d / (20), sampler.start, sampler.rec_length) end, 777 | [4] = function(d) end, --play 778 | [1] = function(d) end, --save 779 | [2] = function(d) end, --clear 780 | [7] = function(d) end, --clear 781 | } 782 | 783 | local trig_params = { 784 | [-3] = function(tr, s, d) -- 785 | data[data.pattern][tr].params[s].div = util.clamp(data[data.pattern][tr].params[s].div + d, 1, 7) 786 | end, 787 | [-2] = function(tr, s, d) -- rule 788 | data[data.pattern][tr].params[s].rule = util.clamp(data[data.pattern][tr].params[s].rule + d, 0, #rules) 789 | end, 790 | [-1] = function(tr, s, d) -- retrig 791 | data[data.pattern][tr].params[s].retrig = util.clamp(data[data.pattern][tr].params[s].retrig + d, 0, 15) 792 | make_retrigs(tr, s, data[data.pattern][tr].params[s].retrig) 793 | end, 794 | [0] = function(tr, s, d) -- offset 795 | data[data.pattern][tr].params[s + data[data.pattern][tr].params[s].offset].offset = util.clamp(data[data.pattern][tr].params[s].offset + d, 0, 15) 796 | move_substep(tr, s, s + data[data.pattern][tr].params[s].offset) 797 | data[data.pattern][tr].params[s].retrig = 0 798 | end, 799 | } 800 | 801 | local controls = { 802 | [1] = function(z) -- start / stop, 803 | if z == 1 then 804 | if sequencer_metro.is_running then 805 | sequencer_metro:stop() 806 | midi_clock:stop() 807 | notes_off_midi() 808 | else 809 | sequencer_metro:start() 810 | midi_clock:start() 811 | end 812 | if MOD then 813 | engine.noteOffAll() 814 | reset_positions() 815 | kill_all_midi() 816 | end 817 | comp_shut(sequencer_metro.is_running) 818 | end 819 | end, 820 | [3] = function(z) if view.notes_input and z == 1 and sequencer_metro.is_running then PATTERN_REC = not PATTERN_REC end end, 821 | [5] = function(z) if z == 1 then if not view.notes_input then set_view('steps_engine') PATTERN_REC = false end tr_change(1) end end, 822 | [6] = function(z) if z == 1 then if not view.notes_input then set_view('steps_midi') PATTERN_REC = false end tr_change(8) end end, 823 | [8] = function(z) if z == 1 then set_view(view.notes_input and (data.selected[1] < 8 and 'steps_engine' or 'steps_midi') or 'notes_input') end end, 824 | [10] = function(z) if z == 1 then set_view(view.sampling and (data.selected[1] < 8 and 'steps_engine' or 'steps_midi') or 'sampling') end end, 825 | [11] = function(z) if z == 1 then set_view(view.patterns and (data.selected[1] < 8 and 'steps_engine' or 'steps_midi') or 'patterns') end end, 826 | [13] = function(z) MOD = z == 1 and true or false if z == 0 then copy = { false, false } end end, 827 | [15] = function(z) ALT = z == 1 and true or false end, 828 | [16] = function(z) SHIFT = z == 1 and true or false end, 829 | } 830 | 831 | local params_fx = { 832 | [1] = function(d) params:set('takt_comp_level', params:get('takt_comp_level') + d) end, 833 | [2] = function(d) params:set('takt_comp_mix', params:get('takt_comp_mix') + d / 50) end, 834 | [3] = function(d) 835 | local val = threshold_map:unmap(params:get('takt_comp_threshold')) 836 | params:set('takt_comp_threshold', threshold_map:map(util.clamp(val + d /200, 0.001, 1 ))) 837 | end, 838 | [4] = function(d) params:set('comp_slopebelow', params:get('comp_slopebelow') + d / 100) end, 839 | [5] = function(d) params:set('comp_slopeabove', params:get('comp_slopeabove') + d / 100) end, 840 | [6] = function(d) params:set('comp_clamptime', params:get('comp_clamptime') + d / 100) end, 841 | [7] = function(d) params:set('comp_relaxtime', params:get('comp_relaxtime') + d / 100) end, 842 | [8] = function(d) params:set('reverb_time', params:get('reverb_time') + d / 5) end, 843 | [9] = function(d) params:set('reverb_size', params:get('reverb_size') + d / 50) end, 844 | [10] = function(d) params:set('reverb_damp', params:get('reverb_damp') + d / 100) end, 845 | [11] = function(d) params:set('reverb_diff', params:get('reverb_diff') + d / 100) end, 846 | [12] = function(d) params:set('delay_level', params:get('delay_level') + d) end, 847 | [13] = function(d) 848 | local val = time_map:unmap(params:get('delay_time')) 849 | params:set('delay_time', time_map:map(util.clamp(val + d /100, 0.001, 1 ))) 850 | end, 851 | [14] = function(d) params:set('delay_feedback', params:get('delay_feedback') + d / 50) end, 852 | [15] = function(d) params:set('lfo_1_freq', params:get('lfo_1_freq') + d / 10) end, 853 | [16] = function(d) params:set('lfo_1_wave_shape', params:get('lfo_1_wave_shape') + d) end, 854 | [17] = function(d) params:set('lfo_2_freq', params:get('lfo_2_freq') + d / 10) end, 855 | [18] = function(d) params:set('lfo_2_wave_shape', params:get('lfo_2_wave_shape') + d) end, 856 | } 857 | 858 | 859 | function init() 860 | 861 | for i = 1, 4 do 862 | midi_out_devices[i] = midi.connect(i) 863 | midi_out_devices[i].event = midi_event 864 | end 865 | 866 | math.randomseed(os.time()) 867 | 868 | params:add_trigger('save_p', "< Save project" ) 869 | params:set_action('save_p', function(x) textentry.enter(save_project, 'new') end) 870 | params:add_trigger('load_p', "> Load project" ) 871 | params:set_action('load_p', function(x) fileselect.enter(norns.state.data, load_project) end) 872 | params:add_trigger('new', "+ New" ) 873 | params:set_action('new', function(x) init() end) 874 | params:add_separator() 875 | 876 | 877 | for i = 1, 14 do 878 | hold[i] = 0 879 | holdmax[i] = 0 880 | first[i] = 0 881 | second[i] = 0 882 | end 883 | hold['p'] = 0 884 | 885 | redraw_params[1] = data[1][1].params[tostring(1)] 886 | redraw_params[2] = data[1][1].params[tostring(1)] 887 | 888 | timber.init() 889 | sampler.init() 890 | ui.init() 891 | 892 | sequencer_metro = metro.init() 893 | sequencer_metro.time = 60 / (data[data.pattern].bpm * 2) / 16 --[[ppqn]] / 4 894 | sequencer_metro.event = function(stage) seqrun(stage) if stage % m_div(data.metaseq.div) == 0 then metaseq(stage) end end 895 | 896 | redraw_metro = metro.init(function(stage) redraw(stage) g:redraw() blink = (blink + 1) % 17 end, 1/30) 897 | redraw_metro:start() 898 | midi_clock = beatclock:new() 899 | midi_clock.on_step = function() end 900 | midi_clock:bpm_change( util.round(data[data.pattern].bpm / midi_dividers[util.clamp(data[data.pattern].sync_div, 1, 7)])) 901 | midi_clock.send = false 902 | end 903 | 904 | function enc(n,d) 905 | norns.encoders.set_sens(1,3) 906 | norns.encoders.set_sens(2,4) 907 | norns.encoders.set_sens(3,3) 908 | norns.encoders.set_accel(1, false) 909 | norns.encoders.set_accel(2, false) 910 | norns.encoders.set_accel(3, true) 911 | 912 | local tr = data.selected[1] 913 | local s = data.selected[2] and data.selected[2] or tostring(tr) 914 | if browser.open then 915 | browser.enc(n, d) 916 | elseif n == 1 then 917 | 918 | local offset = data.selected[1] > 7 and 7 or 0 919 | data.selected[1] = util.clamp(data.selected[1] + d, 1 + offset, 7 + offset) 920 | tr_change(data.selected[1]) 921 | 922 | elseif n == 2 then 923 | 924 | if not view.sampling then 925 | if not K1_is_hold() then 926 | data.ui_index = util.clamp(data.ui_index + d, not data.selected[2] and 1 or -3, (view.steps_midi or view.patterns) and 18 or 20) 927 | else 928 | data.ui_index = util.clamp(data.ui_index + d, view.patterns and -1 or -6, -1) 929 | end 930 | else 931 | if not sampler.rec then 932 | data.ui_index = util.clamp(data.ui_index + d, -1, 6) 933 | end 934 | end 935 | elseif n == 3 then 936 | if view.patterns then 937 | if K1_is_hold() then 938 | track_params[-1](tr, p, d) 939 | else 940 | params_fx[data.ui_index](d) 941 | end 942 | elseif not view.sampling then 943 | 944 | local p = is_lock() 945 | local t = type(p) == 'number' and get_step(p) or p 946 | 947 | data[data.pattern][tr].params[t].lock = data.selected[2] and 1 or 0 948 | 949 | redraw_params[1] = get_params(tr, is_lock()) 950 | redraw_params[2] = redraw_params[1] 951 | 952 | if K1_is_hold() then 953 | track_params[data.ui_index](tr, p, d) 954 | else 955 | 956 | local params_t = data.ui_index < 1 and trig_params or tr < 8 and step_params or tr > 7 and midi_step_params 957 | 958 | if type(p) == 'string' then 959 | params_t[data.ui_index](tr, p, d) 960 | else 961 | if data.ui_index > 0 then 962 | for i = t, t + 15 do params_t[data.ui_index](tr, i, d) end 963 | else 964 | params_t[data.ui_index](tr, t, d) 965 | end 966 | end 967 | 968 | if view.notes_input then set_locks(get_params(tr)) end 969 | end 970 | else 971 | sampling_params[data.ui_index](d) 972 | end 973 | end 974 | end 975 | 976 | function key(n,z) 977 | K1_hold = (n == 1 and z == 1) and true or false 978 | K3_hold = (n == 1 and z == 1) and true or false 979 | if browser.open then 980 | browser.key(n, z) 981 | 982 | elseif n == 1 then 983 | if K1_is_hold() and not view.sampling and not view.patterns then 984 | data.ui_index = -4 985 | elseif K1_is_hold() and view.patterns then 986 | data.ui_index = -1 987 | else 988 | data.ui_index = 1 989 | end 990 | elseif n == 2 and z == 1 then 991 | if view.patterns then 992 | set_view(view.notes_input and (data.selected[1] < 8 and 'steps_engine' or 'steps_midi')) 993 | elseif browser.open then 994 | 995 | browser.exit() 996 | end 997 | elseif n == 3 then 998 | if view.sampling then 999 | sampling_actions[data.ui_index](z) 1000 | if z == 1 and ((data.ui_index == 1 and sampler.rec) or data.ui_index == 4) then ui.waveform = {} end 1001 | elseif view.patterns then 1002 | --open_settings(2) 1003 | --open_settings(3.5) 1004 | --open_settings(5.5) 1005 | elseif not view.steps_midi then 1006 | if data.ui_index == 1 and z == 1 then 1007 | local sample_id = data[data.pattern][data.selected[1]].params[is_lock()].sample 1008 | browser.enter(_path.audio, timber.load_sample, sample_id) 1009 | elseif (data.ui_index == 3 or data.ui_index == 4) and z == 1 and sample_not_loaded(get_sample()) then 1010 | local sample_id = data[data.pattern][data.selected[1]].params[is_lock()].sample 1011 | browser.enter(_path.audio, timber.load_sample, sample_id) 1012 | elseif (data.ui_index == 17 or data.ui_index == 18) and z == 1 then 1013 | change_filter_type() 1014 | elseif lfo_1[data.ui_index] then 1015 | set_view('patterns') 1016 | data.ui_index = 15 1017 | elseif lfo_2[data.ui_index] then 1018 | set_view('patterns') 1019 | data.ui_index = 17 1020 | elseif data.ui_index == 19 then 1021 | set_view('patterns') 1022 | data.ui_index = 12 1023 | elseif data.ui_index == 20 then 1024 | set_view('patterns') 1025 | data.ui_index = 8 1026 | end 1027 | end 1028 | end 1029 | end 1030 | 1031 | function redraw(stage) 1032 | 1033 | local tr = data.selected[1] 1034 | local pos = data[data.pattern].track.pos[tr] 1035 | local params_data = get_params(tr, sequencer_metro.is_running and pos or false, true) 1036 | 1037 | 1038 | 1039 | if data.selected[2] then 1040 | redraw_params[1] = get_params(data.selected[1], get_step(data.selected[2]), true) 1041 | elseif not data.selected[2] then 1042 | redraw_params[1] = redraw_params[2] 1043 | end 1044 | 1045 | screen.clear() 1046 | 1047 | ui.head(redraw_params[1], data, view, K1_is_hold(), rules, PATTERN_REC, browser.preview) 1048 | 1049 | if view.sampling then 1050 | local pos = sampler.get_pos() 1051 | ui.sampling(sampler, data.ui_index, pos) 1052 | elseif view.patterns then 1053 | ui.patterns(data.pattern, data.metaseq, data.ui_index, stage) 1054 | else 1055 | if data.selected[1] < 8 then 1056 | local meta = timber.get_meta(redraw_params[1].sample) 1057 | -- length hack 1058 | local max_len = meta.num_frames 1059 | if params_data.end_frame == 2000000000 and meta.waveform[2] ~= nil then 1060 | get_sample_len(tr, is_lock()) 1061 | elseif params_data.end_frame > max_len then 1062 | get_sample_len(tr, is_lock()) 1063 | elseif params_data.start_frame > max_len then 1064 | get_sample_start(tr, is_lock()) 1065 | end 1066 | ui.main_screen(redraw_params[1], data.ui_index, meta) 1067 | if browser.open then browser.redraw() end 1068 | else 1069 | ui.midi_screen(redraw_params[1], data.ui_index, data[data.pattern].track, data[data.pattern]) 1070 | end 1071 | end 1072 | screen.update() 1073 | end 1074 | 1075 | 1076 | function g.key(x, y, z) 1077 | screen.ping() 1078 | if view.notes_input and not ALT and not SHIFT then 1079 | local tr = data.selected[1] 1080 | local device = data[data.pattern][tr].params[tr].device 1081 | local note = linn.grid_key(x, y, z, device and midi_out_devices[device]) 1082 | local pos = data[data.pattern].track.pos[tr] 1083 | if note then 1084 | if tr < 8 then 1085 | engine.noteOn(data.selected[1], music.note_num_to_freq(note), 1, data[data.pattern][data.selected[1]].params[tr].sample) 1086 | end 1087 | if sequencer_metro.is_running and PATTERN_REC then 1088 | place_note(tr, pos, note ) 1089 | end 1090 | end 1091 | end 1092 | if y < 8 then 1093 | local held 1094 | local cond = have_substeps(y, x) 1095 | if z==1 and hold[y] then 1096 | holdmax[y] = 0 1097 | end 1098 | hold[y] = hold[y] + (z * 2 - 1) 1099 | hold['p'] = hold['p'] + (z * 2 - 1) 1100 | if hold[y] > holdmax[y] then 1101 | holdmax[y] = hold[y] 1102 | end 1103 | if not view.patterns then 1104 | local y = data.selected[1] > 7 and y + 7 or y 1105 | if SHIFT then 1106 | if z == 1 then 1107 | if x == 16 then 1108 | mute_track(y) 1109 | else 1110 | if x < 8 then 1111 | set_div(y, x) 1112 | end 1113 | end 1114 | end 1115 | elseif ALT then 1116 | if hold[y] == 1 then 1117 | first[y] = x 1118 | elseif hold[y] == 2 then 1119 | second[y] = x 1120 | set_loop(y, first[y], second[y]) 1121 | end 1122 | elseif MOD then 1123 | if not copy[1] then 1124 | copy = { y, x } 1125 | else 1126 | copy_step(copy, {y, x}) 1127 | end 1128 | elseif not view.notes_input then 1129 | cond = have_substeps(y, x) 1130 | data.selected = { y, z == 1 and x or false } 1131 | if not data.selected[2] then tr_change(y) end 1132 | if not data.selected[2] and data.ui_index < 1 then data.ui_index = 1 end 1133 | if z == 1 then 1134 | down_time = util.time() 1135 | else 1136 | hold_time = util.time() - down_time 1137 | held = hold_time > 0.2 and true or false 1138 | x = get_step(x) 1139 | if not cond then 1140 | data[data.pattern][y][x] = 1 1141 | elseif cond and not held then 1142 | clear_substeps(y, x) 1143 | data.selected = { y, false } 1144 | tr_change(y) 1145 | end 1146 | end 1147 | end 1148 | elseif view.patterns then 1149 | local id = to_id(x,y) 1150 | if y < 5 and z == 1 then 1151 | if SHIFT then 1152 | if data.pattern ~= id then 1153 | data[id] = nil 1154 | end 1155 | elseif MOD then 1156 | if not ptn_copy then 1157 | ptn_copy = id 1158 | else 1159 | copy_pattern(ptn_copy, id) 1160 | end 1161 | else 1162 | if hold['p'] == 1 then 1163 | first['p'] = id 1164 | if ptn_change_pending then 1165 | change_pattern(ptn_change_pending) 1166 | ptn_change_pending = false 1167 | else 1168 | ptn_change_pending = id 1169 | end 1170 | data.metaseq.from = false 1171 | data.metaseq.to = false 1172 | ptn_copy = false 1173 | elseif hold['p'] == 2 then 1174 | second['p'] = id 1175 | data.metaseq.from = first['p'] 1176 | data.metaseq.to = second['p'] 1177 | end 1178 | end 1179 | elseif y == 6 then 1180 | data.metaseq.div = x 1181 | end 1182 | end 1183 | else 1184 | if controls[x] then 1185 | controls[x](z) 1186 | end 1187 | if z == 1 then 1188 | if view.sampling or view.patterns then 1189 | ui.start_polls() 1190 | else 1191 | ui.stop_polls() 1192 | end 1193 | end 1194 | end 1195 | end 1196 | 1197 | function g.redraw() 1198 | local glow = util.clamp(blink, 5, 15) 1199 | g:all(0) 1200 | if view.notes_input and (not ALT and not SHIFT) then 1201 | linn.grid_redraw(g) 1202 | end 1203 | for y = 1, 7 do 1204 | for x = 1, 16 do 1205 | if not view.patterns then 1206 | local yy = data.selected[1] > 7 and y + 7 or y 1207 | if SHIFT then 1208 | if y < 8 and x < 8 then 1209 | g:led(x, y, x == 5 and 6 or 3) 1210 | end 1211 | g:led(data[data.pattern].track.div[yy], y, 15) 1212 | g:led(16, y, data[data.pattern].track.mute[yy] and 15 or 6 ) 1213 | elseif ALT then 1214 | local t_start = get_tr_start(yy) 1215 | local t_len = get_tr_len(yy) 1216 | if x >= t_start and x <= t_len then 1217 | g:led(x, y, 3) 1218 | end 1219 | elseif not SHIFT and not view.notes_input then 1220 | -- main 1221 | local substeps = have_substeps(yy, x) 1222 | if substeps then 1223 | local t_start = get_tr_start(yy) 1224 | local t_len = get_tr_len(yy) 1225 | local level = data.selected[1] == yy and data.selected[2] == x and 15 1226 | or (x < t_start or x > t_len) and 5 1227 | or data[data.pattern].track.mute[yy] and 5 1228 | or 10 1229 | g:led(x, y, level ) 1230 | end 1231 | end 1232 | else 1233 | -- patterns 1234 | if y < 5 then 1235 | local id = to_id(x,y) 1236 | --print(id) 1237 | local level = 1238 | id == ptn_change_pending and sequencer_metro.is_running and util.clamp(blink, 5, 14) 1239 | or (data.metaseq.from and data.metaseq.to) and id == data.pattern and util.clamp(blink, 5, 14) 1240 | or (id >= (data.metaseq.from and data.metaseq.from or data.pattern) and id <= (data.metaseq.to and data.metaseq.to or data.pattern)) and 9 1241 | or data.pattern == id and 15 1242 | or pattern_exists(x, y) and 6 1243 | or 2 1244 | g:led(x, y, level) 1245 | elseif y == 6 then 1246 | g:led(x, y, x == data.metaseq.div and 15 or 2) 1247 | end 1248 | end 1249 | end 1250 | -- playhead 1251 | if (view.notes_input and ALT ) or (not view.patterns and not view.notes_input) and sequencer_metro.is_running and not SHIFT then 1252 | local yy = view.steps_midi and y + 7 or y 1253 | local pos = math.ceil(data[data.pattern].track.pos[yy] / 16) 1254 | local level = have_substeps(yy, pos) and 15 or 6 1255 | if not data[data.pattern].track.mute[yy] then g:led(pos, y, level) end 1256 | end 1257 | end 1258 | 1259 | g:led(1, 8, sequencer_metro.is_running and 15 or 6 ) 1260 | 1261 | g:led(3, 8, (view.notes_input and PATTERN_REC) and glow or view.notes_input and 6 or 0) 1262 | g:led(5, 8, (view.notes_input and data.selected[1] < 8 or view.steps_engine) and 15 or 6) 1263 | g:led(6, 8, (view.notes_input and data.selected[1] > 7 or view.steps_midi) and 15 or 6) 1264 | 1265 | g:led(8, 8, view.notes_input and 15 or 6) 1266 | g:led(10, 8, view.sampling and 15 or 6) 1267 | g:led(11, 8, view.patterns and 15 or 6) 1268 | 1269 | g:led(13, 8, MOD and glow or 6 ) 1270 | g:led(15, 8, ALT and glow or 6 ) 1271 | g:led(16, 8, SHIFT and glow or 6 ) 1272 | 1273 | g:refresh() 1274 | 1275 | end 1276 | 1277 | --------------------------------------------------------------------------------