| KeyKit :: Hacking the User Interface |
There are two ways to hack KeyKit - modifying the existing tools, and building new tools. Building new tools is the best way that people can share their work, without getting in each others way. If everyone started hacking the Riff tool, it would be difficult to merge all the changes into a single mega-Riff tool, and the result would be unwieldy. Individual tools, on the other hand, can be encapsulated in a single file, are very easy to distribute, and can be selectively added to the Tools menus. So, this guide will emphasize the creation of complete tools, rather than modifying the existing tools. That said, there will still be some discussion of the typical changes you might want to make to the existing tools, such as adding new editing operations to the Group tool.
listports()and you will see a list of the MIDI input and output ports available. By default, KeyKit uses input port 0. To use a different input port (for example, 2), type:
inport(2)KeyKit will then be listening to input port number 2. If you want to make this change permanent, you should add that statement to your \keylocal.k file.
The keylib.k file in the lib directory is an index that KeyKit uses to determine the mapping between functions and the files in which they are defined. When KeyKit sees a reference to an undefined function, it will look in keylib.k and expect to find an entry for that function. When you add new functions to any file in the lib directory, you can run the rereadlib() function in order to check for updates - if it detects changed files, it re-read those files and updates keylib.k. Even more easily, you can use the "Misc->Reread Library" item in the main GUI menu to invoke rereadlib(). If you do a lot of development, tear off that menu item as a button.
If you want to create your own directory of library functions, and use it in addition to the standard library, you can do so by altering the value of Keypath, which is a list of the directories in which KeyKit searches for functions. Each directory in the Keypath should have its own keylib.k file. The easiest way of permanently altering the value of Keypath is to edit \keylocal.k and add statements such as this:
Keypath = Keypath + ";C:\MYLIBDIR" rereadlib()to the keylocal function. The call to rereadlib() is needed after any change to Keypath, and causes all of the directories to be rescanned for their keylib.k files. to
class wvol {
method init {
$.w = new window()
$.inherit($.w)
}
method redraw {
$.w.redraw()
$.w.text("Hello World",$.w.size())
}
method resize (sz) {
if ( nargs() > 0 )
$.w.resize(sz)
}
}
This class is actually complete enough for
a ``Hello World'' tool (see whello.k in the lib directory for
a working copy). All tools are expected to act like window objects,
with standard window methods such as erase and size.
To get this behaviour, we use the window class and
get a new window() object, from which we inherit all the
normal window methods.
Note that the inheritence is manually specified by calling the
inherit method (one of the few built-in methods
that all objects have). This means that if someone invokes a method on
our tool object that we haven't explicitly specified (like erase),
the method in the $.w object will be used.
The example above provides all the functionality for drawing, resizing, saving, and restoring the tool. But it doesn't do anything, and we wanted to build a volume slider. To do that, we need to make use of a slider widget:
class wvol {
method init {
$.w = new window()
$.inherit($.w)
# Add a slider widget as a child
$.slider = new kslider(0,127,120,$,"volchange")
$.addchild($.slider)
}
method redraw {
$.w.redraw()
methodbroadcast()
}
method resize (sz) {
if ( nargs() > 0 )
$.w.resize(sz)
$.slider.resize($.w.size())
}
method volchange (v) {
p = controller(1,0x07,v)
realmidi(p)
}
}
This class now implements a completely functional volume slider.
When an object of class wvol is created, its init method
is called. In the init method, the kslider class is used
to create a new object whose value is assigned
to $.slider, a new data element in the wvol
object we're in the process of creating.
The name of this data element is not special - we could have called
it $.s if we wanted. The addchild method (a built-in method
provided for all classes) is used to add this new slider object
to the list of children for the wvol object. See the KeyKit language
reference manual for a complete description of the children list.
One of the primary uses of the children list is to provide a simple way
to broadcast method invocations. For example, the methodbroadcast()
function that you see in the redraw method above makes use of this
list - it broadcasts a method (in this case, redraw)
to all the children of the current object. We could have explicitly
called $.slider.redraw(), but
methodbroadcast is easier in the long run because most tools have many
children.
The resize method of our tool resizes both the window object ($.w) as well as the slider ($.slider). Note that the size of the slider is set to the size of the window. In this tool, it probably isn't necessary for the resize method to handle the situation where no arguments are passed, since that's only used from within a tool itself, and there are no such uses here.
The first 3 arguments to kslider() specify that we want a slider whose values range from 0 to 127, and that the initial value should be 120. The last 2 arguments to kslider() control what is done when the slider is moved - they specify an object and the name of a method (specified as a string value) to be invoked on that object. In this case, we're indicating that we want to invoke the volchange method of the $ object (which is the wvol object being initialized). So, everytime the slider is moved, it will invoke $.volchange(value), where value is the new value of the slider. And you can see that our wvol class includes the volchange method that will be invoked. This method takes the value, produces a volume controller message for channel 1, and sends it out via MIDI output. The controller() function can be found in lib/basic2.k and realtime() is the built-in function for sending a phrase as raw MIDI output.
Although the code above is completely functional, it doesn't provide the ability for the tool to be saved and restored (i.e. when using the Page feature of the user interface). This can be provided by adding the following methods to our wvol class:
method get { return($.slider.get()) }
method set (v) { return($.slider.set(v)) }
method dump { return(["value"=string($.get())]) }
method restore (state) { $.set(state["value"]) }
The first two methods are the standard methods which get and set the
tool's value. In this case, the value of the tool is just the value of
the slider, so we merely make use of the slider object's get and
set methods.
The dump method returns an array with 1 element. The index of the array element is "value", and the value of the array element is $.get() converted to a string. (Note that $.get() actually results in a call to $.slider.get().) For example, if the value of the slider was 120, the array returned by the dump method would be ["value"=120]. This would be the array given back to $.restore() when the tool was restored. And, $.restore() merely takes the value out of the array and gives it to $.set().
As you can see, it is very common for methods of a tool to reference other methods of the same object. This should be the preferred way of working. Even though the $.dump() statement above could call $.slider.get() directly, it should not. That way, future changes to the way in which the value of the tool is obtained can be made in a single place, in $.get().
Let's say you want to add a Slowdown command to the Edit menu that allows you to apply the following function to the current Pick:
function slowdown(ph) {
tm1 = ph%1.time
sz = sizeof(ph)
leng = phz.time + phz.dur - tm1
start_factor = 1.0
end_factor = 4.0
df = end_factor - start_factor
r = nonnotes(ph)
ph = onlynotes(ph)
for ( nt in ph ) {
dt = nt.time - tm1
thisfactor = start_factor + (dt/float(leng)) * df
nt.time = tm1 + dt * thisfactor
nt.dur *= thisfactor
r |= nt
}
return(r)
}
This function implements a gradual slowing
down by adjusting the time and duration of the
notes. First, put this function into a file in the lib directory,
and run the rereadlib() function which updates the keylib.k index file.
Now, edit lib/mkmenus.k and find the mkmenu_edit function. Add the following line to that function, wherever you want it to appear in the Edit menu (presumably after Shuffle, to make it alphabetical):
o.menucmd("Slowdown",po,"edit","cmd_slowdown")
Now, edit lib/cmds.k, and add the following function:
function cmd_slowdown(p) {
return(slowdown(p))
}
You should now be able to see a Slowdown item in the Edit menu
of the Group tool, and should be able to invoke it and see the
effect on whatever notes you have picked.
For example, the # of Steps menu in the Kboom tool currently only goes up to 32 steps, but there's really no limit in the implementation of Kboom other than the fact that the menu is initialized with values from 2 to 32. Take a look at the source code for the Kboom tool, in lib/wkboom.k, and look for a 32. Change it to 64. You can now create drum patterns with 64 steps.
Another example - the Riff tool has a Start Quant menu with values that go from None to 8b. Let's say you want to add 16b to that list. Take a look at the source code, in lib/wriff.k. Look for a line that has 8b in it. Add another line that looks just like it, except substitute 16b for the two ocurrences of 8b. You're done, and so is this document.