Home | ER-101 | ER-102 | ER-301 | Wiki | Contact

Programming Lua


#1

So, it’s bound to happen sooner or later so thought I’d get the ball rolling with a little preemptive discussion on Lua!

I can program, happy wiggling my way around a command line, I’ve been known to compile the odd kernel here and there, have a reasonable amount experience with I guess quite a lot of programming languages and even written a soon to be released synth, but I’ve never touched Lua!

I’m keen to get going at some point as I would like to write some units for the ER-301, even if they are just little fun things or kind of like ‘macros’ for the sake of convenience in repeat applications; I am kinda hoping I’m going to be able to copy an paste chunks of code and make my own personal units. I’m planning on learning lua to be able to do this.

So, I guess my questions before I embark on this little mission are, who here already knows lua? Is anyone else planning on getting their hands dirty?

Also, any relevant resources would be good to compile here :slight_smile:

Here’s the obvious one: http://www.lua.org


V0.3.x Firmware Workout: Better late than never
#2

I highly recommend reading the book Programming in Lua (PIL) from front to back. It’s very readable, to the point, and clearly denotes all the major gotchas of the language.

FYI, the ER-301 embeds the lua 5.3 interpreter with the following system libraries loaded: package, coroutine, table, io, string, math, utf8, debug. I am using SWIG to wrap C (os-level) and C++ (application-level) interfaces for use in the lua interpreter.


#3

@odevices In the future do you plan to publish any documents regarding Unit development and practices for designing within the ER-301 framework?


#4

Brian, so dsp code written in C++ can be wrapped to make units?


#5

Thanks!! Book seems cool, I started reading :slight_smile:

Lua installed, if you have brew on a Mac you can just type

> brew install lua

… loads of brew stuff here …

==> Summary
🍺  /usr/local/Cellar/lua/5.2.4_4: 144 files, 705.4KB, built in 9 seconds
localhost:~ steverichardson$ lua
Lua 5.2.4  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> print("hello world!")
hello world!
> 

p.s. to create code blocks in forum posts like this, enclose the code with three backtick symbols like this:

```
Paste your code here
```

On a UK keyboard the backtick is main character on the ‘tilda’ key just to the right of the left shift button.


#6

I just reached Chapter 5:

Functions used by a Lua program can be defined both in Lua and in C (or in any other language used by the host application).

http://www.lua.org/pil/5.html

The host runs C and C++, so unless I’ve got something horribly wrong, that’s a yes!!

:astonished:


#7

Will start on this very soon!


#8

Yes, although initially I believe I will rely mainly on working examples to communicate the basics. I plan on having a whole site dedicated to the documentation and knowledge-base building of the framework. I’m evaluating various platforms. Hmm, maybe I should start a topic to get your opinions on that:

A category will be also added to this forum for the questions and discussions the framework will undoubtedly create.


#9

Yes. That is correct.

The topology goes like this:

  • Chains are sequences of units.
  • Units are directed acyclic graphs (DAG) of objects.
  • Objects have a defined set of named input and output ports as well as named parameters/options. Objects also define an algorithm for computing the outputs given the inputs and parameters/options.

Here is what the Limiter object looks like:

/*
 * Limiter.h
 *
 *  Created on: 22 Jun 2016
 *      Author: clarkson
 */

#ifndef APP_OBJECTS_ARITH_LIMITER_H_
#define APP_OBJECTS_ARITH_LIMITER_H_

#include <app/objects/Object.h>

namespace er301 {

class Limiter: public Object {
public:
	Limiter(const std::string & name);
	virtual ~Limiter();

#ifndef SWIGLUA
	virtual void process();
	Inlet mInput { "In" };
	Outlet mOutput { "Out" };
	Option mType { "Type", LimiterChoices::cubic };
#endif

};

} /* namespace er301 */

#endif /* APP_OBJECTS_ARITH_LIMITER_H_ */

/*
 * Limiter.cpp
 *
 *  Created on: 22 Jun 2016
 *      Author: clarkson
 */

#include <app/objects/arith/Limiter.h>
#include <arm_neon.h>

namespace er301 {

Limiter::Limiter(const std::string & name) :
		Object(name) {
	addInput(mInput);
	addOutput(mOutput);
	addOption(mType);
}

Limiter::~Limiter() {

}

static inline void tanhLimiting(float * in, float * out, float * end) {
#if 0
	// tanh approximation (regular floating point)
	// https://varietyofsound.wordpress.com/2011/02/14/efficient-tanh-computation-using-lamberts-continued-fraction/
	// 55k ticks
	while (out < end) {
		float x = *in;
		float x2 = x * x;
		float a = (((x2 + 378) * x2 + 17325) * x2 + 135135) * x;
		float b = ((28 * x2 + 3150) * x2 + 62370) * x2 + 135135;
		*out = a / b;
		in++;
		out++;
	}
#elif 1
	// tanh approximation (neon w/ division via newton's method)
	// https://varietyofsound.wordpress.com/2011/02/14/efficient-tanh-computation-using-lamberts-continued-fraction/
	// 10k ticks
	float32x4_t c1 = vdupq_n_f32(378);
	float32x4_t c2 = vdupq_n_f32(17325);
	float32x4_t c3 = vdupq_n_f32(135135);
	float32x4_t c4 = vdupq_n_f32(28);
	float32x4_t c5 = vdupq_n_f32(3150);
	float32x4_t c6 = vdupq_n_f32(62370);
	float32x4_t x, x2, a, b, invb;

	while (out < end) {
		x = vld1q_f32(in);
		x2 = x * x;
		a = (((x2 + c1) * x2 + c2) * x2 + c3) * x;
		b = ((c4 * x2 + c5) * x2 + c6) * x2 + c3;
		// https://en.wikipedia.org/wiki/Division_algorithm#Newton.E2.80.93Raphson_division
		invb = vrecpeq_f32(b);
		// iterate 3 times for 24 bits of precision
		invb = vmulq_f32(invb,vrecpsq_f32(b,invb));
		invb = vmulq_f32(invb,vrecpsq_f32(b,invb));
		invb = vmulq_f32(invb,vrecpsq_f32(b,invb));
		vst1q_f32(out, a * invb);
		out += 4;
		in += 4;
	}
#elif 0
	// tanh approximation (neon w/ regular division)
	// https://varietyofsound.wordpress.com/2011/02/14/efficient-tanh-computation-using-lamberts-continued-fraction/
	// 28k ticks
	float32x4_t c1 = vdupq_n_f32(378);
	float32x4_t c2 = vdupq_n_f32(17325);
	float32x4_t c3 = vdupq_n_f32(135135);
	float32x4_t c4 = vdupq_n_f32(28);
	float32x4_t c5 = vdupq_n_f32(3150);
	float32x4_t c6 = vdupq_n_f32(62370);
	float32x4_t x, x2, a, b;

	while (out < end) {
		x = vld1q_f32(in);
		x2 = x * x;
		a = (((x2 + c1) * x2 + c2) * x2 + c3) * x;
		b = ((c4 * x2 + c5) * x2 + c6) * x2 + c3;
		vst1q_f32(out, a / b);
		out += 4;
		in += 4;
	}
#endif
}

static inline void invSqrtLimiting(float * in, float * out, float * end) {
	// x/sqrt(x*x + 1) (neon w/ inv sqrt via newton's method)
	// 7k ticks
	float32x4_t one = vdupq_n_f32(1.0f);
	float32x4_t x, b, invb;

	while (out < end) {
		x = vld1q_f32(in);
		b = x * x + one;
		invb = vrsqrteq_f32(b); // 4k ticks
		// iterate 3 times for 24 bits of precision
		invb = invb * vrsqrtsq_f32(b, invb * invb);  // 5k ticks
		invb = invb * vrsqrtsq_f32(b, invb * invb); //  6k ticks
		invb = invb * vrsqrtsq_f32(b, invb * invb); //  7k ticks
		vst1q_f32(out, x * invb);
		out += 4;
		in += 4;
	}
}

static inline void cubicLimiting(float * in, float * out, float * end) {
	// x - x*x*x/6
	// 2200 ticks
	float32x4_t c = vdupq_n_f32(-1.0f/6.0f);
	float32x4_t x, x3;
	float32x4_t max = vdupq_n_f32(1.4142135623730951f);
	float32x4_t min = vdupq_n_f32(-1.4142135623730951f);

	while (out < end) {
		x = vld1q_f32(in);
		x = vminq_f32(x, max);
		x = vmaxq_f32(x, min);
		x3 = vmulq_f32(x,vmulq_f32(x,x));
		vst1q_f32(out, vmlaq_f32(x,c,x3));
		out += 4;
		in += 4;
	}
}

static inline void hardLimiting(float * in, float * out, float * end) {
	float32x4_t max = vdupq_n_f32(1.0);
	float32x4_t min = vdupq_n_f32(-1.0);
	float32x4_t x;

	while (out < end) {
		x = vld1q_f32(in);
		x = vminq_f32(x, max);
		x = vmaxq_f32(x, min);
		vst1q_f32(out, x);
		out += 4;
		in += 4;
	}
}

void Limiter::process() {
	float * in = mInput.buffer();
	float * out = mOutput.buffer();
	float * end = out + global.frameLength;

	switch (mType.value()) {
	case LimiterChoices::invSqrt:
		invSqrtLimiting(in, out, end);
		break;
	case LimiterChoices::cubic:
		cubicLimiting(in, out, end);
		break;
	case LimiterChoices::hard:
		hardLimiting(in, out, end);
		break;
	}
}

}
/* namespace er301 */

Up to here is all C++. Lua is where objects are instantiated, connected in to DAGs inside a unit. Then the UI is also described in Lua at the unit level. Like this and take particular notice of the onLoadGraph and onLoadInterface methods:

-- LimiterUnit.lua
-- GLOBALS: app, os, verboseLevel, connect, tie
local app = app
local Class = require "Class"
local UnitSection = require "Chains.UnitSection"
local Fader = require "UnitControls.Fader"
local ModeSelect = require "UnitControls.ModeSelect"
local Encoder = require "Encoder"
local ply = app.SECTION_PLY

local LimiterUnit = Class{}
LimiterUnit:include(UnitSection)

function LimiterUnit:init(args)
  args.title = "Limiter"
  args.mnemonic = "LR"
  UnitSection.init(self,args)
end

-- creation/destruction states

function LimiterUnit:onLoadGraph(pUnit, channelCount)

  if channelCount==2 then
    self:loadStereoGraph(pUnit)
  else
    self:loadMonoGraph(pUnit)
  end
  self.objects.inGain:hardSet("Gain",1.0)
  self.objects.outGain:hardSet("Gain",1.0)
end

function LimiterUnit:loadMonoGraph(pUnit)
  -- create objects
  local inGain = self:createObject("ConstantGain","inGain")
  local outGain = self:createObject("ConstantGain","outGain")
  local limiter = self:createObject("Limiter","limiter")
  -- connect inputs/outputs
  connect(pUnit,"In1",inGain,"In")
  connect(inGain,"Out",limiter,"In")
  connect(limiter,"Out",outGain,"In")
  connect(outGain,"Out",pUnit,"Out1")
end

function LimiterUnit:loadStereoGraph(pUnit)
  -- create objects
  local inGain1 = self:createObject("ConstantGain","inGain1")
  local outGain1 = self:createObject("ConstantGain","outGain1")
  local limiter1 = self:createObject("Limiter","limiter1")
  -- connect inputs/outputs
  connect(pUnit,"In1",inGain1,"In")
  connect(inGain1,"Out",limiter1,"In")
  connect(limiter1,"Out",outGain1,"In")
  connect(outGain1,"Out",pUnit,"Out1")

  -- create objects
  local inGain2 = self:createObject("ConstantGain","inGain2")
  local outGain2 = self:createObject("ConstantGain","outGain2")
  local limiter2 = self:createObject("Limiter","limiter2")
  -- connect inputs/outputs
  connect(pUnit,"In2",inGain2,"In")
  connect(inGain2,"Out",limiter2,"In")
  connect(limiter2,"Out",outGain2,"In")
  connect(outGain2,"Out",pUnit,"Out2")

  tie(inGain2,"Gain",inGain1,"Gain")
  self.objects.inGain = inGain1

  tie(outGain2,"Gain",outGain1,"Gain")
  self.objects.outGain = outGain1

  tie(limiter2,"Type",limiter1,"Type")
  self.objects.limiter = limiter1
end

local views = {
  menu = {"rename","load","save"},
  expanded = {"pre","type","post"},
  collapsed = {},
}

function LimiterUnit:onLoadInterface(objects,controls)

  controls.pre = Fader {
    button = "pre",
    description = "Pre-Gain",
    param = objects.inGain:getParameter("Gain"),
    monitor = self,
    map = Encoder.getMap("decibel36"),
    units = app.unitDecibels
  }

  controls.post = Fader {
    button = "post",
    description = "Post-Gain",
    param = objects.outGain:getParameter("Gain"),
    monitor = self,
    map = Encoder.getMap("decibel36"),
    units = app.unitDecibels
  }

  controls.type = ModeSelect {
    button = "type",
    description = "Type",
    option = objects.limiter:getOption("Type"),
    choices = {"inv sqrt","cubic","hard"}
  }

  if self.channelCount==1 then
    local outlet = objects.inGain:getOutput("Out")
    controls.pre:setMonoMeterTarget(outlet)
  else
    local left = objects.inGain1:getOutput("Out")
    local right = objects.inGain2:getOutput("Out")
    controls.pre:setStereoMeterTarget(left,right)
  end

  if self.channelCount==1 then
    local outlet = objects.outGain:getOutput("Out")
    controls.post:setMonoMeterTarget(outlet)
  else
    local left = objects.outGain1:getOutput("Out")
    local right = objects.outGain2:getOutput("Out")
    controls.post:setStereoMeterTarget(left,right)
  end

  return views
end

return LimiterUnit

Simple, right? :sweat_smile:


Latency questions
#10

From a quick glance I find the .cpp block with the DSP chunks the most daunting. :cold_sweat: Your Limiter.h and GLOBALS block seem clearly structured. I could ask a million questions. This is all looking really interesting.

You are insanely diligent and talented. Bringing it all together. Massive respect.


#11

Awesome!!

Even though I’ve only read the first 6 or 7 chapters of the Lua book, I could just read that - than you for sharing, it’s everything i hoped it would be!!


#12

It looks like a really nice design!


#13

I started fiddling with Lua as well. If you’re on a Mac like me and want to get the latest version of Lua, go to https://www.lua.org/download.html to get the files. Unzip them, and then do something like the following:

cd ~/Downloads/lua-5.3.4
make macosx test
cp ~/Downloads/lua-5.3.4/src/lua /usr/local/bin/
cp ~/Downloads/lua-5.3.4/src/luac /usr/local/bin/

Homebrew was giving me version 5.2.4, the aforementioned book and the ER-301 are rocking 5.3.


#14

Wanted to ask: Is this readable for a complete coding novice?

Beyond this, does anyone have an (online) Lua coding guide that’s good?

Thank you!


#15

There is an online version (first edition only however) where you can try skimming through it before committing to a purchase. However, I think the answer to your question has to be: No. The book assumes you know how to program and that you just want to transfer your programming chops to the Lua language.


#16

Instead of taking ‘learning Lua’ as a goal, why not have a look at some of the units and see if you can modify one somehow. It seems to me that while it would be very useful to learn Lua, it’s not exactly essential to get your head around creating bespoke units because the amount of syntax that is used is pretty minimal and there are plenty of examples that are really quite straightforward to understand.

Just in case, have you seen this thread:

If you study the examples in there the unit files are really not that complex although they may look that way at first.

In particular look at post 26 where @odevices has very kindly added comments to each line of the unit file:

Happy to try and answer questions, perhaps a good place to start is to recreate a simple patch you made using built in units in Lua. I really mean start with something really small. Start a thread, post your patch and I am sure @Joe, @odevices and myself will help you translate it and make the connections.


#17

Here is another thought for the complete coding novice. While the ER-301 dev environment matures - by this I mean the documentation grows, the number of code samples increase, there become easier ways to deploy/debug/test, you could consider learning a different programming language.

The basic fundamentals of coding are pretty much the same in every language. Syntax can vary, and there may be some nuiances here and there. But overall once you learn one, the next one becomes much easier.

I might recommend javascript. Why?

  • There are practically unlimited free resources on the web for learning it (tutorials, examples, Q/A sites)
  • The syntax is more like C/C++ than Lua, but I think Lua syntax is probably easier, so it should be an easy transition
  • It’s a very immediate, interpreted language. You can write very small amounts of code and see satisfying results fast
  • No special tools needed. You can write it in a text editor (or use a more specialized free tool like Atom or Visual Studio Code) and it runs in your web browser without compiling
  • If it turns out you like programming, you’ll be likely to get some mileage out of javascript. It’s pretty ubiquitous these days. The whole front end of the web is written in javascript. You can even develop phone apps with it.

Another choice might be C++ since I think the plan is there will eventually be a C++ layer exposed in the ER-301.

Edit: I don’t know - I guess you can also download LUA and write programs that run on your PC there. To me it is always more satisfying to learn when I feel like I’m building something “real.”


#18

Hmm, I could say ‘things’ about JavaScript :joy:, but I do see what you mean!

I would highly recommend following a well constructed tutorial and sticking to it rather than wildly running around the web trying to piece things together, something like this, it looks very good:

http://eloquentjavascript.net


#19

Thanks to you both! Yeah, coding as a skill interests me, not only in an audio context, so what you’re saying seems very sensible. The problem as a beginner is picking up the right learning tools and learning something that translates well.


#20

I strongly recommend that you don’t worry about this, just pick one and go! As @Joe said, once you learn one, the second is easier, by the third you will be starting to spot the difference and develop preferences.