Permalink
Checking mergeability…
Don’t worry, you can still create the pull request.
Comparing changes
Choose two branches to see what’s changed or to start a new pull request.
If you need to, you can also .
Open a pull request
Create a new pull request by comparing changes across two branches. If you need to, you can also .
- 16 commits
- 2 files changed
- 0 comments
- 1 contributor
Commits on Dec 31, 2020
Commits on Jan 01, 2021
Commits on Jan 03, 2021
Commits on Jan 04, 2021
Unified
Split
Showing
with
125 additions
and 18 deletions.
- +1 −0 README.md
- +124 −18 sines.lua
| @@ -32,3 +32,4 @@ Saving a pset saves the note selection and midi mapping. The last saved pset is | ||
|
|
||
| Control individual sine amplitude, envelope type, and FM index with norns or a midi controller. Controls are mapped from the norns parameters page. | ||
|
|
||
| Attach an Arc and watch the envelopes spin! Knobs control speed and direction, LED indicates oscillations. 16 voices are addressable by scrolling enc2 on norns in groups of four. | ||
| @@ -7,8 +7,27 @@ | ||
| -- K3 + E2 - change envelope | ||
| -- K3 + E3 - change FM index | ||
| -- K2 + K3 - set voice panning | ||
|
|
||
| -- arc lfo control vars | ||
| a = arc.connect() | ||
| -- aspirational | ||
| -- c = clock.set_source("midi") | ||
| local framerate = 40 | ||
| local arcDirty = true | ||
| local startTime | ||
| local tau = math.pi * 2 | ||
| local newSpeed = false | ||
| local options = {} | ||
| -- sort of overloading #lfo as a synonym for voices. Great start? | ||
| local lfo = {} | ||
| for i=1,16 do | ||
| lfo[i] = {init=1, freq=1, counter=1, interpolator=1} | ||
| end | ||
| local voice_quad = 1 | ||
|
|
||
| -- engine control vars | ||
| local sliders = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} | ||
| local env_types = {"drone", "am1", "am2", "am3", "pulse1", "pulse2", "pulse3", "pulse4", "ramp1", "ramp2", "ramp3", "ramp4", "evolve1", "evolve2", "evolve3", "evolve4"} | ||
| local env_types = {"drone", "am1", "am2", "am3", "pulse1", "pulse2", "pulse3", "pulse4", "ramp1", "ramp2", "ramp3", "ramp4", "evolve1", "evolve2", "evolve3", "evolve4", "arc"} | ||
| -- env_num, env_bias, attack, decay. bias of 1.0 is used to create a static drone | ||
| local envs = {{1, 1.0, 1.0, 1.0},--drone | ||
| {2, 0.0, 0.001, 0.01},--am1 | ||
| @@ -25,7 +44,8 @@ local envs = {{1, 1.0, 1.0, 1.0},--drone | ||
| {13, 0.3, 10.0, 10.0},--evolve1 | ||
| {14, 0.3, 15.0, 11.0},--evolve2 | ||
| {15, 0.3, 20.0, 12.0},--evolve3 | ||
| {16, 0.3, 25.0, 15.0}--evolve4 | ||
| {16, 0.3, 25.0, 15.0},--evolve4 | ||
| {17, 0.0, 1.0, 1.0} -- arc | ||
| } | ||
| local env_values = {} | ||
| local fm_index_values = {} | ||
| @@ -43,11 +63,31 @@ local key_2_pressed = 0 | ||
| local key_3_pressed = 0 | ||
| local toggle = false | ||
| local pan_display = "m" | ||
| local interp_divisor = 100 | ||
|
|
||
|
|
||
| engine.name = "Sines" | ||
| MusicUtil = require "musicutil" | ||
|
|
||
| function init() | ||
| startTime = util.time() | ||
| lfo_metro = metro.init() | ||
| lfo_metro.time = 0.01 | ||
| lfo_metro.count = -10 | ||
| lfo_metro.event = function() | ||
| currentTime = util.time() | ||
| for i = 1,#lfo do | ||
| lfo[i].counter = ((lfo[i].counter + (1*lfo[i].freq)))%100 | ||
| lfo[i].ar = lfo[i].counter*0.64 | ||
| end | ||
| end | ||
| lfo_metro:start() | ||
| local arc_redraw_metro = metro.init() | ||
| arc_redraw_metro.event = function() | ||
| arc_redraw() | ||
| redraw() | ||
| end | ||
| arc_redraw_metro:start(1 / framerate) | ||
| print("loaded Sines engine") | ||
| add_params() | ||
| set_voices() | ||
| @@ -64,29 +104,29 @@ function add_params() | ||
| min = 0, max = 127, default = 60, formatter = function(param) return MusicUtil.note_num_to_name(param:get(), true) end, | ||
| action = function() build_scale() end} | ||
| --set voice vol, fm, env controls | ||
| for i = 1,16 do | ||
| for i = 1,#lfo do | ||
| params:add_control("vol" .. i, "voice " .. i .. " volume", controlspec.new(0.0, 1.0, 'lin', 0.01, 0.0)) | ||
| params:set_action("vol" .. i, function(x) set_voice(i - 1, x) end) | ||
| end | ||
| for i = 1,16 do | ||
| for i = 1,#lfo do | ||
| params:add_control("fm_index" .. i, "fm_index " .. i, controlspec.new(0.1, 100.0, 'lin', 0.1, 3.0)) | ||
| params:set_action("fm_index" .. i, function(x) engine.fm_index(i - 1, x) end) | ||
| end | ||
| for i = 1,16 do | ||
| params:add_number("env" .. i, "env " .. i, 1, 16, 1) | ||
| for i = 1,#lfo do | ||
| params:add_number("env" .. i, "env " .. i, 1, #lfo, 1) | ||
| params:set_action("env" .. i, function(x) set_env(i, x) end) | ||
| end | ||
| params:default() | ||
| edit = 0 | ||
| end | ||
|
|
||
| function build_scale() | ||
| notes = MusicUtil.generate_scale_of_length(params:get("root_note"), params:get("scale_mode"), 16) | ||
| local num_to_add = 16 - #notes | ||
| notes = MusicUtil.generate_scale_of_length(params:get("root_note"), params:get("scale_mode"), #lfo) | ||
| local num_to_add = #lfo - #notes | ||
| for i = 1, num_to_add do | ||
| table.insert(notes, notes[16 - num_to_add]) | ||
| table.insert(notes, notes[#lfo - num_to_add]) | ||
| end | ||
| for i = 1,16 do | ||
| for i = 1,#lfo do | ||
| --also set notes | ||
| set_freq(i, MusicUtil.note_num_to_freq(notes[i])) | ||
| end | ||
| @@ -99,7 +139,7 @@ function set_voice(voice_num, value) | ||
| end | ||
|
|
||
| function set_voices() | ||
| for i = 1,16 do | ||
| for i = 1,#lfo do | ||
| cents_values[i] = 0 | ||
| env_values[i] = "drone" | ||
| fm_index_values[i] = 3.0 | ||
| @@ -111,8 +151,8 @@ function set_voices() | ||
| end | ||
|
|
||
| function set_env(synth_num, env_num) | ||
| --goofy way to loop through the envs list, but whatever | ||
| for i = 1,16 do | ||
| -- goofy way to loop through the envs list, but whatever | ||
| for i = 1,#env_types do | ||
| if envs[i][1] == env_num then | ||
| engine.env_bias(synth_num - 1, envs[i][2]) | ||
| engine.amp_atk(synth_num - 1, envs[i][3]) | ||
| @@ -139,7 +179,7 @@ m.event = function(data) | ||
| local d = midi.to_msg(data) | ||
| if d.type == "cc" then | ||
| --set all the sliders + fm values | ||
| for i = 1,16 do | ||
| for i = 1,#lfo do | ||
| sliders[i] = (params:get("vol" .. i))*32-1 | ||
| fm_index_values[i] = params:get("fm_index" .. i) | ||
| if sliders[i] > 32 then sliders[i] = 32 end | ||
| @@ -156,7 +196,7 @@ function set_pan() | ||
| if toggle then | ||
| pan_display = "l/r" | ||
| --set hard l/r pan values | ||
| for i = 1,16 do | ||
| for i = 1,#lfo do | ||
| if i % 2 == 0 then | ||
| --even, pan right | ||
| set_synth_pan(i,1) | ||
| @@ -168,24 +208,90 @@ function set_pan() | ||
| end | ||
| if not toggle then | ||
| pan_display = "m" | ||
| for i = 1,16 do | ||
| for i = 1,#lfo do | ||
| set_synth_pan(i,0) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| -- hardware functions | ||
|
|
||
| function a.delta(n,delta) | ||
| -- gross, refactor plz, I'm tired of typing the numbers. | ||
| -- this seems like a maths thing. something about a vector of 16 | ||
| -- into a 4x4 matrix? Computer, do what I say in English, not Lua | ||
| local voice = 1 | ||
| if voice_quad == 1 then | ||
| voice = n | ||
| elseif voice_quad == 2 then | ||
| voice = n + 4 | ||
| elseif voice_quad == 3 then | ||
| voice = n + 8 | ||
| elseif voice_quad == 4 then | ||
| voice = n + 12 | ||
| end | ||
| if lfo[voice].interpolater == 1 then | ||
| lfo[voice].freq = lfo[voice].freq + delta/interp_divisor | ||
| newSpeed = true | ||
| -- we need polarity of the LED ring | ||
| if lfo[voice].freq > 0 then | ||
| -- seventeen is a special arc envelope, sorry I know magic numbers... | ||
| envs[17][3] = 0.001 | ||
| -- we need seconds per cycle for the envelope | ||
| envs[17][4] = 1 / lfo[voice].freq | ||
| else | ||
| envs[17][4] = 0.001 | ||
| envs[17][3] = math.abs(1 / lfo[voice].freq) | ||
| end | ||
| set_env(voice, 17) | ||
| end | ||
| lfo[voice].interpolater = 1 | ||
| lastTouched = n | ||
| arcDirty = true | ||
| end | ||
|
|
||
| function arc_redraw() | ||
| local brightness = 12 | ||
| a:all(0) | ||
| -- there are 4 encoders and 16 lfos. Using the same voice_quad logic | ||
| -- as the encoder delta, determine what quadrant we are in before setting | ||
| -- the value of seg | ||
| for n = 1,4 do | ||
| if voice_quad == 1 then | ||
| seg = lfo[n].ar/64 | ||
| elseif voice_quad == 2 then | ||
| seg = lfo[n + 4].ar/64 | ||
| elseif voice_quad == 3 then | ||
| seg = lfo[n + 8].ar/64 | ||
| elseif voice_quad == 4 then | ||
| seg = lfo[n + 12].ar/64 | ||
| end | ||
| a:segment(n, seg*tau, tau*seg+0.2, brightness) | ||
| end | ||
| a:refresh() | ||
| end | ||
|
|
||
| function enc(n, delta) | ||
| if n == 1 then | ||
| params:delta('output_level', delta) | ||
|
|
||
| elseif n == 2 then | ||
| if key_2_pressed == 0 and key_3_pressed == 0 then | ||
| --navigate up/down the list of sliders | ||
| --accum wraps around 0-15 | ||
| accum = (accum + delta) % 16 | ||
| --edit is the slider number | ||
| edit = accum | ||
| -- this would be better with maths | ||
| if edit < 4 then | ||
| voice_quad = 1 | ||
| elseif edit > 3 and edit < 8 then | ||
| voice_quad = 2 | ||
| elseif edit > 7 and edit < 12 then | ||
| voice_quad = 3 | ||
| elseif edit > 11 then | ||
| voice_quad = 4 | ||
| end | ||
| elseif key_2_pressed == 0 and key_3_pressed == 1 then | ||
| env_accum = (env_accum + delta) % 16 | ||
| --env_edit is the env_values selector | ||
| @@ -290,4 +396,4 @@ function redraw() | ||
| screen.level(15) | ||
| screen.text(math.floor((params:get('output_level')) * 10 / 10) .. " dB") | ||
| screen.update() | ||
| end | ||
| end | ||