Skip to content
This repository

Welcome to the lick wiki!

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 objects, see e.g. http://c2.com/cgi/wiki?FunctorObject
  • Interpolation/tween/easing functors
  • Composite procedures for building loops
  • Intervals, Chords, Scales, Arpeggiators
  • Sample-based drum machine emulators
  • HID device classes (keyboard, mouse, joystick, etc.)
  • MIDI device classes (Alesis QX25, MAudio Ozone, MeeBlip, NanoPad, etc.)
  • MIDI support for iOS apps (Animoog, GarageBand, Filtatron, Loopy, etc.)
  • OSC support for desktop apps (IanniX, SooperLooper, etc.)
  • LeapMotion, Monome, ControlOSC, TouchOSC integration
  • Mono and stereo Delays, FeedbackMachines
  • WaveShaper chugen, Distortion and Tremolo chugens
  • Module, Module2, …, Module8; chuck a Ugen into a parameter (cv)
  • “ChucK-Unit”, an Assert class for creating unit tests

Download and installation

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.

Design considerations

ChucK doesn’t provide interfaces or explicit abstract classes, so LiCK uses empty or no-op classes to represent those concepts. For example, List.ck really aught to be an interface that ArrayList.ck and LinkedList.ck would implement. In LiCK, List.ck is a class with empty or no-op implementations of all its methods which are then overridden by ArrayList.ck.

There are a lot of small classes in LiCK, by design. Simplified access to all these small classes is provided by static methods (e.g. Loops.ck provides static methods that create and return instances of Loop.ck or Repeat.ck).

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, Ternary 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 doesn’t allow 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.

References

1, http://c2.com/cgi/wiki?FunctorObject

2, http://en.wikipedia.org/wiki/Unit_testing

Something went wrong with that request. Please try again.