├── 0_wifi.png ├── 7_midi.png ├── 8_osc.png ├── 9_grid.png ├── 1_basics.png ├── 5_output.png ├── 6_input.png ├── 2_interface.png ├── 4_animation.png ├── A_include.png ├── C_softcut.png ├── 3_interaction.png ├── B_parameters.png ├── 1_blank.lua ├── FAQS.md ├── lib ├── engine_input.sc ├── view.lua ├── engine_output.sc └── midi_device_helper.lua ├── 2_interface.lua ├── A_include.lua ├── B_parameters.lua ├── 4_animation.lua ├── 3_interaction.lua ├── 8_osc.lua ├── 5_output.lua ├── 7_midi.lua ├── 9_grid.lua ├── 6_input.lua ├── C_softcut.lua └── README.md /0_wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/0_wifi.png -------------------------------------------------------------------------------- /7_midi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/7_midi.png -------------------------------------------------------------------------------- /8_osc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/8_osc.png -------------------------------------------------------------------------------- /9_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/9_grid.png -------------------------------------------------------------------------------- /1_basics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/1_basics.png -------------------------------------------------------------------------------- /5_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/5_output.png -------------------------------------------------------------------------------- /6_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/6_input.png -------------------------------------------------------------------------------- /2_interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/2_interface.png -------------------------------------------------------------------------------- /4_animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/4_animation.png -------------------------------------------------------------------------------- /A_include.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/A_include.png -------------------------------------------------------------------------------- /C_softcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/C_softcut.png -------------------------------------------------------------------------------- /3_interaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/3_interaction.png -------------------------------------------------------------------------------- /B_parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neauoire/tutorial/HEAD/B_parameters.png -------------------------------------------------------------------------------- /1_blank.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ TUTORIAL 4 | -- ////\\\\ PART 1 5 | -- \\\\//// 6 | -- \\\\//// BLANK 7 | -- \\\\//// 8 | -- 9 | 10 | -- Main 11 | 12 | function init() 13 | print('init') 14 | redraw() 15 | end 16 | 17 | -- Interactions 18 | 19 | function key(id,state) 20 | print('key',id,state) 21 | end 22 | 23 | function enc(id,delta) 24 | print('enc',id,delta) 25 | end 26 | 27 | -- Render 28 | 29 | function redraw() 30 | print('redraw') 31 | screen.clear() 32 | screen.update() 33 | end 34 | 35 | -- Executed on script close/change/quit 36 | 37 | function cleanup() 38 | print('cleanup') 39 | end 40 | -------------------------------------------------------------------------------- /FAQS.md: -------------------------------------------------------------------------------- 1 | # FAQs 2 | 3 | ### Error: SCRIPT ERROR: AUDIO ENGINE 4 | 5 | It is likely that you have a duplicated audio engine, to find the name, in [Maiden](http://norns.local/maiden/), display the REPL console, select the `sc` tab and type `;restart`. This should display something along the lines of 6 | 7 | ``` 8 | ERROR: duplicate Class found: 'Engine_SimplePassThru' 9 | ``` 10 | 11 | ### How to take a screenshot 12 | 13 | In [Maiden](http://norns.local/maiden/), type the following: 14 | 15 | ``` 16 | s_export_png("/home/we/screenshot.png") 17 | ``` 18 | 19 | In SSH, type the following: 20 | 21 | ``` 22 | magick convert $1.png -gamma 1.25 -filter point -resize 400% -gravity center -background black -extent 120% $1-m.png 23 | ``` 24 | -------------------------------------------------------------------------------- /lib/engine_input.sc: -------------------------------------------------------------------------------- 1 | Engine_InputTutorial : CroneEngine { 2 | var amp=0; 3 | var max and max or val 81 | end 82 | 83 | -- Interval 84 | 85 | re = metro.init() 86 | re.time = 1.0 / 15 87 | re.event = function() 88 | viewport.frame = viewport.frame + 1 89 | redraw() 90 | end 91 | re:start() 92 | -------------------------------------------------------------------------------- /3_interaction.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ TUTORIAL 4 | -- ////\\\\ PART 3 5 | -- \\\\//// 6 | -- \\\\//// INTERACTION 7 | -- \\\\//// 8 | -- 9 | 10 | local viewport = { width = 128, height = 64 } 11 | local focus = { x = 0, y = 0 } 12 | 13 | -- Main 14 | 15 | function init() 16 | -- Render Style 17 | screen.level(15) 18 | screen.aa(0) 19 | screen.line_width(1) 20 | -- Center focus 21 | reset() 22 | -- Render 23 | redraw() 24 | end 25 | 26 | function reset() 27 | print('reset') 28 | focus.x = viewport.width/2 29 | focus.y = viewport.height/2 30 | end 31 | 32 | -- Interactions 33 | 34 | function key(id,state) 35 | print('key',id,state) 36 | reset() 37 | redraw() 38 | end 39 | 40 | function enc(id,delta) 41 | print('enc',id,delta) 42 | if id == 2 then 43 | focus.x = clamp(focus.x + delta,6,123) 44 | elseif id == 3 then 45 | focus.y = clamp(focus.y + delta,6,59) 46 | end 47 | redraw() 48 | end 49 | 50 | -- Render 51 | 52 | function draw_frame() 53 | screen.rect(1, 1, viewport.width-1, viewport.height-1) 54 | screen.stroke() 55 | end 56 | 57 | function draw_crosshair() 58 | screen.move(focus.x,focus.y - 4) 59 | screen.line(focus.x,focus.y - 2) 60 | screen.move(focus.x - 4,focus.y) 61 | screen.line(focus.x - 2,focus.y) 62 | screen.move(focus.x,focus.y + 3) 63 | screen.line(focus.x,focus.y + 1) 64 | screen.move(focus.x + 3,focus.y) 65 | screen.line(focus.x + 1,focus.y) 66 | screen.stroke() 67 | end 68 | 69 | function draw_position() 70 | screen.move(5,viewport.height - (8 * 1)) 71 | screen.text(math.floor(focus.x)..','..math.floor(focus.y)) 72 | end 73 | 74 | function redraw() 75 | screen.clear() 76 | draw_frame() 77 | draw_crosshair() 78 | draw_position() 79 | screen.update() 80 | end 81 | 82 | -- Utils 83 | 84 | function clamp(val,min,max) 85 | return val < min and min or val > max and max or val 86 | end 87 | -------------------------------------------------------------------------------- /lib/midi_device_helper.lua: -------------------------------------------------------------------------------- 1 | -- A small library to manage midi input and output parameters. 2 | 3 | local Midi_Device_Helper = { devices = {}, input = nil, output = nil } 4 | 5 | Midi_Device_Helper.init = function(self) 6 | -- Get a list of midi devices 7 | for id,device in pairs(midi.vports) do 8 | print('Midi Device Helper','Found device: '..device.name) 9 | self.devices[id] = device.name 10 | end 11 | -- Create Params 12 | params:add{type = "option", id = "midi_output", name = "Midi Output", options = self.devices, default = 1, action=self.set_output} 13 | params:add{type = "option", id = "midi_input", name = "Midi Input", options = self.devices, default = 2, action=self.set_input} 14 | params:add_separator() 15 | Midi_Device_Helper.set_output() 16 | Midi_Device_Helper.set_input() 17 | end 18 | 19 | Midi_Device_Helper.get_output_name = function(self) 20 | return self.devices[params:get("midi_output")] 21 | end 22 | 23 | Midi_Device_Helper.get_input_name = function(self) 24 | return self.devices[params:get("midi_input")] 25 | end 26 | 27 | Midi_Device_Helper.set_output = function(x) 28 | print('Midi Device Helper','Set output device: '..Midi_Device_Helper:get_output_name()) 29 | Midi_Device_Helper.output = midi.connect(params:get("midi_output")) 30 | end 31 | 32 | Midi_Device_Helper.set_input = function(x) 33 | print('Midi Device Helper','Set input device: '..Midi_Device_Helper:get_input_name()) 34 | Midi_Device_Helper.input = midi.connect(params:get("midi_input")) 35 | end 36 | 37 | Midi_Device_Helper.on_input = function(self,fn) 38 | if self.input == nil then print('Midi Device Helper','Missing Input Device') ; return end 39 | if self.input.event then 40 | self.input.event = nil 41 | end 42 | self.input.event = fn 43 | end 44 | 45 | Midi_Device_Helper.on_output = function(self,fn) 46 | if self.output == nil then print('Midi Device Helper','Missing Output Device') ; return end 47 | if self.output.event then 48 | self.output.event = nil 49 | end 50 | self.output.event = fn 51 | end 52 | 53 | return Midi_Device_Helper -------------------------------------------------------------------------------- /8_osc.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ TUTORIAL 4 | -- ////\\\\ PART 8 5 | -- \\\\//// 6 | -- \\\\//// OSC 7 | -- \\\\//// 8 | -- 9 | 10 | local viewport = { width = 128, height = 64, frame = 0 } 11 | 12 | -- Main 13 | 14 | function init() 15 | connect() 16 | -- Render Style 17 | screen.level(15) 18 | screen.aa(0) 19 | screen.line_width(1) 20 | -- Render 21 | redraw() 22 | end 23 | 24 | function connect() 25 | osc.event = on_osc_event 26 | end 27 | 28 | function on_osc_event(path, args, from) 29 | msg = { path = path, ip = from[1], port = from[2], bytes = args } 30 | redraw(msg) 31 | end 32 | 33 | -- Interactions 34 | 35 | function key(id,state) 36 | if state == 1 and midi_signal then 37 | midi_signal.note_on(60,127) 38 | elseif midi_signal then 39 | midi_signal.note_off(60,127) 40 | end 41 | redraw() 42 | end 43 | 44 | function enc(id,delta) 45 | redraw() 46 | end 47 | 48 | -- Render 49 | 50 | function draw_frame() 51 | screen.level(15) 52 | screen.rect(1, 1, viewport.width-1, viewport.height-1) 53 | screen.stroke() 54 | end 55 | 56 | function draw_labels() 57 | line_height = 8 58 | screen.level(1) 59 | screen.move(5,viewport.height - (line_height * 1)) 60 | screen.text('path') 61 | screen.move(5,viewport.height - (line_height * 2)) 62 | screen.text('ip') 63 | screen.move(5,viewport.height - (line_height * 3)) 64 | screen.text('port') 65 | screen.move(5,viewport.height - (line_height * 4)) 66 | screen.text('len') 67 | end 68 | 69 | function draw_msg(msg) 70 | local line_height = 8 71 | screen.level(15) 72 | if msg.path then 73 | screen.move(30,viewport.height - (line_height * 1)) 74 | screen.text(msg.path) 75 | end 76 | if msg.ip then 77 | screen.move(30,viewport.height - (line_height * 2)) 78 | screen.text(msg.ip) 79 | end 80 | if msg.port then 81 | screen.move(30,viewport.height - (line_height * 3)) 82 | screen.text(msg.port) 83 | end 84 | if msg.bytes then 85 | screen.move(30,viewport.height - (line_height * 4)) 86 | screen.text(#msg.bytes) 87 | end 88 | end 89 | 90 | function redraw(msg) 91 | screen.clear() 92 | draw_frame() 93 | draw_labels() 94 | if msg then 95 | draw_msg(msg) 96 | end 97 | screen.stroke() 98 | screen.update() 99 | end 100 | 101 | -- Utils 102 | 103 | function clamp(val,min,max) 104 | return val < min and min or val > max and max or val 105 | end 106 | 107 | function note_to_hz(note) 108 | return (440 / 32) * (2 ^ ((note - 9) / 12)) 109 | end 110 | -------------------------------------------------------------------------------- /5_output.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ TUTORIAL 4 | -- ////\\\\ PART 5 5 | -- \\\\//// 6 | -- \\\\//// OUTPUT 7 | -- \\\\//// 8 | -- 9 | 10 | engine.name = 'OutputTutorial' 11 | 12 | local viewport = { width = 128, height = 64, frame = 0 } 13 | local focus = { x = 0, y = 0 } 14 | 15 | -- Main 16 | 17 | function init() 18 | -- Render Style 19 | screen.level(15) 20 | screen.aa(0) 21 | screen.line_width(1) 22 | -- Center focus 23 | reset() 24 | -- Render 25 | redraw() 26 | end 27 | 28 | function reset() 29 | focus.x = viewport.width/2 30 | focus.y = viewport.height/2 31 | end 32 | 33 | -- Interactions 34 | 35 | function key(id,state) 36 | reset() 37 | redraw() 38 | end 39 | 40 | function enc(id,delta) 41 | if id == 2 then 42 | focus.x = clamp(focus.x + delta,6,123) 43 | elseif id == 3 then 44 | focus.y = clamp(focus.y + delta,6,59) 45 | re.time = clamp(focus.y/viewport.height,0.05,4) 46 | end 47 | redraw() 48 | end 49 | 50 | -- Render 51 | 52 | function draw_frame() 53 | screen.rect(1, 1, viewport.width-1, viewport.height-1) 54 | screen.stroke() 55 | end 56 | 57 | function draw_crosshair() 58 | screen.move(focus.x,focus.y - 4) 59 | screen.line(focus.x,focus.y - 2) 60 | screen.move(focus.x - 4,focus.y) 61 | screen.line(focus.x - 2,focus.y) 62 | screen.move(focus.x,focus.y + 3) 63 | screen.line(focus.x,focus.y + 1) 64 | screen.move(focus.x + 3,focus.y) 65 | screen.line(focus.x + 1,focus.y) 66 | screen.stroke() 67 | 68 | for i = viewport.width,1,-1 do 69 | if i % 2 == 1 then 70 | screen.move(i, focus.y) 71 | screen.line(i + 1, focus.y) 72 | end 73 | end 74 | 75 | for i = viewport.height,1,-1 do 76 | if i % 2 == 1 then 77 | screen.move(focus.x, i) 78 | screen.line(focus.x, i + 1) 79 | end 80 | end 81 | end 82 | 83 | function draw_freq() 84 | screen.move(5,viewport.height - (8 * 1)) 85 | screen.text(get_freq()..'hz') 86 | screen.move(5,viewport.height - (8 * 2)) 87 | screen.text(clamp(focus.y/viewport.height,0.05,4)..'ms') 88 | end 89 | 90 | function redraw() 91 | screen.clear() 92 | draw_frame() 93 | draw_crosshair() 94 | draw_freq() 95 | screen.stroke() 96 | screen.update() 97 | end 98 | 99 | -- Utils 100 | 101 | function clamp(val,min,max) 102 | return val < min and min or val > max and max or val 103 | end 104 | 105 | function get_freq() 106 | return ((focus.x/viewport.width) * 700) + 300 107 | end 108 | 109 | -- Interval 110 | 111 | re = metro.init() 112 | re.time = 0.5 113 | re.event = function() 114 | viewport.frame = viewport.frame + 1 115 | engine.hz(get_freq()) 116 | end 117 | re:start() 118 | -------------------------------------------------------------------------------- /7_midi.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ TUTORIAL 4 | -- ////\\\\ PART 7 5 | -- \\\\//// 6 | -- \\\\//// MIDI 7 | -- \\\\//// 8 | -- 9 | 10 | engine.name = 'OutputTutorial' 11 | 12 | local midi_signal_in 13 | local midi_signal_out 14 | local viewport = { width = 128, height = 64, frame = 0 } 15 | 16 | -- Main 17 | 18 | function init() 19 | connect() 20 | -- Render Style 21 | screen.level(15) 22 | screen.aa(0) 23 | screen.line_width(1) 24 | -- Render 25 | redraw() 26 | end 27 | 28 | function connect() 29 | midi_signal_in = midi.connect(1) 30 | midi_signal_in.event = on_midi_event 31 | midi_signal_out = midi.connect(2) 32 | end 33 | 34 | function on_midi_event(data) 35 | msg = midi.to_msg(data) 36 | play(msg) 37 | redraw(msg) 38 | end 39 | 40 | function play(msg) 41 | if msg.type == 'note_on' then 42 | hz = note_to_hz(msg.note) 43 | engine.amp(msg.vel / 127) 44 | engine.hz(hz) 45 | end 46 | end 47 | 48 | -- Interactions 49 | 50 | function key(id,state) 51 | if state == 1 and midi_signal_out then 52 | midi_signal_out:note_on(60,127) 53 | elseif midi_signal_ then 54 | midi_signal_out:note_off(60,127) 55 | end 56 | redraw() 57 | end 58 | 59 | function enc(id,delta) 60 | redraw() 61 | end 62 | 63 | -- Render 64 | 65 | function draw_frame() 66 | screen.level(15) 67 | screen.rect(1, 1, viewport.width-1, viewport.height-1) 68 | screen.stroke() 69 | end 70 | 71 | function draw_labels() 72 | local line_height = 8 73 | screen.level(1) 74 | screen.move(5,viewport.height - (line_height * 1)) 75 | screen.text('note') 76 | screen.move(5,viewport.height - (line_height * 2)) 77 | screen.text('ch') 78 | screen.move(5,viewport.height - (line_height * 3)) 79 | screen.text('vel') 80 | screen.move(5,viewport.height - (line_height * 4)) 81 | screen.text('type') 82 | end 83 | 84 | function draw_event(event) 85 | local line_height = 8 86 | screen.level(15) 87 | if event.note then 88 | screen.move(30,viewport.height - (line_height * 1)) 89 | screen.text(msg.note) 90 | end 91 | if event.ch then 92 | screen.move(30,viewport.height - (line_height * 2)) 93 | screen.text(msg.ch) 94 | end 95 | if event.vel then 96 | screen.move(30,viewport.height - (line_height * 3)) 97 | screen.text(msg.vel) 98 | end 99 | if event.type then 100 | screen.move(30,viewport.height - (line_height * 4)) 101 | screen.text(msg.type) 102 | end 103 | end 104 | 105 | function redraw(event) 106 | screen.clear() 107 | draw_frame() 108 | draw_labels() 109 | if event then 110 | draw_event(event) 111 | end 112 | screen.stroke() 113 | screen.update() 114 | end 115 | 116 | -- Utils 117 | 118 | function clamp(val,min,max) 119 | return val < min and min or val > max and max or val 120 | end 121 | 122 | function note_to_hz(note) 123 | return (440 / 32) * (2 ^ ((note - 9) / 12)) 124 | end 125 | -------------------------------------------------------------------------------- /9_grid.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ TUTORIAL 4 | -- ////\\\\ PART 9 5 | -- \\\\//// 6 | -- \\\\//// GRID 7 | -- \\\\//// 8 | -- 9 | 10 | local g 11 | local viewport = { width = 128, height = 64, frame = 0 } 12 | local focus = { x = 1, y = 1, brightness = 15 } 13 | 14 | -- Main 15 | 16 | function init() 17 | connect() 18 | -- Render Style 19 | screen.level(15) 20 | screen.aa(0) 21 | screen.line_width(1) 22 | -- Render 23 | update() 24 | end 25 | 26 | function connect() 27 | g = grid.connect() 28 | g.key = on_grid_key 29 | g.add = on_grid_add 30 | g.remove = on_grid_remove 31 | end 32 | 33 | function is_connected() 34 | return g.device ~= nil 35 | end 36 | 37 | function on_grid_key(x,y,z) 38 | focus.x = x 39 | focus.y = y 40 | update() 41 | end 42 | 43 | function on_grid_add(g) 44 | print('on_add') 45 | end 46 | 47 | function on_grid_remove(g) 48 | print('on_remove') 49 | end 50 | 51 | function update() 52 | g:all(0) 53 | g:led(focus.x,focus.y,focus.brightness) 54 | g:refresh() 55 | redraw() 56 | end 57 | 58 | -- Interactions 59 | 60 | function key(id,state) 61 | if id == 2 and state == 1 then 62 | focus.brightness = 15 63 | elseif id == 3 and state == 1 then 64 | focus.brightness = 5 65 | end 66 | update() 67 | end 68 | 69 | function enc(id,delta) 70 | if id == 2 then 71 | focus.x = clamp(focus.x + delta, 1, 16) 72 | elseif id == 3 then 73 | focus.y = clamp(focus.y + delta, 1, 8) 74 | end 75 | update() 76 | end 77 | 78 | -- Render 79 | 80 | function draw_frame() 81 | screen.level(15) 82 | screen.rect(1, 1, viewport.width-1, viewport.height-1) 83 | screen.stroke() 84 | end 85 | 86 | function draw_pixel(x,y) 87 | if focus.x == x and focus.y == y then 88 | screen.stroke() 89 | screen.level(15) 90 | end 91 | screen.pixel((x*offset.spacing) + offset.x, (y*offset.spacing) + offset.y) 92 | if focus.x == x and focus.y == y then 93 | screen.stroke() 94 | screen.level(1) 95 | end 96 | end 97 | 98 | function draw_grid() 99 | if is_connected() ~= true then return end 100 | screen.level(1) 101 | offset = { x = 30, y = 13, spacing = 4 } 102 | for x=1,16,1 do 103 | for y=1,8,1 do 104 | draw_pixel(x,y) 105 | end 106 | end 107 | screen.stroke() 108 | end 109 | 110 | function draw_label() 111 | screen.level(15) 112 | local line_height = 8 113 | screen.move(5,viewport.height - (line_height * 1)) 114 | if is_connected() ~= true then 115 | screen.text('Grid is not connected.') 116 | else 117 | screen.text(focus.x..','..focus.y) 118 | end 119 | screen.stroke() 120 | end 121 | 122 | function redraw() 123 | screen.clear() 124 | draw_frame() 125 | draw_grid() 126 | draw_label() 127 | screen.stroke() 128 | screen.update() 129 | end 130 | 131 | -- Utils 132 | 133 | function clamp(val,min,max) 134 | return val < min and min or val > max and max or val 135 | end 136 | -------------------------------------------------------------------------------- /6_input.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ TUTORIAL 4 | -- ////\\\\ PART 6 5 | -- \\\\//// 6 | -- \\\\//// INPUT 7 | -- \\\\//// 8 | -- 9 | 10 | engine.name = 'InputTutorial' 11 | 12 | local viewport = { width = 128, height = 64 } 13 | local signal = { amp_in_l = 0, amp_out_l = 0, amp_in_l_max = 0, amp_out_l_max = 0 } 14 | local controls = { amp = 1.0 } 15 | local refresh_rate = 1.0 / 15 16 | local p_amp_in 17 | local p_amp_out 18 | 19 | function init() 20 | -- Render Style 21 | screen.level(15) 22 | screen.aa(0) 23 | screen.line_width(1) 24 | -- Render 25 | redraw() 26 | -- Listen 27 | audio.monitor_mono() 28 | engine.amp(1.0) 29 | -- Poll in 30 | p_amp_in = poll.set("amp_in_l") 31 | p_amp_in.time = refresh_rate 32 | p_amp_in.callback = function(val) 33 | signal.amp_in_l = val 34 | if signal.amp_in_l > signal.amp_in_l_max then 35 | signal.amp_in_l_max = signal.amp_in_l 36 | end 37 | end 38 | -- Poll out 39 | p_amp_out = poll.set("amp_out_l") 40 | p_amp_out.time = refresh_rate 41 | p_amp_out.callback = function(val) 42 | signal.amp_out_l = val 43 | if signal.amp_out_l > signal.amp_out_l_max then 44 | signal.amp_out_l_max = signal.amp_out_l 45 | end 46 | end 47 | end 48 | 49 | function update() 50 | engine.amp(controls.amp) 51 | redraw() 52 | end 53 | 54 | function repoll() 55 | p_amp_in:update() 56 | p_amp_out:update() 57 | end 58 | 59 | -- Controls 60 | 61 | function key(id,state) 62 | if state == 0 then return end 63 | if id == 2 or id == 3 then 64 | if controls.amp == 1 then 65 | controls.amp = 0 66 | else 67 | controls.amp = 1 68 | end 69 | end 70 | update() 71 | end 72 | 73 | function enc(id,delta) 74 | controls.amp = clamp(controls.amp + (delta/10), 0.1, 1) 75 | update() 76 | end 77 | 78 | -- Render 79 | 80 | function draw_frame() 81 | screen.level(15) 82 | screen.rect(1, 1, viewport.width-1, viewport.height-1) 83 | screen.stroke() 84 | end 85 | 86 | function draw_uv(value,maximum,offset) 87 | size = {width = 4, height = viewport.height - 4} 88 | pos = {x = viewport.width - size.width - offset, y = 2} 89 | ratio = value/maximum 90 | activity = clamp(size.height - (ratio * size.height),3,size.height) 91 | screen.line_width(size.width) 92 | -- Draw 93 | screen.level(1) 94 | screen.move(pos.x,pos.y) 95 | screen.line(pos.x,pos.y + size.height) 96 | screen.stroke() 97 | screen.level(15) 98 | screen.move(pos.x,pos.y + size.height) 99 | screen.line(pos.x,activity) 100 | screen.stroke() 101 | screen.line_width(1) 102 | end 103 | 104 | function draw_controls() 105 | x = viewport.width - 16 106 | y = math.floor(viewport.height-(controls.amp * (viewport.height-5)) - 2) 107 | -- Draw 108 | screen.level(15) 109 | screen.line_width(1) 110 | screen.move(x,y) 111 | screen.line(x + 4,y) 112 | screen.stroke() 113 | end 114 | 115 | function draw_label() 116 | line_height = 8 117 | screen.move(5,viewport.height - (line_height * 1)) 118 | screen.text(controls.amp..'amp') 119 | end 120 | 121 | function redraw() 122 | screen.clear() 123 | draw_frame() 124 | draw_controls() 125 | draw_label() 126 | draw_uv(signal.amp_in_l,signal.amp_in_l_max,0) 127 | draw_uv(signal.amp_out_l,signal.amp_out_l_max,5) 128 | screen.stroke() 129 | screen.update() 130 | end 131 | 132 | -- Utils 133 | 134 | function clamp(val,min,max) 135 | return val < min and min or val > max and max or val 136 | end 137 | 138 | -- Interval 139 | 140 | re = metro.init() 141 | re.time = refresh_rate 142 | re.event = function() 143 | repoll() 144 | redraw() 145 | end 146 | re:start() 147 | -------------------------------------------------------------------------------- /C_softcut.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ////\\\\ 3 | -- ////\\\\ TUTORIAL 4 | -- ////\\\\ PART 12 5 | -- \\\\//// 6 | -- \\\\//// SOFTCUT 7 | -- \\\\//// 8 | -- 9 | 10 | local m = metro.init() 11 | local positions = {0,0,0,0} 12 | local viewport = { width = 128, height = 64, center = 64, middle = 32 } 13 | 14 | local rate = 1.0 15 | local fader = 0.5 16 | local offset = 0 17 | local length = 1 18 | 19 | function init() 20 | -- Render Style 21 | screen.level(15) 22 | screen.aa(0) 23 | screen.line_width(1) 24 | -- Setup 25 | m.time = 1.0 26 | audio.level_adc_cut(1) -- send audio input to softcut input 27 | setup_softcut() 28 | m:start() 29 | end 30 | 31 | function update_positions(i,pos) 32 | positions[i] = pos 33 | redraw() 34 | end 35 | 36 | function setup_softcut() 37 | softcut.buffer_clear() 38 | -- Voice 1 39 | softcut.enable(1,1) 40 | softcut.buffer(1,1) 41 | softcut.level(1,1.0) 42 | softcut.loop(1,1) -- voice, 1 = loop 43 | softcut.loop_start(1,0) 44 | softcut.loop_end(1,length) 45 | softcut.position(1,1) 46 | softcut.play(1,1) 47 | softcut.fade_time(1,0.25) 48 | softcut.phase_quant(1,1.0 / 30) 49 | softcut.level_input_cut(1,1,1.0) -- set input rec level: input channel, voice, level 50 | softcut.rec_level(1,0.5) -- set voice 1 record level 51 | softcut.pre_level(1,0.5) -- set voice 1 pre level 52 | softcut.rec(1,1) 53 | -- Voice 2 54 | softcut.fade_time(2,0.50) 55 | softcut.phase_quant(2,1.0 / 30) 56 | softcut.level_input_cut(2,1,1.0) 57 | -- Polls 58 | softcut.event_phase(update_positions) 59 | softcut.poll_start_phase() 60 | end 61 | 62 | function key(n,z) 63 | if z == 1 then 64 | if n == 3 then 65 | rate = clamp(rate+0.2,-4,4) 66 | elseif n == 2 then 67 | rate = clamp(rate-0.2,-4,4) 68 | end 69 | end 70 | refresh() 71 | redraw() 72 | end 73 | 74 | function enc(n,d) 75 | -- fader 76 | if n==1 then 77 | fader = clamp(fader+d/100,0,1) 78 | end 79 | -- offset 80 | if n==2 then 81 | offset = clamp(offset+d/100,0,5-length) 82 | end 83 | -- length 84 | if n==3 then 85 | length = clamp(length+d/100,0.25,4) 86 | end 87 | refresh() 88 | redraw() 89 | end 90 | 91 | function refresh() 92 | softcut.rate(1,rate) 93 | softcut.loop_start(1,offset) 94 | softcut.loop_end(1,offset+length) 95 | softcut.pre_level(1,fader) 96 | softcut.rec_level(1,1-fader) 97 | end 98 | 99 | function draw_frame() 100 | screen.rect(1, 1, viewport.width-1, viewport.height-1) 101 | screen.stroke() 102 | end 103 | 104 | function redraw() 105 | screen.clear() 106 | draw_frame() 107 | local pad = 10 108 | local width = viewport.width - (2*pad) 109 | local limit = 5 110 | local seek_from = (offset/limit) * width 111 | local seek_to = ((offset+length)/limit) * width 112 | local seek_at = clamp((positions[1]/limit) * width,seek_from,seek_to) 113 | -- Fader 114 | screen.move(pad,viewport.middle-pad) 115 | screen.text(string.format("%.2f",fader)) 116 | -- Rate 117 | screen.move(viewport.width-pad,viewport.middle-pad) 118 | screen.text_right(string.format("%.2f",rate)) 119 | -- Offset 120 | screen.move(pad,viewport.middle+pad+5) 121 | screen.text(string.format("%.2f",offset)) 122 | -- Length 123 | screen.move(viewport.width-pad,viewport.middle+pad+5) 124 | screen.text_right(string.format("%.2f",offset+length)) 125 | -- Position 126 | screen.move(viewport.center,viewport.middle+pad+5) 127 | screen.text_center(string.format("%.2f",positions[1])) 128 | screen.level(2) 129 | -- Background 130 | screen.move(pad,viewport.middle) 131 | screen.line(pad + width,viewport.middle) 132 | screen.stroke() 133 | screen.level(15) 134 | screen.move(seek_from+pad,viewport.middle) 135 | screen.line(seek_to+pad,viewport.middle) 136 | screen.move(seek_at+pad,viewport.middle-3) 137 | screen.line(seek_at+pad,viewport.middle+2) 138 | screen.stroke() 139 | screen.update() 140 | end 141 | 142 | function clamp(val,min,max) 143 | return val < min and min or val > max and max or val 144 | end 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | This is a crash-course tutorial to get started with the sound computer [Norns](https://monome.org/norns/), follow the discussion [here](https://llllllll.co/t/norns-tutorial/23241). If you would like to contribute, visit the [repository](https://github.com/neauoire/tutorial). 4 | 5 | ## Connection 6 | 7 | You need to get the device online and **accessible to your WIFI network**. 8 | 9 | - Add your router details to the device, in `WIFI > ADD`. 10 | - Once the device is online, the `IP` field will display the _Norns's IP_, example `192.168.128.109`. 11 | 12 | 13 | 14 | - **To edit code(IDE)**, open [Maiden](http://norns.local/maiden/) by going `http://norns.local/` in your browser. 15 | - **To transfer files(SFTP)**, open your [SFTP client](https://cyberduck.io/download/) and connect to the _Norns's IP_, with username `we` and password `sleep`. 16 | - **To install new projects(SSH)**, open a new [terminal window](https://www.youtube.com/watch?v=IGmfU6QU5dI), type `ssh we@norns.local`, with password `sleep`. 17 | 18 | You are now connected to the device, via the IDE, FTP and SSH. 19 | 20 | ## Setup 21 | 22 | In your **terminal window**, while being connected via **SSH**. 23 | 24 | - **Move into the code folder** where scripts are typically held with `cd dust/code`. 25 | - **Install this tutorial** on your device with `git clone https://github.com/neauoire/tutorial.git` 26 | - **Move into the tutorial folder** with `cd tutorial`. 27 | 28 | You are now ready to use this tutorial. 29 | 30 | ## Basics 31 | 32 | In [Maiden](http://norns.local/maiden/), reload the window to make sure the tutorial files are visible, and look at the [first example file](https://github.com/neauoire/tutorial/blob/master/1_blank.lua) of this tutorial. 33 | 34 | - **Navigate to the first example** with `code > tutorial > 1_blank.lua`. 35 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 36 | - **Look at the blank screen**, there is nothing to see. 37 | - **Look at the logs**, at the bottom of the browser window in the console, it should display `init` and `redraw`. 38 | - This example is not interactive. 39 | 40 | 41 | 42 | You have run your first Norns script, from Github, via Maiden. The basic functions are as follow 43 | 44 | ``` 45 | function init() # On launch 46 | function key(id,state) # On key press 47 | function enc(id,delta) # On knob turn 48 | function cleanup() # On Quit 49 | ``` 50 | 51 | ## Interface 52 | 53 | In [Maiden](http://norns.local/maiden/), look at the [second example file](https://github.com/neauoire/tutorial/blob/master/2_interface.lua) of this tutorial. 54 | 55 | - **Navigate to the second example** with `code > tutorial > 2_interface.lua`. 56 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 57 | - **Look at the screen**, notice the basic lines being drawn. 58 | - This example is not interactive. 59 | 60 | 61 | 62 | The interface is draw by a combination of methods from the [screen object](https://monome.github.io/norns/doc/modules/screen.html). The screen always must be cleared, and updated between changes. 63 | 64 | ``` 65 | screen.clear() 66 | screen.move(10,10) 67 | screen.line(30,30) 68 | screen.update() 69 | ``` 70 | 71 | ## Interaction 72 | 73 | In [Maiden](http://norns.local/maiden/), look at the [third example file](https://github.com/neauoire/tutorial/blob/master/3_interaction.lua) of this tutorial. 74 | 75 | - **Navigate to the third example** with `code > tutorial > 3_interface.lua`. 76 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 77 | - **Move the crosshair** by turning the two knobs to the right of the device. 78 | - **Look at the screen**, notice the crosshair moving. 79 | 80 | 81 | 82 | The interactions are triggering the `key(id,state)` and `enc(id,delta)` functions, remember to `redraw` the interface after an interaction. The key state is a value of either `1`(key_down), or `0`(key_up). The knobs delta is a value of either `-1`(counter_clockwise), or `1`(clockwise). 83 | 84 | ``` 85 | function key(id,state) 86 | print('key',id,state) 87 | end 88 | 89 | function enc(id,delta) 90 | print('enc',id,delta) 91 | end 92 | ``` 93 | 94 | ## Animation 95 | 96 | In [Maiden](http://norns.local/maiden/), look at the [fourth example file](https://github.com/neauoire/tutorial/blob/master/4_animation.lua) of this tutorial. 97 | 98 | - **Navigate to the fourth example** with `code > tutorial > 4_animation.lua`. 99 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 100 | - **Change the animation modulation** by turning the two knobs to the right of the device. 101 | - **Look at the screen**, notice the screen being updated automatically. 102 | 103 | 104 | 105 | The animation loop uses the [metro object](https://monome.github.io/norns/doc/modules/metro.html), the `1.0/15` time parameter sets the reload to the rate of `15fps`. 106 | 107 | ``` 108 | re = metro.init() 109 | re.time = 1.0 / 15 110 | re.event = function() 111 | redraw() 112 | end 113 | re:start() 114 | ``` 115 | 116 | ## Output 117 | 118 | In [Maiden](http://norns.local/maiden/), look at the [fifth example file](https://github.com/neauoire/tutorial/blob/master/5_output.lua) of this tutorial. 119 | 120 | - **Navigate to the fifth example** with `code > tutorial > 5_output.lua`. 121 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 122 | - **Change the rate of notes** by turning the left knob, to the right of the device. 123 | - **Change the frequency of these notes** by turning the right knob, to the right of the device. 124 | - **Listen to the generated sound** by connecting a pair of headphones to the leftmost 1/4" input. 125 | 126 | 127 | 128 | To send messages from Norns to Supercollider, use the `addCommand` method in your supercollider synth. 129 | 130 | ``` 131 | this.addCommand("amp", "f", { arg msg; 132 | amp = msg[1]; 133 | }); 134 | ``` 135 | 136 | ## Input 137 | 138 | In [Maiden](http://norns.local/maiden/), look at the [sixth example file](https://github.com/neauoire/tutorial/blob/master/6_input.lua) of this tutorial. 139 | 140 | - **Navigate to the sixth example** with `code > tutorial > 6_input.lua`. 141 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 142 | - **Send audio to the device** by connecting a sound source in the fourth 1/4" input. 143 | - **Change the amplitude of the outgoing sound** by turning the one of the two knobs, to the right of the device. 144 | - **Listen to the modified sound** by connecting a pair of headphones to the leftmost 1/4" input. 145 | 146 | 147 | 148 | To receive the audio signal, you need to start polling with `poll.set("amp_in_l")`, and bind a callback function to `p_amp_in.callback`. 149 | 150 | ``` 151 | p_amp_in = poll.set("amp_in_l") 152 | p_amp_in.time = refresh_rate 153 | p_amp_in.callback = function(val) 154 | print(val) 155 | end 156 | p_amp_in:start() 157 | ``` 158 | 159 | ### Available polls 160 | 161 | - `amp_in_l` / `amp_in_r` 162 | - `amp_out_l` / `amp_out_r` 163 | - `cpu_avg` / `cpu_peak` 164 | - `pitch_in_l` / `pitch_in_r` 165 | - `tape_play_pos` / `tape_rec_dur` 166 | 167 | ## Midi 168 | 169 | In [Maiden](http://norns.local/maiden/), look at the [seventh example file](https://github.com/neauoire/tutorial/blob/master/7_midi.lua) of this tutorial. 170 | 171 | - **Navigate to the seventh example** with `code > tutorial > 7_midi.lua`. 172 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 173 | - **Send midi to the device** by connecting a midi instrument via usb and pressing a key. 174 | - **Listen to the resulting note** by connecting a pair of headphones to the leftmost 1/4" input. 175 | 176 | You can control which device is sending midi, by selecting it in `SYSTEM > MIDI`, this example will receive midi from the first device, and send midi to the second device. 177 | 178 | 179 | 180 | To receive the midi signal, you need to connect to the midi interface with `midi_signal = midi.connect()`, and give it a method to get the event, like `midi_signal.event = on_midi_event`. 181 | 182 | ``` 183 | midi_signal = midi.connect() 184 | midi_signal.event = on_midi_event 185 | 186 | function on_midi_event(data) 187 | msg = midi.to_msg(data) 188 | tab.print(msg) 189 | end 190 | ``` 191 | 192 | ## OSC 193 | 194 | In [Maiden](http://norns.local/maiden/), look at the [eight example file](https://github.com/neauoire/tutorial/blob/master/8_osc.lua) of this tutorial. 195 | 196 | - **Navigate to the eight example** with `code > tutorial > 8_osc.lua`. 197 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 198 | - **Send osc to the device** by addressing `norns.local` at port `10111`. 199 | - **Look at the incoming data**. 200 | 201 | 202 | 203 | To receive the osc signal, you need to connect to the osc interface with `osc.event = on_osc_event`, and give it a method to get the event, like `on_osc_event(path, args, from)`, the `from` parameter is an array including the `ip` and `port`. 204 | 205 | ## Grid 206 | 207 | In [Maiden](http://norns.local/maiden/), look at the [ninth example file](https://github.com/neauoire/tutorial/blob/master/9_grid.lua) of this tutorial. 208 | 209 | - **Connect a Monome grid device**, and select it in `SYSTEM > GRID`. 210 | - **Navigate to the ninth example** with `code > tutorial > 9_grid.lua`. 211 | - **Run the script** by clicking on the play button to the top right of the Maiden window. 212 | - **Touch a button on the grid** and watch it light up on the Norns. 213 | - **Move the light across the monome** by turning the knobs on the Norns. 214 | - **Change the brightness** by pressing the buttons on the Norns. 215 | 216 | 217 | 218 | To communicate with the grid, you need to connect to the grid with `g = grid.connect()`, and give it a method to get the key event, like `g.key = on_grid_key`. The `led(x,y,brightness)` method allows you to toggle LEDs. 219 | 220 | ``` 221 | g:all(0) 222 | g:led(1,2,15) 223 | g:refresh() 224 | ``` 225 | 226 | ## Include 227 | 228 | In [Maiden](http://norns.local/maiden/), look at the [tenth example file](https://github.com/neauoire/tutorial/blob/master/A_include.lua) of this tutorial. 229 | 230 | - **Navigate to the example** and run the script. 231 | - **Rotate the knobs** to change the position values of the included file. 232 | - **Press the key** to select a different view. 233 | 234 | 235 | 236 | Including files with `local view = include('lib/view')`, will first look in the directory of the current script. This allows using relative paths to use libraries local to the script. The returned value of the included script will be available in your main script file. 237 | 238 | ``` 239 | -- lib/target.lua 240 | return { 241 | value = 5 242 | } 243 | 244 | -- main script 245 | local target = include('lib/target') 246 | print(target.value) 247 | ``` 248 | 249 | In lua, you can create new **objects and methods** as shown in the following snippet, notice how the `self` parameter is omitted when using the colon character before the method name. 250 | 251 | ``` 252 | obj = { c = 4 } 253 | 254 | obj.add = function(self,a,b) 255 | return a + b + self.c 256 | end 257 | 258 | obj:add(2,3) -- 9 259 | ``` 260 | 261 | ## Parameters 262 | 263 | In [Maiden](http://norns.local/maiden/), look at the [eleventh example file](https://github.com/neauoire/tutorial/blob/master/B_parameters.lua) of this tutorial. 264 | 265 | - **Navigate to the example** and run the script. 266 | - **Press the leftmost key**, rotate the leftmost knobs to the right, to see and modify the available parameters. 267 | 268 | 269 | 270 | New parameters can be added with `params:add`, and read with `params:get`. 271 | 272 | ``` 273 | -- Add 274 | params:add{type = "number", id = "number", name = "Number", min = 1, max = 48, default = 4} 275 | params:add{type = "option", id = "option", name = "Option", options = {'yes','no'}, default = 1} 276 | 277 | -- Read 278 | print(params:get("number")) 279 | ``` 280 | 281 | ## Softcut 282 | 283 | In [Maiden](http://norns.local/maiden/), look at the [softcut example file](https://github.com/neauoire/tutorial/blob/master/C_softcut.lua) of this tutorial. 284 | 285 | - **Navigate to the example** and run the script. 286 | - **Connect a sound source**, into the 4th input(Input 1) from the left of the Norns. 287 | - **Fade between the sound source and the recorded loop**, by rotating the leftmost knob. 288 | - **Change the offset of the recorded loop**, by rotating the second knob. 289 | - **Change the length of recorded loop**, by rotating the rightmost knob. 290 | - **Change the playback speed**, by pressing the rightmost buttons. 291 | 292 | 293 | 294 | [Softcut](https://monome.github.io/norns/doc/modules/softcut.html) is a recording tool built into the Norns, it makes it easy to record and play samples at various speed. Generally, the `softcut` methods will operate on a voice, softcut has a maximum of 6 voices, so the functions are always in the format of `.method(voice_id,value)`. For example, here a few methods used in this example file: 295 | 296 | - `softcut.fade_time(1,0.25)`, **set the fade in/out** length of the sample, of `voice1`, to `0.25`. 297 | - `softcut.rate(1,2)`, **set the rate(speed)**, of the sample to play, at speed `200%`, also `-2` would play in reverse. 298 | - `softcut.loop_start(2,0.5)`, **set the offset** of the sample, of `voice2`, to `0.5`. 299 | - `softcut.loop_end(3,4)`, **set the limit** of the sample, of `voice3`, to `4`. 300 | 301 | You can also explore each one with the [Softcut Studies](https://llllllll.co/t/norns-softcut-studies/23585). 302 | 303 | ## Useful Commands 304 | 305 | - If you need to restart the device, type `;restart` in Maiden. 306 | - If you want to take a screenshot, type `s_export_png("/home/we/screenshot.png")` in Maiden. 307 | 308 | ## Without WIFI 309 | 310 | Connect the device via USB, wWhere (tab) appears hit the `tab` key to autocomplete the serial number, then press `enter`. 311 | 312 | ``` 313 | screen /dev/tty.usb(tab) 115200 314 | ``` 315 | 316 | Once logged in with `we/sleep`, type: 317 | 318 | ``` 319 | systemctl stop norns-matron 320 | ~/norns/build/matron/matron 321 | ``` 322 | 323 | ## Useful Links 324 | 325 | - [Help Thread](https://llllllll.co/t/norns-help/14016), support community. 326 | - [Scripting Thread](https://llllllll.co/t/norns-scripting/14120), libraries community. 327 | - [Development Thread](https://llllllll.co/t/norns-development/14073), tools community. 328 | - [Supercollider Thread](https://llllllll.co/t/norns-crone-supercollider/14616), engine community. 329 | - [Ideas Thread](https://llllllll.co/t/norns-ideas/17625), community suggestion. 330 | - [Sketches Thread](https://llllllll.co/t/norns-ideas/17625), snippets community. 331 | - [API docs](https://monome.github.io/norns/doc/index.html), documentation. 332 | - [Repository](https://github.com/monome/norns), source files. 333 | 334 | I hope you enjoyed these simple examples, **good luck**! 335 | --------------------------------------------------------------------------------