-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
seamstress support #4
base: main
Are you sure you want to change the base?
Conversation
also i've only included the most basic patches to make things work. additional feature: hot plugging midi devices i have a more complex & dirty patch that allows supporting the async registration of midi devices in seamstress. this used to be mandatory but robbielyman/seamstress#57 simplified things. still it could be a nice to have to support hot-plugging midi devices into seamstress. here is the patch for completeness
NB_VOICES = 4
-- sync registration of midi devices into nb
function init()
nb.voice_count = NB_VOICES
nb:init()
-- [...] add standard script params
for i=1,NB_VOICES do
nb:add_param("nb_voice_"..i, "nb Voice #"..i)
end
nb:add_player_params()
end
-- async registration of midi devices into nb
if seamstress then
midi.add = function (dev, is_input)
if not is_input then
print("new midi out detected ("..dev.name.."), registering into nb")
nb.add_midi_player_seamstress(dev)
-- update all
for i=1,NB_VOICES do
-- will re-write the definition of the parameter (w/ a new options list, action...)
nb:add_param("nb_voice_" .. i, "nb Voice #" .. i)
end
end
end
end
-- ------------------------------------------------------------------------
-- patched fns
-- Add a voice select parameter. Returns the parameter. You can then call
-- `get_player()` on the parameter object, which will return a player you can
-- use to play notes and stuff.
function nb:add_param(param_id, param_name)
local names = {}
for name, _ in pairs(note_players) do
table.insert(names, name)
end
table.sort(names)
table.insert(names, 1, "none")
local names_inverted = tab.invert(names)
local string_param_id = param_id .. "_hidden_string"
-- THE PATCH IS HERE
local id = params.lookup[param_id]
if id then
-- if we call current fn (`nb:add_param`) on a parameter that already exists, we override its definition w/ a new new one
params.params[id] = make_param_option(param_id, param_name, names, 1)
else
params:add_option(param_id, param_name, names, 1)
params:add_text(string_param_id, "_hidden string", "")
end
params:hide(string_param_id)
local p = params:lookup_param(param_id)
-- [...]
end
-- ------------------------------------------------------------------------
-- added fns
-- like `add_midi_players` but for registering the new device `v`
-- registers the new player but also calls `player:add_params()` at the end
function nb.add_midi_player_seamstress(v)
local i = v.id
for j = 1, nb.voice_count do
(function(i, j)
local conn = midi.connect_output(v.port)
local player = {
conn = conn
}
function player:add_params()
params:add_group("midi_voice_" .. i .. '_' .. j, "midi " .. j .. ": " .. abbreviate(v.name), 3)
params:add_number("midi_chan_" .. i .. '_' .. j, "channel", 1, 16, 1)
params:add_number("midi_modulation_cc_" .. i .. '_' .. j, "modulation cc", 1, 127, 72)
params:add_number("midi_bend_range_" .. i .. "_" .. j, "bend range", 1, 48, 12)
params:hide("midi_voice_" .. i .. '_' .. j)
end
function player:ch()
return params:get("midi_chan_" .. i .. '_' .. j)
end
function player:note_on(note, vel)
self.conn:note_on(note, util.clamp(math.floor(127 * vel), 0, 127), self:ch())
end
function player:note_off(note)
self.conn:note_off(note, 0, self:ch())
end
function player:active()
params:show("midi_voice_" .. i .. '_' .. j)
_menu.rebuild_params()
end
function player:inactive()
params:hide("midi_voice_" .. i .. '_' .. j)
_menu.rebuild_params()
end
function player:modulate(val)
self.conn:cc(params:get("midi_modulation_cc_" .. i .. '_' .. j),
util.clamp(math.floor(127 * val), 0, 127),
self:ch())
end
function player:modulate_note(note, key, value)
if key == "pressure" then
self.conn:key_pressure(note, util.round(value * 127), self:ch())
end
end
function player:pitch_bend(note, amount)
local bend_range = params:get("midi_bend_range_" .. i .. '_' .. j)
if amount < -bend_range then
amount = -bend_range
end
if amount > bend_range then
amount = bend_range
end
local normalized = amount / bend_range -- -1 to 1
local send = util.round(((normalized + 1) / 2) * 16383)
self.conn:pitchbend(send, self:ch())
end
function player:describe()
local mod_d = "cc"
if params.lookup["midi_modulation_cc_" .. i .. '_' .. j] ~= nil then
mod_d = "cc " .. params:get("midi_modulation_cc_" .. i .. '_' .. j)
end
return {
name = "v.name",
supports_bend = true,
supports_slew = false,
note_mod_targets = { "pressure" },
modulate_description = mod_d
}
end
nb.players["midi: " .. abbreviate(v.name) .. " " .. j] = player
player:add_params()
end)(i, j)
end
end
-- copy of `ParamSet:add_option` but without the "register in paramset" at the end
local function make_param_option(id, name, options, default)
local cs = controlspec.new(1, #options, "lin", 1, default, units, 1 / (#options - 1))
local frm = function(param)
return options[(type(param) == "table" and param:get() or param)]
end
return control.new(id, name, cs, frm)
end |
so, hum, seamstress' midi APIs got very close to norns' in the meantime and so there isn't that much difference remaining. this is in a state where this could be squashed & merged now. w/ this, it would be possible to package nb voices alongside various sound engines that could be run on a computer alongside seamstress (which would be neato). |
things seem to work but i haven't done much testing.