LiCK Library for ChucK
Summary
LiCK, a Library for ChucK, was born out of frequent requests on the chuck-users mailing list to have a shared repository for various bits of reusable ChucK code.
LiCK currently provides
- int, float, and Object Lists
- Functor classes
- Interpolating functions
- Composite procedures for building loops
- An Assert class for writing unit tests
Download and installation
To download LiCK, visit the LiCK repository page on GitHub
http://github.com/heuermh/lick
Use git to clone to a local repository, or use the download link for a zip archive or a tarball.
To install and use LiCK in your own ChucK scripts, use the import.ck script
$ chuck --loop &
$ chuck + import.ck
...
"LiCK imported." : (string)
$ chuck + my-script.ck
If you don't want to include all of LiCK, use Machine.add(file name) to include individual LiCK source files. Do be careful to include the LiCK source files of dependencies as well.
Machine.add("FloatFunction.ck"); Machine.add("Interpolation.ck"); Machine.add("ExponentialOut.ck");
Hopefully, a future version of ChucK will support a proper include and namespace mechanism, simplifying the use of external libraries like LiCK.
Contributing to LiCK
LiCK is welcome to any contributions! Don’t worry too much about style or formatting, that can all be worked out later.
Please add the license header (HEADER.txt) to the top of each file and provide an author statement if you wish.
If you add classes to LiCK, be sure to update import.ck with the new classes and their dependencies in the correct order. If you have unit tests for those classes, be sure to update tests.ck with the new unit tests.
Any suggestions as to where examples should live in the repository are also welcome.
int, float, and Object Lists
For those more comfortable with Smalltalk, C#, or Java-style collections than arrays, LiCK provides int, float, and Object Lists.
Lists are created and sized in manner similar to that of ChucK arrays
// create a new object list ArrayList list; // initially the size of the list is zero Assert.assertTrue(0, list.size()); Assert.assertTrue(list.isEmpty()); // pass an argument to the size method to resize the array list.size(16); Assert.assertTrue(16, list.size()); Assert.assertFalse(list.isEmpty()); list.clear(); Assert.assertTrue(0, list.size()); Assert.assertTrue(list.isEmpty()); // or add elements to the list to resize it dynamically Object foo; list.add(foo); Assert.assertTrue(1, list.size()); Assert.assertFalse(list.isEmpty());
Indexed access is provided via get and set methods
ArrayList list; Object foo; Object bar; Object baz; list.set(0, foo); list.set(1, bar); list.set(2, baz); Assert.assertEquals(foo, list.get(0)); Assert.assertEquals(bar, list.get(1)); Assert.assertEquals(baz, list.get(2));
All the elements in a list can be accessed using a for loop over the indices
for (0 => int i; i < list.size(); i++) { list.get(i) @=> Object value; Assert.assertNotNull(value); }
an Iterator
list.iterator() @=> Iterator iterator; while (iterator.hasNext()) { iterator.next() @=> Object value; Assert.assertNotNull(value); }
or an internal iterator via any of the forEach methods
class AssertNotNull extends UnaryProcedure { fun void run(Object value) { Assert.assertNotNull(value); } } AssertNotNull assertNotNull; list.forEach(assertNotNull);
Int and float list implementations also provide similar behaviour.
IntArrayList intList; intList.add(42); intList.set(1, -42); Assert.assertEquals(42, intList.get(0)); Assert.assertEquals(-42, intList.get(1)); FloatArrayList floatList; floatList.size(16); floatList.assign(3.14); floatList.iterator() @=> Iterator iterator; while (iterator.hasNext()) { iterator.next() => float value; Assert.assertEquals(3.14, value, 0.001); }
Functor classes
LiCK provides a suite of Functor classes [1], objects that act as functions or procedures. Functor objects can be passed to methods, as shown above in the ArrayList.forEach(UnaryProcedure) example.
A procedure accepts argument(s)
class Write extends UnaryProcedure { fun void run(Object value) { <<<value>>>; } }
A function accepts argument(s) and returns a value
class Multiply extends FloatFloatFunction { fun float evaluate(float value0, float value1) { return value0 * value1; } }
A predicate accepts argument(s) and returns a boolean value
class AllPositive extends IntIntIntPredicate { fun int test(int value0, int value1, int value2) { return (value > 0) && (value1 > 0) && (value2 > 0); } }
Functor classes are provided for int, float, and Object arguments, in varying number of arguments. For Object functors, the prefix indicates the number of arguments, i.e. Unary is 1 argument, Binary is 2 arguments, Tertiary is 3 arguments, Quaternary is 4 arguments. Thus a QuaternaryFunction accepts 4 Object arguments and returns an Object value.
Similarly, for int and float functors, the number of prefix repeats is the number of arguments, e.g. an IntIntIntIntFunction accepts four int arguments and returns an int value.
For convenience, all of the functions in Math are implemented as functors
// functors can be evaluated against scalar values Log10 log10; log10.evaluate(3.14) => float result; // ...however, they really show their utility when you can pass them to a method ArrayList list; list.size(16); list.assign(3.14); list.transform(log10); // log10 is used to transform all the values in list
Interpolating functions
Interpolating functions.
Composite procedures
Composite procedures.
Sample-based drum machine emulators
LiCK provides classes that trigger samples for various vintage drum machines, such as the Oberheim DMX (OberheimDmx.ck) and the Roland TR-909 (RolandTr909.ck). To use these classes, find or record samples of each instrument and copy them to the paths in the source code, or alternatively edit the paths in the source code to match your samples directory.
For example, the samples directory layout for the Roland CR-78 defaults to
samples/RolandCr78/Claves.wav
samples/RolandCr78/ClosedHat.wav
samples/RolandCr78/CowBell.wav
samples/RolandCr78/Crash.wav
samples/RolandCr78/Guiro.wav
samples/RolandCr78/HighBongo.wav
samples/RolandCr78/Kick.wav
samples/RolandCr78/LowBongo.wav
samples/RolandCr78/LowConga.wav
samples/RolandCr78/Maracas.wav
samples/RolandCr78/OpenHat.wav
samples/RolandCr78/Rim.wav
samples/RolandCr78/Snare.wav
samples/RolandCr78/Tamborine.wav
Each sample is triggered by an IntProcedure that accepts a MIDI velocity value (0 .. 127) mapped to gain. The sample procedures also have rate and maxGain fields. Call these procedures directly in ChucK code
RolandCr78 cr78;
while (true)
{
cr78.kick.run(127);
400::ms => now;
cr78.snare.run(80);
400::ms => now;
}
or use a MIDI controller class, such as the nanoPAD (NanoPad.ck)
NanoPad nanoPad;
RolandCr78 cr78;
// assign sample triggers to nanoPAD buttons
cr78.kick @=> nanoPad.button1;
cr78.snare @=> nanoPad.button2;
cr78.closedHat @=> nanoPad.button3;
// open nanoPAD MIDI device 0
nanoPad.open(0);
Unit tests
Unit testing is a software verification, validation, and documentation method in which a programmer tests if individual units of source code are fit for use [2]. LiCK provides support for unit testing via its Assert.ck class and the following implementation pattern.
ChucK currently does not support calling methods via reflection, so unit tests in LiCK should follow the pattern described below to be executed properly.
Each unit test should be a class which extends Assert.ck
class MyUnitTest extends Assert
{
}
Next, provide test methods that utilize assertXxx methods to make assertions about the class under test. Assertion messages are optional.
class MyUnitTest extends Assert
{
fun void testFoo()
{
assertTrue("this should be true", true);
}
fun void testBar()
{
assertFalse("this should be false", false);
}
}
Provide an pseudo-constructor method that sets exitOnFailure as desired, calls each of the testXxx methods, and prints out a message to stdout on success
class MyUnitTest extends Assert
{
{
true => exitOnFailure;
testFoo();
testBar();
<<<"MyUnitTest ok">>>;
}
fun void testFoo()
{
assertTrue("this should be true", true);
}
fun void testBar()
{
assertFalse("this should be false", false);
}
}
Finally, instantiate the unit test and allow ChucK time to pass.
class MyUnitTest extends Assert
{
{
true => exitOnFailure;
testFoo();
testBar();
<<<"MyUnitTest ok">>>;
}
fun void testFoo()
{
assertTrue("this should be true", true);
}
fun void testBar()
{
assertFalse("this should be false", false);
}
}
MyUnitTest myUnitTest;
1::second => now;
See http://www.junit.org for further documentation on assertions and unit testing in general.
For examples of actual unit tests, see e.g. ArrayListTest.ck, FloatArrayListTest.ck, or IntArrayListTest.ck in LiCK.
License
LiCK Library for ChucK. Copyright (c) 2007-2010 held jointly by the individual authors. LiCK is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. LiCK is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.