Norns "mods" development

i think there are different use cases for menu placement.

a general “setting” (things that are more set-forget) should go in the mod menu.

things in the PARAMS menu should be dynamic, ie, things you would commonly want to control musically and use the mapping capabilities and everything else intrinsic to the PARAM system (ie, also being able to control these with scripts).


there isn’t a way to have a first-run action when installing mods, though you could hack one quite easily by saving state inside a data file (in dust/data). ie, when the mod is activated, check the file, if the state is not found, do the firstrun-action.

2 Likes

if you are going to do this then please make a prominent warning to users, many of whom are not native linux practictioners and may not be able to recover their system after installing this large pile of dependencies.

127.0.0.1 ~ $ sudo apt install mpv
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  ffmpeg libaacs0 libass5 libauthen-sasl-perl libavc1394-0 libavcodec57 libavdevice57 libavfilter6 libavformat57
  libavresample3 libavutil55 libbdplus0 libbluray1 libbs2b0 libcdio-cdda1 libcdio-paranoia1 libcdio13 libchromaprint1
  libdc1394-22 libdvdnav4 libdvdread4 libebur128-1 libencode-locale-perl libfftw3-double3 libfile-basedir-perl
  libfile-desktopentry-perl libfile-listing-perl libfile-mimeinfo-perl libflite1 libfont-afm-perl libfontenc1
  libfribidi0 libgme0 libgraphite2-3 libgsm1 libharfbuzz0b libhtml-form-perl libhtml-format-perl libhtml-parser-perl
  libhtml-tagset-perl libhtml-tree-perl libhttp-cookies-perl libhttp-daemon-perl libhttp-date-perl
  libhttp-message-perl libhttp-negotiate-perl libiec61883-0 libio-html-perl libio-socket-ssl-perl
  libipc-system-simple-perl liblcms2-2 liblua5.2-0 liblwp-mediatypes-perl liblwp-protocol-https-perl libmailtools-perl
  libmp3lame0 libmpg123-0 libnet-dbus-perl libnet-http-perl libnet-smtp-ssl-perl libnet-ssleay-perl libopenal-data
  libopenal1 libopencv-core2.4v5 libopencv-imgproc2.4v5 libopenjp2-7 libopenmpt0 libpgm-5.2-0 libpostproc54
  libraw1394-11 librubberband2 libshine3 libsmbclient libsnappy1v5 libsodium18 libsoxr0 libspeex1 libssh-gcrypt-4
  libswresample2 libswscale4 libtheora0 libtie-ixhash-perl libtimedate-perl libtwolame0 liburi-perl libva-drm1
  libva-wayland1 libva-x11-1 libva1 libvdpau-va-gl1 libvdpau1 libvpx4 libwavpack1 libwbclient0 libwebp6 libwebpmux2
  libwww-perl libwww-robotrules-perl libx11-protocol-perl libx264-148 libx265-95 libxaw7 libxcomposite1 libxft2
  libxml-parser-perl libxml-twig-perl libxml-xpathengine-perl libxmu6 libxpm4 libxvidcore4 libxxf86dga1 libzmq5
  libzvbi-common libzvbi0 mesa-va-drivers mesa-vdpau-drivers perl-openssl-defaults python-samba python3-pkg-resources
  rtmpdump samba samba-common samba-common-bin samba-libs va-driver-all vdpau-driver-all x11-utils x11-xserver-utils
  xdg-utils youtube-dl
Suggested packages:
  ffmpeg-doc libdigest-hmac-perl libgssapi-perl libbluray-bdj libdvdcss2 libfftw3-bin libfftw3-dev alsa-base
  libdata-dump-perl liblcms2-utils libcrypt-ssleay-perl libportaudio2 libraw1394-doc speex libauthen-ntlm-perl
  libunicode-map8-perl libunicode-string-perl xml-twig-tools python-gpgme python3-setuptools bind9 bind9utils ctdb
  ldb-tools ntp | chrony smbldap-tools winbind ufw heimdal-clients mesa-utils nickle cairo-5c xorg-docs-core gvfs-bin
Recommended packages:
  attr samba-dsdb-modules samba-vfs-modules
The following NEW packages will be installed:
  ffmpeg libaacs0 libass5 libauthen-sasl-perl libavc1394-0 libavcodec57 libavdevice57 libavfilter6 libavformat57
  libavresample3 libavutil55 libbdplus0 libbluray1 libbs2b0 libcdio-cdda1 libcdio-paranoia1 libcdio13 libchromaprint1
  libdc1394-22 libdvdnav4 libdvdread4 libebur128-1 libencode-locale-perl libfftw3-double3 libfile-basedir-perl
  libfile-desktopentry-perl libfile-listing-perl libfile-mimeinfo-perl libflite1 libfont-afm-perl libfontenc1
  libfribidi0 libgme0 libgraphite2-3 libgsm1 libharfbuzz0b libhtml-form-perl libhtml-format-perl libhtml-parser-perl
  libhtml-tagset-perl libhtml-tree-perl libhttp-cookies-perl libhttp-daemon-perl libhttp-date-perl
  libhttp-message-perl libhttp-negotiate-perl libiec61883-0 libio-html-perl libio-socket-ssl-perl
  libipc-system-simple-perl liblcms2-2 liblua5.2-0 liblwp-mediatypes-perl liblwp-protocol-https-perl libmailtools-perl
  libmp3lame0 libmpg123-0 libnet-dbus-perl libnet-http-perl libnet-smtp-ssl-perl libnet-ssleay-perl libopenal-data
  libopenal1 libopencv-core2.4v5 libopencv-imgproc2.4v5 libopenjp2-7 libopenmpt0 libpgm-5.2-0 libpostproc54
  libraw1394-11 librubberband2 libshine3 libsmbclient libsnappy1v5 libsodium18 libsoxr0 libspeex1 libssh-gcrypt-4
  libswresample2 libswscale4 libtheora0 libtie-ixhash-perl libtimedate-perl libtwolame0 liburi-perl libva-drm1
  libva-wayland1 libva-x11-1 libva1 libvdpau-va-gl1 libvdpau1 libvpx4 libwavpack1 libwebp6 libwebpmux2 libwww-perl
  libwww-robotrules-perl libx11-protocol-perl libx264-148 libx265-95 libxaw7 libxcomposite1 libxft2 libxml-parser-perl
  libxml-twig-perl libxml-xpathengine-perl libxmu6 libxpm4 libxvidcore4 libxxf86dga1 libzmq5 libzvbi-common libzvbi0
  mesa-va-drivers mesa-vdpau-drivers mpv perl-openssl-defaults python3-pkg-resources rtmpdump va-driver-all
  vdpau-driver-all x11-utils x11-xserver-utils xdg-utils youtube-dl
The following packages will be upgraded:
  libwbclient0 python-samba samba samba-common samba-common-bin samba-libs
6 upgraded, 125 newly installed, 0 to remove and 180 not upgraded.
Need to get 36.9 MB/44.5 MB of archives.
After this operation, 109 MB of additional disk space will be used.
Do you want to continue? [Y/n]

(even better, maybe we can help identify an alternative to mpv - a networked and scriptable video playback / media management environment - with more focused functionality.)

in general i think any plan that involves manipulating a user’s installed linux packages should be considered very carefully. norns can of course be used as a general purpose linux computer, but when implementing features and updates we assume that there are no deviations from installed versions of packages, and can’t guarantee that upgrades from general debian sources will not break the norns system.

6 Likes

well at least now you’ll have youtube-dl

(but yes, agreed)

3 Likes

Thanks so much @tehn and @zebra for your replies. And very good points and things to take into consideration.

Agreed- it’s a fine line between “it’s a turing-compatible music machine that runs Linux, go nuts!” and “everyone should be able to use this”, though. I’m still very much navigating this myself as a user, finding the documentation of some scripts lacking in some places. It’s a fun journey, though.

OK, so selecting a radio station should then probably be under the mod itself I guess. I’ll have a think about it.

2 Likes

we should work on adding something like ffmpeg to the base image for these kinds of needs. even that is a big chunk of space on the factory eemc if installed in the normal fashion… (but is it worth maintaining our own package? hm probably not.)

6 Likes

Is there a limit to how many mods can be enabled and loaded simultaneously?

I am getting inconsistent behavior with mods loading and unloading themselves after they have been enabled.

Broadcast was my initial and only mod and I had no issues there, but when I installed norns-tuning, oblique, and passthrough, some show the . that they are loaded and then mods that were already enabled go back to +

EDIT:

I just was able to load broadcast, oblique, passthrough at the same time but when I enabled norns-tuning and then restarted the norns, all 4 mods reverted back to an enabled but unloaded state. Disabling norns-tuning and then restarting with all other mods enabled loads them again (minus norns-tuning).

1 Like

Any weird output in matron repl?

Did you clone and rename the tuning mod as described in readme?

Please be advised also that tuning mod is not released, if you want to help me test it that is fine, but be prepared to give detailed feedback on odd behavior.

I will try and reproduce the observed behavior when I get a chance

1 Like

that did it - no more weirdeness - my apologies on missing that step :confused:

2 Likes

Can Preset Load/Save/Delete Menu behaviour overriden by mods? For example I lost a lot of presets by accidentally pressing Delete Preset. It would be nice to add confirmation before deleting or make backup each time you save preset. I wonder if it is possible to add this feature using mods.

2 Likes

short answer: yes!

long answer: one of the aims of mods is extend & override default behaviors. your use-case is a good example.

2 Likes

also, i think a delete confirmation would be a not unreasonable candidate for addition to the main branch

11 Likes

I put this question in the Crow v 3.0 thread and am realizing now, this thread may have been a better place.

I’m very curious if it would be feasible/not overly complicated to turn the bowering script into a system mod. I would love to be able to launch crow scripts from the Bowery collection regardless of what Norns script I’m running at the moment. For those who have already dipped into mod work, I’m curious if something of this nature would be a very intensive undertaking or relatively simple in the grand scheme of things.

5 Likes

Hi @tehn and alll! I’ve got a question about creating a Params menu for my mod. I’ve looked at a few mod scripts to see how to do this, and while it looks straightforward, I can’t get the params in my mod script to generate in the global PARAMETERS menu on the Norns. I’ve got my mod running, it loads correctly, I load up a script that has its own parameters, but when I go to see the global parameters menu, I don’t see my own params listed there. I’ve added my code here. Can anyone see anything that I’m doing wrong?:

-- Mod Test for CH-Norns
-- Anthony T. Marasco (2022)

--libs and core
local mod = require "core/mods"
local UI = require("ui")
local textentry = require("textentry")


local pages
local tabs
local tab_titles = {{"Connect", "Clients"},{"Send","Receive"},{"Rooms","Chat"}}
local key_shift = false
connection_menu = {"Change Username", "Change Room", "Connect"}
client_list = {"ed","charles_12", "simon"}
menu_lists = {}
current_menu = nil
menu_selection = nil


-- Params
params:add_separator("Sending")
params:add_group("Global Options", 2)
params:add_binary("global_send_room_toggle", "send all to room (K3)","toggle", 0)
params:set_action("global_send_room_toggle",function(x)
  if x == 0 then
    params:hide("global_send_room_name")
  elseif x == 1 then
    params:show("global_send_room_name")
  end
   _menu.rebuild_params()
end)
params:add{
  type = "option",
  id = "global_send_room_name",
  name = "global room",
  options = {"MaxRoom","WebRoom","PDRoom"},
  default = 1
}
params:hide("global_send_room_name")


mod.hook.register("system_post_startup", "my startup hacks", function()
  state.system_post_startup = true
end)

mod.hook.register("script_pre_init", "my init hacks", function()
  -- tweak global environment here ahead of the script `init()` function being called
end)



local state = {
  isConnected = false
}

--
-- [optional] menu: extending the menu system is done by creating a table with
-- all the required menu functions defined.
--
function update_pages()
    tabs:set_index(1)
    tabs.titles = tab_titles[pages.index]
end


local m = {}

m.key = function(n, z)
    if z == 1 then
      if n == 1 then
        key_shift = true
       -- print("holding k1")
      elseif n == 2 then
        mod.menu.exit()
      elseif n == 3 then
        print("Selected: "..current_menu.entries[current_menu.index])
      end
    else
      if n == 1 then
        key_shift = false
       -- print("k1 released")
      end
    end
end

-- Enc
  m.enc = function(n, d)
    if n == 1 and key_shift then
       tabs:set_index_delta(d, false)
       if tabs.index == 1 then
         print("Tab 1")
         
        else
          print("Tab 2")
       end
        
    elseif n == 2 then
     if pages.index == 1 then
       if tabs.index == 1 then
         menu_lists[1]:set_index_delta(d, false)
         else
         menu_lists[2]:set_index_delta(d, false)
       end
      end
    elseif n == 3 then
        -- Scroll through pages
        pages:set_index_delta(d,false)
        update_pages()
    end

    --if pages.index == 1 then
        --if tabs.index == 1 then
            -- Do Connect Stuff
           -- if n ==2 then
                --
            --elseif n == 3 then
                --
           -- end

        --else
            -- Do Client Stuff
           -- if n ==2 then
                --
           -- elseif n == 3 then
                --
           -- end
        --end
    --elseif pages.index == 2 then
        --if tabs.index == 1 then
            -- Do Send Stuff
           -- if n ==2 then
                --
           -- elseif n == 3 then
                --
           -- end

       -- else
            -- Do Receive Stuff
          --  if n ==2 then
                --
          --  elseif n == 3 then
                --
          --  end
       -- end
   -- elseif pages.index == 3 then
        --if tabs.index == 1 then
            -- Do Rooms Stuff
         --   if n ==2 then
                --
         --   elseif n == 3 then
                --
         --   end

        --else
            -- Do Chat Stuff
         --   if n ==2 then
                --
         --   elseif n == 3 then
                --
          --  end
        --end
    --end
    mod.menu.redraw()
  end


 m.redraw = function()
    screen.clear()
    pages:redraw()
    tabs:redraw()
    
    if pages.index == 1 then
      if tabs.index == 1 then
      current_menu = menu_lists[1]
      --current_menu:redraw()
      else
        current_menu = menu_lists[2]
      --menu_lists[2]:redraw()
      end
      current_menu:redraw()
    end
    -- Page displays
  
  screen.update()
end

-- on menu entry, ie, if you wanted to start timers
m.init = function() 
  -- User Interface
-- Init Pages
pages = UI.Pages.new(1,3)
tabs = UI.Tabs.new(1, tab_titles[pages.index])
menu_lists[1] = UI.List.new(0, 20,1,connection_menu)
menu_lists[2] = UI.List.new(0, 20,1, client_list)
end 


m.deinit = function() end -- on menu exit




-- register the mod menu
--
-- NOTE: `mod.this_name` is a convienence variable which will be set to the name
-- of the mod which is being loaded. in order for the menu to work it must be
-- registered with a name which matches the name of the mod in the dust folder.
--
mod.menu.register(mod.this_name, m)


-- [optional] returning a value from the module allows the mod to provide
-- library functionality to scripts via the normal lua `require` function.
--
-- NOTE: it is important for scripts to use `require` to load mod functionality
-- instead of the norns specific `include` function. using `require` ensures
-- that only one copy of the mod is loaded. if a script were to use `include`
-- new copies of the menu, hook functions, and state would be loaded replacing
-- the previous registered functions/menu each time a script was run.
--
-- here we provide a single function which allows a script to get the mod's
-- state table. using this in a script would look like:
--
-- local mod = require 'name_of_mod/lib/mod'
-- local the_state = mod.get_state()
--
local api = {}

api.get_state = function()
  return state
end

return api

One thing that jumped out at me is whether you’re adding params within a register hook? It doesn’t look like it. For example see here, and here.

I’m just learning mods myself though, hope that helps.

1 Like

That did the trick! Thanks for your help. Weirdly, other Mod scripts that I’ve read through don’t put their param building into any hooks, but still get drawn to the global params menu, so I’m still confused as to why that works.

I don’t think that’s possible. I agree with above answer. I also would recommend that the mod add params after script init by wrapping the init function, to preserve parameters ordering. See my tuning mod for example