Import jack-keyboard 2.5.

git-svn-id: svn://svn.code.sf.net/p/jack-keyboard/code/trunk@2 1fa2bf75-7d80-4145-9e94-f9b4e25a1cb2
This commit is contained in:
trasz 2008-10-11 22:44:38 +00:00
parent bb34c61c76
commit 4dcd545075
16 changed files with 3254 additions and 0 deletions

2
AUTHORS Normal file
View File

@ -0,0 +1,2 @@
Edward Tomasz Napierała <trasz@FreeBSD.org>

24
COPYING Normal file
View File

@ -0,0 +1,24 @@
Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

2
Makefile.am Normal file
View File

@ -0,0 +1,2 @@
SUBDIRS = src man pixmaps

160
NEWS Normal file
View File

@ -0,0 +1,160 @@
User-visible changes between 2.4 and 2.5 include:
- Autotools. This should make life easier for distributors. ;-)
User-visible changes between 2.3 and 2.4 include:
- Keyboard layout switching. Now you can specify "-l QWERTZ"
or "-l AZERTY" and keyboard layout will be (hopefully) usable.
- Implement output rate limiting; specifying "-r 31.25" in a command
line will limit jack-keyboard to the rate mandated by the MIDI
specification.
- Remove the "-n" option; not connecting to the MIDI port at startup
is the default behaviour.
- Start jackd automatically, even when not compiled against LASH.
- Fix "make install" on systems without /usr/local/share/icons/hicolor.
User-visible changes between 2.2 and 2.3 include:
- Fix "make install" on systems without /usr/local/bin.
- Fix crash with jackdmp, reported and tested by Juuso Alasuutari.
- Make it possible to switch between windows using Alt-Tab
when the keyboard is grabbed.
User-visible changes between 2.1 and 2.2 include:
- Add 4 to "Octave". What that means is, C4 is in octave #4,
not #0.
- Icon ;-)
- Documentation updates.
User-visible changes between 2.0 and 2.1 include:
- It's possible to change "high" and "low" velocity. To change
the former, press and hold Shift key and move slider. To change
the latter, do the same with Ctrl key.
Moving the slider without Ctrl or Shift held changes "normal"
velocity.
- Fix ugly memory corruption that could cause strange behaviour
(including crashes) in pianola mode.
User-visible changes between 1.8 and 2.0 include:
- GUI; now jack-keyboard should be more intuitive. It can be turned
off using -G option.
- Support keyboard grabbing - with the -K option, jack-keyboard
will get all the keyboard input, even when it does not have focus.
In other words, you can play while mousing in different program
(synth, for example) at the same time.
Note that this will not work if another application grabs
the keyboard.
- LASH support.
- Install ".desktop" file.
Changes between 1.7 and 1.8:
- Fix compilation with SVN version of JACK.
Changes between 1.6 and 1.7:
- Add "keyboard cue" to show you which part of virtual keyboard your
PC keyboard keys are currently mapped to. It looks a little ugly,
so it's disabled by default; you can enable it using "-C" option.
- Add an "-u" option to automatically send bank/program change messages
after reconnecting
- Warn when bank/program shown in title bar could be different than the
one used by the synth
- Fix setting bank/program from the command line
Changes between 1.5 and 1.6:
- Send messages with proper time offsets. If you don't like this,
you can make jack-keyboard behave same as before by using -t option
- Implement MIDI channel switching (submitted by Nedko Arnaudov)
- Make it possible to enter bank/program number directly using keypad
- Make bank switching actually work
- Don't crash when clicking _and_ releasing mouse button on the gray area
Changes between 1.4 and 1.5:
- Add "pianola mode" - jack-keyboard now has a MIDI input port; events received
on this port will change the visible state of keyboard - basically, you will
see the notes played. Note that, by default, jack-keyboard will refuse to
connect to other clients named 'jack-keyboard'; this is to prevent loops.
- Make panic key more reliable
- Numeric keys (top row on the alphanumeric keyboard) pressed with Shift held
should work correctly now
- Threading fixes - if jack-keyboard behaved in unpredictable way, it should
be fixed now
- Add manual page
- Add 'install' Makefile target ;-)
Changes between 1.3 and 1.4:
- Don't stop the notes when moving mouse outside of the window (suggested by ctaf @ #lad)
- Show some useful information in the window title
- Rewrite MIDI input switching to make it more robust
- Remove the command line MIDI port selection. Just use Insert/Delete keys to cycle to the
port you need
- Don't refuse to start when there are no MIDI input ports to connect to (suggested by Nedko Arnaudov)
Changes between 1.2 and 1.3:
- Use the ringbuffer instead of old 'table of notes'. Now the notes won't
get lost even under high CPU load
- Add 'panic key', under 'Esc' key, that stops all sounds
- Add Bank Change (Home/End)
Changes between 1.1 and 1.2:
- Make it possible to run several instances of jack-keyboard at the same time
- Add the ability to switch MIDI inputs at runtime (Insert/Delete keys)
- Warn about JACK timing problems
- Minor display optimization
- Fix crash after pressing highest (#127) note
Changes between 1.0 and 1.1:
- Speed up redrawing
- Add MIDI port selection ("jack-keyboard 3" will connect to fourth available
JACK MIDI input port)
- Fix sustain key (space)

130
README Normal file
View File

@ -0,0 +1,130 @@
What is it?
-----------
jack-keyboard is a virtual MIDI keyboard - a program that allows you to
send JACK MIDI events (play ;-) using your PC keyboard. It's somewhat
similar to vkeybd, except it uses JACK MIDI instead of ALSA, and the
keyboard mapping is much better - it uses the same layout as trackers
(like Impulse Tracker) did, so you have two and half octaves under your
fingers.
How to compile it?
------------------
If you're using FreeBSD, install from ports - audio/jack-keyboard.
Otherwise, you need JACK with MIDI support, gtk+ 2.6 or higher,
make(1), gcc and all the standard headers. Untar the file, type
'make install' and that's it. If there is any problem, drop me
an email (trasz@FreeBSD.org) and I will help you. Really.
How to use it?
--------------
You need JACK with MIDI support and some softsynth that accepts
JACK MIDI as input. Ghostess, http://home.jps.net/~musound/,
is a good choice. Of course you will also need some DSSI plugin
that will make the actual sound. WhySynth is nice.
When you have all of these installed: first, run jackd. Then run
ghostess with a plugin of choice. Then run jack-keyboard. Press
'z' key. You should hear sound.
Keyboard
--------
Keyboard mapping is the same as in Impulse Tracker. This is your
QWERTY keyboard:
+----+----+ +----+----+----+ +----+----+
| 2 | 3 | | 5 | 6 | 7 | | 9 | 0 |
+----+----+----+----+----+----+----+----+----+----+
| q | w | e | r | t | y | u | i | o | p |
+----+----+----+----+----+----+----+----+----+----+
| s | d | | g | h | j |
+----+----+----+----+----+----+----+
| z | x | c | v | b | n | m |
+----+----+----+----+----+----+----+
And this is MIDI mapping.
+----+----+ +----+----+----+ +----+----+
|C#5 |D#5 | |F#5 |G#5 |A#5 | |C#6 |D#6 |
+----+----+----+----+----+----+----+----+----+----+
| C5 | D5 | E5 | F5 | G5 | A5 | B5 | C6 | D6 | E6 |
+----+----+----+----+----+----+----+----+----+----+
|C#4 |D#4 | |F#4 |G#4 |A#4 |
+----+----+----+----+----+----+----+
| C4 | D4 | E4 | F4 | G4 | A4 | B4 |
+----+----+----+----+----+----+----+
Spacebar is a sustain key. Holding it when pressing or releasing key
will make that key sustained, i.e. Note Off MIDI event won't be sent
after releasing the key. To release (stop) all the sustained notes,
press and release spacebar.
Holding Shift when pressing note will make it louder (it increases
velocity). Holding Ctrl will do the opposite. You can change the
default velocity by moving the Velocity slider. You can change the
"high" and "low" velocity values by moving the slider while holding
Shift or Ctrl keys.
Pressing "-" and "+" keys on numeric keypad changes the octave your
keyboard is mapped to. Pressing "*" and "/" on numeric keypad changes
MIDI program (instrument). Pressing Insert or Delete keys will connect
jack-keyboard to the next/previous MIDI input port (it will cycle
between running instances of ghostess, for example). Home and End keys
change the MIDI channel. Page Up and Page Down keys switch the MIDI
bank.
Esc works as a panic key - when you press it, all sound stops.
Setting channel/bank/program number directly
--------------------------------------------
To switch directly to a channel, bank or program, enter its number on
the numeric keypad (it won't be shown in any way) and press Home or End
(to change channel), Page Up or Page Down (to change bank) or "/" or
"*" (to change program). For example, to change to program number 123,
type, on the numeric keypad, "123/", without quotes.
Titlebar
--------
When -G xor -T is given, some informational messages in the title bar
appear. They are supposed to be self explanatory. If you see
"bank/program change not sent", it means that the bank/program numbers
as seen in the title bar were not sent. In other words, synth the
jack-keyboard is connected to may use different values. This happens
at startup and after switching between synths (using Insert/Delete
keys). To send bank/program change at startup, use -b and -p parame-
ters. To automatically send bank/program change after reconnect, use
the -u option.
Pianola mode
------------
In addition to the MIDI output port, jack-keyboard also opens MIDI
input (listening) port. MIDI events going into this port will be
passed to the output port unmodified, except for channel number, which
will be set to the one jack-keyboard is configured to use. Note On and
Note Off MIDI events will cause visible effect (pressing and releasing)
on keys, just like if they were being pressed using keyboard or mouse.
jack-keyboard will never connect to it's own MIDI input port. It will
also refuse to connect to any other client whose name begins in "jack-
keyboard", unless the "-k" option is given. It is, however, possible
to connect these ports manually, using jack_connect or qjackctl; this
may create feedback loop.
License
-------
JACK Keyboard is distributed under the BSD license, two clause.
Contact
-------
If you have any questions, comments, suggestions, patches or anything,
let me know: Edward Tomasz Napierala <trasz@FreeBSD.org>.

13
TODO Normal file
View File

@ -0,0 +1,13 @@
Things to do, eventually:
- I know nothing about designing GUIs, and I'm afraid it shows. Redesign.
- Jack-keyboard does strange things to input focus and keyboard event
handling. Verify that it does not cause any problems.
- Make the code cleaner. Get rid of all these global variables etc.
- Tooltips.
- Fix language (english) errors in documentation and web page.

81
configure.ac Normal file
View File

@ -0,0 +1,81 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
AC_INIT([jack-keyboard], [2.5], [trasz@FreeBSD.org])
AM_INIT_AUTOMAKE([-Wall foreign])
AC_CONFIG_SRCDIR([config.h.in])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h string.h sys/time.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
AC_HEADER_TIME
AC_C_VOLATILE
# Checks for library functions.
AC_FUNC_MALLOC
AC_FUNC_STRTOD
AC_CHECK_FUNCS([gettimeofday memset strcasecmp strdup])
PKG_CHECK_MODULES(GTK, gtk+-2.0 >= 2.2)
AC_SUBST(GTK_CFLAGS)
AC_SUBST(GTK_LIBS)
PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.2)
AC_SUBST(GLIB_CFLAGS)
AC_SUBST(GLIB_LIBS)
PKG_CHECK_MODULES(GTHREAD, gthread-2.0 >= 2.2)
AC_SUBST(GTHREAD_CFLAGS)
AC_SUBST(GTHREAD_LIBS)
AC_ARG_WITH([x11],
[AS_HELP_STRING([--with-x11],
[support keyboard grabbing @<:@default=check@:>@])],
[],
[with_x11=check])
AS_IF([test "x$with_x11" != xno],
[PKG_CHECK_MODULES(X11, x11, AC_DEFINE([HAVE_X11], [], [Defined if we have X11 support.]),
[if test "x$with_x11" != xcheck; then
AC_MSG_FAILURE([--with-x11 was given, but x11 was not found])
fi
])])
AC_SUBST(X11_CFLAGS)
AC_SUBST(X11_LIBS)
PKG_CHECK_MODULES(JACK, jack >= 0.102.0)
AC_SUBST(JACK_CFLAGS)
AC_SUBST(JACK_LIBS)
PKG_CHECK_MODULES(JACK_MIDI_NEEDS_NFRAMES, jack < 0.105.00, AC_DEFINE(JACK_MIDI_NEEDS_NFRAMES, 1, [whether or not JACK routines need nframes parameter]), true)
AC_ARG_WITH([lash],
[AS_HELP_STRING([--with-lash],
[support LASH @<:@default=check@:>@])],
[],
[with_lash=check])
AS_IF([test "x$with_lash" != xno],
[PKG_CHECK_MODULES(LASH, lash-1.0, AC_DEFINE([HAVE_LASH], [], [Defined if we have LASH support.]),
[if test "x$with_lash" != xcheck; then
AC_MSG_FAILURE([--with-lash was given, but LASH was not found])
fi
])])
AC_SUBST(LASH_CFLAGS)
AC_SUBST(LASH_LIBS)
AC_CONFIG_FILES([Makefile src/Makefile man/Makefile pixmaps/Makefile])
AC_OUTPUT

4
man/Makefile.am Normal file
View File

@ -0,0 +1,4 @@
man_MANS = jack-keyboard.1
EXTRA_DIST = $(man_MANS)

186
man/jack-keyboard.1 Normal file
View File

@ -0,0 +1,186 @@
.\" This manpage has been automatically generated by docbook2man
.\" from a DocBook document. This tool can be found at:
.\" <http://shell.ipoline.com/~elmert/comp/docbook2X/>
.\" Please send any bug reports, improvements, comments, patches,
.\" etc. to Steve Cheng <steve@ggi-project.org>.
.TH "JACK-KEYBOARD" "1" "20 April 2008" "jack-keyboard 2.4" ""
.SH NAME
jack-keyboard \- A virtual keyboard for JACK MIDI
.SH SYNOPSIS
\fBjack-keyboard\fR [ \fB-C\fR ] [ \fB-G\fR ] [ \fB-K\fR ] [ \fB-T\fR ] [ \fB-V\fR ] [ \fB-a \fIinput port\fB\fR ] [ \fB-k\fR ] [ \fB-r \fIrate\fB\fR ] [ \fB-t\fR ] [ \fB-u\fR ] [ \fB-c \fIchannel\fB\fR ] [ \fB-b \fIbank\fB\fR ] [ \fB-p \fIprogram\fB\fR ] [ \fB-l \fIlayout\fB\fR ]
.SH "OPTIONS"
.TP
\fB-C\fR
Enable "keyboard cue" - two
horizontal lines over a part of keyboard; keys under the lower line are mapped
to the lower row of your PC keyboard; keys under the upper line are mapped
to the upper row.
.TP
\fB-G\fR
Disable GUI. It makes \fBjack-keyboard\fR look like it did before version 2.0.
.TP
\fB-K\fR
Grab the keyboard. This makes \fBjack-keyboard\fR receive keyboard events
even when it does not have focus. In other words, you can play while mousing in a different
window.
Note: It's not reliable yet. It does not work when some other application keeps
the keyboard grabbed. It does not work with GNOME. Even when it seems to work,
some keyboard events may get lost.
.TP
\fB-T\fR
Toggle titlebar, on which channel/bank/program information is shown.
With -G option, it disables titlebar; otherwise it enables it.
.TP
\fB-V\fR
Print version number to standard output and exit.
.TP
\fB-a \fIinput port\fB\fR
Automatically connect to the named input port. Note that this may cause problems with LASH.
.TP
\fB-k\fR
Allow connecting to other instances of jack-keyboard (see PIANOLA MODE below).
Without this option, \fBjack-keyboard\fR will refuse to connect to any
JACK client whose name starts in "jack-keyboard"; this is to prevent loops.
Note that it's impossible to connect instance of \fBjack-keyboard\fR
to itself, even with this option set.
.TP
\fB-r \fIrate\fB\fR
Set rate limit to \fIrate\fR, in Kbaud. Limit
defined by the MIDI specification is 31.25. By default this parameter is zero, that
is, rate limiting is disabled.
.TP
\fB-t\fR
Send all MIDI messages with zero time offset, making them play as soon
as they reach the synth. This was the default behavior before version 1.6.
.TP
\fB-u\fR
By default, \fBjack-keyboard\fR does not send program/bank
change messages after reconnecting, so the newly connected instrument
remains at previous settings. This option changes that behaviour.
.TP
\fB-c \fIchannel\fB\fR
Set initial MIDI channel to \fIchannel\fR;
by default it's 1.
.TP
\fB-b \fIbank\fB\fR
Set initial MIDI bank to \fIbank\fR\&.
With this option, \fBjack-keyboard\fR will send the bank/program
change once, when it connects.
.TP
\fB-p \fIprogram\fB\fR
Set initial MIDI program to \fIprogram\fR\&.
With this option, \fBjack-keyboard\fR will send the bank/program
change once, when it connects.
.TP
\fB-l \fIlayout\fB\fR
Specify the layout of computer keyboard being used. Valid arguments are QWERTY,
QWERTZ and AZERTY. Default is QWERTY.
.SH "DESCRIPTION"
.PP
\fBjack-keyboard\fR is a virtual MIDI keyboard - a program that allows
you to send JACK MIDI events (play ;-) using your PC keyboard. It's
somewhat similar to \fBvkeybd\fR, except it uses JACK MIDI instead of
ALSA, and the keyboard mapping is much better - it uses the same
layout as trackers (like Impulse Tracker) did, so you have two and
half octaves under your fingers.
.SH "KEY BINDINGS"
.PP
Keyboard mapping is the same as in Impulse Tracker. This is your
QWERTY keyboard:
.nf
+----+----+ +----+----+----+ +----+----+
| 2 | 3 | | 5 | 6 | 7 | | 9 | 0 |
+----+----+----+----+----+----+----+----+----+----+
| Q | W | E | R | T | Y | U | I | O | P |
+----+----+----+----+----+----+----+----+----+----+
| S | D | | G | H | J |
+----+----+----+----+----+----+----+
| Z | X | C | V | B | N | M |
+----+----+----+----+----+----+----+
.fi
And this is MIDI mapping:
.nf
+----+----+ +----+----+----+ +----+----+
|C#5 |D#5 | |F#5 |G#5 |A#5 | |C#6 |D#6 |
+----+----+----+----+----+----+----+----+----+----+
| C5 | D5 | E5 | F5 | G5 | A5 | B5 | C6 | D6 | E6 |
+----+----+----+----+----+----+----+----+----+----+
|C#4 |D#4 | |F#4 |G#4 |A#4 |
+----+----+----+----+----+----+----+
| C4 | D4 | E4 | F4 | G4 | A4 | B4 |
+----+----+----+----+----+----+----+
.fi
.PP
Spacebar is a sustain key. Holding it when pressing or releasing key
will make that key sustained, i.e. Note Off MIDI event won't be sent
after releasing the key. To release (stop) all the sustained notes,
press and release spacebar.
.PP
Holding Shift when pressing note will make it louder (it increases
velocity). Holding Ctrl will do the opposite. You can change the
default velocity by moving the Velocity slider. You can change the "high"
and "low" velocity values by moving the slider while holding Shift
or Ctrl keys.
.PP
Pressing "-" and "+"
keys on numeric keypad changes the octave your keyboard is mapped to.
Pressing "*" and "/" on numeric keypad changes
MIDI program (instrument). Pressing Insert or Delete keys will
connect \fBjack-keyboard\fR to the next/previous MIDI input port (it will cycle between running
instances of ghostess, for example). Home and End keys change the MIDI channel.
Page Up and Page Down keys switch the MIDI bank.
.PP
Esc works as a panic key - when you press it, all sound stops.
.SH "SETTING CHANNEL/BANK/PROGRAM NUMBER DIRECTLY"
.PP
To switch directly to a channel, bank or program, enter its number on the numeric
keypad (it won't be shown in any way) and press Home or End (to change channel),
Page Up or Page Down (to change bank) or "/" or "*"
(to change program). For example, to change to program number 123,
type, on the numeric keypad, "123/", without quotes.
.SH "TITLEBAR"
.PP
When \fB-G\fR xor \fB-T\fR is given, some informational
messages in the title bar appear. They are supposed to be self explanatory.
If you see "bank/program change not sent",
it means that the bank/program numbers as seen in the title bar were not sent. In other words,
synth the \fBjack-keyboard\fR is connected to may use different values. This happens
at startup and after switching between synths (using Insert/Delete keys). To send bank/program
change at startup, use \fB-b\fR and \fB-p\fR parameters. To automatically
send bank/program change after reconnect, use the \fB-u\fR option.
.SH "PIANOLA MODE"
.PP
In addition to the MIDI output port, \fBjack-keyboard\fR also opens MIDI input (listening) port.
MIDI events going into this port will be passed to the output port unmodified, except for channel number,
which will be set to the one \fBjack-keyboard\fR is configured to use. Note On and Note Off
MIDI events will cause visible effect (pressing and releasing) on keys, just like if they were being pressed
using keyboard or mouse.
.PP
\fBjack-keyboard\fR will never connect to it's own MIDI input port. It will also refuse
to connect to any other client whose name begins in "jack-keyboard", unless the "-k" option is given.
It is, however, possible to connect these ports manually, using \fBjack_connect\fR
or \fBqjackctl\fR; this may create feedback loop.
.SH "SEE ALSO"
.PP
\fBjackd\fR(1),
\fBghostess\fR(1),
\fBqjackctl\fR(1)
.SH "BUGS"
.PP
Key grabbing is unreliable.
.PP
Many PC keyboards have problems with polyphony. For example, with the one I'm using right now,
it's impossible to press "c", "v" and "b" at the same time. It's a hardware problem, not the
software one.
.PP
The spin widgets used to set channel/bank/program number don't take focus, so the value cannot be entered
into them in the usual way. It's because \fBjack-keyboard\fR already uses numeric keys
for different purposes. You can still directly enter channel/bank/program in a way described above.

17
pixmaps/Makefile.am Normal file
View File

@ -0,0 +1,17 @@
pixmapsdir = $(datadir)/pixmaps
pixmaps_DATA = jack-keyboard.png
EXTRA_DIST = $(pixmaps_DATA)
gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor
install-data-hook: update-icon-cache
uninstall-hook: update-icon-cache
update-icon-cache:
@-if test -z "$(DESTDIR)"; then \
echo "Updating Gtk icon cache."; \
$(gtk_update_icon_cache); \
else \
echo "*** Icon cache not updated. After (un)install, run this:"; \
echo "*** $(gtk_update_icon_cache)"; \
fi

BIN
pixmaps/jack-keyboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

10
src/Makefile.am Normal file
View File

@ -0,0 +1,10 @@
bin_PROGRAMS = jack-keyboard
jack_keyboard_SOURCES = jack-keyboard.c pianokeyboard.c pianokeyboard.h
jack_keyboard_LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(GTHREAD_LIBS) $(X11_LIBS) $(JACK_LIBS) $(LASH_LIBS)
jack_keyboard_CFLAGS = $(GTK_CFLAGS) $(GLIB_CFLAGS) $(GTHREAD_CFLAGS) $(X11_CFLAGS) $(JACK_CFLAGS) $(LASH_CFLAGS) \
-DG_LOG_DOMAIN=\"jack-keyboard\"
desktopdir = $(datadir)/applications
desktop_DATA = jack-keyboard.desktop
EXTRA_DIST = $(desktop_DATA)

1865
src/jack-keyboard.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
[Desktop Entry]
Name=JACK Keyboard
Comment=Virtual keyboard for JACK MIDI
Exec=jack-keyboard
Icon=jack-keyboard.png
Categories=Application;AudioVideo;Audio;Midi;
Terminal=false
Type=Application

686
src/pianokeyboard.c Normal file
View File

@ -0,0 +1,686 @@
/*-
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* This is piano_keyboard, piano keyboard-like GTK+ widget. It contains
* no MIDI-specific code.
*
* For questions and comments, contact Edward Tomasz Napierala <trasz@FreeBSD.org>.
*/
#include <assert.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "pianokeyboard.h"
#define PIANO_KEYBOARD_DEFAULT_WIDTH 730
#define PIANO_KEYBOARD_DEFAULT_HEIGHT 70
enum {
NOTE_ON_SIGNAL,
NOTE_OFF_SIGNAL,
LAST_SIGNAL
};
static guint piano_keyboard_signals[LAST_SIGNAL] = { 0 };
static void
draw_keyboard_cue(PianoKeyboard *pk)
{
int w = pk->notes[0].w;
int h = pk->notes[0].h;
GdkGC *gc = GTK_WIDGET(pk)->style->fg_gc[0];
int first_note_in_lower_row = (pk->octave + 5) * 12;
int last_note_in_lower_row = (pk->octave + 6) * 12 - 1;
int first_note_in_higher_row = (pk->octave + 6) * 12;
int last_note_in_higher_row = (pk->octave + 7) * 12 + 4;
gdk_draw_line(GTK_WIDGET(pk)->window, gc, pk->notes[first_note_in_lower_row].x + 3,
h - 6, pk->notes[last_note_in_lower_row].x + w - 3, h - 6);
gdk_draw_line(GTK_WIDGET(pk)->window, gc, pk->notes[first_note_in_higher_row].x + 3,
h - 9, pk->notes[last_note_in_higher_row].x + w - 3, h - 9);
}
static void
draw_note(PianoKeyboard *pk, int note)
{
GdkColor black = {0, 0, 0, 0};
GdkColor white = {0, 65535, 65535, 65535};
GdkGC *gc = GTK_WIDGET(pk)->style->fg_gc[0];
GtkWidget *widget;
int is_white = pk->notes[note].white;
int x = pk->notes[note].x;
int w = pk->notes[note].w;
int h = pk->notes[note].h;
if (pk->notes[note].pressed || pk->notes[note].sustained)
is_white = !is_white;
if (is_white)
gdk_gc_set_rgb_fg_color(gc, &white);
else
gdk_gc_set_rgb_fg_color(gc, &black);
gdk_draw_rectangle(GTK_WIDGET(pk)->window, gc, TRUE, x, 0, w, h);
gdk_gc_set_rgb_fg_color(gc, &black);
gdk_draw_rectangle(GTK_WIDGET(pk)->window, gc, FALSE, x, 0, w, h);
if (pk->enable_keyboard_cue)
draw_keyboard_cue(pk);
/* We need to redraw black keys that partially obscure the white one. */
if (note < NNOTES - 2 && !pk->notes[note + 1].white)
draw_note(pk, note + 1);
if (note > 0 && !pk->notes[note - 1].white)
draw_note(pk, note - 1);
/*
* XXX: This doesn't really belong here. Originally I wanted to pack PianoKeyboard into GtkFrame
* packed into GtkAlignment. I failed to make it behave the way I want. GtkFrame would need
* to adapt to the "proper" size of PianoKeyboard, i.e. to the useful_width, not allocated width;
* that didn't work.
*/
widget = GTK_WIDGET(pk);
gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, pk->widget_margin, 0,
widget->allocation.width - pk->widget_margin * 2 + 1, widget->allocation.height);
}
static int
press_key(PianoKeyboard *pk, int key)
{
assert(key >= 0);
assert(key < NNOTES);
pk->maybe_stop_sustained_notes = 0;
/* This is for keyboard autorepeat protection. */
if (pk->notes[key].pressed)
return 0;
if (pk->sustain_new_notes)
pk->notes[key].sustained = 1;
else
pk->notes[key].sustained = 0;
pk->notes[key].pressed = 1;
g_signal_emit_by_name(GTK_WIDGET(pk), "note-on", key);
draw_note(pk, key);
return 1;
}
static int
release_key(PianoKeyboard *pk, int key)
{
assert(key >= 0);
assert(key < NNOTES);
pk->maybe_stop_sustained_notes = 0;
if (!pk->notes[key].pressed)
return 0;
if (pk->sustain_new_notes)
pk->notes[key].sustained = 1;
pk->notes[key].pressed = 0;
if (pk->notes[key].sustained)
return 0;
g_signal_emit_by_name(GTK_WIDGET(pk), "note-off", key);
draw_note(pk, key);
return 1;
}
static void
stop_unsustained_notes(PianoKeyboard *pk)
{
int i;
for (i = 0; i < NNOTES; i++) {
if (pk->notes[i].pressed && !pk->notes[i].sustained) {
pk->notes[i].pressed = 0;
g_signal_emit_by_name(GTK_WIDGET(pk), "note-off", i);
draw_note(pk, i);
}
}
}
static void
stop_sustained_notes(PianoKeyboard *pk)
{
int i;
for (i = 0; i < NNOTES; i++) {
if (pk->notes[i].sustained) {
pk->notes[i].pressed = 0;
pk->notes[i].sustained = 0;
g_signal_emit_by_name(GTK_WIDGET(pk), "note-off", i);
draw_note(pk, i);
}
}
}
static int
key_binding(PianoKeyboard *pk, const char *key)
{
gpointer notused, note;
gboolean found;
assert(pk->key_bindings != NULL);
found = g_hash_table_lookup_extended(pk->key_bindings, key, &notused, &note);
if (!found)
return -1;
return (int)note;
}
static void
bind_key(PianoKeyboard *pk, const char *key, int note)
{
assert(pk->key_bindings != NULL);
g_hash_table_insert(pk->key_bindings, (gpointer)key, (gpointer)note);
}
static void
clear_notes(PianoKeyboard *pk)
{
assert(pk->key_bindings != NULL);
g_hash_table_remove_all(pk->key_bindings);
}
static void
bind_keys_qwerty(PianoKeyboard *pk)
{
clear_notes(pk);
/* Lower keyboard row - "zxcvbnm". */
bind_key(pk, "z", 12); /* C0 */
bind_key(pk, "s", 13);
bind_key(pk, "x", 14);
bind_key(pk, "d", 15);
bind_key(pk, "c", 16);
bind_key(pk, "v", 17);
bind_key(pk, "g", 18);
bind_key(pk, "b", 19);
bind_key(pk, "h", 20);
bind_key(pk, "n", 21);
bind_key(pk, "j", 22);
bind_key(pk, "m", 23);
/* Upper keyboard row, first octave - "qwertyu". */
bind_key(pk, "q", 24);
bind_key(pk, "2", 25);
bind_key(pk, "w", 26);
bind_key(pk, "3", 27);
bind_key(pk, "e", 28);
bind_key(pk, "r", 29);
bind_key(pk, "5", 30);
bind_key(pk, "t", 31);
bind_key(pk, "6", 32);
bind_key(pk, "y", 33);
bind_key(pk, "7", 34);
bind_key(pk, "u", 35);
/* Upper keyboard row, the rest - "iop". */
bind_key(pk, "i", 36);
bind_key(pk, "9", 37);
bind_key(pk, "o", 38);
bind_key(pk, "0", 39);
bind_key(pk, "p", 40);
}
static void
bind_keys_qwertz(PianoKeyboard *pk)
{
bind_keys_qwerty(pk);
/* The only difference between QWERTY and QWERTZ is that the "y" and "z" are swapped together. */
bind_key(pk, "y", 12);
bind_key(pk, "z", 33);
}
static void
bind_keys_azerty(PianoKeyboard *pk)
{
clear_notes(pk);
/* Lower keyboard row - "wxcvbn,". */
bind_key(pk, "w", 12); /* C0 */
bind_key(pk, "s", 13);
bind_key(pk, "x", 14);
bind_key(pk, "d", 15);
bind_key(pk, "c", 16);
bind_key(pk, "v", 17);
bind_key(pk, "g", 18);
bind_key(pk, "b", 19);
bind_key(pk, "h", 20);
bind_key(pk, "n", 21);
bind_key(pk, "j", 22);
bind_key(pk, "comma", 23);
/* Upper keyboard row, first octave - "azertyu". */
bind_key(pk, "a", 24);
bind_key(pk, "eacute", 25);
bind_key(pk, "z", 26);
bind_key(pk, "quotedbl", 27);
bind_key(pk, "e", 28);
bind_key(pk, "r", 29);
bind_key(pk, "parenleft", 30);
bind_key(pk, "t", 31);
bind_key(pk, "minus", 32);
bind_key(pk, "y", 33);
bind_key(pk, "egrave", 34);
bind_key(pk, "u", 35);
/* Upper keyboard row, the rest - "iop". */
bind_key(pk, "i", 36);
bind_key(pk, "ccedilla", 37);
bind_key(pk, "o", 38);
bind_key(pk, "agrave", 39);
bind_key(pk, "p", 40);
}
static gint
keyboard_event_handler(GtkWidget *mk, GdkEventKey *event, gpointer notused)
{
int note;
char *key;
guint keyval;
GdkKeymapKey kk;
PianoKeyboard *pk = PIANO_KEYBOARD(mk);
/* We're not using event->keyval, because we need keyval with level set to 0.
E.g. if user holds Shift and presses '7', we want to get a '7', not '&'. */
kk.keycode = event->hardware_keycode;
kk.level = 0;
kk.group = 0;
keyval = gdk_keymap_lookup_key(NULL, &kk);
key = gdk_keyval_name(gdk_keyval_to_lower(keyval));
if (key == NULL) {
g_message("gtk_keyval_name() returned NULL; please report this.");
return FALSE;
}
note = key_binding(pk, key);
if (note < 0) {
/* Key was not bound. Maybe it's one of the keys handled in jack-keyboard.c. */
return FALSE;
}
note += pk->octave * 12;
assert(note >= 0);
assert(note < NNOTES);
if (event->type == GDK_KEY_PRESS) {
press_key(pk, note);
} else if (event->type == GDK_KEY_RELEASE) {
release_key(pk, note);
}
return TRUE;
}
static int
get_note_for_xy(PianoKeyboard *pk, int x, int y)
{
int height = GTK_WIDGET(pk)->allocation.height;
int note;
if (y <= height / 2) {
for (note = 0; note < NNOTES - 1; note++) {
if (pk->notes[note].white)
continue;
if (x >= pk->notes[note].x && x <= pk->notes[note].x + pk->notes[note].w)
return note;
}
}
for (note = 0; note < NNOTES - 1; note++) {
if (!pk->notes[note].white)
continue;
if (x >= pk->notes[note].x && x <= pk->notes[note].x + pk->notes[note].w)
return note;
}
return -1;
}
static gboolean
mouse_button_event_handler(PianoKeyboard *pk, GdkEventButton *event, gpointer notused)
{
int x = event->x;
int y = event->y;
int note = get_note_for_xy(pk, x, y);
if (event->button != 1)
return TRUE;
if (event->type == GDK_BUTTON_PRESS) {
/* This is possible when you make the window a little wider and then click
on the grey area. */
if (note < 0) {
return TRUE;
}
if (pk->note_being_pressed_using_mouse >= 0)
release_key(pk, pk->note_being_pressed_using_mouse);
press_key(pk, note);
pk->note_being_pressed_using_mouse = note;
} else if (event->type == GDK_BUTTON_RELEASE) {
if (note >= 0) {
release_key(pk, note);
} else {
if (pk->note_being_pressed_using_mouse >= 0)
release_key(pk, pk->note_being_pressed_using_mouse);
}
pk->note_being_pressed_using_mouse = -1;
}
return TRUE;
}
static gboolean
mouse_motion_event_handler(PianoKeyboard *pk, GdkEventMotion *event, gpointer notused)
{
int note;
if ((event->state & GDK_BUTTON1_MASK) == 0)
return TRUE;
note = get_note_for_xy(pk, event->x, event->y);
if (note != pk->note_being_pressed_using_mouse && note >= 0) {
if (pk->note_being_pressed_using_mouse >= 0)
release_key(pk, pk->note_being_pressed_using_mouse);
press_key(pk, note);
pk->note_being_pressed_using_mouse = note;
}
return TRUE;
}
static gboolean
piano_keyboard_expose(GtkWidget *widget, GdkEventExpose *event)
{
int i;
PianoKeyboard *pk = PIANO_KEYBOARD(widget);
for (i = 0; i < NNOTES; i++)
draw_note(pk, i);
return TRUE;
}
static void
piano_keyboard_size_request(GtkWidget *widget, GtkRequisition *requisition)
{
requisition->width = PIANO_KEYBOARD_DEFAULT_WIDTH;
requisition->height = PIANO_KEYBOARD_DEFAULT_HEIGHT;
}
static void
recompute_dimensions(PianoKeyboard *pk)
{
int number_of_white_keys = (NNOTES - 1) * (7.0 / 12.0);
int key_width;
int black_key_width;
int useful_width;
int note;
int white_key = 0;
int note_in_octave;
int width = GTK_WIDGET(pk)->allocation.width;
int height = GTK_WIDGET(pk)->allocation.height;
key_width = width / number_of_white_keys;
black_key_width = key_width * 0.8;
useful_width = number_of_white_keys * key_width;
pk->widget_margin = (width - useful_width) / 2;
for (note = 0, white_key = 0; note < NNOTES - 2; note++) {
note_in_octave = note % 12;
if (note_in_octave == 1 || note_in_octave == 3 || note_in_octave == 6 ||
note_in_octave == 8 || note_in_octave == 10) {
/* This note is black key. */
pk->notes[note].x = pk->widget_margin + white_key * key_width - black_key_width / 2;
pk->notes[note].w = black_key_width;
pk->notes[note].h = height / 2;
pk->notes[note].white = 0;
continue;
}
/* This note is white key. */
pk->notes[note].x = pk->widget_margin + white_key * key_width;
pk->notes[note].w = key_width;
pk->notes[note].h = height;
pk->notes[note].white = 1;
white_key++;
}
}
static void
piano_keyboard_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
/* XXX: Are these two needed? */
g_return_if_fail(widget != NULL);
g_return_if_fail(allocation != NULL);
widget->allocation = *allocation;
recompute_dimensions(PIANO_KEYBOARD(widget));
if (GTK_WIDGET_REALIZED(widget)) {
gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height);
}
}
static void
piano_keyboard_class_init(PianoKeyboardClass *klass)
{
GtkWidgetClass *widget_klass;
/* Set up signals. */
piano_keyboard_signals[NOTE_ON_SIGNAL] = g_signal_new ("note-on",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
0, NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
piano_keyboard_signals[NOTE_OFF_SIGNAL] = g_signal_new ("note-off",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
0, NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
widget_klass = (GtkWidgetClass*) klass;
widget_klass->expose_event = piano_keyboard_expose;
widget_klass->size_request = piano_keyboard_size_request;
widget_klass->size_allocate = piano_keyboard_size_allocate;
}
static void
piano_keyboard_init(GtkWidget *mk)
{
gtk_widget_add_events(mk, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
g_signal_connect(G_OBJECT(mk), "button-press-event", G_CALLBACK(mouse_button_event_handler), NULL);
g_signal_connect(G_OBJECT(mk), "button-release-event", G_CALLBACK(mouse_button_event_handler), NULL);
g_signal_connect(G_OBJECT(mk), "motion-notify-event", G_CALLBACK(mouse_motion_event_handler), NULL);
g_signal_connect(G_OBJECT(mk), "key-press-event", G_CALLBACK(keyboard_event_handler), NULL);
g_signal_connect(G_OBJECT(mk), "key-release-event", G_CALLBACK(keyboard_event_handler), NULL);
}
GType
piano_keyboard_get_type(void)
{
static GType mk_type = 0;
if (!mk_type) {
static const GTypeInfo mk_info = {
sizeof(PianoKeyboardClass),
NULL, /* base_init */
NULL, /* base_finalize */
(GClassInitFunc) piano_keyboard_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (PianoKeyboard),
0, /* n_preallocs */
(GInstanceInitFunc) piano_keyboard_init,
};
mk_type = g_type_register_static(GTK_TYPE_DRAWING_AREA, "PianoKeyboard", &mk_info, 0);
}
return mk_type;
}
GtkWidget *
piano_keyboard_new(void)
{
GtkWidget *widget = gtk_type_new(piano_keyboard_get_type());
PianoKeyboard *pk = PIANO_KEYBOARD(widget);
pk->maybe_stop_sustained_notes = 0;
pk->sustain_new_notes = 0;
pk->enable_keyboard_cue = 0;
pk->octave = 4;
pk->note_being_pressed_using_mouse = -1;
memset((void *)pk->notes, 0, sizeof(struct Note) * NNOTES);
pk->key_bindings = g_hash_table_new(g_str_hash, g_str_equal);
bind_keys_qwerty(pk);
return widget;
}
void
piano_keyboard_set_keyboard_cue(PianoKeyboard *pk, int enabled)
{
pk->enable_keyboard_cue = enabled;
}
void
piano_keyboard_sustain_press(PianoKeyboard *pk)
{
if (!pk->sustain_new_notes) {
pk->sustain_new_notes = 1;
pk->maybe_stop_sustained_notes = 1;
}
}
void
piano_keyboard_sustain_release(PianoKeyboard *pk)
{
if (pk->maybe_stop_sustained_notes)
stop_sustained_notes(pk);
pk->sustain_new_notes = 0;
}
void
piano_keyboard_set_note_on(PianoKeyboard *pk, int note)
{
if (pk->notes[note].pressed == 0) {
pk->notes[note].pressed = 1;
draw_note(pk, note);
}
}
void
piano_keyboard_set_note_off(PianoKeyboard *pk, int note)
{
if (pk->notes[note].pressed || pk->notes[note].sustained) {
pk->notes[note].pressed = 0;
pk->notes[note].sustained = 0;
draw_note(pk, note);
}
}
void
piano_keyboard_set_octave(PianoKeyboard *pk, int octave)
{
stop_unsustained_notes(pk);
pk->octave = octave;
gtk_widget_queue_draw(GTK_WIDGET(pk));
}
gboolean
piano_keyboard_set_keyboard_layout(PianoKeyboard *pk, const char *layout)
{
assert(layout);
if (!strcasecmp(layout, "QWERTY")) {
bind_keys_qwerty(pk);
} else if (!strcasecmp(layout, "QWERTZ")) {
bind_keys_qwertz(pk);
} else if (!strcasecmp(layout, "AZERTY")) {
bind_keys_azerty(pk);
} else {
/* Unknown layout name. */
return TRUE;
}
return FALSE;
}

66
src/pianokeyboard.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef __PIANO_KEYBOARD_H__
#define __PIANO_KEYBOARD_H__
#include <glib.h>
#include <gtk/gtkdrawingarea.h>
G_BEGIN_DECLS
#define TYPE_PIANO_KEYBOARD (piano_keyboard_get_type ())
#define PIANO_KEYBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_PIANO_KEYBOARD, PianoKeyboard))
#define PIANO_KEYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_PIANO_KEYBOARD, PianoKeyboardClass))
#define IS_PIANO_KEYBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_PIANO_KEYBOARD))
#define IS_PIANO_KEYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_PIANO_KEYBOARD))
#define PIANO_KEYBOARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_PIANO_KEYBOARD, PianoKeyboardClass))
typedef struct _PianoKeyboard PianoKeyboard;
typedef struct _PianoKeyboardClass PianoKeyboardClass;
#define NNOTES 127
#define OCTAVE_MIN -1
#define OCTAVE_MAX 7
struct Note {
int pressed; /* 1 if key is in pressed down state. */
int sustained; /* 1 if note is sustained. */
int x; /* Distance between the left edge of the key
* and the left edge of the widget, in pixels. */
int w; /* Width of the key, in pixels. */
int h; /* Height of the key, in pixels. */
int white; /* 1 if key is white; 0 otherwise. */
};
struct _PianoKeyboard
{
GtkDrawingArea da;
int maybe_stop_sustained_notes;
int sustain_new_notes;
int enable_keyboard_cue;
int octave;
int widget_margin;
int note_being_pressed_using_mouse;
volatile struct Note notes[NNOTES];
/* Table used to translate from PC keyboard character to MIDI note number. */
GHashTable *key_bindings;
};
struct _PianoKeyboardClass
{
GtkDrawingAreaClass parent_class;
};
GType piano_keyboard_get_type (void) G_GNUC_CONST;
GtkWidget* piano_keyboard_new (void);
void piano_keyboard_sustain_press (PianoKeyboard *pk);
void piano_keyboard_sustain_release (PianoKeyboard *pk);
void piano_keyboard_set_note_on (PianoKeyboard *pk, int note);
void piano_keyboard_set_note_off (PianoKeyboard *pk, int note);
void piano_keyboard_set_keyboard_cue (PianoKeyboard *pk, int enabled);
void piano_keyboard_set_octave (PianoKeyboard *pk, int octave);
gboolean piano_keyboard_set_keyboard_layout (PianoKeyboard *pk, const char *layout);
G_END_DECLS
#endif /* __PIANO_KEYBOARD_H__ */