Programming Lua

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.

2 Likes

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:

5 Likes

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.

2 Likes

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!!

It looks like a really nice design!

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.

1 Like

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!

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.

1 Like

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.

1 Like

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.”

1 Like

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

2 Likes

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.

2 Likes

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.

1 Like

If coding interests you, you’ll probably never stop learning.

On the other hand, if you really do want to spend your time choosing one this is good fun, a list of ‘Hello World’ in 28 different languages:

https://excelwithbusiness.com/blog/say-hello-world-in-28-different-programming-languages/

:laughing:

I know most of them to some degree… programmed fairly substantial things in maybe 9 or 10 of them, but again as @Joe says, it takes years to become proficient in any one of them, you will never stop learning.

That was kind of fun to look through and see how many of those languages I’ve worked with over the years. 21 of them! Some very extensively. Some just a very little bit. Some I’d forgotten about - Pascal. :slight_smile:

1 Like

Pascal was my first ever programming language, actually Turbo Pascal - brilliant language in it’s day! Actually, probably still is, I found it a great first language to learn because it is very fast and very well documented!

The one that made me giggle the most was Fortran… I got quite good at it in the end, but urgh - that language is bloomin hard work!! Hard to believe it is still used today… if ever I decide I want to sell out and make a lot of money, I would brush off my Fortran skills!

I’ve been trying to copy the ADSR unit and turning it into a simpler AD but I haven’t been successful yet. :frowning:

The adsr is written in the base layer, and called with this:

local adsr = self:createObject("ADSR","adsr")

So you wouldn’t gain anything computationally, only visually. If you still want to go ahead you will need to remove the S & R display elements and hard set those values to 0.

I’m trying to go through the lectures on lua.org, methodically and exhaustively, and it’s slow work finding out exactly what’s going on, as I have no programming experience at all. There’s the lua list lua-l, but not sure yet how they are with basic questions. However, you gotta start somewhere, and … it’ll be fun to start using it, I’m sure…

It seems that lua and be used to implement CSound, and I’m guessing that’s easier to code in that C, so that’s exciting too!