diff --git a/arcologies.lua b/arcologies.lua index 2dd3648..853134c 100644 --- a/arcologies.lua +++ b/arcologies.lua @@ -6,11 +6,7 @@ -- k2: play k3: delete -- -- --- ........................................ --- v1.1.15 "eternal september" --- <3 @tyleretters --- nor.the-rn.info --- GNU GPL v3.0 +-- v1.2.0 include("arcologies/lib/includes") @@ -20,10 +16,10 @@ function init() filesystem.init() parameters.init() fn.init() - m.init() - g.init() - c.init() - s.init() + _midi.init() + _grid.init() + _crow.init() + _softcut.init() sound.init() counters.init() glyphs.init() @@ -33,6 +29,7 @@ function init() menu.init() popup.init() keeper.init() + api.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 = {}, {{},{},{}}, {{},{},{}} @@ -42,11 +39,13 @@ function init() end music_clock_id = clock.run(counters.conductor) redraw_clock_id = clock.run(counters.redraw_clock) - grid_clock_id = clock.run(g.grid_redraw_clock) + grid_clock_id = clock.run(_grid.grid_redraw_clock) + arc_clock_id = clock.run(_arc.arc_redraw_clock) counters.ui:start() counters.grid:start() page:select(parameters.is_splash_screen_on and 0 or 1) init_done = true + _arc.init() structures:scan() if config.settings.dev_mode then dev:scene(config.settings.dev_scene) end redraw() @@ -66,6 +65,7 @@ function enc(e, d) menu:scroll(d) elseif e == 3 then menu:scroll_value(d) -- e3 only ever changes the menu value + _arc:set_glowing_endless_up(d > 0) end fn.dirty_screen(true) end diff --git a/lib/Cell.lua b/lib/Cell.lua index bbc54c0..803ab4b 100644 --- a/lib/Cell.lua +++ b/lib/Cell.lua @@ -5,13 +5,13 @@ function Cell:new(x, y, g) c.x = x ~= nil and x or 0 c.y = y ~= nil and y or 0 c.generation = g ~= nil and g or 0 - c.structure_name = structures:first() -- typically HIVE c.id = "cell-" .. fn.id() -- unique identifier for this cell c.index = fn.index(c.x, c.y) -- location on the grid c.flag = false -- multipurpse flag used through the keeper:collision() lifecycle c.save_keys = {} c.menu_getters = {} c.menu_setters = {} + c.arc_styles = {} bearing_mixin.init(self) capacity_mixin.init(self) channel_mixin.init(self) @@ -26,6 +26,7 @@ function Cell:new(x, y, g) duration_mixin.init(self) er_mixin.init(self) level_mixin.init(self) + mapping_mixin.init(self) metabolism_mixin.init(self) network_mixin.init(self) notes_mixin.init(self) @@ -38,7 +39,7 @@ function Cell:new(x, y, g) range_mixin.init(self) resilience_mixin.init(self) state_index_mixin.init(self) - structure_stub_mixin.init(self) + structure_mixin.init(self) territory_mixin.init(self) topography_mixin.init(self) turing_mixin.init(self) @@ -60,6 +61,7 @@ function Cell:new(x, y, g) c.setup_duration(c) c.setup_er(c) c.setup_level(c) + c.setup_mapping(c) c.setup_metabolism(c) c.setup_network(c) c.setup_notes(c) @@ -72,7 +74,7 @@ function Cell:new(x, y, g) c.setup_range(c) c.setup_resilience(c) c.setup_state_index(c) - c.setup_structure_stub(c) + c.setup_structure(c) c.setup_territory(c) c.setup_topography(c) c.setup_turing(c) @@ -96,6 +98,10 @@ function Cell:register_menu_getter(key, getter) self.menu_getters[key] = getter end +function Cell:register_arc_style(args) + self.arc_styles[args.key] = args +end + function Cell:set_attribute_value(key, value) if self.menu_setters[key] ~= nil then self.menu_setters[key](self, value) @@ -110,34 +116,11 @@ function Cell:get_menu_value_by_attribute(a) end end -function Cell:is(name) - return self.structure_name == name -end - -function Cell:has(name) - return fn.table_has(self:get_attributes(), name) -end - -function Cell:get_attributes() - return structures:get_structure_attributes(self.structure_name) -end - -function Cell:change(name) - local match = false - for k, v in pairs(structures:all_enabled()) do - if v.name == name then - self:set_structure_by_name(name) - match = true - end - end - if not match then - self:set_structure_by_name(structures:first()) - end -end - -function Cell:set_structure_by_name(name) - self.structure_name = name - self:change_checks() +function Cell:menu_items() + local items = fn.deep_copy(self:get_attributes()) + items = self:inject_notes_into_menu(items) + items = self:check_output_items(items) + return items end function Cell:prepare_for_paste(x, y, g) @@ -150,13 +133,6 @@ function Cell:prepare_for_paste(x, y, g) self:set_available_ports() end -function Cell:menu_items() - local items = fn.deep_copy(self:get_attributes()) - items = self:inject_notes_into_menu(items) - items = self:check_output_items(items) - return items -end - --[[ from here out we get into what is essentially "descendent class behaviors" since all cells can change structures at any time, it makes no sense to @@ -169,14 +145,17 @@ theres only ~40 lines of code below... -- to keep traits reasonably indempotent, even though the have to interact with one another function Cell:callback(method) if method == "set_state_index" then - if self:is("CRYPT") then s:crypt_load(self.state_index) end + if self:is("CRYPT") then _softcut:crypt_load(self.state_index) end elseif method == "set_metabolism" then - if self:has("PULSES") and self.pulses > self.metabolism then self.pulses = self.metabolism end + if self:has("PULSES") then self:set_pulses_max(self:get_metabolism()) end + if self:has("PULSES") and self:get_pulses() > self:get_metabolism() then self:set_pulses(self:get_metabolism()) end if self:is("DOME") then self:set_er() end elseif method == "set_pulses" then if self:is("DOME") then self:set_er() end elseif method == "set_bearing" then if self:is("WINDFARM") then self:close_all_ports() self:open_port(self:get_bearing_cardinal()) end + elseif method == "set_note_count" then + if self:has("INDEX") then self:set_max_state_index(self:get_note_count()) end end end @@ -184,6 +163,7 @@ end function Cell:change_checks() local max_state_index = self:is("CRYPT") and 6 or 8 self:set_max_state_index(max_state_index) + self:update_note_max(#sound:get_scale_notes()) if self:is("DOME") then self:set_er() @@ -258,11 +238,12 @@ end -- does this cell need to do anything to boot up this beat? function Cell:setup() if self:is("RAVE") and self:is_spawning() then self:drugs() - elseif self:is("MAZE") then self:set_turing() - elseif self:is("SOLARIUM") then self:compare_capacity_and_charge() - elseif self:is("MIRAGE") then self:shall_we_drift_today() - elseif self:is("INSTITUTION") then self:has_crumbled() - elseif self:is("KUDZU") then self:close_all_ports() self:lifecycle() + elseif self:is("MAZE") then self:set_turing() + elseif self:is("SOLARIUM") then self:compare_capacity_and_charge() + elseif self:is("MIRAGE") then self:shall_we_drift_today() + elseif self:is("INSTITUTION") then self:has_crumbled() + elseif self:is("KUDZU") then self:close_all_ports() + self:lifecycle() end end diff --git a/lib/_arc.lua b/lib/_arc.lua new file mode 100644 index 0000000..585c2c4 --- /dev/null +++ b/lib/_arc.lua @@ -0,0 +1,870 @@ +_arc = {} +_arc.device = arc.connect() + +function _arc.init() + _arc.orientation = 0 + _arc.bindings = {} + _arc.encs = {{},{},{},{}} + _arc.frame, _arc.slow_frame, _arc.rotate_frame, _arc.topography_frame = 0, 0, 0, 0 + _arc.standby_up = true + _arc.glowing_endless_up = true + _arc.glowing_endless_cache = 1 + _arc.glowing_endless_position = 1 + _arc.glowing_endless_key = nil + _arc.drift_direction_up = true + _arc.structure_popup_active = false + _arc.structure_going_up = nil + _arc.note_popup_active = false + _arc.note_going_up = nil + + -- each also needs to be setup in config.arc_bindings to make available to paramters.lua! + _arc:register_all_available_bindings() + + -- bind each encoder to what the user has specified + for n = 1, 4 do + _arc:bind(n, config.arc_bindings[params:get("arc_encoder_" .. n)].id) + end + + fn.dirty_arc(true) +end + +function _arc.arc_redraw_clock() + while true do + if fn.dirty_arc() then + _arc:refresh_values() + _arc:arc_redraw() + _arc.device:refresh() + -- fn.dirty_arc(false) -- disabled for the sparkly animations + _arc:increment_frames() + end + clock.sleep(1/30) + end +end + +function _arc:increment_frames() + self.frame = self.frame + 1 + self.slow_frame = (self.frame % 2 == 1) and self.slow_frame + 1 or self.slow_frame + self.rotate_frame = (self.slow_frame % 64) == 0 and 64 or self.slow_frame % 64 + self.topography_frame = (self.slow_frame % 21) == 0 and 21 or self.slow_frame % 21 + self.standby_up = (self.slow_frame % 5 == 1) and not self.standby_up or self.standby_up + if self.frame % (math.random(1, 2) == 1 and 32 or 64) == 1 then + self.drift_direction_up = not self.drift_direction_up + end +end + +function arc.delta(n, delta) + -- this only works after the rest of arcologies loads + if not init_done then return end + + -- which enc are we operating on + local enc = _arc.encs[n] + + -- end the clock for this encoder + if enc.takeover_clock ~= nil then + enc.takeover = false + clock.cancel(enc.takeover_clock) + enc.takeover_clock = nil + end + + -- run the deltas + _arc:run_delta(enc, delta) + + -- duplicate bindings are possible + _arc:refresh_duplicate_bindings(enc) + + -- things done changed + fn.dirty_arc(true) + screen.ping() + + -- start the clock for this encoder + if enc.takeover_clock == nil then + _arc.encs[n].takeover = true + enc.takeover_clock = clock.run(_arc.enc_wait, n) + end +end + +function _arc:run_delta(enc, delta) + local value = 0 + if enc.style_getter() == "glowing_endless" then + self:set_glowing_endless_up(delta > 0) + value = fn.cycle(enc.value + (enc.sensitivity() * delta), enc.min_getter(), enc.max_getter() + 1) + self.encs[enc.enc_id].value = util.clamp(value, enc.min_getter(), enc.max_getter() + 1) + enc.value_setter(math.floor(self.encs[enc.enc_id].value)) + elseif enc.style_getter() == "glowing_fulcrum" then + value = enc.value + (enc.sensitivity() * delta) + self.encs[enc.enc_id].value = util.clamp(value, enc.min_getter(), enc.max_getter()) + enc.value_setter(math.floor(fn.round(self.encs[enc.enc_id].value))) + elseif enc.style_getter() == "glowing_structure" then + local going_up = delta > 0 + if self.structure_going_up ~= going_up then + self.structure_going_up = going_up + end + -- this uses the value_cache instead of the value + value_cache = enc.value_cache + (enc.sensitivity() * delta) + self.encs[enc.enc_id].value_cache = util.clamp(value_cache, enc.min_getter(), enc.max_getter()) + local popup_delta = 0 + if math.floor(value_cache) ~= popup:get_cached_index() then + popup_delta = going_up and 1 or -1 + end + popup:launch("structure", popup_delta, "arc", enc.enc_id) + elseif enc.style_getter() == "glowing_note" then + local going_up = delta > 0 + if self.note_going_up ~= going_up then + self.note_going_up = going_up + end + -- this uses the value_cache instead of the value + value_cache = enc.value_cache + (enc.sensitivity() * delta) + self.encs[enc.enc_id].value_cache = util.clamp(value_cache, enc.min_getter(), enc.max_getter()) + local popup_delta = 0 + if math.floor(value_cache) ~= popup:get_cached_index() then + popup_delta = going_up and 1 or -1 + end + + popup:launch("note", popup_delta, "arc", enc.enc_id, enc.extras().note_number) + + else + if enc.style_getter() ~= "variable" and enc.wrap_getter() then + value = fn.cycle(enc.value + (enc.sensitivity() * delta), enc.min_getter(), enc.max_getter()) + else + value = enc.value + (enc.sensitivity() * delta) + end + self.encs[enc.enc_id].value = util.clamp(value, enc.min_getter(), enc.max_getter()) + enc.value_setter(self:map_to_segment(enc)) + end +end + +function _arc.enc_wait(n) + clock.sleep(.1) + _arc.encs[n].takeover = false +end + +function _arc:refresh_values() + for n = 1, 4 do + if not self.encs[n].takeover then + self.encs[n].value = self.encs[n].value_getter() + end + end +end + +function _arc:set_orientation(i) + self.orientation = i +end + +function _arc:set_glowing_endless_up(bool) + self.glowing_endless_up = bool +end + +function _arc:reset_glowing_endless() + self.glowing_endless_up = true + self.glowing_endless_cache = 1 + self.glowing_endless_position = 1 + self.glowing_endless_key = nil +end + +function _arc:arc_redraw() + for n = 1, 4 do + local enc = self.encs[n] + local s = enc.style_getter() + if s == "divided" then self:draw_divided_segment(enc) + elseif s == "glowing_boolean" then self:draw_glowing_boolean(enc) + elseif s == "glowing_clock" then self:draw_glowing_clock(enc) + elseif s == "glowing_compass" then self:draw_glowing_compass(enc) + elseif s == "glowing_divided" then self:draw_glowing_divided(enc) + elseif s == "glowing_drift" then self:draw_glowing_drift(enc) + elseif s == "glowing_endless" then self:draw_glowing_endless(enc) + elseif s == "glowing_fulcrum" then self:draw_glowing_fulcrum(enc) + elseif s == "glowing_note" then self:draw_glowing_note(enc) + elseif s == "glowing_range" then self:draw_glowing_range(enc) + elseif s == "glowing_segment" then self:draw_glowing_segment(enc) + elseif s == "glowing_structure" then self:draw_glowing_structure(enc) + elseif s == "glowing_territory" then self:draw_glowing_territory(enc) + elseif s == "glowing_topography" then self:draw_glowing_topography(enc) + elseif s == "scaled" then self:draw_scaled_segment(enc) + elseif s == "sweet_sixteen" then self:draw_sweet_sixteen(enc) + elseif s == "standby" then self:draw_standby(enc) + else self:draw_standby(enc) + end + end +end + +function _arc:draw_segment(n, segment, level) + if segment.valid then + local adjusted_from = self:get_orientation_adjusted_radian(segment.from) + local adjusted_to = self:get_orientation_adjusted_radian(segment.to) + self.device:segment(n, adjusted_from, adjusted_to, 15) + end +end + +function _arc:get_orientation_adjusted_radian(value) + return fn.over_cycle(self:degs_to_rads(self.orientation) + value, 0, 2 * math.pi) +end + +function _arc:draw_led(n, led, level) + self.device:led(n, self:get_orientation_adjusted_led(led), level) +end + +function _arc:get_orientation_adjusted_led(value) + return fn.over_cycle(util.linlin(0, 360, 0, 64, self.orientation) + value, 0, 64) +end + + + +-- animations + + + +function _arc:clear_ring(n) + for i = 1, 64 do _arc.device:led(n, i, 0) end +end + + +function _arc:draw_glowing_structure(enc) + self:clear_ring(enc.enc_id) + if not self.structure_popup_active then + local ring = {} + for i = 1, 3 do + local m = (i - 1) * 22 + ring[m + 1] = 1 + ring[m + 2] = 1 + ring[m + 3] = 2 + ring[m + 4] = 3 + ring[m + 5] = 5 + ring[m + 6] = 8 + ring[m + 7] = 13 + ring[m + 8] = 2 + ring[m + 9] = math.random(10,15) + ring[m + 10] = 2 + ring[m + 11] = 13 + ring[m + 12] = 8 + ring[m + 13] = 5 + ring[m + 14] = 3 + ring[m + 15] = 2 + ring[m + 16] = 1 + ring[m + 17] = 1 + ring[m + 18] = 0 + ring[m + 19] = 0 + ring[m + 20] = 0 + ring[m + 21] = 0 + ring[m + 22] = 0 + end + ring = fn.shift_table(ring, 2) + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end + else + local style_max = util.linlin(1, 360, 1, 64, enc.style_max_getter()) + local segment_size = style_max / enc.max_getter() + local segments = {} + local cached_index = popup:get_cached_index() + for i = 1, cached_index do + local convert_to_led = util.linlin(0, 360, 1, 64, enc.style_offset()) + segments[i] = {} + segments[i].from = fn.round(fn.over_cycle(convert_to_led + (segment_size * (i - 1)), 1, 64)) + segments[i].to = fn.round(segments[i].from + segment_size) + end + for x = segments[cached_index].from, segments[cached_index].to do + self:draw_led(enc.enc_id, x, math.random(10, 15)) + end + end +end + +function _arc:draw_divided_segment(enc) + self:clear_ring(enc.enc_id) + local segment_size = enc.style_max_getter() / enc.max_getter() + local segments = {} + for i = 1, enc.max_getter() do + local from_raw = enc.style_offset() + (segment_size * (i - 1)) + local from = self:cycle_degrees(from_raw) + local to = self:cycle_degrees(from_raw + segment_size) + segments[i] = {} + segments[i].from = self:degs_to_rads(from, enc.snap_getter()) + segments[i].to = self:degs_to_rads(to, enc.snap_getter()) + end + self:draw_segment(enc.enc_id, self:validate_segment(segments[self:map_to_segment(enc)]), 15) +end + +function _arc:draw_scaled_segment(enc) + local max = (enc.style_max_getter() == 360) and 359.9 or enc.style_max_getter() -- compensate for circles, 0 == 360, etc. + local from = enc.style_offset() + local to = self:cycle_degrees(util.linlin(0, 360, 0, enc.style_max_getter(), self:scale_to_degrees(enc)) + enc.style_offset()) + local segment = {} + segment.from = self:degs_to_rads(from, enc.snap_getter()) + segment.to = self:degs_to_rads(to, enc.snap_getter()) + self:draw_segment(enc.enc_id, self:validate_segment(segment), 15) +end + +function _arc:draw_glowing_note(enc) + -- notes can have a value of 0 + self:draw_glowing_divided(enc) +end + +function _arc:draw_glowing_divided(enc) + if enc.value_getter() > 0 then + self:clear_ring(enc.enc_id) + local style_max = util.linlin(1, 360, 1, 64, enc.style_max_getter()) + local segment_size = style_max / enc.max_getter() + local segments = {} + for i = 1, enc.value_getter() do + local convert_to_led = util.linlin(0, 360, 1, 64, enc.style_offset()) + segments[i] = {} + segments[i].from = fn.round(fn.over_cycle(convert_to_led + (segment_size * (i - 1)), 1, 64)) + segments[i].to = fn.round(segments[i].from + segment_size) + end + for x = segments[enc.value_getter()].from, segments[enc.value_getter()].to do + self:draw_led(enc.enc_id, x, math.random(10, 15)) + end + else + self:draw_standby(enc) + end +end + +function _arc:draw_glowing_segment(enc) + self:clear_ring(enc.enc_id) + if enc.value_getter() == 0 then + self:draw_led(enc.enc_id, fn.round(util.linlin(0, 360, 1, 64, enc.style_offset())), math.random(10, 15)) + else + local style_max = util.linlin(1, 360, 1, 64, enc.style_max_getter()) + local segment_size = style_max / enc.max_getter() + for i = 1, enc.value_getter() do + local convert_to_led = util.linlin(0, 360, 1, 64, enc.style_offset()) + local from = fn.round(fn.over_cycle(convert_to_led + (segment_size * (i - 1)), 1, 64)) + local to = fn.round(from + segment_size) + for x = from, to do + self:draw_led(enc.enc_id, x, math.random(10, 15)) + end + end + end +end + +function _arc:draw_glowing_range(enc) + -- only works for selected cells for now + -- there are no global attributes that use range and are mappable + if not keeper.is_cell_selected then return end + self:clear_ring(enc.enc_id) + local from = keeper.selected_cell.arc_styles["RANGE MIN"].value_getter(keeper.selected_cell) + local to = keeper.selected_cell.arc_styles["RANGE MAX"].value_getter(keeper.selected_cell) + local style_max = util.linlin(1, 360, 1, 64, enc.style_max_getter()) + local from_led = fn.round(util.linlin(0, 100, 1, style_max, from)) + local to_led = fn.round(util.linlin(0, 100, 1, style_max, to)) + local ring = {} + for i = 1, 64 do + ring[i] = (i >= from_led) and (i <= to_led) and math.random(10, 15) or 0 + end + ring = fn.shift_table(ring, util.linlin(0, 360, 1, 64, enc.style_offset())) + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + + +function _arc:draw_glowing_fulcrum(enc) + self:clear_ring(enc.enc_id) + if enc.value_getter() == 0 then + self:draw_led(enc.enc_id, 63, 3) + self:draw_led(enc.enc_id, 64, 5) + self:draw_led(enc.enc_id, 2, 5) + self:draw_led(enc.enc_id, 3, 3) + else + local style_max = util.linlin(1, 360, 1, 64, enc.style_max_getter()) / 2 + local segment_size = style_max / enc.max_getter() * math.abs(enc.value_getter()) + local ring = {} + for i = 1, 64 do + ring[i] = (i < segment_size) and math.random(10, 15) or 0 + end + if enc.value_getter() < 0 then + ring[64] = 5 + ring[63] = 3 + ring = fn.shift_table(ring, 64 + math.ceil(segment_size * -1)) + else + ring[math.floor(segment_size) + 1] = 5 + ring[math.floor(segment_size) + 2] = 3 + end + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end + end + self:draw_led(enc.enc_id, 1, math.random(10, 15)) +end + +function _arc:draw_glowing_endless(enc) + self:clear_ring(enc.enc_id) + if self.glowing_endless_key ~= enc.key_getter() then + self:reset_glowing_endless() + self.glowing_endless_key = enc.key_getter() + self.glowing_endless_position = math.random(1, 8) + end + local ring = {} + for i = 1, 64 do + ring[i] = (i > 2 and i < 9) and math.random(10, 15) or 0 + end + ring[2], ring[9] = 5, 5 + ring[1], ring[10] = 3, 3 + if self.glowing_endless_cache ~= math.floor(enc.value) then + self.glowing_endless_cache = math.floor(enc.value) + local direction = self.glowing_endless_up and 1 or -1 + self.glowing_endless_position = fn.cycle(self.glowing_endless_position + direction, 1, 8) + end + ring = fn.shift_table(ring, self.glowing_endless_position * 8) + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + +function _arc:draw_sweet_sixteen(enc) + self:clear_ring(enc.enc_id) + if enc.value_getter() == 0 then + self:draw_led(enc.enc_id, fn.round(util.linlin(0, 360, 1, 64, enc.style_offset())), math.random(10, 15)) + else + for i = 1, enc.value_getter() do + local convert_to_led = fn.round(util.linlin(0, 360, 1, 64, enc.style_offset())) + local from = fn.round(fn.over_cycle(convert_to_led + (4 * (i - 1)), 1, 64)) + local to = fn.round(from + 3) + for x = from, to do + local l = x == to and 3 or math.random(10, 15) + self:draw_led(enc.enc_id, x, l) + end + end + end +end + +function _arc:draw_glowing_boolean(enc) + self:clear_ring(enc.enc_id) + local ring = {} + for i = 1, 64 do + if enc.value_getter() == 0 and i > 33 then + ring[i] = math.random(2, 5) + elseif enc.value_getter() == 1 and i <= 33 then + ring[i] = math.random(5, 15) + else + ring[i] = 0 + end + end + local shift_amount = fn.round(util.linlin(0, 360, 1, 64, enc.style_offset())) + ring = fn.shift_table(ring, shift_amount) + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + +function _arc:draw_glowing_drift(enc) + self:clear_ring(enc.enc_id) + local ring = {} + -- 1 = north/south, 2 = east/west, 3 = ??? + for i = 1, 64 do + if enc.value_getter() ~= 3 then + ring[i] = ((i < 16) or (i > 33 and i < 48)) and math.random(10, 15) or 0 + else + ring[i] = (i < 16) and math.random(10, 15) or 0 + end + end + if enc.value_getter() ~= 3 then + ring = fn.shift_table(ring, ((enc.value_getter() - 1) * 16)) + else + if self.drift_direction_up then + ring = fn.reverse_shift_table(ring, self.rotate_frame) + else + ring = fn.shift_table(ring, self.rotate_frame) + end + end + -- adjust for "compass" + ring = fn.shift_table(ring, 57) + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + +function _arc:draw_glowing_compass(enc) + self:clear_ring(enc.enc_id) + local ring = {} + for i = 1, 64 do + ring[i] = (i < 16) and math.random(10, 15) or 0 + end + -- 1 = north, 2 = east, 3 = south, 4 = west + ring = fn.shift_table(ring, ((enc.value_getter() - 1) * 16)) + -- adjust for "compass" + ring = fn.shift_table(ring, 57) + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + +function _arc:draw_glowing_territory(enc) + self:clear_ring(enc.enc_id) + local ring = {} + local shift = 0 + -- 1 = north, 2 = east, 3 = south, 4 = west + -- 5 = n/e, 6 = s/e, 7 = s/w, 8 = n/w + -- 9 = all, 10 = fringes + -- matching on strings is safer here + local t = keeper.selected_cell:territory_menu_getter(enc.value_getter()) + if t == "NORTH" or t == "EAST" or t == "SOUTH" or t =="WEST" then + for i = 1, 64 do + ring[i] = (i < 16) and math.random(10, 15) or 0 + end + if t == "NORTH" then shift = 0 + elseif t == "EAST" then shift = 1 + elseif t == "SOUTH" then shift = 2 + elseif t == "WEST" then shift = 3 + end + elseif t == "N/E" or t == "S/E" or t == "S/W" or t =="N/W" then + for i = 1, 64 do + ring[i] = i <= 32 and math.random(10, 15) or 0 + end + if t == "N/E" then shift = 0 + elseif t == "S/E" then shift = 1 + elseif t == "S/W" then shift = 2 + elseif t == "N/W" then shift = 3 + end + elseif t == "ALL" then + for i = 1, 64 do + ring[i] = math.random(10, 15) + end + elseif t == "FRINGES" then + for i = 1, 64 do + ring[i] = (i <= 8 or + (i > 16 and i <= 24) or + (i > 32 and i <= 40) or + (i > 48 and i <= 56)) and math.random(10, 15) or 0 + end + ring = fn.shift_table(ring, 11) + end + + + ring = fn.shift_table(ring, (shift * 16)) + -- adjust for "compass" + ring = fn.shift_table(ring, 57) + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + +function _arc:draw_glowing_clock(enc) + local ring = {} + for i = 1, 64 do ring[i] = 0 end + local l = self.slow_frame % 5 + if self.standby_up then l = util.linlin(1, 5, 5, 1, l) end + l = l + 10 + if enc.value == 0 then + for i = 1, 10 do ring[i] = l - i end + ring = fn.reverse_shift_table(ring, self.rotate_frame) + else + for i = 1, 10 do ring[11 - i] = l - i end + ring = fn.shift_table(ring, self.rotate_frame) + end + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + +function _arc:draw_glowing_topography(enc) + -- 1 = clockwise, 2 = counterclockwise, 3 = pendulum, 4 = drunk + local value = enc.value_getter() + local ring = {} + for i = 1, 64 do ring[i] = 0 end + local l = self.slow_frame % 5 + if self.standby_up then l = util.linlin(1, 5, 5, 1, l) end + l = l + 10 + if value == 1 then + for i = 1, 10 do ring[11 - i] = l - i end + ring = fn.shift_table(ring, self.rotate_frame) + elseif value == 2 then + for i = 1, 10 do ring[i] = l - i end + ring = fn.reverse_shift_table(ring, self.rotate_frame) + elseif value == 3 then + ring[21 - self.topography_frame] = 15 + for i = 1, 10 do ring[21 - self.topography_frame + i] = 15 - i end + ring[45 + self.topography_frame] = 15 + for i = 1, 10 do ring[45 + self.topography_frame - i] = 15 - i end + elseif value == 4 then + for i = 1, 64 do + ring[i] = math.random(1, 8) == 1 and math.random(l) or 0 + end + end + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + +function _arc:draw_standby(enc) + local ring = {} + for i = 1, 64 do ring[i] = 0 end + local l = self.slow_frame % 5 + if self.standby_up then l = util.linlin(1, 5, 5, 1, l) end + ring[1], ring[21], ring[42] = l, l, l + ring = fn.reverse_shift_table(ring, self.rotate_frame) + for k, v in pairs(ring) do + self:draw_led(enc.enc_id, k, v) + end +end + +-- guard against race conditions +function _arc:validate_segment(segment) + if segment ~= nil and segment.from ~= nil and segment.to ~= nil then + segment.valid = true + return segment + else + return { valid = false } + end +end + +function _arc:map_to_segment(enc) + local segment_size = 360 / enc.max_getter() + local test = util.linlin(enc.min_getter(), enc.max_getter(), 0, 360, enc.value) + if enc.value == 0 and enc.min_getter() == 0 then + return 0 + elseif test == 360 then -- compensate for circles, 0 == 360, etc. + return enc.max_getter() + else + local match = 1 + for i = 1, enc.max_getter() do + if (test >= segment_size * (i - 1)) and (test < segment_size * i) then + match = i + end + end + return match + end +end + + + +-- utilities + + + +function _arc:scale_to_radians(enc) + return self:degs_to_rads(self:scale_to_degrees(enc), false) +end + +function _arc:scale_to_degrees(enc) + return util.linlin(enc.min_getter(), enc.max_getter(), 0, 360, enc.value) +end + +function _arc:degs_to_rads(d, snap) + if snap then + d = self:snap_degrees_to_leds(d) + end + return d * (math.pi / 180) +end + +function _arc:rads_to_degs(r, snap) + local d = r * (180 / math.pi) + if snap then + d = self:snap_degrees_to_leds(d) + end + return d +end + +function _arc:set_structure_popup_active(bool) + self.structure_popup_active = bool +end + +-- to stop arc anti-aliasing +function _arc:snap_degrees_to_leds(d) + return util.linlin(0, 64, 0, 360, math.ceil(util.linlin(0, 360, 0, 64, d))) +end + +function _arc:cycle_degrees(d) + if d > 360 then + return self:cycle_degrees(d - 360) + elseif d < 0 then + return self:cycle_degrees(360 - d) + else + return d + end +end + + + +-- bindings + + + +-- initialize an encoder to a specific binding +function _arc:init_enc(args) + self.encs[args.enc_id] = { + binding_id = args.binding_id, + enc_id = args.enc_id, + extras = args.extras or {}, + key_getter = args.key_getter, + max_getter = args.max_getter, + min_getter = args.min_getter, + sensitivity = args.sensitivity, + style_getter = args.style_getter, + style_max_getter = args.style_max_getter, + style_offset = args.style_offset, + snap_getter = args.snap_getter, + takeover = false, + takeover_clock = nil, + value = args.value, + value_cache = args.value, + value_getter = args.value_getter, + value_setter = args.value_setter, + wrap_getter = args.wrap_getter, + } +end + +-- make available a binding +function _arc:register_binding(binding) + self.bindings[binding.binding_id] = binding +end + +-- configure each available binding +function _arc:register_all_available_bindings() + _arc:register_binding({ + binding_id = "norns_e1", + key_getter = function() return "norns_e1" end, + max_getter = function() return page:get_page_count() end, + min_getter = function() return 1 end, + offset_getter = function() return 240 end, + sensitivity_getter = function() return .01 end, + snap_getter = function() return true end, + style_getter = function() return "divided" end, + style_max_getter = function() return 240 end, + value_getter = function() return page.active_page end, + value_setter = function(x) if x ~= page.active_page then page:select(x) end end, + wrap_getter = function() return false end, + }) + _arc:register_binding({ + binding_id = "norns_e2", + key_getter = function() return "norns_e2" end, + max_getter = function() return menu:get_item_count() end, + min_getter = function() return menu:get_item_count_minimum() end, + offset_getter = function() return 240 end, + sensitivity_getter = function() return .02 end, + snap_getter = function() return true end, + style_getter = function() return (menu:get_item_count_minimum() == 0) and "standby" or "divided" end, + style_max_getter = function() return 240 end, + value_getter = function() return menu:get_selected_item() end, + value_setter = function(x) menu:select_item(x) end, + wrap_getter = function() return false end, + }) + _arc:register_binding({ + binding_id = "norns_e3", + extras = function() return menu:adaptor("extras") end, + key_getter = function() return menu:adaptor("key") end, + max_getter = function() return menu:adaptor("max") end, + min_getter = function() return menu:adaptor("min") end, + offset_getter = function() return menu:adaptor("offset") end, + sensitivity_getter = function() return menu:adaptor("sensitivity") end, + snap_getter = function() return menu:adaptor("snap_getter") end, + style_getter = function() return menu:adaptor("style_getter") end, + style_max_getter = function() return menu:adaptor("style_max_getter") end, + value_getter = function() return menu:adaptor("value_getter") end, + value_setter = function(x) menu:adaptor("value_setter", x) end, + wrap_getter = function() return menu:adaptor("wrap_getter") end, + }) + _arc:register_binding({ + binding_id = "bpm", + key_getter = function() return menu.arc_styles.BPM.key end, + max_getter = function() return menu.arc_styles.BPM.max end, + min_getter = function() return menu.arc_styles.BPM.min end, + offset_getter = function() return menu.arc_styles.BPM.offset end, + sensitivity_getter = function() return menu.arc_styles.BPM.sensitivity end, + snap_getter = function() return menu.arc_styles.BPM.snap end, + style_getter = menu.arc_styles.BPM.style_getter, + style_max_getter = menu.arc_styles.BPM.style_max_getter, + value_getter = menu.arc_styles.BPM.value_getter, + value_setter = menu.arc_styles.BPM.value_setter, + wrap_getter = function() return menu.arc_styles.BPM.wrap end, + }) + _arc:register_binding({ + binding_id = "transpose", + key_getter = function() return menu.arc_styles.TRANSPOSE.key end, + max_getter = function() return menu.arc_styles.TRANSPOSE.max end, + min_getter = function() return menu.arc_styles.TRANSPOSE.min end, + offset_getter = function() return menu.arc_styles.TRANSPOSE.offset end, + sensitivity_getter = function() return menu.arc_styles.TRANSPOSE.sensitivity end, + snap_getter = function() return menu.arc_styles.TRANSPOSE.snap end, + style_getter = menu.arc_styles.TRANSPOSE.style_getter, + style_max_getter = menu.arc_styles.TRANSPOSE.style_max_getter, + value_getter = menu.arc_styles.TRANSPOSE.value_getter, + value_setter = menu.arc_styles.TRANSPOSE.value_setter, + wrap_getter = function() return menu.arc_styles.TRANSPOSE.wrap end, + }) + _arc:register_binding({ + binding_id = "danger_zone_clock_sync", + key_getter = function() return menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.key end, + max_getter = function() return menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.max end, + min_getter = function() return menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.min end, + offset_getter = function() return menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.offset end, + sensitivity_getter = function() return menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.sensitivity end, + snap_getter = function() return menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.snap end, + style_getter = menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.style_getter, + style_max_getter = menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.style_max_getter, + value_getter = menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.value_getter, + value_setter = menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.value_setter, + wrap_getter = function() return menu.arc_styles.DANGER_ZONE_CLOCK_SYNC.wrap end, + }) + _arc:register_binding({ + binding_id = "crypt_directory", + key_getter = function() return "CRYPT DIRECTORY" end, + max_getter = function() return filesystem.crypts_names ~= nil and #filesystem.crypts_names or 1 end, + min_getter = function() return 1 end, + offset_getter = function() return 240 end, + sensitivity_getter = function() return .05 end, + snap_getter = function() return true end, + style_getter = function() return "glowing_divided" end, + style_max_getter = function() return 240 end, + value_getter = function() return filesystem:get_crypt_index() end, + value_setter = function(args) filesystem:set_crypt_from_arc(args) end, + wrap_getter = function() return false end, + }) + _arc:register_binding({ + binding_id = "browse_cells", + key_getter = function() return "BROWSE CELLS" end, + max_getter = function() return #keeper.cells > 0 and #keeper.cells or 0 end, + min_getter = function() return #keeper.cells > 0 and 1 or 0 end, + offset_getter = function() return 240 end, + sensitivity_getter = function() return .05 end, + snap_getter = function() return true end, + style_getter = function() return "glowing_divided" end, + style_max_getter = function() return 240 end, + value_getter = function() return keeper:get_selected_cell_index() end, + value_setter = function(args) keeper:set_selected_cell_index(args) end, + wrap_getter = function() return false end, + }) +end + +function _arc:bind(n, binding_id) + if init_done ~= true then return end -- the rest of arcologies needs to load before _arc.lua + self:init_enc({ + value = 0, + binding_id = binding_id, + enc_id = n, + extras = self.bindings[binding_id].extras, + key_getter = self.bindings[binding_id].key_getter, + max_getter = self.bindings[binding_id].max_getter, + min_getter = self.bindings[binding_id].min_getter, + sensitivity = self.bindings[binding_id].sensitivity_getter, + snap_getter = self.bindings[binding_id].snap_getter, + style_getter = self.bindings[binding_id].style_getter, + style_max_getter = self.bindings[binding_id].style_max_getter, + style_offset = self.bindings[binding_id].offset_getter, + value_getter = self.bindings[binding_id].value_getter, + value_setter = self.bindings[binding_id].value_setter, + wrap_getter = self.bindings[binding_id].wrap_getter, + }) + fn.dirty_arc(true) +end + +--[[ + if, for whatever reason, a user wants to bind the same value to multiple + encoders we need to manually update these as the takeovers will + prevent their values from being updated. + + todo - make sure this also works for norns_e3 situations (i.e. bpm is mapped to e4) +]] +function _arc:refresh_duplicate_bindings(enc) + local duplicates = {} + for n = 1, 4 do + duplicates[n] = self.encs[n].binding_id == enc.binding_id and n ~= enc.enc_id + for k, v in pairs(duplicates) do + if v then + self.encs[k].value = self.encs[n].value + end + end + end +end + + +return _arc \ No newline at end of file diff --git a/lib/_crow.lua b/lib/_crow.lua new file mode 100644 index 0000000..526d3ea --- /dev/null +++ b/lib/_crow.lua @@ -0,0 +1,23 @@ +_crow = {} + +function _crow.init() + crow.init() + crow.clear() + crow.reset() + crow.output[2].action = "pulse(.025, 5, 1)" + crow.output[4].action = "pulse(.025, 5, 1)" + crow.ii.pullup(true) + 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]].execute() +end + +return _crow \ No newline at end of file diff --git a/lib/g.lua b/lib/_grid.lua similarity index 67% rename from lib/g.lua rename to lib/_grid.lua index 9ede90f..5224dd3 100644 --- a/lib/g.lua +++ b/lib/_grid.lua @@ -1,66 +1,84 @@ -g = grid.connect() - -function g.init() - g.counter = {} - g.toggled = {} - g.signal_deaths = {} - g.signal_and_cell_collisions = {} - g.flickers = {} - g.first_in_x = nil - g.first_in_y = nil - g.paste_x = nil - g.paste_y = nil - g.is_copying = false - g.is_pasting = false - g.paste_counter = 15 - g.disconnect_dismissed = true - g.last_known_width = g.cols - g.last_known_height = g.rows - for x = 1, fn.grid_width() do - g.counter[x] = {} - for y = 1, fn.grid_height() do - g.counter[x][y] = nil +_grid = {} +_grid.device = grid.connect() + +function _grid.init() + _grid.counter = {} + _grid.toggled = {} + _grid.signal_deaths = {} + _grid.signal_and_cell_collisions = {} + _grid.flickers = {} + _grid.first_in_x = nil + _grid.first_in_y = nil + _grid.paste_x = nil + _grid.paste_y = nil + _grid.is_copying = false + _grid.is_pasting = false + _grid.paste_counter = 15 + _grid.disconnect_dismissed = true + _grid.last_known_width = _grid.device.cols + _grid.last_known_height = _grid.device.rows + for x = 1, _grid.last_known_width do + _grid.counter[x] = {} + for y = 1, _grid.last_known_height do + _grid.counter[x][y] = nil end end end -function grid.add() - g.init() - g.last_known_width = g.cols - g.last_known_height = g.rows - fn.dirty_grid(true) +-- grid redefined, _grid ~= grid + +function grid.key(x, y, z) + fn.break_splash(true) + if z == 1 then + _grid.counter[x][y] = clock.run(_grid.grid_long_press, g, x, y) + elseif z == 0 then -- otherwise, if a grid key is released... + if _grid.counter[x][y] then -- and the long press is still waiting... + clock.cancel(_grid.counter[x][y]) -- then cancel the long press clock, + _grid:short_press(x,y) -- and execute a short press instead. + end + -- release the copy + if _grid.first_in_x == x and _grid.first_in_y == y then + _grid.first_in_x = nil + _grid.first_in_y = nil + keeper.copied_cell = {} + _grid.is_copying = false + graphics:set_message("CLIPBOARD CLEARED") + end + end end function grid.remove() - g:alert_disconnect() + _grid:alert_disconnect() end -function g:alert_disconnect() - g.disconnect_dismissed = false +-- _grid proper + +function _grid:alert_disconnect() + self.disconnect_dismissed = false end -function g:dismiss_disconnect() - g.disconnect_dismissed = true +function _grid:dismiss_disconnect() + self.disconnect_dismissed = true end -function g.grid_redraw_clock() +function _grid.grid_redraw_clock() while true do clock.sleep(1 / 30) if fn.dirty_grid() == true then - g:grid_redraw() + _grid:grid_redraw() fn.dirty_grid(false) end if keeper.is_cell_selected - or #g.signal_deaths > 0 - or #g.flickers > 0 - or #g.signal_and_cell_collisions > 0 then + or #_grid.signal_deaths > 0 + or #_grid.flickers > 0 + or #_grid.signal_and_cell_collisions > 0 then fn.dirty_grid(true) end end end -function g:grid_redraw() - self:all(0) +function _grid:grid_redraw() + self.device:all(0) self:led_leylines() self:led_territories() self:led_cells() @@ -72,31 +90,10 @@ function g:grid_redraw() self:led_cell_ports() self:led_cell_analysis() self:led_paste_animation() - self:refresh() + self.device:refresh() end -function g.key(x, y, z) - fn.break_splash(true) - if z == 1 then - g.counter[x][y] = clock.run(g.grid_long_press, g, x, y) - elseif z == 0 then -- otherwise, if a grid key is released... - if g.counter[x][y] then -- and the long press is still waiting... - clock.cancel(g.counter[x][y]) -- then cancel the long press clock, - g:short_press(x,y) -- and execute a short press instead. - end - -- release the copy - if g.first_in_x == x and g.first_in_y == y then - g.first_in_x = nil - g.first_in_y = nil - keeper.copied_cell = {} - g.is_copying = false - graphics:set_message("CLIPBOARD CLEARED") - end - end -end - - -function g:short_press(x, y) +function _grid:short_press(x, y) if self.is_copying then local paste_over_cell = keeper:get_cell(fn.index(x, y)) if paste_over_cell then @@ -142,31 +139,31 @@ function g:short_press(x, y) fn.dirty_screen(true) end -function g:grid_long_press(x, y) +function _grid:grid_long_press(x, y) clock.sleep(.5) - if not self.is_copying then + if not _grid.is_copying then keeper:select_cell(x, y) graphics:top_message_cell_structure() - self.first_in_x = x - self.first_in_y = y + _grid.first_in_x = x + _grid.first_in_y = y keeper.copied_cell = fn.deep_copy(keeper.selected_cell) - self.is_copying = true + _grid.is_copying = true graphics:set_message("COPIED " .. keeper.selected_cell.structure_name) end - self.counter[x][y] = nil + _grid.counter[x][y] = nil fn.dirty_grid(true) end -function g:led_signals() +function _grid:led_signals() local level = page.active_page == 3 and menu.selected_item == 1 and 10 or 2 for k,v in pairs(keeper.signals) do if v.generation <= counters.music_generation then - self:led(v.x, v.y, level) + self.device:led(v.x, v.y, level) end end end -function g:register_signal_death_at(x, y) +function _grid:register_signal_death_at(x, y) local signal = {} signal.x = x signal.y = y @@ -175,18 +172,18 @@ function g:register_signal_death_at(x, y) table.insert(self.signal_deaths, signal) end -function g:led_signal_deaths() +function _grid:led_signal_deaths() for k,v in pairs(self.signal_deaths) do if v.level == 0 or v.generation + 2 < counters.music_generation then table.remove(self.signal_deaths, k) else - self:led(v.x, v.y, v.level) + self.device:led(v.x, v.y, v.level) v.level = v.level - 1 end end end -function g:register_flicker_at(x, y) +function _grid:register_flicker_at(x, y) local flicker = {} flicker.x = x flicker.y = y @@ -195,18 +192,18 @@ function g:register_flicker_at(x, y) table.insert(self.flickers, flicker) end -function g:led_flickers() +function _grid:led_flickers() for k, v in pairs(self.flickers) do if v.level == 0 or v.generation + 2 < counters.music_generation then table.remove(self.flickers, k) else - self:led(v.x, v.y, v.level) + self.device:led(v.x, v.y, v.level) v.level = v.level - 1 end end end -function g:register_collision_at(x, y) +function _grid:register_collision_at(x, y) local collision = {} collision.x = x collision.y = y @@ -215,25 +212,25 @@ function g:register_collision_at(x, y) table.insert(self.signal_and_cell_collisions, collision) end -function g:led_signal_and_cell_collision() +function _grid:led_signal_and_cell_collision() for k,v in pairs(self.signal_and_cell_collisions) do if v.level == 0 or v.generation + 2 < counters.music_generation then table.remove(self.signal_and_cell_collisions, k) else - self:led(v.x, v.y, v.level) + self.device:led(v.x, v.y, v.level) v.level = v.level - 1 end end end -function g:led_cells() +function _grid:led_cells() for k,v in pairs(keeper.cells) do - self:led(v.x, v.y, 5) + self.device:led(v.x, v.y, 5) end end --- note the led analysis is up above in a g:led_signals -function g:led_cell_analysis() +-- note the led analysis is up above in a _grid:led_signals +function _grid:led_cell_analysis() if page.active_page == 3 then for k,v in pairs(keeper.cells) do if v.structure_name == menu.selected_item_string then @@ -243,17 +240,17 @@ function g:led_cell_analysis() end end -function g:led_selected_cell() +function _grid:led_selected_cell() if keeper.is_cell_selected then self:highlight_cell(keeper.selected_cell) end end -function g:highlight_cell(cell) - self:led(cell.x, cell.y, util.clamp(counters.grid_frame() % 15, 5, 15)) +function _grid:highlight_cell(cell) + self.device:led(cell.x, cell.y, util.clamp(counters.grid_frame() % 15, 5, 15)) end -function g:led_paste_animation() +function _grid:led_paste_animation() if self.paste_counter == 0 then self.paste_counter = 15 self.is_pasting = false @@ -261,37 +258,37 @@ function g:led_paste_animation() self.paste_y = nil end if self.is_pasting then - self:led(self.paste_x, self.paste_y, self.paste_counter) + self.device:led(self.paste_x, self.paste_y, self.paste_counter) self.paste_counter = self.paste_counter - 1 end end -function g:led_cell_ports() +function _grid:led_cell_ports() if not keeper.is_cell_selected or self.is_copying then return end local x = keeper.selected_cell_x local y = keeper.selected_cell_y local high = util.clamp(counters.grid_frame() % 15, 10, 15) local low = 2 if fn.in_bounds(x, y - 1) then - self:led(x, y - 1, keeper.selected_cell:is_port_open("n") and high or low) + self.device:led(x, y - 1, keeper.selected_cell:is_port_open("n") and high or low) end if fn.in_bounds(x + 1, y) then - self:led(x + 1, y, keeper.selected_cell:is_port_open("e") and high or low) + self.device:led(x + 1, y, keeper.selected_cell:is_port_open("e") and high or low) end if fn.in_bounds(x, y + 1) then - self:led(x, y + 1, keeper.selected_cell:is_port_open("s") and high or low) + self.device:led(x, y + 1, keeper.selected_cell:is_port_open("s") and high or low) end if fn.in_bounds(x - 1 , y) then - self:led(x - 1, y, keeper.selected_cell:is_port_open("w") and high or low) + self.device:led(x - 1, y, keeper.selected_cell:is_port_open("w") and high or low) end end -function g:led_leylines() +function _grid:led_leylines() if not keeper.is_cell_selected then return end - if keeper.selected_cell:is_port_open("n") then g:draw_northern_leyline() end - if keeper.selected_cell:is_port_open("e") then g:draw_eastern_leyline() end - if keeper.selected_cell:is_port_open("s") then g:draw_southern_leyline() end - if keeper.selected_cell:is_port_open("w") then g:draw_western_leyline() end + if keeper.selected_cell:is_port_open("n") then self:draw_northern_leyline() end + if keeper.selected_cell:is_port_open("e") then self:draw_eastern_leyline() end + if keeper.selected_cell:is_port_open("s") then self:draw_southern_leyline() end + if keeper.selected_cell:is_port_open("w") then self:draw_western_leyline() end end @@ -300,7 +297,7 @@ end way this could be made a bit more maintable would be to introduce a get_column_neighbors and get_row_neighbors, and then compare < and > afterwards instead of all in one go. ]] -function g:draw_northern_leyline() +function _grid:draw_northern_leyline() local neighbors, destination = {}, {} for k, cell in pairs(keeper.cells) do if cell.x == keeper.selected_cell.x and cell.id ~= keeper.selected_cell.id and cell.y < keeper.selected_cell.y then @@ -309,7 +306,7 @@ function g:draw_northern_leyline() end destination["x"] = keeper.selected_cell.x destination["y"] = (#neighbors ~= 0) and fn.nearest_value(neighbors, keeper.selected_cell.y) + 1 or 1 - g:draw_leyline( + self:draw_leyline( keeper.selected_cell.x, keeper.selected_cell.y - 2, -- -1 for this cell & -1 for its open port destination.x, @@ -317,7 +314,7 @@ function g:draw_northern_leyline() ) end -function g:draw_eastern_leyline() +function _grid:draw_eastern_leyline() local neighbors, destination = {}, {} for k, cell in pairs(keeper.cells) do if cell.y == keeper.selected_cell.y and cell.id ~= keeper.selected_cell.id and cell.x > keeper.selected_cell.x then @@ -326,7 +323,7 @@ function g:draw_eastern_leyline() end destination["x"] = (#neighbors ~= 0) and fn.nearest_value(neighbors, keeper.selected_cell.x) - 1 or fn.grid_width() destination["y"] = keeper.selected_cell.y - g:draw_leyline( + self:draw_leyline( keeper.selected_cell.x + 2, -- +1 for this cell & +1 for its open port keeper.selected_cell.y, destination.x, @@ -334,7 +331,7 @@ function g:draw_eastern_leyline() ) end -function g:draw_southern_leyline() +function _grid:draw_southern_leyline() local neighbors, destination = {}, {} for k, cell in pairs(keeper.cells) do if cell.x == keeper.selected_cell.x and cell.id ~= keeper.selected_cell.id and cell.y > keeper.selected_cell.y then @@ -343,7 +340,7 @@ function g:draw_southern_leyline() end destination["x"] = keeper.selected_cell.x destination["y"] = (#neighbors ~= 0) and fn.nearest_value(neighbors, keeper.selected_cell.y) - 1 or fn.grid_height() - g:draw_leyline( + self:draw_leyline( keeper.selected_cell.x, keeper.selected_cell.y + 2, -- +1 for this cell & +1 for its open port destination.x, @@ -351,7 +348,7 @@ function g:draw_southern_leyline() ) end -function g:draw_western_leyline() +function _grid:draw_western_leyline() local neighbors, destination = {}, {} for k, cell in pairs(keeper.cells) do if cell.y == keeper.selected_cell.y and cell.id ~= keeper.selected_cell.id and cell.x < keeper.selected_cell.x then @@ -360,7 +357,7 @@ function g:draw_western_leyline() end destination["x"] = (#neighbors ~= 0) and fn.nearest_value(neighbors, keeper.selected_cell.x) + 1 or 1 destination["y"] = keeper.selected_cell.y - g:draw_leyline( + self:draw_leyline( keeper.selected_cell.x - 2, -- -1 for this cell & -1 for its open port keeper.selected_cell.y, destination.x, @@ -368,14 +365,14 @@ function g:draw_western_leyline() ) end -function g:draw_leyline(start_x, start_y, end_x, end_y) +function _grid:draw_leyline(start_x, start_y, end_x, end_y) if start_x == end_x then -- vertical for i = math.min(start_y, end_y), math.max(start_y, end_y) do - if fn.in_bounds(start_x, i) then self:led(start_x, i, 1) end + if fn.in_bounds(start_x, i) then self.device:led(start_x, i, 1) end end elseif start_y == end_y then -- horizontal for i = math.min(start_x, end_x), math.max(start_x, end_x) do - if fn.in_bounds(i, start_y) then self:led(i, start_y, 1) end + if fn.in_bounds(i, start_y) then self.device:led(i, start_y, 1) end end else print("Error: leylines must be perpendicular.") @@ -383,16 +380,16 @@ function g:draw_leyline(start_x, start_y, end_x, end_y) fn.dirty_grid(true) end -function g:led_territories() +function _grid:led_territories() if not keeper.is_cell_selected or not keeper.selected_cell:has("TERRITORY") then return end local c = keeper.selected_cell:get_territory_coordinates() for x = c.x1, c.x2 do for y = c.y1, c.y2 do local l = math.ceil(util.linlin(0, 15, 0, 3, counters.grid_frame() % 15)) - if fn.in_bounds(x, y) then self:led(x, y, l) end + if fn.in_bounds(x, y) then self.device:led(x, y, l) end end end fn.dirty_grid(true) end -return g \ No newline at end of file +return _grid \ No newline at end of file diff --git a/lib/midi.lua b/lib/_midi.lua similarity index 69% rename from lib/midi.lua rename to lib/_midi.lua index da96551..770b689 100644 --- a/lib/midi.lua +++ b/lib/_midi.lua @@ -1,15 +1,15 @@ -m = {} +_midi = {} -function m.init() - m.devices = {} - m.devices[1] = midi.connect(1) - m.devices[2] = midi.connect(2) - m.devices[3] = midi.connect(3) - m.devices[4] = midi.connect(4) - m.notes = {} +function _midi.init() + _midi.devices = {} + _midi.devices[1] = midi.connect(1) + _midi.devices[2] = midi.connect(2) + _midi.devices[3] = midi.connect(3) + _midi.devices[4] = midi.connect(4) + _midi.notes = {} end -function m:setup() +function _midi:setup() for k, note in pairs(self.notes) do note.duration = note.duration - 1 if note.duration <= 0 then @@ -19,14 +19,14 @@ function m:setup() end end -function m:play(note, velocity, channel, duration, device) +function _midi:play(note, velocity, channel, duration, device) local transposed_note = sound:transpose_note(note) self:register_note(transposed_note, velocity, channel, duration, device) self.devices[device]:note_off(transposed_note, velocity, channel) self.devices[device]:note_on(transposed_note, velocity, channel) end -function m:register_note(tranposed_note, velocity, channel, duration, device) +function _midi:register_note(tranposed_note, velocity, channel, duration, device) local new = { note = tranposed_note, velocity = velocity, @@ -45,14 +45,14 @@ function m:register_note(tranposed_note, velocity, channel, duration, device) table.insert(self.notes, new) end -function m:all_off() +function _midi:all_off() for note = 1, 127 do for channel = 1, 16 do for device = 1, 4 do - m.devices[device]:note_off(note, 0, channel) + _midi.devices[device]:note_off(note, 0, channel) end end end end -return m \ No newline at end of file +return _midi \ No newline at end of file diff --git a/lib/softcut.lua b/lib/_softcut.lua similarity index 59% rename from lib/softcut.lua rename to lib/_softcut.lua index 0cd5e37..64fb949 100644 --- a/lib/softcut.lua +++ b/lib/_softcut.lua @@ -1,8 +1,8 @@ -s = {} +_softcut = {} -- thanks @dndrks & @its_your_bedtime -function s.init() +function _softcut.init() softcut.reset() softcut.buffer_clear() audio.level_cut(1) @@ -33,51 +33,51 @@ function s.init() softcut.filter_bp(i, 0) softcut.filter_rq(i, 0) end - s.clip = {} + _softcut.clip = {} for i = 1, 6 do - s.clip[i] = {} - s.clip[i]["sample_length"] = 16 - s.clip[i]["max"] = nil - s.clip[i]["min"] = s:get_crypt_start(i) + _softcut.clip[i] = {} + _softcut.clip[i]["sample_length"] = 16 + _softcut.clip[i]["max"] = nil + _softcut.clip[i]["min"] = _softcut:get_crypt_start(i) end end -function s:get_crypt_start(index) +function _softcut:get_crypt_start(index) return 1 + ((index - 1) * 16) end -function s:crypt_table() +function _softcut:crypt_table() for i = 1, 6 do - s.clip[i].min = s:get_crypt_start(i) - s.clip[i].max = s.clip[i].min + s.clip[i].sample_length + self.clip[i].min = self:get_crypt_start(i) + self.clip[i].max = self.clip[i].min + self.clip[i].sample_length end end -function s:crypt_load(index) +function _softcut:crypt_load(index) local file = filesystem:get_crypt() .. index .. ".wav" local ch, len = audio.file_info(file) if (len / 48000) < 16 then - s.clip[index].sample_length = len / 48000 + self.clip[index].sample_length = len / 48000 else - s.clip[index].sample_length = 16 + self.clip[index].sample_length = 16 end - softcut.buffer_clear_region_channel(2, s:get_crypt_start(index), 16) - softcut.buffer_read_mono(file, 0, s:get_crypt_start(index), s.clip[index].sample_length + 0.05, 1, 2) - s:crypt_table() + softcut.buffer_clear_region_channel(2, self:get_crypt_start(index), 16) + softcut.buffer_read_mono(file, 0, self:get_crypt_start(index), self.clip[index].sample_length + 0.05, 1, 2) + self:crypt_table() end -function s:one_shot(index, level) +function _softcut:one_shot(index, level) local voice = index -- voices are hard-coupled for now if tonumber(index) then -- some edge cases were happening where nil indexes were coming in softcut.buffer(voice, 2) softcut.play(voice, 0) softcut.level(voice, level) - softcut.position(voice, s.clip[index].min) - softcut.loop_start(voice, s.clip[index].min) - softcut.loop_end(voice, s.clip[index].max) + softcut.position(voice, self.clip[index].min) + softcut.loop_start(voice, self.clip[index].min) + softcut.loop_end(voice, self.clip[index].max) softcut.loop(voice, 0) softcut.play(voice, 1) end end -return s \ No newline at end of file +return _softcut \ No newline at end of file diff --git a/lib/api.lua b/lib/api.lua new file mode 100644 index 0000000..143ef8e --- /dev/null +++ b/lib/api.lua @@ -0,0 +1,15 @@ +api = {} + +function api.init() + +end + +function api:get_menu_value_as_percentage() + +end + +function api:set_menu_value_as_percentage(x) + +end + +return api \ No newline at end of file diff --git a/lib/config.lua b/lib/config.lua index c0a4f28..c2950df 100644 --- a/lib/config.lua +++ b/lib/config.lua @@ -2,8 +2,8 @@ config = {} config["settings"] = { ["version_major"] = 1, - ["version_minor"] = 1, - ["version_patch"] = 15, + ["version_minor"] = 2, + ["version_patch"] = 0, ["playback"] = 0, ["length"] = 16, ["root"] = 0, @@ -53,4 +53,15 @@ config["popup_messages"] = { } } +config["arc_bindings"] = { + { id = "norns_e1", label = "NORNS E1" }, + { id = "norns_e2", label = "NORNS E2" }, + { id = "norns_e3", label = "NORNS E3" }, + { id = "browse_cells", label = "BROWSE CELLS" }, + { id = "crypt_directory", label = "CRYPT DIRECTORY" }, + { id = "danger_zone_clock_sync", label = "clock.sync(x)" }, + { id = "transpose", label = "TRANSPOSE" }, + { id = "bpm", label = "BPM" }, +} + return config \ No newline at end of file diff --git a/lib/counters.lua b/lib/counters.lua index 954a04a..b7123aa 100644 --- a/lib/counters.lua +++ b/lib/counters.lua @@ -27,7 +27,7 @@ function counters.conductor() clock.sync(parameters.danger_zone_clock_sync_value or 1) if counters.playback == 1 then counters.music_generation = counters.music_generation + 1 - m:setup() + _midi:setup() keeper:setup() keeper:spawn_signals() keeper:propagate_signals() @@ -45,6 +45,10 @@ function counters:set_playback(i) self.playback = util.clamp(i, 0, 1) end +function counters:get_playback() + return self.playback +end + function clock.transport.start() counters:start() end @@ -60,7 +64,7 @@ end function counters:stop() self:set_playback(0) - m:all_off() + _midi:all_off() graphics:set_message("PAUSED", self.default_message_length) end @@ -79,7 +83,10 @@ function counters.redraw_clock() fn.dirty_screen(false) end if fn.dirty_grid() then - g:grid_redraw() + _grid:grid_redraw() + end + if fn.dirty_arc() then + _arc:arc_redraw() end clock.sleep(1 / 30) end @@ -101,7 +108,7 @@ function counters.optician() if counters.ui.frame % 4 == 0 then counters.ui.quarter_frame = counters.ui.quarter_frame +1 end - if not g.disconnect_dismissed then page:set_error(1) else page:clear_error() end + if not _grid.disconnect_dismissed then page:set_error(1) else page:clear_error() end fn.dirty_screen(true) redraw() end diff --git a/lib/crow.lua b/lib/crow.lua index 557a90b..526d3ea 100644 --- a/lib/crow.lua +++ b/lib/crow.lua @@ -1,6 +1,6 @@ -c = {} +_crow = {} -function c.init() +function _crow.init() crow.init() crow.clear() crow.reset() @@ -10,14 +10,14 @@ function c.init() crow.ii.jf.mode(1) end -function c:jf(note) +function _crow:jf(note) crow.ii.jf.play_note((sound:snap_note(sound:transpose_note(note)) - 60) / 12, 5) end -function c:play(note, pair) +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]].execute() end -return c \ No newline at end of file +return _crow \ No newline at end of file diff --git a/lib/filesystem.lua b/lib/filesystem.lua index 9b5fa81..fd7a7c4 100644 --- a/lib/filesystem.lua +++ b/lib/filesystem.lua @@ -80,6 +80,21 @@ function filesystem:get_crypt() return self.current end +function filesystem:get_crypt_index() + local index = false + for k, v in pairs(self.crypts_names) do + if v == self.crypt_name then + index = k + end + end + return index +end + +function filesystem:set_crypt_from_arc(index) + params:set("crypts_directory", index) + filesystem:set_crypt(index) +end + function filesystem.save_map(text) if text then print("saving map...") diff --git a/lib/functions.lua b/lib/functions.lua index befac83..fdf299d 100644 --- a/lib/functions.lua +++ b/lib/functions.lua @@ -82,7 +82,7 @@ end function fn.cleanup() crow.ii.jf.mode(0) - m:all_off() + _midi:all_off() clock.cancel(music_clock_id) clock.cancel(redraw_clock_id) clock.cancel(grid_clock_id) @@ -107,6 +107,18 @@ function fn.dirty_grid(bool) return grid_dirty end +function fn.dirty_arc(bool) + if bool == nil then return arc_dirty end + arc_dirty = bool + return arc_dirty +end + +function fn.dirty_arc_values(bool) + if bool == nil then return arc_values_dirty end + arc_values_dirty = bool + return arc_values_dirty +end + function fn.dirty_screen(bool) if bool == nil then return screen_dirty end screen_dirty = bool @@ -121,7 +133,7 @@ end function fn.dismiss_messages() fn.break_splash(true) - g:dismiss_disconnect() + _grid:dismiss_disconnect() end function fn.long_press(k) @@ -145,11 +157,11 @@ end -- grid function fn.grid_width() - return g.last_known_width + return _grid.last_known_width end function fn.grid_height() - return g.last_known_height + return _grid.last_known_height end function fn.index(x, y) @@ -233,7 +245,7 @@ end function fn.cycle(value, min, max) if value > max then return min - elseif value < 1 then + elseif value < min then return max else return value @@ -243,7 +255,7 @@ end function fn.over_cycle(value, min, max) if value > max then return fn.over_cycle(value - max, min, max) - elseif value < 1 then + elseif value < min then return fn.over_cycle(max - value, min, max) else return value @@ -261,7 +273,7 @@ function fn.wrap(t, l) end function fn.table_find(t, element) - for i,v in pairs(t) do + for i, v in pairs(t) do if v == element then return i end @@ -269,6 +281,15 @@ function fn.table_find(t, element) return false end +function fn.key_find(t, element) + for k, v in pairs(t) do + if k == element then + return true + end + end + return false +end + function fn.table_has(tab, val) for index, value in ipairs(tab) do if value == val then @@ -293,6 +314,26 @@ function fn.deep_copy(orig) return copy end +function fn.shift_table(t, shift_amount) + if shift_amount == 0 then return t end + for i = 1, shift_amount do + local last_value = t[#t] + table.insert(t, 1, last_value) + table.remove(t, #t) + end + return t +end + +function fn.reverse_shift_table(t, shift_amount) + if shift_amount == 0 then return t end + for i = 1, shift_amount do + local first_value = t[1] + table.remove(t, 1) + table.insert(t, #t + 1, first_value) + end + return t +end + -- dev function rerun() @@ -306,7 +347,7 @@ end function fn.wtfscale() for i = 1, #sound.scale_notes do - print(sound.scale_notes[i], mu.note_num_to_name(sound.scale_notes[i])) + print(sound.scale_notes[i], musicutil.note_num_to_name(sound.scale_notes[i])) end end diff --git a/lib/glyphs.lua b/lib/glyphs.lua index 970ecfc..697f5de 100644 --- a/lib/glyphs.lua +++ b/lib/glyphs.lua @@ -13,7 +13,7 @@ function glyphs:draw_glyph(s, x, y, l) if self[string.lower(s)] ~= nil then assert(load("glyphs:" .. string.lower(s) .. "(...)"))(x, y, l) else - glyphs:cell(x, y, l) + glyphs:cell(x, y, l) end end @@ -21,7 +21,7 @@ function glyphs:draw_small_glyph(s, x, y, l) if self["small_" .. string.lower(s)] ~= nil then assert(load("glyphs:" .. "small_" .. string.lower(s) .. "(...)"))(x, y, l) else - glyphs:small_cell(x, y, l) + glyphs:small_cell(x, y, l) end end @@ -49,9 +49,8 @@ function glyphs:test() local y = 20 local l = 15 self:bounding_box(x, y, l) - self:prairie(x, y, l) - self:small_prairie(x+60, y, l) - -- self:small_casino(x+69, y, l) + self:cloakroom(x, y, l) + self:small_cloakroom(x+60, y, l) end function glyphs:bounding_box(x, y, l) @@ -315,6 +314,19 @@ function glyphs:prairie(x, y, l) graphics:rect(x+10, y+26, 2, 2, l) end +function glyphs:cloakroom(x, y, l) + self:roof(x, y, l) + graphics:rect(x, y, 2, 20, l) + self:three_quarter_right_wall(x, y, l) + self:basement(x, y, l) + graphics:rect(x, y+6, 9, 2, l) + graphics:rect(x+13, y+6, 9, 2, l) + graphics:rect(x, y+12, 9, 2, l) + graphics:rect(x+13, y+12, 9, 2, l) + graphics:rect(x, y+18, 9, 2, l) + graphics:rect(x+13, y+18, 9, 2, l) +end + function glyphs:left_wall(x, y, l) graphics:rect(x, y, 2, 26, l) end @@ -697,6 +709,20 @@ function glyphs:small_prairie(x, y, l) graphics:rect(x+2, y+8, 1, 1, l) end + +function glyphs:small_cloakroom(x, y, l) + self:small_roof(x, y, l) + graphics:mlrs(x, y, 0, 5, l) + self:small_three_quarter_right_wall(x, y, l) + self:small_basement(x, y, l) + graphics:mlrs(x-1, y+2, 3, 0, l) + graphics:mlrs(x+3, y+2, 3, 0, l) + graphics:mlrs(x-1, y+4, 3, 0, l) + graphics:mlrs(x+3, y+4, 3, 0, l) + graphics:mlrs(x-1, y+6, 3, 0, l) + graphics:mlrs(x+3, y+6, 3, 0, l) +end + function glyphs:small_left_wall(x, y, l) graphics:mls(x, y-1, x, y+8, l) end diff --git a/lib/includes.lua b/lib/includes.lua index 3928377..34557dd 100644 --- a/lib/includes.lua +++ b/lib/includes.lua @@ -1,26 +1,28 @@ -- ships with norns crow = require("crow") er = require("er") -fs = require("fileselect") -mu = require("musicutil") -te = require("textentry") -tu = require("tabutil") +fileselect = require("fileselect") +musicutil = require("musicutil") +textentry = require("textentry") +tabutil = require("tabutil") engine.name = "PolyPerc" +local lib = "arcologies/lib/" + -- stores application configuration and cell composition data -config = include("arcologies/lib/config") -config_ = io.open(_path["code"] .. "arcologies/lib/config_.lua", "r") +config = include(lib .. "config") +config_ = io.open(_path["code"] .. lib .. "config_.lua", "r") if config_ ~= nil then io.close(config_) - include("arcologies/lib/config_") + include(lib .. "config_") end -- defines cell structures -include("arcologies/lib/structures") +include(lib .. "structures") -- the core concept of arcologies, interact with Signals -include("arcologies/lib/Cell") +include(lib .. "Cell") --[[ attributes of cells. adding more minimally requires: @@ -30,67 +32,76 @@ attributes of cells. adding more minimally requires: - probably some logic in keeper:collision() - saveload.lua ]] -include("arcologies/lib/mixins/bearing_mixin") -include("arcologies/lib/mixins/capacity_mixin") -include("arcologies/lib/mixins/channel_mixin") -include("arcologies/lib/mixins/charge_mixin") -include("arcologies/lib/mixins/clockwise_mixin") -include("arcologies/lib/mixins/crow_out_mixin") -include("arcologies/lib/mixins/crumble_mixin") -include("arcologies/lib/mixins/deflect_mixin") -include("arcologies/lib/mixins/device_mixin") -include("arcologies/lib/mixins/docs_stub_mixin") -include("arcologies/lib/mixins/drift_mixin") -include("arcologies/lib/mixins/duration_mixin") -include("arcologies/lib/mixins/er_mixin") -include("arcologies/lib/mixins/level_mixin") -include("arcologies/lib/mixins/metabolism_mixin") -include("arcologies/lib/mixins/network_mixin") -include("arcologies/lib/mixins/notes_mixin") -include("arcologies/lib/mixins/offset_mixin") -include("arcologies/lib/mixins/operator_mixin") -include("arcologies/lib/mixins/output_mixin") -include("arcologies/lib/mixins/ports_mixin") -include("arcologies/lib/mixins/probability_mixin") -include("arcologies/lib/mixins/pulses_mixin") -include("arcologies/lib/mixins/range_mixin") -include("arcologies/lib/mixins/resilience_mixin") -include("arcologies/lib/mixins/state_index_mixin") -include("arcologies/lib/mixins/structure_stub_mixin") -include("arcologies/lib/mixins/territory_mixin") -include("arcologies/lib/mixins/topography_mixin") -include("arcologies/lib/mixins/turing_mixin") -include("arcologies/lib/mixins/velocity_mixin") +local mixins = { + "bearing_mixin", + "capacity_mixin", + "channel_mixin", + "charge_mixin", + "clockwise_mixin", + "crow_out_mixin", + "crumble_mixin", + "deflect_mixin", + "device_mixin", + "docs_stub_mixin", + "drift_mixin", + "duration_mixin", + "er_mixin", + "level_mixin", + "mapping_mixin", + "metabolism_mixin", + "network_mixin", + "notes_mixin", + "offset_mixin", + "operator_mixin", + "output_mixin", + "ports_mixin", + "probability_mixin", + "pulses_mixin", + "range_mixin", + "resilience_mixin", + "state_index_mixin", + "structure_mixin", + "territory_mixin", + "topography_mixin", + "turing_mixin", + "velocity_mixin" +} +for k, v in pairs(mixins) do + include(lib .. "mixins/" .. v) +end -- emitted by Cells, "bangs" that move n, e, s, w -include("arcologies/lib/Signal") +include(lib .. "Signal") -- global functions -fn = include("arcologies/lib/functions") +fn = include(lib .. "functions") -- all the save and load routines -saveload = include("arcologies/lib/saveload") +saveload = include(lib .. "saveload") + +-- arc interactions and leds +_arc = include(lib .. "_arc") -- the whole murder of them -c = include("arcologies/lib/crow") +_crow = include(lib .. "_crow") -- clocks, metros, timing -counters = include("arcologies/lib/counters") +counters = include(lib .. "counters") -- in app documentation -docs = include("arcologies/lib/docs") +docs = include(lib .. "docs") -- read & write on norns -filesystem = include("arcologies/lib/filesystem") +filesystem = include(lib .. "filesystem") -- grid interactions and leds -g = include("arcologies/lib/g") +_grid = include(lib .. "_grid") -- structure glyph drawings -glyphs = include("arcologies/lib/glyphs") +glyphs = include(lib .. "glyphs") -- all norns screen rendering -graphics = include("arcologies/lib/graphics") +graphics = include(lib .. "graphics") --[[ "keeper" is state machine for Cells and Signals @@ -106,32 +117,35 @@ furthermore, Signals know nothing about Cells. Cells know nothing about Signals. keeper:collide() is the great atom smasher. ]] -keeper = include("arcologies/lib/keeper") +keeper = include(lib .. "keeper") -- build the side menus for norns pages -menu = include("arcologies/lib/menu") +menu = include(lib .. "menu") -- midi interface -m = include("arcologies/lib/midi") +_midi = include(lib .. "_midi") -- controller for norns pages -page = include("arcologies/lib/page") +page = include(lib .. "page") -- exposed norns parameters -parameters = include("arcologies/lib/parameters") +parameters = include(lib .. "parameters") -- popup menu for selecting complex values -popup = include("arcologies/lib/popup") +popup = include(lib .. "popup") -- softcut -s = include("arcologies/lib/softcut") +_softcut = include(lib .. "_softcut") -- all things musical -sound = include("arcologies/lib/sound") +sound = include(lib .. "sound") + +-- experimental +api = include(lib .. "api") -- dev only stuff -dev = io.open(_path["code"] .. "arcologies/lib/dev.lua", "r") +dev = io.open(_path["code"] .. lib .. "dev.lua", "r") if dev ~= nil then io.close(dev) - include("arcologies/lib/dev") + include(lib .. "dev") end diff --git a/lib/keeper.lua b/lib/keeper.lua index d2b796c..7891095 100644 --- a/lib/keeper.lua +++ b/lib/keeper.lua @@ -23,7 +23,7 @@ function keeper:collision(signal, cell) -- all collisions result in signal deaths self:register_delete_signal(signal.id) - g:register_signal_death_at(cell.x, cell.y) + _grid:register_signal_death_at(cell.x, cell.y) -- bang a closed port and gates redirect invert ports if cell:is("GATE") and not self:are_signal_and_port_compatible(signal, cell) then @@ -39,7 +39,7 @@ function keeper:collision(signal, cell) -- crypts play samples elseif cell:is("CRYPT") then - s:one_shot(cell.state_index, cell.level / 100) + _softcut:one_shot(cell.state_index, cell.level / 100) -- shrines play single notes via sc elseif cell:is("SHRINE") then @@ -47,11 +47,11 @@ function keeper:collision(signal, cell) -- uxbs play single notes via midi elseif cell:is("UXB") then - m:play(cell.notes[1], cell.velocity, cell.channel, cell.duration, cell.device) + _midi:play(cell.notes[1], cell.velocity, cell.channel, cell.duration, cell.device) -- aviaries play single notes via crow elseif cell:is("AVIARY") then - c:play(cell.notes[1], cell.crow_out) + _crow:play(cell.notes[1], cell.crow_out) -- stores signals as charge elseif cell:is("SOLARIUM") then @@ -69,12 +69,12 @@ function keeper:collision(signal, cell) -- topiaries cylce through notes elseif cell:is("CASINO") then cell:over_cycle_state_index(cell:topography_operation()) - m:play(cell.notes[cell.state_index], cell.velocity, cell.channel, cell.duration, cell.device) + _midi:play(cell.notes[cell.state_index], cell.velocity, cell.channel, cell.duration, cell.device) -- forests cylce through notes elseif cell:is("FOREST") then cell:over_cycle_state_index(cell:topography_operation()) - c:play(cell.notes[cell.state_index], cell.crow_out) + _crow:play(cell.notes[cell.state_index], cell.crow_out) -- send signals to other tunnels elseif cell:is("TUNNEL") then @@ -86,7 +86,7 @@ function keeper:collision(signal, cell) if cell:get_output_string() == "SYNTH" then sound:play(random_note, cell.velocity) elseif cell:get_output_string() == "MIDI" then - m:play(random_note, cell.velocity, cell.channel, cell.duration, cell.device) + _midi:play(random_note, cell.velocity, cell.channel, cell.duration, cell.device) end -- fractures play random velocities @@ -95,17 +95,17 @@ function keeper:collision(signal, cell) if cell:get_output_string() == "SYNTH" then sound:play(cell.notes[1], random_velocity) elseif cell:get_output_string() == "MIDI" then - m:play(cell.notes[1], random_velocity, cell.channel, cell.duration, cell.device) + _midi:play(cell.notes[1], random_velocity, cell.channel, cell.duration, cell.device) end -- spomeniks play single notes on jf elseif cell:is("SPOMENIK") then - c:jf(cell.notes[1]) + _crow:jf(cell.notes[1]) -- autons cycle through notes on jf elseif cell:is("AUTON") then cell:over_cycle_state_index(cell:topography_operation()) - c:jf(cell.notes[cell.state_index]) + _crow:jf(cell.notes[cell.state_index]) -- hydroponics operate at a distance elseif cell:is("HYDROPONICS") then @@ -262,7 +262,7 @@ function keeper:cropdust() if cell:is("KUDZU") then table.insert(kudzu, cell) end end for k, cell in pairs(kudzu) do - g:register_flicker_at(cell.x, cell.y) + _grid:register_flicker_at(cell.x, cell.y) cell:raw_set_crumble(cell:get_crumble() - params:get("kudzu_cropdust_potency")) cell:has_crumbled() end @@ -304,7 +304,7 @@ function keeper:collide_signals() and fn.in_bounds(signal_from_set_b.x, signal_from_set_b.y) then self:register_delete_signal(signal_from_set_a.id) self:register_delete_signal(signal_from_set_b.id) - g:register_signal_death_at(signal_from_set_a.x, signal_from_set_a.y) + _grid:register_signal_death_at(signal_from_set_a.x, signal_from_set_a.y) end end end @@ -434,8 +434,12 @@ function keeper:deselect_cell() self.selected_cell_id = "" self.selected_cell_x = "" self.selected_cell_y = "" + if page.titles[page.active_page] == "DESIGNER" then + menu:reset() + end fn.dirty_grid(true) fn.dirty_screen(true) + fn.dirty_arc(true) end function keeper:count_cells(name) @@ -467,7 +471,8 @@ end function keeper:update_all_notes() for k,cell in pairs(self.cells) do if cell:has("NOTES") then - for i=1, #cell.notes do + cell:update_note_max(#sound:get_scale_notes()) + for i = 1, #cell.notes do -- delta of zero just jiggles the handle cell:browse_notes(0, i) end @@ -477,7 +482,7 @@ end -- happens when a new crypt directory is selected function keeper:update_all_crypts() - s:crypt_table() + _softcut:crypt_table() for k,cell in pairs(self.cells) do if cell:is("CRYPT") then cell:cycle_state_index(0, i) @@ -485,4 +490,26 @@ function keeper:update_all_crypts() end end +function keeper:get_selected_cell_index() + local index = 0 + for k, cell in pairs(self.cells) do + index = index + 1 + if cell.id == self.selected_cell_id then + return index + end + end + return index +end + +function keeper:set_selected_cell_index(index) + local cell = {} + local cell_index = 0 + for k, cell in pairs(self.cells) do + cell_index = cell_index + 1 + if cell_index == index then + self:select_cell(cell.x, cell.y) + end + end +end + return keeper \ No newline at end of file diff --git a/lib/menu.lua b/lib/menu.lua index 288d360..37f6f50 100644 --- a/lib/menu.lua +++ b/lib/menu.lua @@ -2,18 +2,29 @@ menu = {} function menu.init() menu.threshold = 6 + menu.arc_styles = {} + menu:register_arc_styles() menu:reset() end function menu:reset() self.show_all = true self.items = {} - self.selected_item = 1 + self.item_count = 0 + self.selected_item = self:get_item_count_minimum() self.selected_item_string = "" self.offset = 0 docs:set_active(false) end +function menu:get_item_count_minimum() + if page:get_page_title() == "DESIGNER" and not keeper.is_cell_selected then + return 0 + else + return 1 + end +end + function menu:render(bool) local render_values = (bool == nil) and true or bool -- rectangular highlight @@ -30,7 +41,7 @@ function menu:render(bool) graphics:mls(2, offset - 2, 40, offset - 2, 15) end -- all values come from the mixins - if page.active_page == 2 and render_values then + if page:get_page_title() == "DESIGNER" and render_values then graphics:text(56, offset, keeper.selected_cell:get_menu_value_by_attribute(item)(keeper.selected_cell), 0) end end @@ -56,29 +67,29 @@ end function menu:scroll_value(d) local s = self.selected_item_string -- home - if page.active_page == 1 then + if page.active_page == 1 then -- note the name of page 1 can change if s == fn.playback() then counters:set_playback(d) - elseif s == "BPM" then self:handle_scroll_bpm(d) + elseif s == "BPM" then self:handle_scroll_bpm(d, "delta") elseif s == "LENGTH" then sound:cycle_length(d) elseif s == "ROOT" then sound:cycle_root(d) - elseif s == "SCALE" then sound:set_scale(sound.scale + d) + elseif s == "SCALE" then sound:cycle_scale(d) elseif s == "TRANSPOSE" then sound:cycle_transpose(d) end -- cell designer - elseif page.active_page == 2 then + elseif page:get_page_title() == "DESIGNER" then keeper.selected_cell:set_attribute_value(s, d) -- analysis - elseif page.active_page == 3 then + elseif page:get_page_title() == "ANALYSIS" then -- nothing to change here end end function menu:scroll(d) - if page.active_page == 3 then keeper:deselect_cell() end self:select_item(util.clamp(self.selected_item + d, 1, #self.items)) end function menu:select_item(i) + if page.active_page == 3 then keeper:deselect_cell() end self.selected_item = i == nil and self.selected_item or i self.selected_item_string = self.items[self.selected_item] self.offset = self.selected_item > self.threshold and self.selected_item - self.threshold or 0 @@ -91,14 +102,193 @@ end function menu:set_items(items) self.items = items + self.item_count = #items +end + +function menu:get_item_count() + return self.item_count +end + +function menu:get_selected_item() + return self.selected_item +end + +function menu:get_selected_item_string() + return self.selected_item_string end -function menu:handle_scroll_bpm(d) +function menu:handle_scroll_bpm(value, mode) if fn.is_clock_internal() then - params:set("clock_tempo", params:get("clock_tempo") + d) + if mode == "delta" then + params:set("clock_tempo", params:get("clock_tempo") + value) + elseif mode == "absolute" then + params:set("clock_tempo", value) + end else graphics:set_message("EXTERNAL CONTROL ON", counters.default_message_length) end end +function menu:adaptor(lookup, args) + local s = self:get_selected_item_string() + if keeper.is_cell_selected and page:get_page_title() == "DESIGNER" then + local c = keeper.selected_cell + local match = fn.key_find(c.arc_styles, s) + if match then + if lookup == "key" then return c.arc_styles[s].key + elseif lookup == "extras" then return c.arc_styles[s].extras or {} + elseif lookup == "max" then return c.arc_styles[s].max + elseif lookup == "min" then return c.arc_styles[s].min + elseif lookup == "offset" then return c.arc_styles[s].offset + elseif lookup == "sensitivity" then return c.arc_styles[s].sensitivity + elseif lookup == "snap_getter" then return c.arc_styles[s].snap + elseif lookup == "style_getter" then return c.arc_styles[s].style_getter() + elseif lookup == "style_max_getter" then return c.arc_styles[s].style_max_getter() + elseif lookup == "value_getter" then return c.arc_styles[s].value_getter(c) + elseif lookup == "value_setter" then return c.arc_styles[s].value_setter(c, args) + elseif lookup == "wrap_getter" then return c.arc_styles[s].wrap + else print("Error: no cell arc style found for \"" .. lookup .. "\".") + end + end + else + local match = fn.key_find(self.arc_styles, s) + if match then + if lookup == "key" then return self.arc_styles[s].key + elseif lookup == "max" then return self.arc_styles[s].max + elseif lookup == "extras" then return {} -- stub for symmetry + elseif lookup == "min" then return self.arc_styles[s].min + elseif lookup == "offset" then return self.arc_styles[s].offset + elseif lookup == "sensitivity" then return self.arc_styles[s].sensitivity + elseif lookup == "snap_getter" then return self.arc_styles[s].snap + elseif lookup == "style_getter" then return self.arc_styles[s].style_getter() + elseif lookup == "style_max_getter" then return self.arc_styles[s].style_max_getter() + elseif lookup == "value_getter" then return self.arc_styles[s].value_getter() + elseif lookup == "value_setter" then return self.arc_styles[s].value_setter(args) + elseif lookup == "wrap_getter" then return self.arc_styles[s].wrap + else print("Error: no menu arc style found for \"" .. lookup .. "\".") + end + end + end +end + +function menu:register_arc_styles() + self.arc_styles["READY"] = { + key = "READY", + max = 1, + min = 0, + offset = 0, + sensitivity = .5, + snap = true, + style_getter = function() return "glowing_boolean" end, + style_max_getter = function() return 360 end, + value_getter = function() return counters:get_playback() end, + value_setter = function(args) return counters:set_playback(args) end, + wrap = false, + } + self.arc_styles["PLAYING"] = { + key = "PLAYING", + max = self.arc_styles["READY"].max, + min = self.arc_styles["READY"].min, + offset = self.arc_styles["READY"].offset, + sensitivity = self.arc_styles["READY"].sensitivity, + snap = self.arc_styles["READY"].snap, + style_getter = self.arc_styles["READY"].style_getter, + style_max_getter = self.arc_styles["READY"].style_max_getter, + value_getter = self.arc_styles["READY"].value_getter, + value_setter = self.arc_styles["READY"].value_setter, + wrap = self.arc_styles["READY"].wrap, + } + self.arc_styles["BPM"] = { + key = "BPM", + max = 300, + min = 1, + offset = 240, + sensitivity = .5, + snap = false, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + value_getter = function() return params:get("clock_tempo") end, + value_setter = function(args) menu:handle_scroll_bpm(args, "absolute") end, + wrap = false, + } + self.arc_styles["LENGTH"] = { + key = "LENGTH", + max = 16, + min = 1, + offset = 180, + sensitivity = .05, + snap = false, + style_getter = function() return "sweet_sixteen" end, + style_max_getter = function() return 360 end, + value_getter = function() return sound:get_length() end, + value_setter = function(args) sound:set_length(args) end, + wrap = false, + } + self.arc_styles["ROOT"] = { + key = "ROOT", + max = 12, + min = 1, + offset = 0, + sensitivity = .05, + snap = false, + style_getter = function() return "glowing_endless" end, + style_max_getter = function() return 360 end, + value_getter = function() return sound:get_root() end, + value_setter = function(args) sound:set_root(args) end, + wrap = true, + } + self.arc_styles["SCALE"] = { + key = "SCALE", + max = #sound.scale_names, + min = 1, + offset = 0, + sensitivity = .05, + snap = false, + style_getter = function() return "glowing_endless" end, + style_max_getter = function() return 360 end, + value_getter = function() return sound:get_scale() end, + value_setter = function(args) sound:set_scale(args) end, + wrap = true, + } + self.arc_styles["TRANSPOSE"] = { + key = "TRANSPOSE", + max = 6, + min = -6, + offset = 0, + sensitivity = .05, + snap = false, + style_getter = function() return "glowing_fulcrum" end, + style_max_getter = function() return 240 end, + value_getter = function() return sound:get_transpose() end, + value_setter = function(args) sound:set_transpose(args) end, + wrap = false, + } + self.arc_styles["DANGER_ZONE_CLOCK_SYNC"] = { + key = "DANGER_ZONE_CLOCK_SYNC", + max = 16, + min = 1, + offset = 245, + sensitivity = .05, + snap = false, + style_getter = function() return "glowing_divided" end, + style_max_getter = function() return 240 end, + value_getter = function() return params:get("danger_zone_clock_sync") end, + value_setter = function(args) params:set("danger_zone_clock_sync", args) end, + wrap = false, + } + self.arc_styles["DOCS"] = { + key = "DOCS", + max = 0, + min = 0, + offset = 0, + sensitivity = 0, + snap = false, + style_getter = function() return "standby" end, + style_max_getter = function() return 360 end, + value_getter = function() return 0 end, + value_setter = function(args) end, + wrap = false, + } +end + return menu \ No newline at end of file diff --git a/lib/mixins/bearing_mixin.lua b/lib/mixins/bearing_mixin.lua index b2c364f..6285e9f 100644 --- a/lib/mixins/bearing_mixin.lua +++ b/lib/mixins/bearing_mixin.lua @@ -6,10 +6,25 @@ bearing_mixin.init = function(self) self.setup_bearing = function(self) self.bearing_key = "BEARING" self.bearing = 1 + self.bearing_min = 1 + self.bearing_max = 4 self:register_save_key("bearing") self.bearing_menu_values = {"NORTH", "EAST", "SOUTH", "WEST"} self:register_menu_getter(self.bearing_key, self.bearing_menu_getter) self:register_menu_setter(self.bearing_key, self.bearing_menu_setter) + self:register_arc_style({ + key = self.bearing_key, + style_getter = function() return "glowing_compass" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 0, + wrap = true, + snap = true, + min = self.bearing_min, + max = self.bearing_max, + value_getter = self.get_bearing, + value_setter = self.set_bearing + }) end self.get_bearing = function(self) @@ -21,7 +36,7 @@ bearing_mixin.init = function(self) end self.set_bearing = function(self, i) - self.bearing = util.clamp(i, 1, 4) + self.bearing = util.clamp(i, self.bearing_min, self.bearing_max) self.callback(self, "set_bearing") end diff --git a/lib/mixins/capacity_mixin.lua b/lib/mixins/capacity_mixin.lua index c587dc2..c7ae3b1 100644 --- a/lib/mixins/capacity_mixin.lua +++ b/lib/mixins/capacity_mixin.lua @@ -5,9 +5,24 @@ capacity_mixin.init = function(self) self.setup_capacity = function(self) self.capacity_key = "CAPACITY" self.capacity = 4 + self.capacity_min = 0 + self.capacity_max = 100 self:register_save_key("capacity") self:register_menu_getter(self.capacity_key, self.capacity_menu_getter) self:register_menu_setter(self.capacity_key, self.capacity_menu_setter) + self:register_arc_style({ + key = self.capacity_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .5, + offset = 240, + wrap = false, + snap = false, + min = self.capacity_min, + max = self.capacity_max, + value_getter = self.get_capacity, + value_setter = self.set_capacity + }) end self.get_capacity = function(self) @@ -15,7 +30,7 @@ capacity_mixin.init = function(self) end self.set_capacity = function(self, i) - self.capacity = util.clamp(i, 0, 100) + self.capacity = util.clamp(i, self.capacity_min, self.capacity_max) self.callback(self, "set_capacity") end diff --git a/lib/mixins/channel_mixin.lua b/lib/mixins/channel_mixin.lua index 5c74eca..014c8df 100644 --- a/lib/mixins/channel_mixin.lua +++ b/lib/mixins/channel_mixin.lua @@ -5,9 +5,24 @@ channel_mixin.init = function(self) self.setup_channel = function(self) self.channel_key = "CHANNEL" self.channel = 1 + self.channel_min = 1 + self.channel_max = 16 self:register_save_key("channel") self:register_menu_getter(self.channel_key, self.channel_menu_getter) self:register_menu_setter(self.channel_key, self.channel_menu_setter) + self:register_arc_style({ + key = self.channel_key, + style_getter = function() return "sweet_sixteen" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 180, + wrap = false, + snap = true, + min = self.channel_min, + max = self.channel_max, + value_getter = self.get_channel, + value_setter = self.set_channel + }) end self.get_channel = function(self) @@ -15,7 +30,7 @@ channel_mixin.init = function(self) end self.set_channel = function(self, i) - self.channel = util.clamp(i, 1, 16) + self.channel = util.clamp(i, self.channel_min, self.channel_max) self.callback(self, "set_channel") end diff --git a/lib/mixins/charge_mixin.lua b/lib/mixins/charge_mixin.lua index c9faa87..2a8a120 100644 --- a/lib/mixins/charge_mixin.lua +++ b/lib/mixins/charge_mixin.lua @@ -5,9 +5,24 @@ charge_mixin.init = function(self) self.setup_charge = function(self) self.charge_key = "CHARGE" self.charge = 0 + self.charge_min = 0 + self.charge_max = 100 self:register_save_key("charge") self:register_menu_getter(self.charge_key, self.charge_menu_getter) self:register_menu_setter(self.charge_key, self.charge_menu_setter) + self:register_arc_style({ + key = self.charge_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .5, + offset = 240, + wrap = false, + snap = false, + min = self.charge_min, + max = self.charge_max, + value_getter = self.get_charge, + value_setter = self.set_charge + }) end self.get_charge = function(self) @@ -15,7 +30,7 @@ charge_mixin.init = function(self) end self.set_charge = function(self, i) - self.charge = util.clamp(i, 0, 100) + self.charge = util.clamp(i, self.charge_min, self.charge_max) self.callback(self, "set_charge") end diff --git a/lib/mixins/clockwise_mixin.lua b/lib/mixins/clockwise_mixin.lua index 6306360..d620119 100644 --- a/lib/mixins/clockwise_mixin.lua +++ b/lib/mixins/clockwise_mixin.lua @@ -9,6 +9,19 @@ clockwise_mixin.init = function(self) self.clockwise_menu_values = { "YES", "COUNTER" } self:register_menu_getter(self.clockwise_key, self.clockwise_menu_getter) self:register_menu_setter(self.clockwise_key, self.clockwise_menu_setter) + self:register_arc_style({ + key = self.clockwise_key, + style_getter = function() return "glowing_clock" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 0, + wrap = false, + snap = false, + min = 0, + max = 1, + value_getter = self.clockwise_boolean_to_int_getter, + value_setter = self.clockwise_menu_setter + }) end self.get_clockwise = function(self) @@ -28,4 +41,9 @@ clockwise_mixin.init = function(self) self.clockwise_menu_setter = function(self, i) self:set_clockwise(i > 0) end + + self.clockwise_boolean_to_int_getter = function(self) + return self.clockwise and 1 or 0 + end + end \ No newline at end of file diff --git a/lib/mixins/crow_out_mixin.lua b/lib/mixins/crow_out_mixin.lua index d389add..7513957 100644 --- a/lib/mixins/crow_out_mixin.lua +++ b/lib/mixins/crow_out_mixin.lua @@ -9,6 +9,19 @@ crow_out_mixin.init = function(self) self.crow_out_menu_values = {"1/2", "3/4"} self:register_menu_getter(self.crow_out_key, self.crow_out_menu_getter) self:register_menu_setter(self.crow_out_key, self.crow_out_menu_setter) + self:register_arc_style({ + key = self.crow_out_key, + style_getter = function() return "glowing_divided" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = true, + min = 1, + max = 2, + value_getter = self.get_crow_out, + value_setter = self.set_crow_out + }) end self.get_crow_out = function(self) diff --git a/lib/mixins/crumble_mixin.lua b/lib/mixins/crumble_mixin.lua index 64b38c7..c794ce8 100644 --- a/lib/mixins/crumble_mixin.lua +++ b/lib/mixins/crumble_mixin.lua @@ -5,9 +5,24 @@ crumble_mixin.init = function(self) self.setup_crumble = function(self) self.crumble_key = "CRUMBLE" self.crumble = 4 + self.crumble_min = 0 + self.crumble_max = 100 self:register_save_key("crumble") self:register_menu_getter(self.crumble_key, self.crumble_menu_getter) self:register_menu_setter(self.crumble_key, self.crumble_menu_setter) + self:register_arc_style({ + key = self.crumble_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .5, + offset = 240, + wrap = false, + snap = false, + min = self.crumble_min, + max = self.crumble_max, + value_getter = self.get_crumble, + value_setter = self.set_crumble + }) end self.get_crumble = function(self) @@ -15,7 +30,7 @@ crumble_mixin.init = function(self) end self.set_crumble = function(self, i) - self.crumble = util.clamp(i, 0, 100) + self.crumble = util.clamp(i, self.crumble_min, self.crumble_max) self.callback(self, "set_crumble") end diff --git a/lib/mixins/deflect_mixin.lua b/lib/mixins/deflect_mixin.lua index ac56afb..36db0a0 100644 --- a/lib/mixins/deflect_mixin.lua +++ b/lib/mixins/deflect_mixin.lua @@ -5,10 +5,25 @@ deflect_mixin.init = function(self) self.setup_deflect = function(self) self.deflect_key = "DEFLECT" self.deflect = 1 + self.deflect_min = 1 + self.deflect_max = 4 self:register_save_key("deflect") self.deflect_menu_values = {"NORTH", "EAST", "SOUTH", "WEST"} self:register_menu_getter(self.deflect_key, self.deflect_menu_getter) self:register_menu_setter(self.deflect_key, self.deflect_menu_setter) + self:register_arc_style({ + key = self.deflect_key, + style_getter = function() return "glowing_compass" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 0, + wrap = true, + snap = true, + min = self.deflect_min, + max = self.deflect_max, + value_getter = self.get_deflect, + value_setter = self.set_deflect + }) end self.get_deflect = function(self) @@ -16,7 +31,7 @@ deflect_mixin.init = function(self) end self.set_deflect = function(self, i) - self.deflect = util.clamp(i, 1, 4) + self.deflect = util.clamp(i, self.deflect_min, self.deflect_max) self.callback(self, "set_deflect") end diff --git a/lib/mixins/device_mixin.lua b/lib/mixins/device_mixin.lua index c3c8d7f..2b19772 100644 --- a/lib/mixins/device_mixin.lua +++ b/lib/mixins/device_mixin.lua @@ -5,9 +5,24 @@ device_mixin.init = function(self) self.setup_device = function(self) self.device_key = "DEVICE" self.device = 1 + self.device_min = 1 + self.device_max = 4 self:register_save_key("device") self:register_menu_getter(self.device_key, self.device_menu_getter) self:register_menu_setter(self.device_key, self.device_menu_setter) + self:register_arc_style({ + key = self.device_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = false, + min = self.device_min, + max = self.device_max, + value_getter = self.get_device, + value_setter = self.set_device + }) end self.get_device = function(self) @@ -15,7 +30,7 @@ device_mixin.init = function(self) end self.set_device = function(self, i) - self.device = util.clamp(i, 1, 4) + self.device = util.clamp(i, self.device_min, self.device_max) self.callback(self, "set_device") end diff --git a/lib/mixins/drift_mixin.lua b/lib/mixins/drift_mixin.lua index 7048c9a..f9bf474 100644 --- a/lib/mixins/drift_mixin.lua +++ b/lib/mixins/drift_mixin.lua @@ -5,10 +5,25 @@ drift_mixin.init = function(self) self.setup_drift = function(self) self.drift_key = "DRIFT" self.drift = 1 + self.drift_min = 1 + self.drift_max = 3 self:register_save_key("drift") self.drift_menu_values = {"N/S", "E/W", "???"} self:register_menu_getter(self.drift_key, self.drift_menu_getter) self:register_menu_setter(self.drift_key, self.drift_menu_setter) + self:register_arc_style({ + key = self.drift_key, + style_getter = function() return "glowing_drift" end, + style_max_getter = function() return 360 end, + sensitivity = .01, + offset = 0, + wrap = false, + snap = true, + min = self.drift_min, + max = self.drift_max, + value_getter = self.get_drift, + value_setter = self.set_drift + }) end self.get_drift = function(self) @@ -16,7 +31,7 @@ drift_mixin.init = function(self) end self.set_drift = function(self, i) - self.drift = util.clamp(i, 1, 3) + self.drift = util.clamp(i, self.drift_min, self.drift_max) self.callback(self, "set_drift") end diff --git a/lib/mixins/duration_mixin.lua b/lib/mixins/duration_mixin.lua index a68d7c3..3b51298 100644 --- a/lib/mixins/duration_mixin.lua +++ b/lib/mixins/duration_mixin.lua @@ -5,9 +5,24 @@ duration_mixin.init = function(self) self.setup_duration = function(self) self.duration_key = "DURATION" self.duration = 1 + self.duration_min = 1 + self.duration_max = 16 self:register_save_key("duration") self:register_menu_getter(self.duration_key, self.duration_menu_getter) self:register_menu_setter(self.duration_key, self.duration_menu_setter) + self:register_arc_style({ + key = self.duration_key, + style_getter = function() return "sweet_sixteen" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 180, + wrap = false, + snap = true, + min = self.duration_min, + max = self.duration_max, + value_getter = self.get_duration, + value_setter = self.set_duration + }) end self.get_duration = function(self) @@ -15,7 +30,7 @@ duration_mixin.init = function(self) end self.set_duration = function(self, i) - self.duration = util.clamp(i, 1, 16) + self.duration = util.clamp(i, self.duration_min, self.duration_max) self.callback(self, "set_duration") end diff --git a/lib/mixins/level_mixin.lua b/lib/mixins/level_mixin.lua index 7886706..1da5e85 100644 --- a/lib/mixins/level_mixin.lua +++ b/lib/mixins/level_mixin.lua @@ -5,9 +5,24 @@ level_mixin.init = function(self) self.setup_level = function(self) self.level_key = "LEVEL" self.level = 50 + self.level_min = 0 + self.level_max = 100 self:register_save_key("level") self:register_menu_getter(self.level_key, self.level_menu_getter) self:register_menu_setter(self.level_key, self.level_menu_setter) + self:register_arc_style({ + key = self.level_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .5, + offset = 240, + wrap = false, + snap = false, + min = self.level_min, + max = self.level_max, + value_getter = self.get_level, + value_setter = self.set_level + }) end self.get_level = function(self) @@ -15,7 +30,7 @@ level_mixin.init = function(self) end self.set_level = function(self, i) - self.level = util.clamp(i, 0, 100) + self.level = util.clamp(i, self.level_min, self.level_max) self.callback(self, "set_level") end diff --git a/lib/mixins/mapping_mixin.lua b/lib/mixins/mapping_mixin.lua new file mode 100644 index 0000000..81a137c --- /dev/null +++ b/lib/mixins/mapping_mixin.lua @@ -0,0 +1,30 @@ +mapping_mixin = {} + +mapping_mixin.init = function(self) + + self.setup_mapping = function(self) + self.mapping_key = "MAPPING" + self.mapping = 1 + self:register_save_key("mapping") + self:register_menu_getter(self.mapping_key, self.mapping_menu_getter) + self:register_menu_setter(self.mapping_key, self.mapping_menu_setter) + end + + self.get_mapping = function(self) + return self.mapping + end + + self.set_mapping = function(self, i) + self.mapping = util.clamp(i, 1, 4) + self.callback(self, "set_mapping") + end + + self.mapping_menu_getter = function(self) + return self:get_mapping() + end + + self.mapping_menu_setter = function(self, i) + self:set_mapping(self.mapping + i) + end + +end \ No newline at end of file diff --git a/lib/mixins/metabolism_mixin.lua b/lib/mixins/metabolism_mixin.lua index 4736a67..bd76594 100644 --- a/lib/mixins/metabolism_mixin.lua +++ b/lib/mixins/metabolism_mixin.lua @@ -6,9 +6,24 @@ metabolism_mixin.init = function(self) self.setup_metabolism = function(self) self.metabolism_key = "METABOLISM" self.metabolism = 13 + self.metabolism_min = 0 + self.metabolism_max = 16 self:register_save_key("metabolism") self:register_menu_getter(self.metabolism_key, self.metabolism_menu_getter) self:register_menu_setter(self.metabolism_key, self.metabolism_menu_setter) + self:register_arc_style({ + key = self.metabolism_key, + style_getter = function() return "sweet_sixteen" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 180, + wrap = false, + snap = true, + min = self.metabolism_min, + max = self.metabolism_max, + value_getter = self.get_metabolism, + value_setter = self.set_metabolism + }) end self.get_metabolism = function(self) @@ -16,7 +31,7 @@ metabolism_mixin.init = function(self) end self.set_metabolism = function(self, i) - self.metabolism = util.clamp(i, 0, 16) + self.metabolism = util.clamp(i, self.metabolism_min, self.metabolism_max) self.callback(self, "set_metabolism") end diff --git a/lib/mixins/network_mixin.lua b/lib/mixins/network_mixin.lua index c7eef09..76d45d7 100644 --- a/lib/mixins/network_mixin.lua +++ b/lib/mixins/network_mixin.lua @@ -5,6 +5,8 @@ network_mixin.init = function(self) self.setup_network = function(self) self.network_key = "NETWORK" self.network = 1 + self.network_min = 1 + self.network_max = 26 self:register_save_key("network") self.network_menu_values = { "A", "B", "C", "D", "E", "F", "G", "H", @@ -14,6 +16,19 @@ network_mixin.init = function(self) } self:register_menu_getter(self.network_key, self.network_menu_getter) self:register_menu_setter(self.network_key, self.network_menu_setter) + self:register_arc_style({ + key = self.network_key, + style_getter = function() return "glowing_divided" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = false, + min = self.network_min, + max = self.network_max, + value_getter = self.get_network, + value_setter = self.set_network + }) end self.get_network = function(self) @@ -21,7 +36,7 @@ network_mixin.init = function(self) end self.set_network = function(self, i) - self.network = util.clamp(i, 1, 26) + self.network = util.clamp(i, self.network_min, self.network_max) self.callback(self, "set_network") end diff --git a/lib/mixins/notes_mixin.lua b/lib/mixins/notes_mixin.lua index a6b3fa3..a6215d1 100644 --- a/lib/mixins/notes_mixin.lua +++ b/lib/mixins/notes_mixin.lua @@ -4,12 +4,31 @@ notes_mixin = {} notes_mixin.init = function(self) self.setup_notes = function(self, count) + + self.note_count_key = "NOTE COUNT" -- code key, not music key... self.note_count = (count == nil) and 1 or count + self.note_count_min = 1 + self.note_count_max = 8 self:register_save_key("note_count") - self.note_count_key = "NOTE COUNT" -- code key, not music key... - self.max_note_count = 8 + self:register_menu_getter(self.note_count_key, self.note_count_menu_getter) + self:register_menu_setter(self.note_count_key, self.note_count_menu_setter) + self:register_arc_style({ + key = self.note_count_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = true, + min = self.note_count_min, + max = self.note_count_max, + value_getter = self.get_note_count, + value_setter = self.set_note_count + }) + + self.note_key = "NOTE" -- code key, not music key... - for i = 1, self.max_note_count do + for i = 1, self.note_count_max do self["note_" .. i .. "_key"] = "NOTE #" .. i end self.notes = {} @@ -26,12 +45,43 @@ notes_mixin.init = function(self) end self:register_menu_getter(self.note_key, self.note_menu_getter) self:register_menu_setter(self.note_key, self.note_menu_setter) - for i = 1, self.max_note_count do + for i = 1, self.note_count_max do self:register_menu_getter(self["note_" .. i .. "_key"], self["note_" .. i .. "_menu_getter"]) self:register_menu_setter(self["note_" .. i .. "_key"], self["note_" .. i .. "_menu_setter"]) end - self:register_menu_getter(self.note_count_key, self.note_count_menu_getter) - self:register_menu_setter(self.note_count_key, self.note_count_menu_setter) + + self:register_arc_style({ + key = self.note_key, + style_getter = function() return "glowing_note" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 180, + wrap = false, + snap = true, + min = 1, + max = #sound:get_scale_notes(), + value_getter = self.get_note_1, + value_setter = self.set_note_1, + extras = { note_number = 1 } + }) + + for i = 1, self.note_count_max do + self:register_arc_style({ + key = "NOTE #" .. i, + style_getter = function() return "glowing_note" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 180, + wrap = false, + snap = true, + min = 1, + max = #sound:get_scale_notes(), + value_getter = self["get_note_" .. i], + value_setter = self["set_note_" .. i], + extras = { note_number = i } + }) + end + end self.note_menu_setter = function(self, i) popup:launch("note", i, "enc", 3, 1) end @@ -54,6 +104,36 @@ notes_mixin.init = function(self) self.note_7_menu_getter = function(self) return self:note_to_menu_string(7) end self.note_8_menu_getter = function(self) return self:note_to_menu_string(8) end + -- arc getters + self.get_note_1 = function(self) return self:get_note(1) end + self.get_note_2 = function(self) return self:get_note(2) end + self.get_note_3 = function(self) return self:get_note(3) end + self.get_note_4 = function(self) return self:get_note(4) end + self.get_note_5 = function(self) return self:get_note(5) end + self.get_note_6 = function(self) return self:get_note(6) end + self.get_note_7 = function(self) return self:get_note(7) end + self.get_note_8 = function(self) return self:get_note(8) end + + self.get_note = function(self, i) + return self.notes[i] + end + + -- arc setters + self.set_note_1 = function(self, note) return self:set_note(note, 1) end + self.set_note_2 = function(self, note) return self:set_note(note, 2) end + self.set_note_3 = function(self, note) return self:set_note(note, 3) end + self.set_note_4 = function(self, note) return self:set_note(note, 4) end + self.set_note_5 = function(self, note) return self:set_note(note, 5) end + self.set_note_6 = function(self, note) return self:set_note(note, 6) end + self.set_note_7 = function(self, note) return self:set_note(note, 7) end + self.set_note_8 = function(self, note) return self:set_note(note, 8) end + + self.set_note = function(self, note, index) + local i = index ~= nil and index or 1 + self.notes[i] = note + self.callback("set_note") + end + self.note_to_menu_string = function(self, index) local prefix = (self.state_index == index and self.note_count > 1) and "> " or "" return prefix .. self:get_note_name(index) @@ -61,16 +141,10 @@ notes_mixin.init = function(self) self.get_note_name = function(self, index) if self.notes[index] ~= nil then - return mu.note_num_to_name(self.notes[index], true) + return musicutil.note_num_to_name(self.notes[index], true) end end - self.set_note = function(self, note, index) - local i = index ~= nil and index or 1 - self.notes[i] = note - self.callback("set_note") - end - self.browse_notes = function(self, delta, index) local snap = sound:snap_note(self.notes[index]) local scale_index = fn.table_find(sound.scale_notes, snap) @@ -78,15 +152,27 @@ notes_mixin.init = function(self) self:set_note(note, index) end + -- how many notes does this cell have? 1-8 self.get_note_count = function(self) return self.note_count end + -- how many notes does this cell have? 1-8 self.set_note_count = function(self, i) - self.note_count = util.clamp(i, 1, self.max_note_count) + self.note_count = util.clamp(i, self.note_count_min, self.note_count_max) self.callback(self, "set_note_count") end + -- how many notes *are in this scale* + self.update_note_max = function(max) + if self.arc_styles ~= nil then + self.arc_styles.NOTE.max = max + for i = 1, 8 do + self.arc_styles["NOTE #" .. i ].max = max + end + end + end + self.note_count_menu_getter = function(self) return self:get_note_count() end diff --git a/lib/mixins/offset_mixin.lua b/lib/mixins/offset_mixin.lua index c9042fe..bf11fd7 100644 --- a/lib/mixins/offset_mixin.lua +++ b/lib/mixins/offset_mixin.lua @@ -5,9 +5,24 @@ offset_mixin.init = function(self) self.setup_offset = function(self) self.offset_key = "OFFSET" self.offset = 0 + self.offset_min = 0 + self.offset_max = 15 self:register_save_key("offset") self:register_menu_getter(self.offset_key, self.offset_menu_getter) self:register_menu_setter(self.offset_key, self.offset_menu_setter) + self:register_arc_style({ + key = self.offset_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 180, -- style offset, cell offset :) + wrap = false, + snap = true, + min = self.offset_min, + max = self.offset_max, + value_getter = self.get_offset, + value_setter = self.set_offset + }) end self.get_offset = function(self) @@ -15,7 +30,7 @@ offset_mixin.init = function(self) end self.set_offset = function(self, i) - self.offset = util.clamp(i, 0, 15) + self.offset = util.clamp(i, self.offset_min, self.offset_max) self.callback(self, "set_offset") end diff --git a/lib/mixins/operator_mixin.lua b/lib/mixins/operator_mixin.lua index 764acc9..ff03ba4 100644 --- a/lib/mixins/operator_mixin.lua +++ b/lib/mixins/operator_mixin.lua @@ -5,10 +5,25 @@ operator_mixin.init = function(self) self.setup_operator = function(self) self.operator_key = "OPERATOR" self.operator = 1 + self.operator_min = 1 + self.operator_max = 6 self:register_save_key("operator") self.operator_menu_values = { "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "SET" } self:register_menu_getter(self.operator_key, self.operator_menu_getter) self:register_menu_setter(self.operator_key, self.operator_menu_setter) + self:register_arc_style({ + key = self.operator_key, + style_getter = function() return "glowing_divided" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = false, + min = self.operator_min, + max = self.operator_max, + value_getter = self.get_operator, + value_setter = self.set_operator + }) end self.get_operator = function(self) @@ -16,7 +31,7 @@ operator_mixin.init = function(self) end self.set_operator = function(self, i) - self.operator = util.clamp(i, 1, 6) + self.operator = util.clamp(i, self.operator_min, self.operator_max) self.callback(self, "set_operator") end diff --git a/lib/mixins/output_mixin.lua b/lib/mixins/output_mixin.lua index d2c5033..4272780 100644 --- a/lib/mixins/output_mixin.lua +++ b/lib/mixins/output_mixin.lua @@ -5,10 +5,25 @@ output_mixin.init = function(self) self.setup_output = function(self) self.output_key = "OUTPUT" self.output = 1 + self.output_min = 1 self:register_save_key("output") self.output_menu_values = { "SYNTH", "MIDI" } + self.output_max = #self.output_menu_values self:register_menu_getter(self.output_key, self.output_menu_getter) self:register_menu_setter(self.output_key, self.output_menu_setter) + self:register_arc_style({ + key = self.output_key, + style_getter = function() return "glowing_divided" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = true, + min = self.output_min, + max = self.output_max, + value_getter = self.get_output, + value_setter = self.set_output + }) end self.get_output = function(self) @@ -20,7 +35,7 @@ output_mixin.init = function(self) end self.set_output = function(self, i) - self.output = util.clamp(i, 1, #self.output_menu_values) + self.output = util.clamp(i, self.output_min, self.output_max) self.callback(self, "set_output") end diff --git a/lib/mixins/ports_mixin.lua b/lib/mixins/ports_mixin.lua index 47ffcb1..277c957 100644 --- a/lib/mixins/ports_mixin.lua +++ b/lib/mixins/ports_mixin.lua @@ -41,7 +41,7 @@ ports_mixin.init = function(self, x, y) end self.is_port_open = function(self, p) - return tu.contains(self.ports, p) + return tabutil.contains(self.ports, p) end self.open_port = function(self, p) diff --git a/lib/mixins/probability_mixin.lua b/lib/mixins/probability_mixin.lua index e48101c..8e129bb 100644 --- a/lib/mixins/probability_mixin.lua +++ b/lib/mixins/probability_mixin.lua @@ -5,9 +5,24 @@ probability_mixin.init = function(self) self.setup_probability = function(self) self.probability_key = "PROBABILITY" self.probability = 50 + self.probability_min = 0 + self.probability_max = 100 self:register_save_key("probability") self:register_menu_getter(self.probability_key, self.probability_menu_getter) self:register_menu_setter(self.probability_key, self.probability_menu_setter) + self:register_arc_style({ + key = self.probability_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .5, + offset = 240, + wrap = false, + snap = false, + min = self.probability_min, + max = self.probability_max, + value_getter = self.get_probability, + value_setter = self.set_probability + }) end self.get_probability = function(self) @@ -15,7 +30,7 @@ probability_mixin.init = function(self) end self.set_probability = function(self, i) - self.probability = util.clamp(i, 0, 100) + self.probability = util.clamp(i, self.probability_min, self.probability_max) self.callback(self, "set_probability") end diff --git a/lib/mixins/pulses_mixin.lua b/lib/mixins/pulses_mixin.lua index 996148b..f1b881b 100644 --- a/lib/mixins/pulses_mixin.lua +++ b/lib/mixins/pulses_mixin.lua @@ -6,9 +6,24 @@ pulses_mixin.init = function(self) self.setup_pulses = function(self) self.pulses_key = "PULSES" self.pulses = 0 + self.pulses_min = 0 + self.pulses_max = self:get_metabolism() self:register_save_key("pulses") self:register_menu_getter(self.pulses_key, self.pulses_menu_getter) self:register_menu_setter(self.pulses_key, self.pulses_menu_setter) + self:register_arc_style({ + key = self.pulses_key, + style_getter = function() return "sweet_sixteen" end, + style_max_getter = function() return 360 end, + sensitivity = .05, + offset = 180, + wrap = false, + snap = true, + min = self.pulses_min, + max = self.pulses_max, + value_getter = self.get_pulses, + value_setter = self.set_pulses + }) end self.get_pulses = function(self) @@ -16,7 +31,7 @@ pulses_mixin.init = function(self) end self.set_pulses = function(self, i) - self.pulses = util.clamp(i, 0, self:get_metabolism()) + self.pulses = util.clamp(i, self.pulses_min, self.pulses_max) self.callback(self, "set_pulses") end @@ -28,4 +43,9 @@ pulses_mixin.init = function(self) self:set_pulses(self.pulses + i) end + self.set_pulses_max = function(self, i) + self.pulses_max = i + self.arc_styles.PULSES.max = i + end + end \ No newline at end of file diff --git a/lib/mixins/range_mixin.lua b/lib/mixins/range_mixin.lua index faaabf9..1e2fc59 100644 --- a/lib/mixins/range_mixin.lua +++ b/lib/mixins/range_mixin.lua @@ -5,24 +5,58 @@ range_mixin.init = function(self) self.setup_range = function(self) self.range_min_key = "RANGE MIN" self.range_min = 0 + self.range_min_min = 0 + self.range_min_max = 100 self:register_save_key("range_min") self:register_menu_getter(self.range_min_key, self.range_min_menu_getter) self:register_menu_setter(self.range_min_key, self.range_min_menu_setter) + self:register_arc_style({ + key = self.range_min_key, + style_getter = function() return "glowing_range" end, + style_max_getter = function() return 360 end, + sensitivity = .5, + offset = 180, + wrap = false, + snap = false, + min = self.range_min_min, + max = self.range_min_max, + value_getter = self.get_range_min, + value_setter = self.set_range_min + }) self.range_max_key = "RANGE MAX" self.range_max = 100 + self.range_max_min = 0 + self.range_max_max = 100 self:register_save_key("range_max") self:register_menu_getter(self.range_max_key, self.range_max_menu_getter) self:register_menu_setter(self.range_max_key, self.range_max_menu_setter) + self:register_arc_style({ + key = self.range_max_key, + style_getter = function() return "glowing_range" end, + style_max_getter = function() return 360 end, + sensitivity = .5, + offset = 180, + wrap = false, + snap = false, + min = self.range_max_min, + max = self.range_max_max, + value_getter = self.get_range_max, + value_setter = self.set_range_max + }) end self.set_range_min = function(self, i) - self.range_min = util.clamp(i, 0, 100) + local min = util.clamp(i, self.range_min_min, self.range_min_max) + self.range_min = min + self.range_max_min = min self.range_max = util.clamp(self.range_max, self.range_min, 100) self.callback(self, "set_range_min") end self.set_range_max = function(self, i) - self.range_max = util.clamp(i, 0, 100) + local max = util.clamp(i, self.range_max_min, self.range_max_max) + self.range_max = max + self.range_min_max = max self.range_min = util.clamp(self.range_min, 0, self.range_max) self.callback(self, "set_range_max") end diff --git a/lib/mixins/resilience_mixin.lua b/lib/mixins/resilience_mixin.lua index cb962f7..3f76fb1 100644 --- a/lib/mixins/resilience_mixin.lua +++ b/lib/mixins/resilience_mixin.lua @@ -5,9 +5,24 @@ resilience_mixin.init = function(self) self.setup_resilience = function(self) self.resilience_key = "RESILIENCE" self.resilience = 50 + self.resilience_min = 0 + self.resilience_max = 100 self:register_save_key("resilience") self:register_menu_getter(self.resilience_key, self.resilience_menu_getter) self:register_menu_setter(self.resilience_key, self.resilience_menu_setter) + self:register_arc_style({ + key = self.resilience_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .5, + offset = 240, + wrap = false, + snap = false, + min = self.resilience_min, + max = self.resilience_max, + value_getter = self.get_resilience, + value_setter = self.set_resilience + }) end self.get_resilience = function(self) @@ -15,7 +30,7 @@ resilience_mixin.init = function(self) end self.set_resilience = function(self, i) - self.resilience = util.clamp(i, 0, 100) + self.resilience = util.clamp(i, self.resilience_min, self.resilience_max) self.callback(self, "set_resilience") end diff --git a/lib/mixins/state_index_mixin.lua b/lib/mixins/state_index_mixin.lua index a82d85f..45ccdec 100644 --- a/lib/mixins/state_index_mixin.lua +++ b/lib/mixins/state_index_mixin.lua @@ -6,10 +6,24 @@ state_index_mixin.init = function(self) self.setup_state_index = function(self) self.state_index_key = "INDEX" self.max_state_index = 8 + self.min_state_index = 1 self.state_index = 1 self:register_save_key("state_index") self:register_menu_getter(self.state_index_key, self.state_index_menu_getter) self:register_menu_setter(self.state_index_key, self.state_index_menu_setter) + self:register_arc_style({ + key = self.state_index_key, + style_getter = function() return "glowing_divided" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = false, + min = self.min_state_index, + max = self.max_state_index, + value_getter = self.get_state_index, + value_setter = self.set_state_index + }) end self.get_state_index = function(self) @@ -20,7 +34,7 @@ state_index_mixin.init = function(self) if self:has("NOTE COUNT") then self.state_index = util.clamp(i, 1, self.note_count) else - self.state_index = util.clamp(i, 1, self.max_state_index) + self.state_index = util.clamp(i, self.min_state_index, self.max_state_index) end self:callback("set_state_index") end @@ -35,6 +49,10 @@ state_index_mixin.init = function(self) self.set_max_state_index = function(self, i) self.max_state_index = i + self.arc_styles.INDEX.max = i + if self.state_index > i then + self.state_index = i + end end -- only handles increments of 1 (encoders) diff --git a/lib/mixins/structure_mixin.lua b/lib/mixins/structure_mixin.lua new file mode 100644 index 0000000..a887971 --- /dev/null +++ b/lib/mixins/structure_mixin.lua @@ -0,0 +1,75 @@ +-- structure is integral to Cell already +-- this stub is for normalized menu behavior +structure_mixin = {} + +structure_mixin.init = function(self) + + self.setup_structure = function(self) + self.structure_name = structures:first() -- typically HIVE + self.structure_key = "STRUCTURE" + self.structure_min = 1 + self.structure_max = #structures:all_enabled() + self:register_menu_getter(self.structure_key, self.structure_menu_getter) + self:register_menu_setter(self.structure_key, self.structure_menu_setter) + self:register_arc_style({ + key = self.structure_key, + style_getter = function() return "glowing_structure" end, + style_max_getter = function() return 240 end, + sensitivity = .05, + offset = 240, + wrap = false, + snap = true, + min = self.structure_min, + max = self.structure_max, + value_getter = self.get_structure, + value_setter = self.set_structure + }) + end + + self.get_structure = function(self) + return structures:get_index(self.structure_name) + end + + self.set_structure = function(self, i) + self:change(structures:all_enabled()[i].name) + end + + self.structure_menu_getter = function(self) + return "" + end + + self.structure_menu_setter = function(self, i) + popup:launch("structure", i, "enc", 3) + end + + self.is = function(self, name) + return self.structure_name == name + end + + self.has = function(self, name) + return fn.table_has(self:get_attributes(), name) + end + + self.get_attributes = function(self) + return structures:get_structure_attributes(self.structure_name) + end + + self.change = function(self, name) + local match = false + for k, v in pairs(structures:all_enabled()) do + if v.name == name then + self:set_structure_by_name(name) + match = true + end + end + if not match then + self:set_structure_by_name(structures:first()) + end + end + + self.set_structure_by_name = function(self, name) + self.structure_name = name + self:change_checks() + end + +end \ No newline at end of file diff --git a/lib/mixins/structure_stub_mixin.lua b/lib/mixins/structure_stub_mixin.lua deleted file mode 100644 index e13dd73..0000000 --- a/lib/mixins/structure_stub_mixin.lua +++ /dev/null @@ -1,29 +0,0 @@ --- structure is integral to Cell already --- this stub is for normalized menu behavior -structure_stub_mixin = {} - -structure_stub_mixin.init = function(self) - - self.setup_structure_stub = function(self) - self.structure_stub_key = "STRUCTURE" - self:register_menu_getter(self.structure_stub_key, self.structure_stub_menu_getter) - self:register_menu_setter(self.structure_stub_key, self.structure_stub_menu_setter) - end - - self.get_structure_stub = function(self) - -- empty - end - - self.set_structure_stub = function(self, i) - -- empty - end - - self.structure_stub_menu_getter = function(self) - return "" - end - - self.structure_stub_menu_setter = function(self, i) - popup:launch("structure", i, "enc", 3) - end - -end \ No newline at end of file diff --git a/lib/mixins/territory_mixin.lua b/lib/mixins/territory_mixin.lua index e4aef21..f60a603 100644 --- a/lib/mixins/territory_mixin.lua +++ b/lib/mixins/territory_mixin.lua @@ -5,10 +5,25 @@ territory_mixin.init = function(self) self.setup_territory = function(self) self.territory_key = "TERRITORY" self.territory = 1 - self:register_save_key("territory") self.territory_menu_values = {"NORTH", "EAST", "SOUTH", "WEST", "N/E", "S/E", "S/W", "N/W", "ALL", "FRINGES"} + self.territory_min = 1 + self.territory_max = #self.territory_menu_values + self:register_save_key("territory") self:register_menu_getter(self.territory_key, self.territory_menu_getter) self:register_menu_setter(self.territory_key, self.territory_menu_setter) + self:register_arc_style({ + key = self.territory_key, + style_getter = function() return "glowing_territory" end, + style_max_getter = function() return 360 end, + sensitivity = .01, + offset = 0, + wrap = false, + snap = true, + min = self.territory_min, + max = self.territory_max, + value_getter = self.get_territory, + value_setter = self.set_territory + }) end self.get_territory = function(self) @@ -16,7 +31,7 @@ territory_mixin.init = function(self) end self.set_territory = function(self, i) - self.territory = util.clamp(i, 1, #self.territory_menu_values) + self.territory = util.clamp(i, self.territory_min, self.territory_max) self.callback(self, "set_territory") end @@ -127,7 +142,7 @@ territory_mixin.init = function(self) local f = self:get_fringes() x1 = f.x1 y1 = f.y1 - x2 = f.y2 + x2 = f.x2 y2 = f.y2 end diff --git a/lib/mixins/topography_mixin.lua b/lib/mixins/topography_mixin.lua index 2b8ad14..d824bd2 100644 --- a/lib/mixins/topography_mixin.lua +++ b/lib/mixins/topography_mixin.lua @@ -7,11 +7,26 @@ topography_mixin.init = function(self) self.setup_topography = function(self) self.topography_key = "TOPOGRAPHY" self.topography = 1 + self.topography_min = 1 + self.topography_max = 4 self:register_save_key("topography") self.topography_menu_values = {">>>>", "<<<<", ">><<", "DRUNK"} self.topography_pendulum = true self:register_menu_getter(self.topography_key, self.topography_menu_getter) self:register_menu_setter(self.topography_key, self.topography_menu_setter) + self:register_arc_style({ + key = self.topography_key, + style_getter = function() return "glowing_topography" end, + style_max_getter = function() return 360 end, + sensitivity = .01, + offset = 0, + wrap = false, + snap = false, + min = self.topography_min, + max = self.topography_max, + value_getter = self.get_topography, + value_setter = self.set_topography + }) end self.get_topography = function(self) @@ -32,7 +47,7 @@ topography_mixin.init = function(self) end self.set_topography = function(self, i) - self.topography = util.clamp(i, 1, 4) + self.topography = util.clamp(i, self.topography_min, self.topography_max) self.callback(self, "set_topography") end diff --git a/lib/mixins/velocity_mixin.lua b/lib/mixins/velocity_mixin.lua index 4408cd0..1afab42 100644 --- a/lib/mixins/velocity_mixin.lua +++ b/lib/mixins/velocity_mixin.lua @@ -5,9 +5,24 @@ velocity_mixin.init = function(self) self.setup_velocity = function(self) self.velocity_key = "VELOCITY" self.velocity = 127 + self.velocity_min = 0 + self.velocity_max = 127 self:register_save_key("velocity") self:register_menu_getter(self.velocity_key, self.velocity_menu_getter) self:register_menu_setter(self.velocity_key, self.velocity_menu_setter) + self:register_arc_style({ + key = self.velocity_key, + style_getter = function() return "glowing_segment" end, + style_max_getter = function() return 240 end, + sensitivity = .5, + offset = 240, + wrap = false, + snap = false, + min = self.velocity_min, + max = self.velocity_max, + value_getter = self.get_velocity, + value_setter = self.set_velocity + }) end self.get_velocity = function(self) @@ -15,7 +30,7 @@ velocity_mixin.init = function(self) end self.set_velocity = function(self, i) - self.velocity = util.clamp(i, 0, 127) + self.velocity = util.clamp(i, self.velocity_min, self.velocity_max) self.callback(self, "set_velocity") end diff --git a/lib/page.lua b/lib/page.lua index 67acb94..0ba2b54 100644 --- a/lib/page.lua +++ b/lib/page.lua @@ -8,11 +8,16 @@ function page.init() page.error_code = 0 end +function page:get_page_title() + return self.titles[self.active_page] +end + function page:scroll(d) self:select(util.clamp(page.active_page + d, 1, #page.titles)) end function page:select(i, then_select_item) + if init_done and (i < 1 or i > #page.titles) then return end self.active_page = i self.then_select_item = then_select_item or nil menu:reset() @@ -49,7 +54,7 @@ function page:home() graphics:bpm(55, 32, math.floor(fn.round(params:get("clock_tempo"), 0)), 0) graphics:playback_icon(56, 35) graphics:icon(76, 35, sound.length, menu.selected_item_string == "LENGTH" and 1 or 0) - graphics:text(56, 61, graphics:format_transpose(sound.transpose) .. mu.note_num_to_name(sound.root) .. " " .. sound.scale_name, 0) + graphics:text(56, 61, graphics:format_transpose(sound.transpose) .. musicutil.note_num_to_name(sound.root) .. " " .. sound.scale_name, 0) graphics:rect(126, 55, 2, 7, 15) end graphics:title_bar_and_tabs() @@ -113,4 +118,12 @@ function page:clear_error() self.error_code = 0 end +function page:get_page_count() + return #self.titles +end + +function page:get_active_page() + return self.active_page +end + return page \ No newline at end of file diff --git a/lib/parameters.lua b/lib/parameters.lua index 25e1e6d..494e674 100644 --- a/lib/parameters.lua +++ b/lib/parameters.lua @@ -5,18 +5,17 @@ function parameters.init() params:add_separator("") params:add_separator("- A R C O L O G I E S -") - params:add_trigger("save", "< SAVE" ) - params:set_action("save", function(x) te.enter(filesystem.save) end) - - params:add_trigger("load", "> LOAD" ) - params:set_action("load", function(x) fs.enter(norns.state.data, filesystem.load) end) + params:add_trigger("save", "< SAVE ARCOLOGY" ) + params:set_action("save", function(x) textentry.enter(filesystem.save) end) + params:add_trigger("load", "> LOAD ARCOLOGY" ) + params:set_action("load", function(x) fileselect.enter(norns.state.data, filesystem.load) end) params:add_trigger("save_map", "< SAVE MAP" ) - params:set_action("save_map", function(x) te.enter(filesystem.save_map) end) + params:set_action("save_map", function(x) textentry.enter(filesystem.save_map) end) params:add_trigger("midi_panic", "> MIDI PANIC!" ) - params:set_action("midi_panic", function() m:all_off() end) + params:set_action("midi_panic", function() _midi:all_off() end) params:add_option("crypts_directory", "CRYPT(S)", filesystem.crypts_names, 1) params:set_action("crypts_directory", function(index) filesystem:set_crypt(index) end) @@ -88,6 +87,23 @@ function parameters.init() } params:set_action("note_range_max", function(x) params:set("note_range_min", util.clamp(params:get("note_range_min"), 0, x)) end) + params:add_separator("") + params:add_separator("ARC BINDINGS") + + parameters.arc_binding_labels = {} + for i = 1, #config.arc_bindings do + parameters.arc_binding_labels[i] = config.arc_bindings[i].label + end + for i = 1, 4 do + local id = "arc_encoder_" .. i + params:add_option(id , "ENCODER " .. i, parameters.arc_binding_labels) + params:set_action(id, function(index) _arc:bind(i, config.arc_bindings[index].id) end) + params:set(id, i) + end + parameters.arc_orientations = { 0, 90, 180, 270 } + params:add_option("arc_orientation", "ROTATION", parameters.arc_orientations) + params:set_action("arc_orientation", function(index) _arc:set_orientation(parameters.arc_orientations[index]) end) + params:add_separator("") params:add_separator("STRUCTURES") diff --git a/lib/popup.lua b/lib/popup.lua index 01cc8d6..4f5bad5 100644 --- a/lib/popup.lua +++ b/lib/popup.lua @@ -2,11 +2,11 @@ popup = {} function popup.init() popup.active = false - popup.input = "" -- key or enc + popup.input = "" -- key, enc, or arc popup.number = 0 popup.current_attribute = "" popup.current_value = "" - popup.cached_index = 0 + popup.cached_index = 1 popup.key_timer = 0 popup.messages = config.popup_messages popup.note_number = nil @@ -23,8 +23,12 @@ function popup:launch(attribute, value, input, number, note_number) self.input = input self.number = number self.note_number = note_number or nil - if not popup.active and self.current_attribute == "structure" then + if not popup.active then + if self.current_attribute == "structure" then self.cached_index = structures:get_index(keeper.selected_cell.structure_name) + elseif self.current_attribute == "note" then + self.cached_index = keeper.selected_cell.arc_styles["NOTE #" .. note_number].value_getter(keeper.selected_cell) + end end self.active = true self:start() @@ -44,8 +48,8 @@ end function popup:start() - -- encoders wait for a period after you stop spinning - if self.input == "enc" then + -- encoders on both norns and arc wait for a period after you stop spinning + if self.input == "enc" or self.input == "arc" then self:title_message(self.messages[self.current_attribute]["start"]) if enc_counter[self.number]["this_clock"] ~= nil then clock.cancel(enc_counter[self.number]["this_clock"]) @@ -93,13 +97,15 @@ function popup:enc_wait() end function popup:change() - if self.current_attribute == "structure" then + if self.current_attribute == "structure" then + _arc:set_structure_popup_active(true) self.cached_index = util.clamp(self.current_value + self.cached_index, 1, #structures:all_enabled()) self:title_message(structures:all_enabled()[self.cached_index].name) end if self.current_attribute == "note" then keeper.selected_cell:browse_notes(self.current_value, self.note_number) + self.cached_index = util.clamp(self.current_value + self.cached_index, 1, #sound:get_scale_notes()) self:title_message("MIDI " .. keeper.selected_cell.notes[self.note_number]) end @@ -125,6 +131,7 @@ function popup:done() menu:reset() menu:set_items(keeper.selected_cell:menu_items()) menu:select_item_by_name("STRUCTURE") + _arc:set_structure_popup_active(false) elseif self.current_attribute == "delete_all" then keeper:delete_all_cells() elseif self.current_attribute == "note" then @@ -133,4 +140,8 @@ function popup:done() end +function popup:get_cached_index() + return self.cached_index +end + return popup \ No newline at end of file diff --git a/lib/saveload.lua b/lib/saveload.lua index a7b09cb..bfe0057 100644 --- a/lib/saveload.lua +++ b/lib/saveload.lua @@ -27,7 +27,7 @@ end function saveload:load(data) counters:stop() - s.init() + _softcut.init() arcology_name = data.arcology_name params:set("clock_tempo", data.clock_tempo or data.bpm) sound.length = data.length @@ -48,7 +48,7 @@ function saveload:load_cells(data) for k, load_cell in pairs(data.keeper_cells) do local tmp = Cell:new(load_cell.x, load_cell.y, load_cell.generation) -- pre 1.8 used a different key for structures - local structure_key = (data.version_major == 1 and data.version_minor <= 1 and data.version_patch <= 7) and "structure_value" or "structure_name" + local structure_key = (data.version_major == 1 and data.version_minor >= 2 or (data.version_minor <= 1 and data.version_patch <= 7)) and "structure_value" or "structure_name" -- if the structure doesn't exist anymore, load it as a hive. tmp.structure_name = fn.table_find(structures:all_names(), load_cell[structure_key]) and load_cell[structure_key] or "HIVE" for k, v in pairs(tmp:get_save_keys()) do diff --git a/lib/sound.lua b/lib/sound.lua index aa60430..9451362 100644 --- a/lib/sound.lua +++ b/lib/sound.lua @@ -8,19 +8,35 @@ function sound.init() sound.transpose = 0 sound.scale_name = "" sound.scale_names = {} - for k,v in pairs(mu.SCALES) do sound.scale_names[k] = mu.SCALES[k].name end + for k,v in pairs(musicutil.SCALES) do sound.scale_names[k] = musicutil.SCALES[k].name end sound.scale_notes = {} sound:set_scale(sound.scale) end +function sound:get_scale_notes() + return self.scale_notes +end + function sound:cycle_transpose(i) - self.transpose = util.clamp(self.transpose + i, -6, 6) + self:set_transpose(self.transpose + i) +end + +function sound:set_transpose(i) + self.transpose = util.clamp(i, -6, 6) +end + +function sound:get_transpose() + return self.transpose end function sound:transpose_note(note) return note + (self.transpose * 12) end +function sound:cycle_scale(i) + self:set_scale(fn.cycle(self.scale + i, 1, #self.scale_names)) +end + function sound:set_scale(i) self.scale = util.clamp(i, 1, #self.scale_names) self.scale_name = sound.scale_names[sound.scale] @@ -30,26 +46,46 @@ function sound:set_scale(i) end end +function sound:get_scale() + return self.scale +end + function sound:build_scale() - self.scale_notes = mu.generate_scale(self.root, self.scale_name, self.octaves) + self.scale_notes = musicutil.generate_scale(self.root, self.scale_name, self.octaves) end function sound:snap_note(note) - return mu.snap_note_to_array(note, self.scale_notes) + return musicutil.snap_note_to_array(note, self.scale_notes) end function sound:cycle_length(i) - self.length = util.clamp(self.length + i, 1, 16) + self:set_length(self.length + i) +end + +function sound:set_length(i) + self.length = util.clamp(i, 1, 16) +end + +function sound:get_length() + return self.length end function sound:cycle_root(i) - self.root = fn.cycle(self.root + i, 0, 12) + self:set_root(fn.cycle(self.root + i, 1, 12)) +end + +function sound:set_root(i) + self.root = util.clamp(i, 1, 12) self:build_scale() if init_done then keeper:update_all_notes() end end +function sound:get_root() + return self.root +end + function sound:set_random_root() self:cycle_root(math.random(0, 12)) end @@ -77,7 +113,7 @@ end function sound:play(note, velocity) engine.amp(velocity / 127) - engine.hz(mu.note_num_to_freq(self:snap_note(self:transpose_note(note)))) + engine.hz(musicutil.note_num_to_freq(self:snap_note(self:transpose_note(note)))) end return sound \ No newline at end of file diff --git a/lib/structures.lua b/lib/structures.lua index 6a726e6..37afe94 100644 --- a/lib/structures.lua +++ b/lib/structures.lua @@ -6,7 +6,6 @@ to add new structures and attributes, minimally: - structures:register() this file - keeper:collision() for business logic - - saveload:load_cells() to map attributes - new glyphs automatically picked up via the structure string structure names _must_ be a single word due to string matching for glyphs @@ -16,7 +15,7 @@ structure names _must_ be a single word due to string matching for glyphs function structures.init() structures.order = 1 structures.database = {} - -- structures:register("STUBBY", {}) + -- structures:register("STUBBY", { "TERRITORY" }) structures:register("HIVE", { "METABOLISM", "OFFSET" }) structures:register("SHRINE", { "NOTES", "VELOCITY" }) structures:register("GATE", {}) @@ -40,7 +39,7 @@ function structures.init() structures:register("KUDZU", { "METABOLISM", "RESILIENCE", "CRUMBLE" }) structures:register("WINDFARM", { "METABOLISM", "BEARING", "CLOCKWISE" }) structures:register("FRACTURE", { "NOTES", "RANGE MIN", "RANGE MAX", "OUTPUT", "DURATION", "CHANNEL", "DEVICE" }) - -- structures:register("PRAIRIE", {} ) + -- structures:register("CLOAKROOM", { "MAPPING" } ) end function structures:register(name, attributes)