From 2edbdc98599affbc40b1f0bf83b7b40db96b55b6 Mon Sep 17 00:00:00 2001 From: postsolarpunk <87605057+entzmingerc@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:24:49 -0500 Subject: [PATCH] the rewilding (#12) * delete unused crow file * initASL added * prematurely bump version * add boilerplate apiary * add boilerpalte nb mixin * remove duplicate condition * unignore dev for now * more boilerplate + get comfy for developing together * nb first steps * change periods to colons * test commit * broken: the nb_mixins need to be combined * update displayed voice when changing NB VOICE * made apiary glyph big and small * -- * -- * -- * refactor * refactor nb directory to only have lib * update doc entry * apiary animation v1 * swap voice and select in menu for visibility * fixed nb voice menu display bug * count nb voices manually during init * added normalize velocity param for apiary * fourth row into structures popup * page indicator line 2px shorter * remove dev scene --------- Co-authored-by: Tyler Etters --- .gitignore | 1 - arcologies.lua | 4 +- lib/Cell.lua | 8 ++ lib/_crow.lua | 15 +- lib/config.lua | 4 +- lib/crow.lua | 24 ---- lib/dev.lua | 246 +++++++++++++++++++++++++++++++++ lib/docs.lua | 8 ++ lib/glyphs.lua | 35 +++++ lib/graphics.lua | 9 +- lib/includes.lua | 14 +- lib/keeper.lua | 15 +- lib/mixins/nb_select_mixin.lua | 62 +++++++++ lib/mixins/nb_voice_mixin.lua | 68 +++++++++ lib/nb/nb.lua | 196 ++++++++++++++++++++++++++ lib/nb/player.lua | 137 ++++++++++++++++++ lib/parameters.lua | 20 +++ lib/sharer.lua | 109 --------------- lib/structures.lua | 1 + 19 files changed, 822 insertions(+), 154 deletions(-) delete mode 100644 lib/crow.lua create mode 100644 lib/dev.lua create mode 100644 lib/mixins/nb_select_mixin.lua create mode 100755 lib/mixins/nb_voice_mixin.lua create mode 100755 lib/nb/nb.lua create mode 100644 lib/nb/player.lua delete mode 100644 lib/sharer.lua diff --git a/.gitignore b/.gitignore index 35108a4..1e0a943 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ .DS_Store .fuse* lib/config_.lua -lib/dev.lua \ No newline at end of file diff --git a/arcologies.lua b/arcologies.lua index 0199a06..ead7f6c 100644 --- a/arcologies.lua +++ b/arcologies.lua @@ -6,12 +6,11 @@ -- k2: play k3: delete -- -- --- v1.2.8 +-- v1.3.0-dev include("arcologies/lib/includes") function init() - sharer.init() audio:pitch_off() structures.init() filesystem.init() @@ -31,6 +30,7 @@ function init() popup.init() keeper.init() api.init() + nb:init() arcology_name = "arcology" .. os.time(os.date("!*t")) grid_dirty, screen_dirty, splash_break, arcology_loaded = false, false, false, false keys, key_counter, enc_counter = {}, {{},{},{}}, {{},{},{}} diff --git a/lib/Cell.lua b/lib/Cell.lua index b394d18..cc98255 100644 --- a/lib/Cell.lua +++ b/lib/Cell.lua @@ -29,6 +29,8 @@ function Cell:new(x, y, g) state_index_mixin.init(self) -- alphabetically this is "I" level_mixin.init(self) metabolism_mixin.init(self) + nb_select_mixin.init(self) + nb_voice_mixin.init(self) network_mixin.init(self) notes_mixin.init(self) offset_mixin.init(self) @@ -62,6 +64,8 @@ function Cell:new(x, y, g) c.setup_state_index(c) -- alphabetically this is "I" c.setup_level(c) c.setup_metabolism(c) + c.setup_nb_select(c) + c.setup_nb_voice(c) c.setup_network(c) c.setup_notes(c) c.note_registrations(c) @@ -225,6 +229,10 @@ function Cell:change_checks() self:close_all_ports() self:open_port(self:get_bearing_cardinal()) + elseif self:is("APIARY") then + self:set_note_count(8) + self:setup_notes(8) + end end diff --git a/lib/_crow.lua b/lib/_crow.lua index a870c6d..1dbfcba 100644 --- a/lib/_crow.lua +++ b/lib/_crow.lua @@ -1,8 +1,7 @@ _crow = {} function _crow.init() - crow.output[2].action = "pulse(.025, 5)" - crow.output[4].action = "pulse(.025, 5)" + initASL() crow.ii.jf.mode(1) end @@ -16,9 +15,21 @@ function _crow:jf(note) end function _crow:play(note, pair) + initASL() local output_pairs = {{1,2},{3,4}} crow.output[output_pairs[pair][1]].volts = (note - 60) / 12 + -- setting the action before triggering fixes the problem where + -- power cycling crow results in default ASL after arcologies has already initialized, better way? + crow.output[output_pairs[pair][2]]() + -- power cycle crow sets default ASL loaded on outputs, that's why only note is working + -- action is triggering on 2, there's just nothing there + -- reload script, it initializes crow output ASL actions +end + +function initASL() + crow.output[2].action = "pulse(.025, 5)" + crow.output[4].action = "pulse(.025, 5)" end return _crow \ No newline at end of file diff --git a/lib/config.lua b/lib/config.lua index 8ef3d74..283c6e5 100644 --- a/lib/config.lua +++ b/lib/config.lua @@ -2,8 +2,8 @@ config = {} config["settings"] = { ["version_major"] = 1, - ["version_minor"] = 2, - ["version_patch"] = 8, + ["version_minor"] = 3, + ["version_patch"] = 0, ["playback"] = 0, ["length"] = 16, ["root"] = 0, diff --git a/lib/crow.lua b/lib/crow.lua deleted file mode 100644 index 7db4969..0000000 --- a/lib/crow.lua +++ /dev/null @@ -1,24 +0,0 @@ -_crow = {} - -function _crow.init() - crow.output[2].action = "pulse(.025, 5, 1)" - crow.output[4].action = "pulse(.025, 5, 1)" - crow.ii.jf.mode(1) -end - -norns.crow.add = function() - norns.crow.init() - crow.ii.jf.mode(1) -end - -function _crow:jf(note) - crow.ii.jf.play_note((sound:snap_note(sound:transpose_note(note)) - 60) / 12, 5) -end - -function _crow:play(note, pair) - local output_pairs = {{1,2},{3,4}} - crow.output[output_pairs[pair][1]].volts = (note - 60) / 12 - crow.output[output_pairs[pair][2]]() -end - -return _crow \ No newline at end of file diff --git a/lib/dev.lua b/lib/dev.lua new file mode 100644 index 0000000..7045fb9 --- /dev/null +++ b/lib/dev.lua @@ -0,0 +1,246 @@ +local dev = {} + +function rerun(option) + if option == 1 then + print("option 1") + end + norns.script.load(norns.state.script) +end + +function r() + rerun() +end + +function dev:scene(i) + + if i == 1 then + page:select(2) + menu:select_item(4) + keeper:select_cell(8, 4) + keeper.selected_cell:open_port("w") + keeper:select_cell(4, 4) + keeper.selected_cell:change("APIARY") + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("s") + keeper.selected_cell:open_port("w") + -- keeper:deselect_cell() + -- test commit comment + + elseif i == 2 then + sound:set_random_root() + sound:set_random_scale() + keeper:select_cell(4, 1) + keeper.selected_cell:open_port("s") + keeper:select_cell(4, 4) + keeper.selected_cell:change("AVIARY") + keeper.selected_cell:open_port("n") + -- keeper.selected_cell:open_port("e") + -- keeper.selected_cell:open_port("s") + -- keeper.selected_cell:open_port("w") + -- keeper.selected_cell:set_state_index(1) + page:select(2) + menu:select_item(1) + -- keeper:deselect_cell() + counters:toggle_playback() + + elseif i == 3 then + -- fn.seed_cells() + counters:toggle_playback() + keeper:select_cell(1, 1) + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("w") + keeper:deselect_cell() + keeper:select_cell(4, 1) + keeper.selected_cell:set_metabolism(1) + keeper.selected_cell:change("SOLARIUM") + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("w") + + page:select(2) + + + + + elseif i == 4 then + -- fn.seed_cells() + -- params:set("seed", 4) + -- fn.seed_cells() + page:select(4) + + elseif i == 10 then + -- ode to joy + sound:set_scale(47) + page:select(2) + keeper:select_cell(1, 4) + keeper.selected_cell:open_port("e") + keeper.selected_cell:set_metabolism(16) + + keeper:select_cell(5, 4) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(65) -- yeah these are very wrong + + + keeper:select_cell(6, 4) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(65) -- yeah these are very wrong + + keeper:select_cell(7, 4) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(66) -- yeah these are very wrong + + keeper:select_cell(8, 4) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(68) -- yeah these are very wrong + + keeper:select_cell(9, 4) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(68) -- yeah these are very wrong + + keeper:select_cell(10, 4) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(66) -- yeah these are very wrong + + keeper:select_cell(11, 4) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(65) -- yeah these are very wrong + + keeper:select_cell(12, 4) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("s") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(63) -- yeah these are very wrong + + keeper:select_cell(12, 5) + keeper.selected_cell:open_port("n") + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("s") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(61) -- yeah these are very wrong + + keeper:select_cell(11, 5) + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("s") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(61) -- yeah these are very wrong + + keeper:select_cell(10, 5) + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("s") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(63) -- yeah these are very wrong + + keeper:select_cell(9, 5) + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("s") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(65) -- yeah these are very wrong + + keeper:select_cell(8, 5) + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("s") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(65) -- yeah these are very wrong + + keeper:select_cell(7, 5) + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("s") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(63) -- yeah these are very wrong + + keeper:select_cell(6, 5) + keeper.selected_cell:open_port("w") + keeper.selected_cell:open_port("e") + keeper.selected_cell:open_port("s") + keeper.selected_cell:change("SHRINE") + keeper.selected_cell:set_note(63) -- yeah these are very wrong + + keeper:deselect_cell() + + + else + print('else block') + + end +end + + +order = 0 + +-- thank you @dndrks +function screenshot() + --_norns.screen_export_png("/home/we/"..menu.."-"..os.time()..".png") + local which_screen = string.match(string.match(string.match(norns.state.script,"/home/we/dust/code/(.*)"),"/(.*)"),"(.+).lua") + _norns.screen_export_png("/home/we/dust/".. order .. "-" .. which_screen .. "-" .. os.time() .. ".png") + order = order + 1 +end + +function wtfscale() + for i = 1, #sound.scale_notes do + print(sound.scale_notes[i], mu.note_num_to_name(sound.scale_notes[i])) + end +end + +function kc() + return keeper.selected_cell +end + +function arcdebug() + print(" ") + print(" ") + print(" ") + print("start arcologies debug -------------------------------") + print(" ") + print("generated at: " .. os.date("%Y_%m_%d_%H_%M_%S") .. " / " .. os.time()) + print("version: " .. fn.get_arcology_version()) + print("root: " .. sound.root) + print("scale: " .. sound.scale_name) + print("cell count: " .. #keeper.cells) + print("signal count: " .. #keeper.signals) + print(" ") + print("cell census:") + for k,cell in pairs(keeper.cells) do + local coords = "x" .. cell.x .. "y" .. cell.y + print(coords, cell.id, cell.structure_value) + end + print(" ") + print("signal census:") + for k,signal in pairs(keeper.signals) do + local coords = "x" .. signal.x .. "y" .. signal.y + print(coords, signal.id, signal.heading) + end + print(" ") + print("end arcologies debug -------------------------------") + print(" ") + print(" ") + print(" ") +end + +return dev \ No newline at end of file diff --git a/lib/docs.lua b/lib/docs.lua index b8875a4..5601e49 100644 --- a/lib/docs.lua +++ b/lib/docs.lua @@ -209,4 +209,12 @@ docs.sheets["CLOAKROOM"] = { "N & E ports: -" } +docs.sheets["APIARY"] = { + "The nb library", + "plays notes well.", + "", + "Trigger sounds", + "with signals." +} + return docs \ No newline at end of file diff --git a/lib/glyphs.lua b/lib/glyphs.lua index 3d0d654..6f5e400 100644 --- a/lib/glyphs.lua +++ b/lib/glyphs.lua @@ -7,6 +7,13 @@ function glyphs.init() glyphs.shimmer_2 = { 1, 2, 3, 4, 3, 2, 1, 0 } glyphs.shimmer_3 = { 2, 3, 4, 3, 2, 1, 0, 1 } glyphs.shimmer_4 = { 3, 4, 3, 2, 1, 0, 1, 2 } + glyphs.buzz_index = 1 + glyphs.buzz = {} + glyphs.buzz_1 = { 0, 1, 2, 3, 4, 3, 2, 1 } + glyphs.buzz_2 = { 1, 2, 3, 4, 3, 2, 1, 0 } + glyphs.buzz_3 = { 2, 3, 4, 3, 2, 1, 0, 1 } + glyphs.buzz_4 = { 3, 4, 3, 2, 1, 0, 1, 2 } + glyphs.buzz_5 = { 3, 2, 1, 0, 1, 2, 3, 4 } end function glyphs:draw_glyph(s, x, y, l) @@ -327,6 +334,21 @@ function glyphs:cloakroom(x, y, l) graphics:rect(x+13, y+18, 9, 2, l) end +function glyphs:apiary(x, y, l) + self.buzz_index = fn.cycle(counters.ui.quarter_frame % 8 + 1, 1, 8) + graphics:rect(x, y+8, 2, 4, l) + graphics:rect(x+20, y+8, 2, 4, l) + self:half_left_wall(x+5, y, l) + self:half_right_wall(x-5, y, l) + self:third_floor(x, y, l) + self:second_floor(x, y, l) + graphics:rect(x - self.buzz_1[(self.buzz_index+5) % 8 + 1] + 0, y + self.buzz_1[self.buzz_index], 2, 2, l) + graphics:rect(x - self.buzz_2[(self.buzz_index+5) % 8 + 1] + 5, y + self.buzz_2[self.buzz_index]-1, 2, 2, l) + graphics:rect(x + 10, y + self.buzz_3[self.buzz_index], 2, 2, l) + graphics:rect(x + self.buzz_4[(self.buzz_index+4) % 8 + 1] + 15, y + self.buzz_4[self.buzz_index]-1, 2, 2, l) + graphics:rect(x + self.buzz_5[(self.buzz_index+5) % 8 + 1] + 20, y + self.buzz_5[self.buzz_index], 2, 2, l) +end + function glyphs:left_wall(x, y, l) graphics:rect(x, y, 2, 26, l) end @@ -722,6 +744,19 @@ function glyphs:small_cloakroom(x, y, l) graphics:mlrs(x+3, y+6, 3, 0, l) end +function glyphs:small_apiary(x, y, l) + graphics:rect(x-1, y+2, 1, 2, l) + graphics:rect(x+5, y+2, 1, 2, l) + self:small_half_left_wall(x+1, y, l) + self:small_half_right_wall(x-1, y, l) + self:small_third_floor(x, y, l) + self:small_second_floor(x, y, l) + graphics:rect(x-1, y-1, 1, 1, l) + graphics:rect(x+1, y-1, 1, 1, l) + graphics:rect(x+3, y-1, 1, 1, l) + graphics:rect(x+5, y-1, 1, 1, l) +end + function glyphs:small_left_wall(x, y, l) graphics:mls(x, y-1, x, y+8, l) end diff --git a/lib/graphics.lua b/lib/graphics.lua index 5fc0568..b05280e 100644 --- a/lib/graphics.lua +++ b/lib/graphics.lua @@ -23,9 +23,9 @@ end function graphics:structure_palette(i) local start_x = 7 - local start_y = 18 + local start_y = 12 local margin_x = 15 - local margin_y = 15 + local margin_y = 14 local p = {} for setup = 1, #structures:all_enabled() do p[setup] = { @@ -39,8 +39,9 @@ function graphics:structure_palette(i) for k,v in pairs(p) do local row, col = 1,1 -- call me hardcode kassidy - if k >= 9 and k <= 16 then row = 2 end + if k >= 9 and k <= 16 then row = 2 end if k >= 17 and k <= 24 then row = 3 end + if k >= 25 and k <= 32 then row = 4 end p[k].y = start_y + (margin_y * (row - 1)) col = k % 8 col = col == 0 and 8 or col @@ -211,7 +212,7 @@ end function graphics:select_tab(i) self:rect(self:get_tab_x(i), self.tab_padding, self.tab_width, self.tab_height + 1, 0) - self:mlrs(self:get_tab_x(i) + 2, 3, 1, 6) + self:mlrs(self:get_tab_x(i) + 2, 3, 1, 4) end function graphics:page_name() diff --git a/lib/includes.lua b/lib/includes.lua index 31fb642..20d72ce 100644 --- a/lib/includes.lua +++ b/lib/includes.lua @@ -48,6 +48,8 @@ local mixins = { "er_mixin", "level_mixin", "metabolism_mixin", + "nb_select_mixin", + "nb_voice_mixin", "network_mixin", "notes_mixin", "offset_mixin", @@ -144,12 +146,8 @@ sound = include(lib .. "sound") -- experimental api = include(lib .. "api") --- dev only stuff -dev = io.open(_path["code"] .. lib .. "dev.lua", "r") -if dev ~= nil then - io.close(dev) - include(lib .. "dev") -end +-- https://github.com/sixolet/nb +nb = include(lib .. "nb/nb") --- upload/download -sharer = include('lib/sharer') +-- dev only stuff +dev = include(lib .. "dev") diff --git a/lib/keeper.lua b/lib/keeper.lua index 3d7c811..59ff96e 100644 --- a/lib/keeper.lua +++ b/lib/keeper.lua @@ -34,7 +34,7 @@ function keeper:collision(signal, cell) -- empty -- these don't allow signals in - elseif cell:is("HIVE") or cell:is("RAVE") or cell:is("DOME") or cell:is("RAVE") then + elseif cell:is("HIVE") or cell:is("RAVE") or cell:is("DOME") then -- empty -- crypts play samples @@ -115,6 +115,16 @@ function keeper:collision(signal, cell) elseif cell:is("CLOAKROOM") then cell:psyop_signal_adaptor(signal.heading) + -- apiaries cylce through notes for nb voices + elseif cell:is("APIARY") then + cell:over_cycle_state_index(cell:topography_operation()) + local player = params:lookup_param(nb_selector_names[cell.nb_select]):get_player() + if params:get("apiary_normalize_velocity") == 1 then -- we should ask to standardize the nb voices to use 0 - 127 + player:play_note(cell.notes[cell.state_index], cell.velocity / 127, cell.duration) + else + player:play_note(cell.notes[cell.state_index], cell.velocity, cell.duration) + end + end --[[ the below structures reroute & split @@ -131,7 +141,8 @@ function keeper:collision(signal, cell) or cell:is("FOREST") or cell:is("HYDROPONICS") or cell:is("MIRAGE") - or cell:is("AUTON") then + or cell:is("AUTON") + or cell:is("APIARY") then for k, port in pairs(cell.ports) do if (port == "n" and signal.heading ~= "s") then self:create_signal(cell.x, cell.y - 1, "n", "tomorrow") elseif (port == "e" and signal.heading ~= "w") then self:create_signal(cell.x + 1, cell.y, "e", "tomorrow") diff --git a/lib/mixins/nb_select_mixin.lua b/lib/mixins/nb_select_mixin.lua new file mode 100644 index 0000000..c1d73a2 --- /dev/null +++ b/lib/mixins/nb_select_mixin.lua @@ -0,0 +1,62 @@ +-- requires nb_voice_mixin +-- select nb selector 1, 2, 3, or 4 +nb_select_mixin = {} + +nb_select_mixin.init = function(self) + + self.setup_nb_select = function(self) + -- nb_select + self.nb_select_key = "NB SELECT" + self.nb_select = 1 + self:register_save_key("nb_select") + self.nb_select_menu_values = {"nb_1", "nb_2", "nb_3", "nb_4"} + self.nb_select_count = 4 -- arbitrarily chosen maximum (8 sounds good tho) + self:register_menu_getter(self.nb_select_key, self.nb_select_menu_getter) + self:register_menu_setter(self.nb_select_key, self.nb_select_menu_setter) + self:register_arc_style({ + key = self.nb_select_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = true, + min = 1, + max = self.nb_select_count, + value_getter = self.get_nb_select, + value_setter = self.set_nb_select + }) + + self:register_modulation_target({ + key = self.nb_select_key, + inc = self.nb_select_increment, + dec = self.nb_select_decrement + }) + end + + self.nb_select_increment = function(self, i) + self:set_nb_select(self.nb_select + 1) + end + + self.nb_select_decrement = function(self, i) + self:set_nb_select(self.nb_select - 1) + end + + self.get_nb_select = function(self) + return self.nb_select + end + + self.set_nb_select = function(self, i) + self.nb_select = util.clamp(i, 1, self.nb_select_count) + self.callback(self, "set_nb_select") + end + + self.nb_select_menu_getter = function(self) + return self.nb_select_menu_values[self.nb_select] + end + + self.nb_select_menu_setter = function(self, i) + self:set_nb_select(self.nb_select + i) + end + +end \ No newline at end of file diff --git a/lib/mixins/nb_voice_mixin.lua b/lib/mixins/nb_voice_mixin.lua new file mode 100755 index 0000000..6b218c2 --- /dev/null +++ b/lib/mixins/nb_voice_mixin.lua @@ -0,0 +1,68 @@ +-- requires nb_select_mixin +-- chooses which NB voice to send the note to, (none, nb_out, emplaitress 1, ...) +nb_voice_mixin = {} + +nb_voice_mixin.init = function(self) + + self.setup_nb_voice = function(self) + -- nb_voice + self.nb_voice_key = "NB VOICE" + self.nb_voice = params:get("nb_1") + self:register_save_key("nb_voice") + + -- the nb "note_players" variable ends up with connected midi devices, doesn't accurately list options in nb_1 + self.nb_voice_count = nb_player_count -- global from parameters.lua + self:register_menu_getter(self.nb_voice_key, self.nb_voice_menu_getter) + self:register_menu_setter(self.nb_voice_key, self.nb_voice_menu_setter) + self:register_arc_style({ + key = self.nb_voice_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = true, + min = 1, + max = self.nb_voice_count, + value_getter = self.get_nb_voice, + value_setter = self.set_nb_voice + }) + + self:register_modulation_target({ + key = self.nb_voice_key, + inc = self.nb_voice_increment, + dec = self.nb_voice_decrement + }) + end + + self.nb_voice_increment = function(self, i) + self.nb_voice = util.wrap(self:get_nb_voice() + 1, 1, self.nb_voice_count) + self:set_nb_voice(self.nb_voice) -- sets the voice selector param + end + + self.nb_voice_decrement = function(self, i) + self.nb_voice = util.wrap(self:get_nb_voice() - 1, 1, self.nb_voice_count) + self:set_nb_voice(self.nb_voice) -- sets the voice selector param + end + + self.get_nb_voice = function(self) + -- requires nb_select_mixin + -- example: looks up "nb_1" and returns the index integer of nb voice + return params:get(self.nb_select_menu_values[self.nb_select]) + end + + self.set_nb_voice = function(self, i) + self.nb_voice = util.clamp(i, 1, self.nb_voice_count) + params:set(self.nb_select_menu_values[self.nb_select], self.nb_voice) + self.callback(self, "set_nb_voice") + end + + self.nb_voice_menu_getter = function(self) + return params:string(self.nb_select_menu_values[self.nb_select]) + end + + self.nb_voice_menu_setter = function(self, i) + self:set_nb_voice(self:get_nb_voice() + i) + end + +end \ No newline at end of file diff --git a/lib/nb/nb.lua b/lib/nb/nb.lua new file mode 100755 index 0000000..443d909 --- /dev/null +++ b/lib/nb/nb.lua @@ -0,0 +1,196 @@ +local mydir = debug.getinfo(1).source:match("@?" .. _path.code .. "(.*/)") +local player_lib = include(mydir .. "player") +local nb = {} + +if note_players == nil then + note_players = {} +end + +-- note_players is a global that you can add note-players to from anywhere before +-- you call nb:add_param. +nb.players = note_players -- alias the global here. Helps with standalone use. + +nb.none = player_lib:new() + +-- Set this before init() to affect the number of voices added for some mods. +nb.voice_count = 1 + +local abbreviate = function(s) + if string.len(s) < 8 then return s end + local acronym = util.acronym(s) + if string.len(acronym) > 3 then return acronym end + return string.sub(s, 1, 8) +end + +local function add_midi_players() + for i, v in ipairs(midi.vports) do + for j = 1, nb.voice_count do + (function(i, j) + if v.connected then + local conn = midi.connect(i) + local player = { + conn = conn + } + function player:add_params() + params:add_group("midi_voice_" .. i .. '_' .. j, "midi "..j..": " .. abbreviate(v.name), 2) + params:add_number("midi_chan_" .. i .. '_' .. j, "channel", 1, 16, 1) + params:add_number("midi_modulation_cc_" .. i .. '_' .. j, "modulation cc", 1, 127, 72) + params:hide("midi_voice_" .. i .. '_' .. j) + end + + function player:ch() + return params:get("midi_chan_" .. i .. '_' .. j) + end + + function player:note_on(note, vel) + self.conn:note_on(note, util.clamp(math.floor(127 * vel), 0, 127), self:ch()) + end + + function player:note_off(note) + self.conn:note_off(note, 0, self:ch()) + end + + function player:active() + params:show("midi_voice_" .. i .. '_' .. j) + _menu.rebuild_params() + end + + function player:inactive() + params:hide("midi_voice_" .. i .. '_' .. j) + _menu.rebuild_params() + end + + function player:modulate(val) + self.conn:cc(params:get("midi_modulation_cc_" .. i.. '_' .. j), util.clamp(math.floor(127 * val), 0, 127), + self:ch()) + end + + function player:describe() + local mod_d = "cc" + if params.lookup["midi_modulation_cc_" .. i .. '_' .. j] ~= nil then + mod_d = "cc " .. params:get("midi_modulation_cc_" .. i .. '_' .. j) + end + return { + name = "v.name", + supports_bend = false, + supports_slew = false, + modulate_description = mod_d + } + end + + nb.players["midi: " .. abbreviate(v.name) .. " " .. j] = player + end + end)(i, j) + end + end +end + +-- Call from your init method. +function nb:init() + nb_player_refcounts = {} + add_midi_players() + self:stop_all() +end + +-- Add a voice select parameter. Returns the parameter. You can then call +-- `get_player()` on the parameter object, which will return a player you can +-- use to play notes and stuff. +function nb:add_param(param_id, param_name) + local names = {} + for name, _ in pairs(note_players) do + table.insert(names, name) + end + table.sort(names) + table.insert(names, 1, "none") + local names_inverted = tab.invert(names) + params:add_option(param_id, param_name, names, 1) + local string_param_id = param_id .. "_hidden_string" + params:add_text(string_param_id, "_hidden string", "") + params:hide(string_param_id) + local p = params:lookup_param(param_id) + local initialized = false + function p:get_player() + local name = params:get(string_param_id) + if name == "none" then + if p.player ~= nil then + p.player:count_down() + end + p.player = nil + return nb.none + elseif p.player ~= nil and p.player.name == name then + return p.player + else + if p.player ~= nil then + p.player:count_down() + end + local ret = player_lib:new(nb.players[name]) + ret.name = name + p.player = ret + ret:count_up() + return ret + end + end + + clock.run(function() + clock.sleep(1) + p:get_player() + initialized = true + end, p) + params:set_action(string_param_id, function(name_param) + local i = names_inverted[params:get(string_param_id)] + if i ~= nil then + -- silently set the interface param. + params:set(param_id, i, true) + end + p:get_player() + end) + params:set_action(param_id, function() + if not initialized then return end + local i = p:get() + params:set(string_param_id, names[i]) + end) +end + +local function pairsByKeys(t, f) + local a = {} + for n in pairs(t) do table.insert(a, n) end + table.sort(a, f) + local i = 0 -- iterator variable + local iter = function() -- iterator function + i = i + 1 + if a[i] == nil then return nil + else return a[i], t[a[i]] + end + end + return iter +end + +function nb:add_player_params() + if params.lookup['nb_sentinel_param'] then + return + end + for name, player in pairsByKeys(self:get_players()) do + player:add_params() + end + params:add_binary('nb_sentinel_param', 'nb_sentinel_param') + params:hide('nb_sentinel_param') +end + +-- Return all the players in an object by name. +function nb:get_players() + local ret = {} + for k, v in pairs(self.players) do + ret[k] = player_lib:new(v) + end + table.sort(ret) + return ret +end + +-- Stop all voices. Call when you load a pset to avoid stuck notes. +function nb:stop_all() + for _, player in pairs(self:get_players()) do + player:stop_all() + end +end + +return nb diff --git a/lib/nb/player.lua b/lib/nb/player.lua new file mode 100644 index 0000000..89bce5b --- /dev/null +++ b/lib/nb/player.lua @@ -0,0 +1,137 @@ +local player = { + length = 1.0 +} + +if nb_player_refcounts == nil then + nb_player_refcounts = {} +end + +-- Wrap an object to be a full "player", with default implementations of all +-- the player methods +function player:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + +-- Implement this to add params from your player to the script +function player:add_params() +end + +-- Implement to do midi-style note-on. If you only implement play_note, +-- you should implement a trivial note_on to call it. +-- "properties" is an optional table of note mod properties. It should +-- contain only keys that the vocie says it can modulate in note_mod_targets +-- in the description. +function player:note_on(note, vel, properties) +end + +-- Implement to do midi-style note-off. This is optional if you implement +-- play_note instead. +function player:note_off(note) +end + +-- Optional. Send pitch bend to the voice. If the voice doesn't support +-- per-note pitch bend, may bend all notes. Amount is given in semitones. +function player:pitch_bend(note, amount) +end + +-- Optional. Modulate the voice, in whatever way seems best. Range 0-1. +function player:modulate(val) +end + +-- Optional. Set the slew time for the voice. +function player:set_slew(slew) +end + +-- Optional. Modulate the note. `key` should be present in note_mod_targets +-- in the description. `value` should be between -1 and 1. +-- Depending on the parameter, the voice may interperet modulation as either +-- additive to, multiplicative of, or replacing any timbral properties. +-- For example, modulating `amp` should multiply with the `amp` in properties, +-- but modulating a filter cutoff should be additive. +function player:modulate_note(note, key, value) +end + +-- Recommended. Describe the voice's capabilities. +-- Supported keys: +-- supports_bend (does this voice support pitch bend?) +-- supports_slew (does this voice support set_slew?) +-- note_mod_targets (optional list of targets for "note mod") +function player:describe() + return { + name = "none", + supports_bend = false, + supports_slew = false, + modulate_description = "unsupported", + } +end + +-- Optional. Callback for when a voice is used by at least one selector. +-- Suggest using it to show parameters to control the voice. +function player:active() + self.is_active = true + self.active_routine = clock.run(function() + clock.sleep(1) + if self.is_active then + self:delayed_active() + end + self.active_routine = nil + end) +end + +-- Optional. Callback for when a voice is slected for more than one second. +-- This is where you want to change modes on external devices or whatever. +function player:delayed_active() +end + +-- Optional. Callback for when a voice is no longer used. Useful for hiding +-- parameters or whatnot. +function player:inactive() + self.is_active = false + if self.active_routine ~= nil then + clock.cancel(self.active_routine) + end +end + +-- Stop all voices. Use on pset load. +function player:stop_all() +end + +-- Play a note for a given length. `properties` is optional, see note_on +-- for further description. +function player:play_note(note, vel, length, properties) + self:note_on(note, vel, properties) + clock.run(function() + clock.sleep(length*clock.get_beat_sec()) + self:note_off(note) + end) +end + +-- Private. +function player:count_up() + if self.name ~= nil then + if nb_player_refcounts[self.name] == nil then + nb_player_refcounts[self.name] = 1 + self:active() + else + nb_player_refcounts[self.name] = nb_player_refcounts[self.name] + 1 + end + end +end + +-- Private +function player:count_down() + if self.name ~= nil then + if nb_player_refcounts[self.name] ~= nil then + nb_player_refcounts[self.name] = nb_player_refcounts[self.name] - 1 + if nb_player_refcounts[self.name] == 0 then + nb_player_refcounts[self.name] = nil + self:inactive() + end + end + end +end + +return player \ No newline at end of file diff --git a/lib/parameters.lua b/lib/parameters.lua index 16e2c1c..c4a44c9 100644 --- a/lib/parameters.lua +++ b/lib/parameters.lua @@ -36,6 +36,26 @@ function parameters.init() params:add_option("grayscale", "GRAYSCALE", {"DISABLED", "ENABLED"}) params:set_action("grayscale", function(index) parameters.is_grayscale_on = index == 2 and true or false end) + params:add_separator("") + params:add_separator("NB / APIARY") + nb_selector_names = {"nb_1", "nb_2", "nb_3", "nb_4"} + params:add_option("apiary_normalize_velocity", "NORMALIZE VELOCITY", {"ENABLED", "DISABLED"}) + nb:add_param(nb_selector_names[1], nb_selector_names[1]) + nb:add_param(nb_selector_names[2], nb_selector_names[2]) + nb:add_param(nb_selector_names[3], nb_selector_names[3]) + nb:add_param(nb_selector_names[4], nb_selector_names[4]) + nb:add_player_params() + -- we need to count the number of nb players here instead of the mixin + -- this brute force method has a side effect of calling all the active/inactive functions of each player :( + nb_player_count = 0 -- we'll always have "none" + while nb_player_count ~= params:get(nb_selector_names[1]) do + print(params:string(nb_selector_names[1])) + nb_player_count = nb_player_count + 1 + params:set(nb_selector_names[1], nb_player_count+1) + end + params:set(nb_selector_names[1], 1) -- reset to "none" + print("NB COUNT: "..nb_player_count) + params:add_separator("") params:add_separator("KUDZU MANAGEMENT") diff --git a/lib/sharer.lua b/lib/sharer.lua deleted file mode 100644 index e388350..0000000 --- a/lib/sharer.lua +++ /dev/null @@ -1,109 +0,0 @@ -local sharer={} - -local CCDATA_DIR=_path.data.."arcologies/" - -function sharer.init() - script_name = "arcologies" - -- only continue if norns.online exists - if not util.file_exists(_path.code.."norns.online") then - print("need to donwload norns.online") - do return end - end - - -- prevents initial bang for reloading directory - preventbang=true - clock.run(function() - clock.sleep(2) - preventbang=false - end) - - -- load norns.online lib - local share=include("norns.online/lib/share") - - -- start uploader with name of your script - local uploader=share:new{script_name=script_name} - if uploader==nil then - print("uploader failed, no username?") - do return end - end - - -- add parameters - params:add_group("SHARE",4) - - -- uploader (CHANGE THIS TO FIT WHAT YOU NEED) - -- select a save from the names folder - params:add_file("share_upload","upload",CCDATA_DIR) - params:set_action("share_upload",function(y) - -- prevent banging - local x=y - params:set("share_download",CCDATA_DIR) - if #x<=#CCDATA_DIR then - do return end - end - - -- choose data name - -- (here dataname is from the selector) - local dataname=share.trim_prefix(x,CCDATA_DIR) - dataname=dataname:gsub("%.arcology","") -- remove suffix - dataname=dataname:gsub("%.pset","") -- remove suffix - params:set("share_message","uploading...") - _menu.redraw() - print("uploading "..x.." as "..dataname) - - -- upload arcology file - target=CCDATA_DIR..uploader.upload_username.."-"..dataname..".arcology" - pathtofile=CCDATA_DIR..dataname..".arcology" - msg=uploader:upload{dataname=dataname,pathtofile=pathtofile,target=target} - - -- upload pset file - target=CCDATA_DIR..uploader.upload_username.."-"..dataname..".pset" - pathtofile=CCDATA_DIR..dataname..".pset" - msg=uploader:upload{dataname=dataname,pathtofile=pathtofile,target=target} - - -- goodbye - params:set("share_message","uploaded.") - end) - - -- downloader - download_dir=share.get_virtual_directory(script_name) - params:add_file("share_download","download",download_dir) - params:set_action("share_download",function(y) - -- prevent banging - local x=y - params:set("share_download",download_dir) - if #x<=#download_dir then - do return end - end - - -- download - print("downloading!") - params:set("share_message","downloading...") - _menu.redraw() - local msg=share.download_from_virtual_directory(x) - params:set("share_message",msg) - end) - - -- add a button to refresh the directory - params:add{type='binary',name='refresh directory',id='share_refresh',behavior='momentary',action=function(v) - if preventbang then - do return end - end - print("updating directory") - params:set("share_message","refreshing directory.") - _menu.redraw() - share.make_virtual_directory() - params:set("share_message","directory updated.") - end -} -params:add_text('share_message',">","") -end - - - -sharer.trim_prefix=function(s,p) - local t=(s:sub(0,#p)==p) and s:sub(#p+1) or s - return t -end - - -return sharer diff --git a/lib/structures.lua b/lib/structures.lua index cb08de7..713f04b 100644 --- a/lib/structures.lua +++ b/lib/structures.lua @@ -40,6 +40,7 @@ function structures.init() structures:register("WINDFARM", { "METABOLISM", "BEARING", "CLOCKWISE" }) structures:register("FRACTURE", { "NOTES", "RANGE MIN", "RANGE MAX", "OUTPUT", "DURATION", "CHANNEL", "DEVICE" }) structures:register("CLOAKROOM", { "TARGET", "PSYOP" } ) + structures:register("APIARY", { "INDEX", "NOTE COUNT", "NOTES", "TOPOGRAPHY", "DURATION", "VELOCITY", "NB VOICE", "NB SELECT"}) end function structures:register(name, attributes)