Day 3: Lua and the World

You’ve started down the path to adventure, treading lightly at first through Lua’s gates. You’ve found the simplicity and power in its data structures and concurrency routines. Now, it’s time to apply that knowledge and use Lua to build a real project.

Today, we’re going to make music with Lua. Lua doesn’t come with a sound library, but there are plenty around that are written in other languages. We’ll use one of Lua’s strongest features, its C interface, to control an open source music library.

A few stalwart adventurers have blazed this trail before us. They’ve used Lua’s expressiveness for their program’s logic, C for the performance-critical code, and the techniques in this chapter to glue the two together. For instance, Adobe Lightroom,[14] World of Warcraft,[15] and Angry Birds[16] all use Lua either internally or as a customer-facing extensibility language.

Making Music

There are lots of ways to make music with a computer. Today, we’ll generate Musical Instrument Digital Interface (MIDI) notes using a C++ library.[17] This means we’ll start the day with just enough C++ to show off Lua’s awesome C integration. We’ll quickly step back into the world of Lua, where we can wrap everything in an expressive API.

Outfitting for Adventure

Before you set out on today’s adventure, you’ll need some supplies. Specifically, you’ll need to have the following programs installed and ready to go:

  • A C++ compiler for your platform

  • The CMake tool for building C++ projects[19]

  • The Lua C headers and libraries (these should have come with your Lua distribution)

  • The RtMidi sound library[20]

  • A MIDI synthesizer app, so you can hear your music

The installation instructions vary greatly from platform to platform. Here’s a quick summary of the steps you need for Windows, Mac, and Linux. If you get stuck, drop us a line in the forums.[21]

Windows

  1. Install Visual Studio Express 2013 for Windows Desktop.[22]

  2. Download the source code to RtMidi, open the .sln file in Visual Studio, and build the library.

  3. Download and install the Windows version of CMake.

  4. Install the VirtualMIDISynth MIDI player.[23]

Mac

  1. Make sure you have a C++ compiler on your system, such as the Xcode command-line tools.[24]

  2. Install the Homebrew package manager.[25]

  3. Add the C sound project package sources:

     
    brew tap kunstmusik/csound
  4. Install Lua, CMake, and RtMidi:

     
    brew install lua cmake rtmidi
  5. Download and install the SimpleSynth MIDI player.[26]

Linux

Here’s how to get rolling on Ubuntu Linux; you’ll need to tweak these instructions for your distribution.

  1. Using the Synaptic package manager, add the universe repository to make more packages available.[27]

  2. Install the compilers, Lua, CMake, and RtMidi:

     
    sudo apt-get install build-essential lua5.2 lua5.2-dev cmake rtmidi
  3. Install and configure a MIDI synthesizer for Linux.[28] MIDI on Linux is a bit more of a Wild West situation, but one approach is to use a synthesizer called ZynAddSubFX together with a helper program called padsp:[29][30]

     
    sudo apt-get install zynaddsubfx pulseaudio-utils

Creating the Project

Our goal is to produce a command-line program, play, that will play whichever song we give to it. Songs will be in a Lua-based music notation we’re going to invent. The system will consist of three parts:

  1. A short C++ routine to create a new Lua interpreter and run a script supplied by the musician (that’s you!)

  2. A Lua function that sends messages to MIDI devices; written in C++, but playable from Lua

  3. A library of Lua helper routines to provide an easy syntax for writing music

A Tiny Interpreter

Let’s start with the Lua interpreter. Create a file in your project directory called play.cpp with the following contents:

lua/day3/a/play.cpp
 
extern​ ​"C"
 
{
 
#include "lua.h"
 
#include "lauxlib.h"
 
#include "lualib.h"
 
}

This will make the main Lua runtime and its auxiliary libraries available to your C++ program. The extern "C" wrapper tells the compiler and linker that the external Lua code is C, rather than C++.

Now, add a main() function, the place where command-line C programs begin their lives:

lua/day3/a/play.cpp
 
int​ main(​int​ argc, ​const​ ​char​* argv[])
 
{
 
lua_State* L = luaL_newstate();
 
luaL_openlibs(L);
 
 
luaL_dostring(L, ​"print('Hello world!')"​);
 
 
lua_close(L);
 
return​ 0;
 
}

Here, we use luaL_newstate() to create a new Lua interpreter. The default interpreter is designed to be lightweight, so bringing in Lua’s standard libraries requires a call to a second function, luaL_openlibs().

Once our interpreter is loaded and ready, we send it some Lua code with the luaL_dostring() function. Eventually, this Lua code will contain a song. For now, we’ll just print some text to the console.

At the end of the program, we tear down our interpreter with lua_close().

Building the Project

Now we’re ready to build. This takes two steps:

  1. Create a project file using CMake

  2. Compile the C program with make or Visual Studio

CMake just needs a quick description of your project to get started. Place the following text in a file called CMakeLists.txt:

lua/day3/a/CMakeLists.txt
 
cmake_minimum_required (VERSION 2.8)
 
project (play)
 
add_executable (play play.cpp)
 
target_link_libraries (play lua)

If your Lua headers are somewhere other than the system-wide default location, you may need to add an include_directories() line; for example:

lua/day3/a/CMakeLists.txt
 
include_directories(/usr/local/include)

Now, tell CMake to create your project file by typing the following command into the terminal from your project directory:

 
$ ​cmake .

On Mac and Linux, this will generate a Makefile, which you can use to build the project by typing the make command. On Windows, CMake will create a .sln file you can load into Visual Studio and build. Go ahead and do that step now.

Once you’ve built the project, you should have a program called play.exe or play in your project directory. If you’re on Windows, run your program like so:

 
C:day3>​ play.exe
 
Hello world!

On Mac and Linux, type the following command:

 
$ ​./play
 
Hello world!

Did you get the console message? Excellent! Now, let’s create some sound.

Adding Audio

First, we need to bring in the RtMidi library. Add the following code to the top of your C++ program, just after the closing brace of the extern "C" block:

lua/day3/b/play.cpp
 
#include "RtMidi.h"
 
static​ RtMidiOut midi;

The RtMidiOut object is our interface to the MIDI generator. Here, we’re just stashing it in a global variable. Normally we’d use Lua’s sophisticated registry to store this kind of data, but that’d be overkill for our purposes.[31]

Now, update your main() function to connect to your MIDI synthesizer:

lua/day3/b/play.cpp
 
int​ main(​int​ argc, ​const​ ​char​* argv[])
 
{
*
if​ (argc < 1) { ​return​ -1; }
*
*
unsigned​ ​int​ ports = midi.getPortCount();
*
if​ (ports < 1) { ​return​ -1; }
*
midi.openPort(0);
 
 
lua_State* L = luaL_newstate();
 
luaL_openlibs(L);
 
*
lua_pushcfunction(L, midi_send);
*
lua_setglobal(L, ​"midi_send"​);
*
*
luaL_dofile(L, argv[1]);
 
 
lua_close(L);
 
return​ 0;
 
}

The highlighted lines show the new additions. First, we use the RtMidi API to look for a running synthesizer (and exit the program if we fail to find one). Next, we open the Lua interpreter like we did before. Then, we register a C++ function that will do the grunt work of playing the notes. Finally, we run our Lua code and close the interpreter.

How do we connect C or C++ code to Lua? Lua uses a simple stack model to interoperate with C. We push our function’s memory address onto the stack, then call the built-in lua_setglobal() function to store our function in a Lua variable.

You’ll notice that we’ve also changed luaL_dostring() to luaL_doFile(). This loads the Lua code from an external file (we get the filename from the user via the command line; for example, play song.lua). This way, we don’t have to keep recompiling our C++ program every time we make a tweak to our Lua code.

Let There Be Sound

And now for the music! To play a note, we need to send two MIDI messages to the synthesizer: a Note On message and a Note Off message. The standard assigns a number to each of these messages, and specifies that they each take two parameters: a note and a velocity.[32]

That means our midi_send() Lua function will take three arguments: the message number, plus the two numeric parameters. When Lua encounters a call like the following one:

 
midi_send(144, 60, 96)

…it will push the values 144, 60, and 96 onto the stack, then jump into our C++ function. We’ll need to retrieve these parameters by their position on the stack. The top of the stack is at index -1 in Lua; this would be the last value pushed, or 96.

Because Lua is dynamically typed, these values could be anything: numbers, strings, tables, functions, and so on. We’re in complete control of what goes in the .lua script, though. We’re not going to pass in anything other than numbers, and so that’s the only case we’ll handle here. Add the following code to your C++ program, just above your main() function:

lua/day3/b/play.cpp
 
int​ midi_send(lua_State* L)
 
{
 
double​ status = lua_tonumber(L, -3);
 
double​ data1 = lua_tonumber(L, -2);
 
double​ data2 = lua_tonumber(L, -1);
 
 
// ...rest of C++ function here...
 
 
return​ 0;
 
}

If our function needed to hand any data back to Lua, we’d push one or more items back onto the stack and return a positive number. Here, we return zero to indicate no data.

Updating the Project File

All that’s left is to convert these three numbers into the format RtMidi needs, and send them to the synthesizer. Add the following code to your midi_send() function, just before the return:

lua/day3/b/play.cpp
 
std::vector<​unsigned​ ​char​> message(3);
 
message[0] = ​static_cast​<​unsigned​ ​char​>(status);
 
message[1] = ​static_cast​<​unsigned​ ​char​>(data1);
 
message[2] = ​static_cast​<​unsigned​ ​char​>(data2);
 
 
midi.sendMessage(&message);

We now need to link our project to both Lua and RtMidi. Change the target_link_libraries() line of your CMakeLists.txt file to the following:

lua/day3/b/CMakeLists.txt
 
target_link_libraries (play lua RtMidi)

Go ahead and rebuild your project. While that’s cooking, let’s write a short Lua test program to play a single note, middle C, for one second. The following code goes into one_note_song.lua:

lua/day3/b/one_note_song.lua
 
NOTE_DOWN = 0x90
 
NOTE_UP = 0x80
 
VELOCITY = 0x7f
 
 
function​ play(note)
 
midi_send(NOTE_DOWN, note, VELOCITY)
 
while​ os.​clock​() < 1 ​do​ ​end
 
midi_send(NOTE_UP, note, VELOCITY)
 
end
 
 
play(60)

Give it a try! Start your MIDI synth, then run your play program:

 
./play one_note_song.lua

You should hear a piano note play middle C for one second.

From Notes to Songs

A one-note song is fine if you’re Tenacious D.[33] But let’s shoot for something a little more ambitious.

First, it’d be nice to write songs in something easier to remember than MIDI note numbers—something closer to musical notation. Let’s grab the sheet music to an easily recognizable song, such as Happy Birthday to You!, or perhaps the public-domain soundalike Good Morning to All (which predates any copyright claims on the former by over 40 years).[34]

Since this book isn’t called Seven Musical Notations in Seven Weeks, I’ll gloss over translating the sheet music, and get straight to the names of the notes in this song. If you’d like to learn more about musical notation, the ReadSheetMusic project has a good tutorial.[35]

The first few notes of the song are D, E, D, G, F#, and they’re all the same duration (quarter notes), except F# (which is a half note, lasting twice as long). The song is played in the Middle C octave, which is octave number 4 in scientific pitch notation.[36]

We could represent these notes in Lua in a number of different ways. For now, let’s choose a simple string notation: note letter (e.g., Fs for “F sharp”), followed by octave number (e.g., 4), followed by value (e.g., h for “half note”).

Place the following song in good_morning_to_all.lua:

lua/day3/b/good_morning_to_all.lua
 
notes = {
 
'D​4q​',
 
'E​4q​',
 
'D​4q​',
 
'G​4q​',
 
'F​s4h​'
 
}

We need to be able to parse these strings into MIDI note numbers and durations. Since we’ll want to reuse this parsing routine from song to song, let’s put it in a new file, notation.lua:

lua/day3/b/notation.lua
 
local​ ​function​ parse_note(s)
 
local​ letter, octave, value =
 
string.​match​(s, ​"([A-Gs]+)(%d+)(%a+)"​)
 
 
if​ ​not​ (letter ​and​ octave ​and​ value) ​then
 
return​ nil
 
end
 
 
return​ {
 
note = note(letter, octave),
 
duration = duration(value)
 
}
 
end

First, we use Lua’s string.match() function to make sure the input follows the pattern we expect. If it does, we then call out to a couple of helper functions to calculate the MIDI note number and the duration in seconds. Finally, we return a table with the keys note and duration.

The first helper function, note(), is straightforward multiplication and addition. The following definition goes at the top of notation.lua:

lua/day3/b/notation.lua
 
local​ ​function​ note(letter, octave)
 
local​ notes = {
 
C = 0, Cs = 1, D = 2, Ds = 3, E = 4,
 
F = 5, Fs = 6, G = 7, Gs = 8, A = 9,
 
As = 10, B = 11
 
}
 
 
local​ notes_per_octave = 12
 
 
return​ (octave + 1) * notes_per_octave + notes[letter]
 
end

To translate from a note value (for example, q for “quarter note”) to a number of seconds, we need to know the tempo of the song. We’ll pick a default tempo of 100 beats per minute, and let individual songs override that if they need to. Put this definition just after note():

lua/day3/b/notation.lua
 
local​ tempo = 100
 
 
local​ ​function​ duration(value)
 
local​ quarter = 60 / tempo
 
local​ durations = {
 
h = 2.0,
 
q = 1.0,
 
ed = 0.75,
 
e = 0.5,
 
s = 0.25,
 
}
 
 
return​ durations[value] * quarter
 
end

The ed entry in the table, incidentally, is a dotted eighth note, lasting one and a half times as long as a regular eighth note. We’ll need that for another song later on.

Looping through this table to play these notes is easy enough. Back in good_morning_to_all.lua, add the following function:

lua/day3/b/good_morning_to_all.lua
 
scheduler = require ​'s​cheduler​'
 
notation = require ​'n​otation​'
 
 
function​ play_song()
 
for​ i = 1, #notes ​do
 
local​ symbol = notation.parse_note(notes[i])
*
notation.play(symbol.note, symbol.duration)
 
end
 
end

Since we’re specifying both a note number and a duration now, we’ll need a new definition of play(). We need to send the Note On message, wait for the right duration, and then send Note Off.

How can we wait without blocking the entire program? Wait, didn’t we encounter a situation like this before on Day 2? Go grab that awesome scheduler you wrote in Multitasking, and drop a copy into this project. Then, add the following code to notation.lua:

lua/day3/b/notation.lua
 
local​ scheduler = require ​'s​cheduler​'
 
 
local​ NOTE_DOWN = 0x90
 
local​ NOTE_UP = 0x80
 
local​ VELOCITY = 0x7f
 
 
local​ ​function​ play(note, duration)
 
midi_send(NOTE_DOWN, note, VELOCITY)
 
scheduler.wait(duration)
 
midi_send(NOTE_UP, note, VELOCITY)
 
end

Since we’re making a Lua module, we’ll need to export our public functions at the end of the file:

lua/day3/b/notation.lua
 
return​ {
 
parse_note = parse_note,
 
play = play
 
}

Just to recap, notation.lua now contains the following items in order:

  1. Our private helper functions, note() and duration()

  2. The public parse_note() function

  3. The public play() function with a few local variables

  4. The return statement describing our Lua module

Using the scheduler does add one extra step to the song. We’ll have to kick off the event loop at the end of good_morning_to_all.lua:

lua/day3/b/good_morning_to_all.lua
 
scheduler.schedule(0.0, ​coroutine​.​create​(play_song))
 
scheduler.run()

Ready to give your song a listen?

 
./play good_morning_to_all.lua

Now that we’ve programmed an easy song, let’s sink our teeth into something a little more substantial.

Voices

Our little homegrown Lua musical notation is coming along nicely. There are just a couple of things that are going to get tiresome as we encode longer songs:

  • The lack of an API for multiple voices

  • The need to enclose all the notes in quote marks

What we’d really like to do is write something like the following:

 
song.part{
 
D3q, A2q, B2q, Fs2q
 
}
 
 
song.part{
 
D5q, Cs5q, B4q, A4q
 
}
 
 
song.go()

…and have both of these parts play at the same time. Thanks to our scheduler, we can handle the simultaneous playing. Add the following code to notation.lua, before the final return:

lua/day3/b/notation.lua
 
local​ ​function​ part(t)
 
local​ ​function​ play_part()
 
for​ i = 1, #t ​do
 
play(t[i].note, t[i].duration)
 
end
 
end
 
 
scheduler.schedule(0.0, ​coroutine​.​create​(play_part))
 
end

This function takes an array of notes, t, creates a new function play_part() that plays these particular notes in order, and then schedules this part to be played as soon as the top-level song calls run().

That just leaves the question of how to get rid of the quote marks. Without quotes, the names of our notes become global variable lookups. Lua stores its global variables in a table called _G. All we have to do is use the metatable techniques from Day 2 to do the note lookup on the fly:

lua/day3/b/notation.lua
 
local​ mt = {
 
__index = ​function​(t, s)
 
local​ result = parse_note(s)
*
return​ result ​or​ rawget(t, s)
 
end
 
}
 
 
setmetatable(_G, mt)

This function will be called for any global variable lookup, not just the ones in our songs. So if there’s a typo in our program somewhere, it will hit this same lookup function. That’s why we fall back on looking up the value in _G. The rawget() call, incidentally, bypasses our custom lookup function—so we won’t get in an infinite loop if we’re looking for an undefined name.

All that’s left are a couple of utility functions. We need to let the musician set the song tempo, and we should provide a wrapper around scheduler.run(), so that the final song doesn’t need to load the scheduler module explicitly:

lua/day3/b/notation.lua
 
local​ ​function​ set_tempo(bpm)
 
tempo = bpm
 
end
 
 
local​ ​function​ go()
 
scheduler.run()
 
end

Don’t forget to update your module’s return statement to add the new public functions:

lua/day3/b/notation.lua
 
return​ {
 
parse_note = parse_note,
 
play = play,
 
part = part,
 
set_tempo = set_tempo,
 
go = go
 
}

Now, we have all we need to take on a more complex song.

Canon in D

You can find lots of public-domain musical scores at the Petrucci Project.[37] I’ve chosen Pachelbel’s Canon in D.[38]

Here’s a small part of the canon:

lua/day3/b/canon.lua
 
song = require ​'n​otation​'
 
 
song.set_tempo(50)
 
 
song.part{
 
D3s, Fs3s, A3s, D4s,
 
A2s, Cs3s, E3s, A3s,
 
B2s, D3s, Fs3s, B3s,
 
Fs2s, A2s, Cs3s, Fs3s,
 
 
G2s, B2s, D3s, G3s,
 
D2s, Fs2s, A2s, D3s,
 
G2s, B2s, D3s, G3s,
 
A2s, Cs3s, E3s, A3s,
 
}
 
 
song.part{
 
Fs4ed, Fs5s,
 
Fs5s, G5s, Fs5s, E5s,
 
D5ed, D5s,
 
D5s, E5s, D5s, Cs5s,
 
 
B4q,
 
D5q,
 
D5s, C5s, B4s, C5s,
 
A4q
 
}
 
 
song.go()

If you type this all in and run ./play canon.lua, you’ll be treated to one of my favorite pieces of music—with multiple parts playing at the same time, no less!

What We Learned in Day 3

On the final step of our journey through Lua, we learned Lua’s clean C API. By pushing and popping arguments and return values off Lua’s stack, we can easily exchange data between the Lua and C worlds.

We put this newfound knowledge together with the metatable and coroutine skills we learned on Day 2, as we wrote a simple MIDI player in C++ and Lua. This is exactly the sort of thing people use Lua for: wrapping low-level libraries into an easy-to-use interface. And this is how I got out of that jungle of code I mentioned at the beginning of Day 1.

Your Turn

Find…

  • How the luaL_dofile() function signals an error in the script

  • How to retrieve Lua error information from the stack in C

  • The busted unit test framework

Do (Easy):

  • Find the music for your favorite adventure movie’s theme song, and translate it to Lua. Play it with the music player you wrote.

  • The way it stands, we have to put require ’notation’ at the beginning of every song and song.go() at the end. Modify play.cpp to do this for you so that songs can just contain the tempo and parts.

Do (Medium):

  • We’ve always played notes at one constant volume. Design a notation for louder or quieter notes, and modify your music player to support it.

  • If there’s an error in the Lua script, the whole C++ program just exits without a word. Modify play.cpp to report any error information returned from the Lua interpreter.

Do (Hard):

  • The current implementation of play.cpp opens one global MIDI output port. Change it to allow the user to pass a port into midi_send() so that you can control more than one device from the same script.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset