jack-keyboard-2.7.1/000755 000000 003720 00000000000 11745540216 015375 5ustar00roothans_other000000 000000 jack-keyboard-2.7.1/AUTHORS000644 003720 003720 00000000235 11676312727 017637 0ustar00hans_otherhans_other000000 000000 Edward Tomasz Napierała Hans Petter Selasky Jeff Snyder Dan Muresan jack-keyboard-2.7.1/CMakeLists.txt000644 003720 003720 00000005100 11676312350 021313 0ustar00hans_otherhans_other000000 000000 # # Copyright (c) 2011 Hans Petter Selasky. 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. # # # Makefile for Jack Keyboard # cmake_minimum_required(VERSION 2.8) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") set(VERSION "2.7.1") set(JackEnable ON CACHE BOOL "Enable support for Jack") set(LashEnable ON CACHE BOOL "Enable support for Lash") set(X11Enable ON CACHE BOOL "Enable support for X11") project(jack-keyboard) add_executable(jack-keyboard src/jack-keyboard src/pianokeyboard) find_package(GTK2 2.2 REQUIRED gtk) include_directories(${GTK2_INCLUDE_DIRS}) target_link_libraries(jack-keyboard ${GTK2_LIBRARIES}) if(JackEnable) find_package(JACK) include_directories(${JACK_INCLUDE_DIR}) target_link_libraries(jack-keyboard ${JACK_LIBRARIES}) add_definitions(-DHAVE_JACK=1) endif() if(LashEnable) find_package(LASH) include_directories(${LASH_INCLUDE_DIR}) target_link_libraries(jack-keyboard ${LASH_LIBRARIES}) add_definitions(-DHAVE_LASH=1) endif() if(X11Enable) find_package(X11) include_directories(${X11_INCLUDE_DIR}) target_link_libraries(jack-keyboard ${X11_LIBRARIES}) add_definitions(-DHAVE_X11=1) endif() install(TARGETS jack-keyboard RUNTIME DESTINATION bin) install(FILES pixmaps/jack-keyboard.png DESTINATION share/pixmaps) install(FILES src/jack-keyboard.desktop DESTINATION share/applications) install(FILES man/jack-keyboard.1 DESTINATION man/man1) jack-keyboard-2.7.1/COPYING000644 003720 003720 00000002446 11560716656 017631 0ustar00hans_otherhans_other000000 000000 Copyright (c) 2007, 2008 Edward Tomasz Napierała 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. jack-keyboard-2.7.1/Makefile000644 003720 003720 00000001275 11676312341 020224 0ustar00hans_otherhans_other000000 000000 VERSION?=2.7.1 help: @echo "Targets: configure all clean install package" configure: rm -rf build mkdir build cd build ; cmake .. all: make -C build all install: make -C build install clean: make -C build clean package: make -C build clean || echo -n tar -cvf temp.tar --exclude="*~" --exclude="*#" \ --exclude=".svn" --exclude="*.orig" --exclude="*.rej" \ AUTHORS \ CMakeLists.txt \ COPYING \ Makefile \ NEWS \ README \ TODO \ cmake \ man \ pixmaps \ src rm -rf jack-keyboard-${VERSION} mkdir jack-keyboard-${VERSION} tar -xvf temp.tar -C jack-keyboard-${VERSION} rm -rf temp.tar tar -zcvf jack-keyboard-${VERSION}.tar.gz jack-keyboard-${VERSION} jack-keyboard-2.7.1/NEWS000644 003720 003720 00000013365 11676314775 017303 0ustar00hans_otherhans_other000000 000000 User-visible changes between 2.6 and 2.7.1 include: - fix a warning regarding the redefinition of NNOTES - raise NNOTES from 127 to 128 (only notes 0..125 were being shown because 127 was < NNOTES and 126 is black) - only show the keys that exist on a real piano by default, and add a "-f" ('full midi keyboard') command line option to show all midi notes. - make the dimensions slightly closer to a real piano by: > making the black key hight two third of the white key height > making the black key positions vary based on their position in the octave - adding a QWERTY_REV layout that has the lower keys on the upper row of the keyboard - adding QWERTY_UK and QWERTY_UK_REV layouts that extend the QWERTY and QWERTY_REV layouts with the punctuation keys available on those rows User-visible changes between 2.5 and 2.6 include: - Bugfix: A key volume of zero means key off. - Bugfix: Make pianola/OMNI mode the default. - Feature: Don't start jackd when starting jack-keyboard. 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) jack-keyboard-2.7.1/README000644 003720 003720 00000012227 11560716656 017454 0ustar00hans_otherhans_other000000 000000 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 . jack-keyboard-2.7.1/TODO000644 003720 003720 00000000647 11560716656 017267 0ustar00hans_otherhans_other000000 000000 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. - Add user-configurable key binding. jack-keyboard-2.7.1/cmake/000755 003720 003720 00000000000 11720375701 017636 5ustar00hans_otherhans_other000000 000000 jack-keyboard-2.7.1/man/000755 003720 003720 00000000000 11720375701 017331 5ustar00hans_otherhans_other000000 000000 jack-keyboard-2.7.1/pixmaps/000755 003720 003720 00000000000 11720375701 020237 5ustar00hans_otherhans_other000000 000000 jack-keyboard-2.7.1/src/000755 003720 003720 00000000000 11745540055 017350 5ustar00hans_otherhans_other000000 000000 jack-keyboard-2.7.1/src/jack-keyboard.c000644 003720 003720 00000135257 11745537736 022253 0ustar00hans_otherhans_other000000 000000 /*- * Copyright (c) 2007, 2008 Edward Tomasz Napierała * 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. */ /* * jack-keyboard is a virtual keyboard for JACK MIDI. * * For questions and comments, you can contact: * - Edward Tomasz Napierala * - Hans Petter Selasky . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "pianokeyboard.h" #ifdef HAVE_LASH #include #endif #ifdef HAVE_X11 #include #endif #define VELOCITY_MAX 127 #define VELOCITY_HIGH 100 #define VELOCITY_NORMAL 64 #define VELOCITY_LOW 32 #define VELOCITY_MIN 1 #define OUTPUT_PORT_NAME "midi_out" #define INPUT_PORT_NAME "midi_in" #define PACKAGE_NAME "jack-keyboard" #define PACKAGE_VERSION "2.7.1" jack_port_t *output_port; jack_port_t *input_port; int entered_number = -1; int allow_connecting_to_own_kind = 0; int enable_gui = 1; int grab_keyboard_at_startup = 0; volatile int keyboard_grabbed = 0; int enable_window_title = 0; int time_offsets_are_zero = 0; int send_program_change_at_reconnect = 0; int send_program_change_once = 0; int program_change_was_sent = 0; int velocity_high = VELOCITY_HIGH; int velocity_normal = VELOCITY_NORMAL; int velocity_low = VELOCITY_LOW; int *current_velocity = &velocity_normal; int octave = 4; double rate_limit = 0.0; jack_client_t *jack_client = NULL; #ifdef HAVE_LASH lash_client_t *lash_client; #endif #define MIDI_NOTE_ON 0x90 #define MIDI_NOTE_OFF 0x80 #define MIDI_PROGRAM_CHANGE 0xC0 #define MIDI_CONTROLLER 0xB0 #define MIDI_RESET 0xFF #define MIDI_HOLD_PEDAL 64 #define MIDI_ALL_SOUND_OFF 120 #define MIDI_ALL_MIDI_CONTROLLERS_OFF 121 #define MIDI_ALL_NOTES_OFF 123 #define MIDI_BANK_SELECT_MSB 0 #define MIDI_BANK_SELECT_LSB 32 #define BANK_MIN 0 #define BANK_MAX 127 #define PROGRAM_MIN 0 #define PROGRAM_MAX 127 #define CHANNEL_MIN 1 #define CHANNEL_MAX 16 GtkWidget *window, *sustain_button, *channel_spin, *bank_spin, *program_spin, *connected_to_combo, *velocity_hscale, *grab_keyboard_checkbutton, *octave_spin; PianoKeyboard *keyboard; GtkListStore *connected_to_store; #ifdef HAVE_X11 Display *dpy; #endif struct MidiMessage { jack_nframes_t time; int len; /* Length of MIDI message, in bytes. */ unsigned char data[3]; }; #define RINGBUFFER_SIZE 1024*sizeof(struct MidiMessage) /* Will emit a warning if time between jack callbacks is longer than this. */ #define MAX_TIME_BETWEEN_CALLBACKS 0.1 /* Will emit a warning if execution of jack callback takes longer than this. */ #define MAX_PROCESSING_TIME 0.01 jack_ringbuffer_t *ringbuffer; /* Number of currently used program. */ int program = 0; /* Number of currently selected bank. */ int bank = 0; /* Number of currently selected channel (0..15). */ int channel = 0; void draw_note(int key); void queue_message(struct MidiMessage *ev); double get_time(void) { double seconds; int ret; struct timeval tv; ret = gettimeofday(&tv, NULL); if (ret) { perror("gettimeofday"); exit(EX_OSERR); } seconds = tv.tv_sec + tv.tv_usec / 1000000.0; return (seconds); } double get_delta_time(void) { static double previously = -1.0; double now; double delta; now = get_time(); if (previously == -1.0) { previously = now; return (0); } delta = now - previously; previously = now; assert(delta >= 0.0); return (delta); } gboolean process_received_message_async(gpointer evp) { int i; struct MidiMessage *ev = (struct MidiMessage *)evp; int b0 = ev->data[0]; int b1 = ev->data[1]; /* Strip channel from channel messages */ if (b0 >= 0x80 && b0 <= 0xEF) b0 = b0 & 0xF0; if (b0 == MIDI_RESET || (b0 == MIDI_CONTROLLER && (b1 == MIDI_ALL_NOTES_OFF || b1 == MIDI_ALL_SOUND_OFF))) { for (i = 0; i < NNOTES; i++) { piano_keyboard_set_note_off(keyboard, i); } } if (b0 == MIDI_NOTE_ON) { if (ev->data[2] == 0) piano_keyboard_set_note_off(keyboard, ev->data[1]); else piano_keyboard_set_note_on(keyboard, ev->data[1]); } if (b0 == MIDI_NOTE_OFF) { piano_keyboard_set_note_off(keyboard, ev->data[1]); } ev->data [0] = b0 | channel; queue_message(ev); return (FALSE); } struct MidiMessage * midi_message_from_midi_event(jack_midi_event_t event) { struct MidiMessage *ev = malloc(sizeof(*ev)); if (ev == NULL) { perror("malloc"); return (NULL); } assert(event.size >= 1 && event.size <= 3); ev->len = event.size; ev->time = event.time; memcpy(ev->data, event.buffer, ev->len); return (ev); } gboolean warning_async(gpointer s) { const char *str = (const char *)s; g_warning(str); return (FALSE); } void warn_from_jack_thread_context(const char *str) { g_idle_add(warning_async, (gpointer)str); } void process_midi_input(jack_nframes_t nframes) { int read, events, i; void *port_buffer; jack_midi_event_t event; port_buffer = jack_port_get_buffer(input_port, nframes); if (port_buffer == NULL) { warn_from_jack_thread_context("jack_port_get_buffer failed, cannot receive anything."); return; } #ifdef JACK_MIDI_NEEDS_NFRAMES events = jack_midi_get_event_count(port_buffer, nframes); #else events = jack_midi_get_event_count(port_buffer); #endif for (i = 0; i < events; i++) { struct MidiMessage *rev; #ifdef JACK_MIDI_NEEDS_NFRAMES read = jack_midi_event_get(&event, port_buffer, i, nframes); #else read = jack_midi_event_get(&event, port_buffer, i); #endif if (read) { warn_from_jack_thread_context("jack_midi_event_get failed, RECEIVED NOTE LOST."); continue; } if (event.size > 3) { warn_from_jack_thread_context("Ignoring MIDI message longer than three bytes, probably a SysEx."); continue; } rev = midi_message_from_midi_event(event); if (rev == NULL) { warn_from_jack_thread_context("midi_message_from_midi_event failed, RECEIVED NOTE LOST."); continue; } g_idle_add(process_received_message_async, rev); } } double nframes_to_ms(jack_nframes_t nframes) { jack_nframes_t sr; sr = jack_get_sample_rate(jack_client); assert(sr > 0); return ((nframes * 1000.0) / (double)sr); } void process_midi_output(jack_nframes_t nframes) { int read, t, bytes_remaining; unsigned char *buffer; void *port_buffer; jack_nframes_t last_frame_time; struct MidiMessage ev; last_frame_time = jack_last_frame_time(jack_client); port_buffer = jack_port_get_buffer(output_port, nframes); if (port_buffer == NULL) { warn_from_jack_thread_context("jack_port_get_buffer failed, cannot send anything."); return; } #ifdef JACK_MIDI_NEEDS_NFRAMES jack_midi_clear_buffer(port_buffer, nframes); #else jack_midi_clear_buffer(port_buffer); #endif /* We may push at most one byte per 0.32ms to stay below 31.25 Kbaud limit. */ bytes_remaining = nframes_to_ms(nframes) * rate_limit; while (jack_ringbuffer_read_space(ringbuffer)) { read = jack_ringbuffer_peek(ringbuffer, (char *)&ev, sizeof(ev)); if (read != sizeof(ev)) { warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss."); jack_ringbuffer_read_advance(ringbuffer, read); continue; } bytes_remaining -= ev.len; if (rate_limit > 0.0 && bytes_remaining <= 0) { warn_from_jack_thread_context("Rate limiting in effect."); break; } t = ev.time + nframes - last_frame_time; /* If computed time is too much into the future, we'll need to send it later. */ if (t >= (int)nframes) break; /* If computed time is < 0, we missed a cycle because of xrun. */ if (t < 0) t = 0; if (time_offsets_are_zero) t = 0; jack_ringbuffer_read_advance(ringbuffer, sizeof(ev)); #ifdef JACK_MIDI_NEEDS_NFRAMES buffer = jack_midi_event_reserve(port_buffer, t, ev.len, nframes); #else buffer = jack_midi_event_reserve(port_buffer, t, ev.len); #endif if (buffer == NULL) { warn_from_jack_thread_context("jack_midi_event_reserve failed, NOTE LOST."); break; } memcpy(buffer, ev.data, ev.len); } } int process_callback(jack_nframes_t nframes, void *notused) { #ifdef MEASURE_TIME if (get_delta_time() > MAX_TIME_BETWEEN_CALLBACKS) warn_from_jack_thread_context("Had to wait too long for JACK callback; scheduling problem?"); #endif /* Check for impossible condition that actually happened to me, caused by some problem between jackd and OSS4. */ if (nframes <= 0) { warn_from_jack_thread_context("Process callback called with nframes = 0; bug in JACK?"); return 0; } process_midi_input(nframes); process_midi_output(nframes); #ifdef MEASURE_TIME if (get_delta_time() > MAX_PROCESSING_TIME) warn_from_jack_thread_context("Processing took too long; scheduling problem?"); #endif return (0); } void queue_message(struct MidiMessage *ev) { int written; if (jack_ringbuffer_write_space(ringbuffer) < sizeof(*ev)) { g_critical("Not enough space in the ringbuffer, NOTE LOST."); return; } written = jack_ringbuffer_write(ringbuffer, (char *)ev, sizeof(*ev)); if (written != sizeof(*ev)) g_warning("jack_ringbuffer_write failed, NOTE LOST."); } void queue_new_message(int b0, int b1, int b2) { struct MidiMessage ev; /* For MIDI messages that specify a channel number, filter the original channel number out and add our own. */ if (b0 >= 0x80 && b0 <= 0xEF) { b0 &= 0xF0; b0 += channel; } if (b1 == -1) { ev.len = 1; ev.data[0] = b0; } else if (b2 == -1) { ev.len = 2; ev.data[0] = b0; ev.data[1] = b1; } else { ev.len = 3; ev.data[0] = b0; ev.data[1] = b1; ev.data[2] = b2; } ev.time = jack_frame_time(jack_client); queue_message(&ev); } gboolean update_connected_to_combo_async(gpointer notused) { int i, count = 0; const char **connected, **available, *my_name; GtkTreeIter iter; if (jack_client == NULL || output_port == NULL) return (FALSE); connected = jack_port_get_connections(output_port); available = jack_get_ports(jack_client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput); my_name = jack_port_name(input_port); assert(my_name); /* There will be at least one listening MIDI port - the one we create. */ assert(available); if (available != NULL) { gtk_list_store_clear(connected_to_store); for (i = 0; available[i] != NULL; i++) { if (!strcmp(available[i], my_name) || (!allow_connecting_to_own_kind && !strncmp(available[i], my_name, strlen(PACKAGE_NAME)))) continue; count++; gtk_list_store_append(connected_to_store, &iter); gtk_list_store_set(connected_to_store, &iter, 0, available[i], -1); if (connected != NULL && connected[0] != NULL && !strcmp(available[i], connected[0])) gtk_combo_box_set_active_iter(GTK_COMBO_BOX(connected_to_combo), &iter); } } if (count > 0) gtk_widget_set_sensitive(connected_to_combo, TRUE); else gtk_widget_set_sensitive(connected_to_combo, FALSE); if (connected != NULL) free(connected); free(available); return (FALSE); } void draw_window_title(void) { int i, off = 0; char title[256]; const char **connected_ports; if (window == NULL) return; if (enable_window_title && jack_client != NULL) { connected_ports = jack_port_get_connections(output_port); off += snprintf(title, sizeof(title) - off, "%s: channel %d, bank %d, program %d", jack_get_client_name(jack_client), channel + 1, bank, program); if (!program_change_was_sent) off += snprintf(title + off, sizeof(title) - off, " (bank/program change not sent)"); if (connected_ports == NULL || connected_ports[0] == NULL) { off += snprintf(title + off, sizeof(title) - off, ", NOT CONNECTED"); } else { off += snprintf(title + off, sizeof(title) - off, ", connected to "); for (i = 0; connected_ports[i] != NULL; i++) { off += snprintf(title + off, sizeof(title) - off, "%s%s", i == 0 ? "" : ", ", connected_ports[i]); } } if (connected_ports != NULL) free(connected_ports); gtk_window_set_title(GTK_WINDOW(window), title); } else { /* May be null if JACK is not initialized yet. */ if (jack_client != NULL) gtk_window_set_title(GTK_WINDOW(window), jack_get_client_name(jack_client)); else gtk_window_set_title(GTK_WINDOW(window), PACKAGE_NAME); } } gboolean update_window_title_async(gpointer notused) { if (window != NULL) draw_window_title(); return (FALSE); } int graph_order_callback(void *notused) { g_idle_add(update_window_title_async, NULL); g_idle_add(update_connected_to_combo_async, NULL); return (0); } void send_program_change(void) { if (jack_port_connected(output_port) == 0) return; queue_new_message(MIDI_CONTROLLER, MIDI_BANK_SELECT_LSB, bank % 128); queue_new_message(MIDI_CONTROLLER, MIDI_BANK_SELECT_MSB, bank / 128); queue_new_message(MIDI_PROGRAM_CHANGE, program, -1); program_change_was_sent = 1; } /* Connects to the specified input port, disconnecting already connected ports. */ int connect_to_input_port(const char *port) { int ret; if (!strcmp(port, jack_port_name(input_port))) return (-1); if (!allow_connecting_to_own_kind) { if (!strncmp(port, jack_port_name(input_port), strlen(PACKAGE_NAME))) return (-2); } ret = jack_port_disconnect(jack_client, output_port); if (ret) { g_warning("Cannot disconnect MIDI port."); return (-3); } ret = jack_connect(jack_client, jack_port_name(output_port), port); if (ret) { g_warning("Cannot connect to %s.", port); return (-4); } g_warning("Connected to %s.", port); program_change_was_sent = 0; if (send_program_change_at_reconnect || send_program_change_once) { send_program_change(); draw_window_title(); } send_program_change_once = 0; return (0); } void connect_to_another_input_port(int up_not_down) { const char **available_midi_ports; const char **connected_ports; const char *current = NULL; int i, max, current_index; available_midi_ports = jack_get_ports(jack_client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput); /* There will be at least one listening MIDI port - the one we create. */ assert(available_midi_ports); /* * max is the highest possible index into available_midi_ports[], * i.e. number of elements - 1. */ for (max = 0; available_midi_ports[max + 1] != NULL; max++); /* Only one input port - our own. */ if (max == 0) { g_warning("No listening JACK MIDI input ports found. " "Run some softsynth and press Insert or Delete key."); return; } connected_ports = jack_port_get_connections(output_port); if (connected_ports != NULL && connected_ports[0] != NULL) current = connected_ports[0]; else current = available_midi_ports[0]; /* * current is the index of currently connected port into available_midi_ports[]. */ for (i = 0; i <= max && strcmp(available_midi_ports[i], current); i++); current_index = i; assert(!strcmp(available_midi_ports[current_index], current)); /* XXX: rewrite. */ if (up_not_down) { for (i = current_index + 1; i <= max; i++) { assert(available_midi_ports[i] != NULL); if (connect_to_input_port(available_midi_ports[i]) == 0) goto connected; } for (i = 0; i <= current_index; i++) { assert(available_midi_ports[i] != NULL); if (connect_to_input_port(available_midi_ports[i]) == 0) goto connected; } } else { for (i = current_index - 1; i >= 0; i--) { assert(available_midi_ports[i] != NULL); if (connect_to_input_port(available_midi_ports[i]) == 0) goto connected; } for (i = max; i >= current_index; i--) { assert(available_midi_ports[i] != NULL); if (connect_to_input_port(available_midi_ports[i]) == 0) goto connected; } } g_warning("Cannot connect to any of the input ports."); connected: free(available_midi_ports); if (connected_ports != NULL) free(connected_ports); } void connect_to_next_input_port(void) { connect_to_another_input_port(1); } void connect_to_prev_input_port(void) { connect_to_another_input_port(0); } void init_jack(void) { int err; #ifdef HAVE_LASH lash_event_t *event; #endif jack_client = jack_client_open(PACKAGE_NAME, JackNoStartServer, NULL); if (jack_client == NULL) { g_critical("Could not connect to the JACK server; run jackd first?"); exit(EX_UNAVAILABLE); } ringbuffer = jack_ringbuffer_create(RINGBUFFER_SIZE); if (ringbuffer == NULL) { g_critical("Cannot create JACK ringbuffer."); exit(EX_SOFTWARE); } jack_ringbuffer_mlock(ringbuffer); #ifdef HAVE_LASH event = lash_event_new_with_type(LASH_Client_Name); assert (event); /* Documentation does not say anything about return value. */ lash_event_set_string(event, jack_get_client_name(jack_client)); lash_send_event(lash_client, event); lash_jack_client_name(lash_client, jack_get_client_name(jack_client)); #endif err = jack_set_process_callback(jack_client, process_callback, 0); if (err) { g_critical("Could not register JACK process callback."); exit(EX_UNAVAILABLE); } err = jack_set_graph_order_callback(jack_client, graph_order_callback, 0); if (err) { g_critical("Could not register JACK graph order callback."); exit(EX_UNAVAILABLE); } output_port = jack_port_register(jack_client, OUTPUT_PORT_NAME, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); if (output_port == NULL) { g_critical("Could not register JACK output port."); exit(EX_UNAVAILABLE); } input_port = jack_port_register(jack_client, INPUT_PORT_NAME, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); if (input_port == NULL) { g_critical("Could not register JACK input port."); exit(EX_UNAVAILABLE); } if (jack_activate(jack_client)) { g_critical("Cannot activate JACK client."); exit(EX_UNAVAILABLE); } } #ifdef HAVE_LASH void load_config_from_lash(void) { lash_config_t *config; const char *key; int value; while ((config = lash_get_config(lash_client))) { key = lash_config_get_key(config); value = lash_config_get_value_int(config); if (!strcmp(key, "channel")) { if (value < CHANNEL_MIN || value > CHANNEL_MAX) { g_warning("Bad value '%d' for 'channel' property received from LASH.", value); } else { gtk_spin_button_set_value(GTK_SPIN_BUTTON(channel_spin), value); } } else if (!strcmp(key, "bank")) { if (value < BANK_MIN || value > BANK_MAX) { g_warning("Bad value '%d' for 'bank' property received from LASH.", value); } else { gtk_spin_button_set_value(GTK_SPIN_BUTTON(bank_spin), value); } } else if (!strcmp(key, "program")) { if (value < PROGRAM_MIN || value > PROGRAM_MAX) { g_warning("Bad value '%d' for 'program' property received from LASH.", value); } else { gtk_spin_button_set_value(GTK_SPIN_BUTTON(program_spin), value); } } else if (!strcmp(key, "keyboard_grabbed")) { if (value < 0 || value > 1) { g_warning("Bad value '%d' for 'keyboard_grabbed' property received from LASH.", value); } else { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grab_keyboard_checkbutton), value); } } else if (!strcmp(key, "octave")) { if (value < OCTAVE_MIN || value > OCTAVE_MAX) { g_warning("Bad value '%d' for 'octave' property received from LASH.", value); } else { gtk_spin_button_set_value(GTK_SPIN_BUTTON(octave_spin), value); } } else if (!strcmp(key, "velocity_normal")) { if (value < VELOCITY_MIN || value > VELOCITY_MAX) { g_warning("Bad value '%d' for 'velocity_normal' property received from LASH.", value); } else { velocity_normal = value; gtk_range_set_value(GTK_RANGE(velocity_hscale), *current_velocity); } } else if (!strcmp(key, "velocity_high")) { if (value < VELOCITY_MIN || value > VELOCITY_MAX) { g_warning("Bad value '%d' for 'velocity_high' property received from LASH.", value); } else { velocity_high = value; gtk_range_set_value(GTK_RANGE(velocity_hscale), *current_velocity); } } else if (!strcmp(key, "velocity_low")) { if (value < VELOCITY_MIN || value > VELOCITY_MAX) { g_warning("Bad value '%d' for 'velocity_low' property received from LASH.", value); } else { velocity_low = value; gtk_range_set_value(GTK_RANGE(velocity_hscale), *current_velocity); } } else { g_warning("Received unknown config key '%s' (value '%d') from LASH.", key, value); } lash_config_destroy(config); } } void save_config_int(const char *name, int value) { lash_config_t *config; config = lash_config_new_with_key(name); lash_config_set_value_int(config, value); lash_send_config(lash_client, config); } void save_config_into_lash(void) { save_config_int("channel", channel + 1); save_config_int("bank", bank); save_config_int("program", program); save_config_int("keyboard_grabbed", keyboard_grabbed); save_config_int("octave", octave); save_config_int("velocity_normal", velocity_normal); save_config_int("velocity_high", velocity_high); save_config_int("velocity_low", velocity_low); } gboolean lash_callback(gpointer notused) { lash_event_t *event; while ((event = lash_get_event(lash_client))) { switch (lash_event_get_type(event)) { case LASH_Restore_Data_Set: load_config_from_lash(); lash_send_event(lash_client, event); break; case LASH_Save_Data_Set: save_config_into_lash(); lash_send_event(lash_client, event); break; case LASH_Quit: g_warning("Exiting due to LASH request."); exit(EX_OK); break; default: g_warning("Receieved unknown LASH event of type %d.", lash_event_get_type(event)); lash_event_destroy(event); } } return (TRUE); } void init_lash(lash_args_t *args) { /* XXX: Am I doing the right thing wrt protocol version? */ lash_client = lash_init(args, PACKAGE_NAME, LASH_Config_Data_Set, LASH_PROTOCOL(2, 0)); if (!lash_server_connected(lash_client)) { g_critical("Cannot initialize LASH. Continuing anyway."); /* exit(EX_UNAVAILABLE); */ return; } /* Schedule a function to process LASH events, ten times per second. */ g_timeout_add(100, lash_callback, NULL); } #endif /* HAVE_LASH */ gboolean sustain_event_handler(GtkToggleButton *widget, gpointer pressed) { if (pressed) { gtk_toggle_button_set_active(widget, TRUE); piano_keyboard_sustain_press(keyboard); } else { gtk_toggle_button_set_active(widget, FALSE); piano_keyboard_sustain_release(keyboard); } return (FALSE); } void channel_event_handler(GtkSpinButton *spinbutton, gpointer notused) { channel = gtk_spin_button_get_value(spinbutton) - 1; draw_window_title(); } void bank_event_handler(GtkSpinButton *spinbutton, gpointer notused) { bank = gtk_spin_button_get_value(spinbutton); send_program_change(); draw_window_title(); } void program_event_handler(GtkSpinButton *spinbutton, gpointer notused) { program = gtk_spin_button_get_value(spinbutton); send_program_change(); draw_window_title(); } void connected_to_event_handler(GtkComboBox *widget, gpointer notused) { GtkTreeIter iter; gchar *connect_to; const char **connected_ports; if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(connected_to_combo), &iter) == FALSE) return; gtk_tree_model_get(GTK_TREE_MODEL(connected_to_store), &iter, 0, &connect_to, -1); connected_ports = jack_port_get_connections(output_port); if (connected_ports != NULL && connected_ports[0] != NULL && !strcmp(connect_to, connected_ports[0])) { free(connected_ports); return; } connect_to_input_port(connect_to); free(connected_ports); } void velocity_event_handler(GtkRange *range, gpointer notused) { assert(current_velocity); *current_velocity = gtk_range_get_value(range); } #ifdef HAVE_X11 int grab_x_error_handler(Display *dpy, XErrorEvent *notused) { keyboard_grabbed = 0; return (42); /* Returned value is ignored. */ } GdkFilterReturn keyboard_grab_filter(GdkXEvent *xevent, GdkEvent *event, gpointer notused) { XEvent *xe; XKeyEvent *xke; xe = (XEvent *)xevent; if (xe->type != KeyPress && xe->type != KeyRelease) return (GDK_FILTER_CONTINUE); xke = (XKeyEvent *)xevent; /* Lie to GDK, pretending we are the proper recipient of this XEvent. Without it, GDK would discard it. */ xke->window = GDK_WINDOW_XWINDOW(window->window); return (GDK_FILTER_CONTINUE); } void ungrab_keyboard(void) { static int (*standard_x_error_handler)(Display *dpy, XErrorEvent *notused); standard_x_error_handler = XSetErrorHandler(grab_x_error_handler); XUngrabKey(dpy, AnyKey, AnyModifier, GDK_ROOT_WINDOW()); XSync(dpy, FALSE); XSetErrorHandler(standard_x_error_handler); keyboard_grabbed = 0; } void grab_keyboard(void) { int i; static int (*standard_x_error_handler)(Display *dpy, XErrorEvent *notused); KeySym keys_to_grab[] = { XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9, XK_0, XK_minus, XK_equal, XK_q, XK_w, XK_e, XK_r, XK_t, XK_y, XK_u, XK_i, XK_o, XK_p, XK_a, XK_s, XK_d, XK_f, XK_g, XK_h, XK_j, XK_k, XK_l, XK_z, XK_x, XK_c, XK_v, XK_b, XK_n, XK_m, XK_Shift_L, XK_Shift_R, XK_Control_L, XK_Control_R, XK_space, XK_Insert, XK_Delete, XK_Home, XK_End, XK_Page_Up, XK_Page_Down, XK_KP_1, XK_KP_2, XK_KP_3, XK_KP_4, XK_KP_5, XK_KP_6, XK_KP_7, XK_KP_8, XK_KP_9, XK_KP_0, XK_Num_Lock, XK_KP_Multiply, XK_KP_Divide, XK_KP_Subtract, XK_KP_Add, XK_KP_Enter, XK_Escape, /* End of list. */ XK_VoidSymbol}; if (keyboard_grabbed) return; dpy = GDK_WINDOW_XDISPLAY(window->window); keyboard_grabbed = 1; standard_x_error_handler = XSetErrorHandler(grab_x_error_handler); for (i = 0; keys_to_grab[i] != XK_VoidSymbol; i++) { KeyCode kc = XKeysymToKeycode(dpy, keys_to_grab[i]); if (kc == 0) { g_critical("XKeysymToKeycode failed. Please report this to the author."); continue; } XGrabKey(dpy, kc, 0, GDK_ROOT_WINDOW(), True, GrabModeAsync, GrabModeAsync); XGrabKey(dpy, kc, ShiftMask, GDK_ROOT_WINDOW(), True, GrabModeAsync, GrabModeAsync); XGrabKey(dpy, kc, ControlMask, GDK_ROOT_WINDOW(), True, GrabModeAsync, GrabModeAsync); XGrabKey(dpy, kc, ShiftMask | ControlMask, GDK_ROOT_WINDOW(), True, GrabModeAsync, GrabModeAsync); } XSync(dpy, FALSE); XSetErrorHandler(standard_x_error_handler); if (keyboard_grabbed == 0) { g_critical("XGrabKey() failed; keyboard grabbing not possible. " "Maybe some other application grabbed the keyboard?"); /* Make sure we don't keep any keys grabbed. Only one of XGrabKey invocations failed, others might have been successfull. */ ungrab_keyboard(); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grab_keyboard_checkbutton), keyboard_grabbed); return; } g_atexit(ungrab_keyboard); gdk_window_add_filter(NULL, keyboard_grab_filter, NULL); } #else /* ! HAVE_X11 */ void ungrab_keyboard(void) { } void grab_keyboard(void) { g_critical("Compiled without XGrabKey support; keyboard grabbing not possible."); } #endif /* ! HAVE_X11 */ void grab_keyboard_handler(GtkToggleButton *togglebutton, gpointer notused) { gboolean active = gtk_toggle_button_get_active(togglebutton); if (active) grab_keyboard(); else ungrab_keyboard(); } void octave_event_handler(GtkSpinButton *spinbutton, gpointer notused) { octave = gtk_spin_button_get_value(spinbutton); piano_keyboard_set_octave(keyboard, octave); } void panic(void) { int i; /* * These two have to be sent first, in case we have no room in the * ringbuffer for all these MIDI_NOTE_OFF messages sent five lines below. */ queue_new_message(MIDI_CONTROLLER, MIDI_ALL_NOTES_OFF, 0); queue_new_message(MIDI_CONTROLLER, MIDI_ALL_SOUND_OFF, 0); for (i = 0; i < NNOTES; i++) { queue_new_message(MIDI_NOTE_OFF, i, 0); piano_keyboard_set_note_off(keyboard, i); usleep(100); } queue_new_message(MIDI_CONTROLLER, MIDI_HOLD_PEDAL, 0); queue_new_message(MIDI_CONTROLLER, MIDI_ALL_MIDI_CONTROLLERS_OFF, 0); queue_new_message(MIDI_CONTROLLER, MIDI_ALL_NOTES_OFF, 0); queue_new_message(MIDI_CONTROLLER, MIDI_ALL_SOUND_OFF, 0); queue_new_message(MIDI_RESET, -1, -1); } void add_digit(int digit) { if (entered_number == -1) entered_number = 0; else entered_number *= 10; entered_number += digit; } int maybe_add_digit(GdkEventKey *event) { /* * User can enter a number from the keypad; after that, pressing * '*'/'/', Page Up/Page Down etc will set program/bank/whatever * to the number that was entered. */ /* * XXX: This is silly. Find a way to enter the number without * all these contitional instructions. */ if (event->keyval == GDK_KP_0 || event->keyval == GDK_KP_Insert) { if (event->type == GDK_KEY_PRESS) add_digit(0); return (TRUE); } if (event->keyval == GDK_KP_1 || event->keyval == GDK_KP_End) { if (event->type == GDK_KEY_PRESS) add_digit(1); return (TRUE); } if (event->keyval == GDK_KP_2 || event->keyval == GDK_KP_Down) { if (event->type == GDK_KEY_PRESS) add_digit(2); return (TRUE); } if (event->keyval == GDK_KP_3 || event->keyval == GDK_KP_Page_Down) { if (event->type == GDK_KEY_PRESS) add_digit(3); return (TRUE); } if (event->keyval == GDK_KP_4 || event->keyval == GDK_KP_Left) { if (event->type == GDK_KEY_PRESS) add_digit(4); return (TRUE); } if (event->keyval == GDK_KP_5 || event->keyval == GDK_KP_Begin) { if (event->type == GDK_KEY_PRESS) add_digit(5); return (TRUE); } if (event->keyval == GDK_KP_6 || event->keyval == GDK_KP_Right) { if (event->type == GDK_KEY_PRESS) add_digit(6); return (TRUE); } if (event->keyval == GDK_KP_7 || event->keyval == GDK_KP_Home) { if (event->type == GDK_KEY_PRESS) add_digit(7); return (TRUE); } if (event->keyval == GDK_KP_8 || event->keyval == GDK_KP_Up) { if (event->type == GDK_KEY_PRESS) add_digit(8); return (TRUE); } if (event->keyval == GDK_KP_9 || event->keyval == GDK_KP_Page_Up) { if (event->type == GDK_KEY_PRESS) add_digit(9); return (TRUE); } return (FALSE); } int get_entered_number(void) { int tmp; tmp = entered_number; entered_number = -1; return (tmp); } int clip(int val, int lo, int hi) { if (val < lo) val = lo; if (val > hi) val = hi; return (val); } gint keyboard_event_handler(GtkWidget *widget, GdkEventKey *event, gpointer notused) { int tmp; gboolean retval = FALSE; /* Pass signal to piano_keyboard widget. Is there a better way to do this? */ if (event->type == GDK_KEY_PRESS) g_signal_emit_by_name(keyboard, "key-press-event", event, &retval); else g_signal_emit_by_name(keyboard, "key-release-event", event, &retval); if (retval) return (TRUE); if (maybe_add_digit(event)) return (TRUE); /* * '+' key shifts octave up. '-' key shifts octave down. */ if (event->keyval == GDK_KP_Add || event->keyval == GDK_equal) { if (event->type == GDK_KEY_PRESS && octave < OCTAVE_MAX) gtk_spin_button_set_value(GTK_SPIN_BUTTON(octave_spin), octave + 1); return (TRUE); } if (event->keyval == GDK_KP_Subtract || event->keyval == GDK_minus) { if (event->type == GDK_KEY_PRESS && octave > OCTAVE_MIN) gtk_spin_button_set_value(GTK_SPIN_BUTTON(octave_spin), octave - 1); return (TRUE); } /* * '*' character increases program number. '/' character decreases it. */ if (event->keyval == GDK_KP_Multiply) { if (event->type == GDK_KEY_PRESS) { tmp = get_entered_number(); if (tmp < 0) tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(program_spin)) + 1; gtk_spin_button_set_value(GTK_SPIN_BUTTON(program_spin), clip(tmp, PROGRAM_MIN, PROGRAM_MAX)); } return (TRUE); } if (event->keyval == GDK_KP_Divide) { if (event->type == GDK_KEY_PRESS) { tmp = get_entered_number(); if (tmp < 0) tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(program_spin)) - 1; gtk_spin_button_set_value(GTK_SPIN_BUTTON(program_spin), clip(tmp, PROGRAM_MIN, PROGRAM_MAX)); } return (TRUE); } /* * PgUp key increases bank number, PgDown decreases it. */ if (event->keyval == GDK_Page_Up) { if (event->type == GDK_KEY_PRESS) { tmp = get_entered_number(); if (tmp < 0) tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(bank_spin)) + 1; gtk_spin_button_set_value(GTK_SPIN_BUTTON(bank_spin), clip(tmp, BANK_MIN, BANK_MAX)); } return (TRUE); } if (event->keyval == GDK_Page_Down) { if (event->type == GDK_KEY_PRESS) { tmp = get_entered_number(); if (tmp < 0) tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(bank_spin)) - 1; gtk_spin_button_set_value(GTK_SPIN_BUTTON(bank_spin), clip(tmp, BANK_MIN, BANK_MAX)); } return (TRUE); } /* * Home key increases channel number, End decreases it. */ if (event->keyval == GDK_Home) { if (event->type == GDK_KEY_PRESS) { tmp = get_entered_number(); if (tmp < 0) tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(channel_spin)) + 1; gtk_spin_button_set_value(GTK_SPIN_BUTTON(channel_spin), clip(tmp, CHANNEL_MIN, CHANNEL_MAX)); } return (TRUE); } if (event->keyval == GDK_End) { if (event->type == GDK_KEY_PRESS) { tmp = get_entered_number(); if (tmp < 0) tmp = gtk_spin_button_get_value(GTK_SPIN_BUTTON(channel_spin)) - 1; gtk_spin_button_set_value(GTK_SPIN_BUTTON(channel_spin), clip(tmp, CHANNEL_MIN, CHANNEL_MAX)); } return (TRUE); } /* * Insert key connects to the next input port. Delete connects to the previous one. */ if (event->keyval == GDK_Insert) { if (event->type == GDK_KEY_PRESS) connect_to_next_input_port(); return (TRUE); } if (event->keyval == GDK_Delete) { if (event->type == GDK_KEY_PRESS) connect_to_prev_input_port(); return (TRUE); } if (event->keyval == GDK_Escape) { if (event->type == GDK_KEY_PRESS) panic(); return (TRUE); } /* * Spacebar works as a 'sustain' key. Holding spacebar while * releasing note will cause the note to continue. Pressing and * releasing spacebar without pressing any note keys will make all * the sustained notes end (it will send 'note off' midi messages for * all the sustained notes). */ if (event->keyval == GDK_space) { if (event->type == GDK_KEY_PRESS) gtk_button_pressed(GTK_BUTTON(sustain_button)); else gtk_button_released(GTK_BUTTON(sustain_button)); return (TRUE); } /* * Shift increases velocity, i.e. holding it while pressing note key * will make the sound louder. Ctrl decreases velocity. */ if (event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R) { if (event->type == GDK_KEY_PRESS) current_velocity = &velocity_high; else current_velocity = &velocity_normal; gtk_range_set_value(GTK_RANGE(velocity_hscale), *current_velocity); return (TRUE); } if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) { if (event->type == GDK_KEY_PRESS) current_velocity = &velocity_low; else current_velocity = &velocity_normal; gtk_range_set_value(GTK_RANGE(velocity_hscale), *current_velocity); return (TRUE); } return (FALSE); } void note_on_event_handler(GtkWidget *widget, int note) { assert(current_velocity); queue_new_message(MIDI_NOTE_ON, note, *current_velocity); } void note_off_event_handler(GtkWidget *widget, int note) { assert(current_velocity); queue_new_message(MIDI_NOTE_OFF, note, *current_velocity); } void init_gtk_1(int *argc, char ***argv) { GdkPixbuf *icon = NULL; GError *error = NULL; gtk_init(argc, argv); icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "jack-keyboard", 48, 0, &error); if (icon == NULL) { fprintf(stderr, "%s: Cannot load icon: %s.\n", G_LOG_DOMAIN, error->message); g_error_free(error); } else { gtk_window_set_default_icon(icon); } /* Window. */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_container_set_border_width(GTK_CONTAINER(window), 2); } void init_gtk_2(void) { GtkTable *table; GtkWidget *label; GtkCellRenderer *renderer; /* Table. */ table = GTK_TABLE(gtk_table_new(4, 8, FALSE)); gtk_table_set_row_spacings(table, 5); gtk_table_set_col_spacings(table, 5); if (enable_gui) gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(table)); /* Channel label and spin. */ label = gtk_label_new("Channel:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(table, label, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); channel_spin = gtk_spin_button_new_with_range(1, CHANNEL_MAX, 1); GTK_WIDGET_UNSET_FLAGS(channel_spin, GTK_CAN_FOCUS); gtk_table_attach(table, channel_spin, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); g_signal_connect(G_OBJECT(channel_spin), "value-changed", G_CALLBACK(channel_event_handler), NULL); /* Bank label and spin. */ label = gtk_label_new("Bank:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(table, label, 2, 3, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); bank_spin = gtk_spin_button_new_with_range(0, BANK_MAX, 1); GTK_WIDGET_UNSET_FLAGS(bank_spin, GTK_CAN_FOCUS); gtk_table_attach(table, bank_spin, 3, 4, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); g_signal_connect(G_OBJECT(bank_spin), "value-changed", G_CALLBACK(bank_event_handler), NULL); /* Program label and spin. */ label = gtk_label_new("Program:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(table, label, 4, 5, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); program_spin = gtk_spin_button_new_with_range(0, PROGRAM_MAX, 1); GTK_WIDGET_UNSET_FLAGS(program_spin, GTK_CAN_FOCUS); gtk_table_attach(table, program_spin, 5, 6, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); g_signal_connect(G_OBJECT(program_spin), "value-changed", G_CALLBACK(program_event_handler), NULL); /* "Connected to" label and combo box. */ label = gtk_label_new("Connected to:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(table, label, 6, 7, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); connected_to_store = gtk_list_store_new(1, G_TYPE_STRING); connected_to_combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(connected_to_store)); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(connected_to_combo), renderer, FALSE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(connected_to_combo), renderer, "text", 0, NULL); GTK_WIDGET_UNSET_FLAGS(connected_to_combo, GTK_CAN_FOCUS); gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(connected_to_combo), FALSE); gtk_table_attach(table, connected_to_combo, 7, 8, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); gtk_widget_set_size_request(GTK_WIDGET(connected_to_combo), 200, -1); g_signal_connect(G_OBJECT(connected_to_combo), "changed", G_CALLBACK(connected_to_event_handler), NULL); /* Octave label and spin. */ label = gtk_label_new("Octave:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(table, label, 0, 1, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); octave_spin = gtk_spin_button_new_with_range(OCTAVE_MIN, OCTAVE_MAX, 1); GTK_WIDGET_UNSET_FLAGS(octave_spin, GTK_CAN_FOCUS); gtk_spin_button_set_value(GTK_SPIN_BUTTON(octave_spin), octave); gtk_table_attach(table, octave_spin, 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); g_signal_connect(G_OBJECT(octave_spin), "value-changed", G_CALLBACK(octave_event_handler), NULL); /* "Grab keyboard" label and checkbutton. */ label = gtk_label_new("Grab keyboard:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(table, label, 4, 5, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); grab_keyboard_checkbutton = gtk_check_button_new(); GTK_WIDGET_UNSET_FLAGS(grab_keyboard_checkbutton, GTK_CAN_FOCUS); g_signal_connect(G_OBJECT(grab_keyboard_checkbutton), "toggled", G_CALLBACK(grab_keyboard_handler), NULL); gtk_table_attach(table, grab_keyboard_checkbutton, 5, 6, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); /* Velocity label and hscale */ label = gtk_label_new("Velocity:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(table, label, 6, 7, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); velocity_hscale = gtk_hscale_new_with_range(VELOCITY_MIN, VELOCITY_MAX, 1); gtk_scale_set_draw_value(GTK_SCALE(velocity_hscale), FALSE); GTK_WIDGET_UNSET_FLAGS(velocity_hscale, GTK_CAN_FOCUS); g_signal_connect(G_OBJECT(velocity_hscale), "value-changed", G_CALLBACK(velocity_event_handler), NULL); gtk_range_set_value(GTK_RANGE(velocity_hscale), VELOCITY_NORMAL); gtk_table_attach(table, velocity_hscale, 7, 8, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); gtk_widget_set_size_request(GTK_WIDGET(velocity_hscale), 200, -1); /* Sustain. It's a toggle button, not an ordinary one, because we want gtk_whatever_set_active() to work.*/ sustain_button = gtk_toggle_button_new_with_label("Sustain"); gtk_button_set_focus_on_click(GTK_BUTTON(sustain_button), FALSE); gtk_table_attach(table, sustain_button, 0, 8, 2, 3, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); g_signal_connect(G_OBJECT(sustain_button), "pressed", G_CALLBACK(sustain_event_handler), (void *)1); g_signal_connect(G_OBJECT(sustain_button), "released", G_CALLBACK(sustain_event_handler), (void *)0); /* PianoKeyboard widget. */ keyboard = PIANO_KEYBOARD(piano_keyboard_new()); if (enable_gui) gtk_table_attach_defaults(table, GTK_WIDGET(keyboard), 0, 8, 3, 4); else gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(keyboard)); g_signal_connect(G_OBJECT(keyboard), "note-on", G_CALLBACK(note_on_event_handler), NULL); g_signal_connect(G_OBJECT(keyboard), "note-off", G_CALLBACK(note_off_event_handler), NULL); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(keyboard_event_handler), NULL); g_signal_connect(G_OBJECT(window), "key-release-event", G_CALLBACK(keyboard_event_handler), NULL); gtk_widget_show_all(window); draw_window_title(); } void log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused) { GtkWidget *dialog; fprintf(stderr, "%s: %s\n", log_domain, message); if ((log_level | G_LOG_LEVEL_CRITICAL) == G_LOG_LEVEL_CRITICAL) { dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, message); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } } void show_version(void) { fprintf(stdout, "%s\n", PACKAGE_NAME " v" PACKAGE_VERSION); exit(EX_OK); } void usage(void) { fprintf(stderr, "usage: jack-keyboard [-CGKTVkturf] [ -a ] [-c ] [-b ] [-p ] [-l ]\n"); fprintf(stderr, " where is MIDI channel to use for output, from 1 to 16,\n"); fprintf(stderr, " is MIDI bank to use, from 0 to 16383,\n"); fprintf(stderr, " is MIDI program to use, from 0 to 127,\n"); fprintf(stderr, " and is QWERTY, QWERTY_REV, QWERTY_UK, QWERTY_UK_REV, QWERTZ, AZERTY or DVORAK.\n"); fprintf(stderr, "See manual page for details.\n"); exit(EX_USAGE); } int main(int argc, char *argv[]) { int ch, enable_keyboard_cue = 0, initial_channel = 1, initial_bank = 0, initial_program = 0, full_midi_keyboard = 0; char *keyboard_layout = NULL, *autoconnect_port_name = NULL; #ifdef HAVE_LASH lash_args_t *lash_args; #endif g_thread_init(NULL); #ifdef HAVE_LASH lash_args = lash_extract_args(&argc, &argv); #endif init_gtk_1(&argc, &argv); g_log_set_default_handler(log_handler, NULL); while ((ch = getopt(argc, argv, "CGKTVa:nktur:c:b:p:l:f")) != -1) { switch (ch) { case 'C': enable_keyboard_cue = 1; break; case 'G': enable_gui = 0; enable_window_title = !enable_window_title; break; case 'K': grab_keyboard_at_startup = 1; break; case 'T': enable_window_title = !enable_window_title; break; case 'V': show_version(); break; case 'a': autoconnect_port_name = strdup(optarg); break; case 'n': /* Do nothing; backward compatibility. */ break; case 'k': allow_connecting_to_own_kind = 1; break; case 'l': keyboard_layout = strdup(optarg); break; case 't': time_offsets_are_zero = 1; break; case 'u': send_program_change_at_reconnect = 1; break; case 'c': initial_channel = atoi(optarg); if (initial_channel < CHANNEL_MIN || initial_channel > CHANNEL_MAX) { g_critical("Invalid MIDI channel number specified on the command line; " "valid values are %d-%d.", CHANNEL_MIN, CHANNEL_MAX); exit(EX_USAGE); } break; case 'b': initial_bank = atoi(optarg); send_program_change_once = 1; if (initial_bank < BANK_MIN || initial_bank > BANK_MAX) { g_critical("Invalid MIDI bank number specified on the command line; " "valid values are %d-%d.", BANK_MIN, BANK_MAX); exit(EX_USAGE); } break; case 'p': initial_program = atoi(optarg); send_program_change_once = 1; if (initial_program < PROGRAM_MIN || initial_program > PROGRAM_MAX) { g_critical("Invalid MIDI program number specified on the command line; " "valid values are %d-%d.", PROGRAM_MIN, PROGRAM_MAX); exit(EX_USAGE); } break; case 'r': rate_limit = strtod(optarg, NULL); if (rate_limit <= 0.0) { g_critical("Invalid rate limit specified.\n"); exit(EX_USAGE); } break; case 'f': full_midi_keyboard = 1; break; case '?': default: usage(); } } argc -= optind; argv += optind; init_gtk_2(); if (full_midi_keyboard) piano_keyboard_enable_all_midi_notes(keyboard); if (keyboard_layout != NULL) { int ret = piano_keyboard_set_keyboard_layout(keyboard, keyboard_layout); if (ret) { g_critical("Invalid layout, proper choices are QWERTY, QWERTY_REV, QWERTY_UK, QWERTY_UK_REV, QWERTZ, AZERTY and DVORAK."); exit(EX_USAGE); } } #ifdef HAVE_LASH init_lash(lash_args); #endif init_jack(); if (autoconnect_port_name) { if (connect_to_input_port(autoconnect_port_name)) { g_critical("Couldn't connect to '%s', exiting.", autoconnect_port_name); exit(EX_UNAVAILABLE); } } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grab_keyboard_checkbutton), grab_keyboard_at_startup); gtk_spin_button_set_value(GTK_SPIN_BUTTON(channel_spin), initial_channel); gtk_spin_button_set_value(GTK_SPIN_BUTTON(bank_spin), initial_bank); gtk_spin_button_set_value(GTK_SPIN_BUTTON(program_spin), initial_program); piano_keyboard_set_keyboard_cue(keyboard, enable_keyboard_cue); gtk_main(); return (0); } jack-keyboard-2.7.1/src/pianokeyboard.c000644 003720 003720 00000051603 11676313412 022346 0ustar00hans_otherhans_other000000 000000 /*- * Copyright (c) 2007, 2008 Edward Tomasz Napierała * 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 . */ #include #include #include #include #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, h, first_note_in_lower_row, last_note_in_lower_row, first_note_in_higher_row, last_note_in_higher_row; GdkGC *gc; w = pk->notes[0].w; h = pk->notes[0].h; gc = GTK_WIDGET(pk)->style->fg_gc[0]; first_note_in_lower_row = (pk->octave + 5) * 12; last_note_in_lower_row = (pk->octave + 6) * 12 - 1; first_note_in_higher_row = (pk->octave + 6) * 12; 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) { if (note < pk->min_note) return; if (note > pk->max_note) return; int is_white, x, w, h; GdkColor black = {0, 0, 0, 0}; GdkColor white = {0, 65535, 65535, 65535}; GdkGC *gc = GTK_WIDGET(pk)->style->fg_gc[0]; GtkWidget *widget; is_white = pk->notes[note].white; x = pk->notes[note].x; w = pk->notes[note].w; 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, ¬used, ¬e); if (!found) return (-1); return ((long)note); } static void bind_key(PianoKeyboard *pk, const char *key, long 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); /* C1 */ 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); /* C2 */ bind_key(pk, "9", 37); bind_key(pk, "o", 38); bind_key(pk, "0", 39); bind_key(pk, "p", 40); } static void bind_keys_qwerty_uk(PianoKeyboard *pk) { bind_keys_qwerty(pk); /* Lower keyboard row - "zxcvbnm". */ bind_key(pk, "backslash", 11); /* B0 */ /* ... */ bind_key(pk, "comma", 24); /* C1 */ bind_key(pk, "l", 25); bind_key(pk, "period", 26); bind_key(pk, "semicolon", 27); bind_key(pk, "slash", 28); /* Upper keyboard row, the rest - "iop". */ bind_key(pk, "bracketleft", 41); /* F6 */ bind_key(pk, "equal", 42); bind_key(pk, "bracketright", 43); } static void bind_keys_qwerty_rev(PianoKeyboard *pk) { clear_notes(pk); /* Lower keyboard row - "zxcvbnm". */ bind_key(pk, "z", 24); /* C1 */ bind_key(pk, "s", 25); bind_key(pk, "x", 26); bind_key(pk, "d", 27); bind_key(pk, "c", 28); bind_key(pk, "v", 29); bind_key(pk, "g", 30); bind_key(pk, "b", 31); bind_key(pk, "h", 32); bind_key(pk, "n", 33); bind_key(pk, "j", 34); bind_key(pk, "m", 35); /* Upper keyboard row, first octave - "qwertyu". */ bind_key(pk, "q", 12); /* C0 */ bind_key(pk, "2", 13); bind_key(pk, "w", 14); bind_key(pk, "3", 15); bind_key(pk, "e", 16); bind_key(pk, "r", 17); bind_key(pk, "5", 18); bind_key(pk, "t", 19); bind_key(pk, "6", 20); bind_key(pk, "y", 21); bind_key(pk, "7", 22); bind_key(pk, "u", 23); /* Upper keyboard row, the rest - "iop". */ bind_key(pk, "i", 24); /* C1 */ bind_key(pk, "9", 25); bind_key(pk, "o", 26); bind_key(pk, "0", 27); bind_key(pk, "p", 28); } static void bind_keys_qwerty_uk_rev(PianoKeyboard *pk) { bind_keys_qwerty_rev(pk); /* Lower keyboard row - "zxcvbnm". */ bind_key(pk, "backslash", 23); /* B-1 */ /* ... */ bind_key(pk, "comma", 36); /* C2 */ bind_key(pk, "l", 37); bind_key(pk, "period", 38); bind_key(pk, "semicolon", 39); bind_key(pk, "slash", 40); /* Upper keyboard row, the rest - "iop". */ bind_key(pk, "bracketleft", 29); bind_key(pk, "equal", 30); bind_key(pk, "bracketright", 31); } 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 void bind_keys_dvorak(PianoKeyboard *pk) { clear_notes(pk); /* Lower keyboard row - ";qjkxbm". */ bind_key(pk, "semicolon", 12); /* C0 */ bind_key(pk, "o", 13); bind_key(pk, "q", 14); bind_key(pk, "e", 15); bind_key(pk, "j", 16); bind_key(pk, "k", 17); bind_key(pk, "i", 18); bind_key(pk, "x", 19); bind_key(pk, "d", 20); bind_key(pk, "b", 21); bind_key(pk, "h", 22); bind_key(pk, "m", 23); bind_key(pk, "w", 24); /* overlaps with upper row */ bind_key(pk, "n", 25); bind_key(pk, "v", 26); bind_key(pk, "s", 27); bind_key(pk, "z", 28); /* Upper keyboard row, first octave - "',.pyfg". */ bind_key(pk, "apostrophe", 24); bind_key(pk, "2", 25); bind_key(pk, "comma", 26); bind_key(pk, "3", 27); bind_key(pk, "period", 28); bind_key(pk, "p", 29); bind_key(pk, "5", 30); bind_key(pk, "y", 31); bind_key(pk, "6", 32); bind_key(pk, "f", 33); bind_key(pk, "7", 34); bind_key(pk, "g", 35); /* Upper keyboard row, the rest - "crl". */ bind_key(pk, "c", 36); bind_key(pk, "9", 37); bind_key(pk, "r", 38); bind_key(pk, "0", 39); bind_key(pk, "l", 40); bind_key(pk, "slash", 41); /* extra F */ bind_key(pk, "bracketright", 42); bind_key(pk, "equal", 43); } 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, note; height = GTK_WIDGET(pk)->allocation.height; if (y <= ((height * 2) / 3)) { /* might be a black key */ for (note = 0; note <= pk->max_note; ++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 <= pk->max_note; ++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, y, note; x = event->x; y = event->y; 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 int is_black(int key) { int note_in_octave = key % 12; if( note_in_octave == 1 || note_in_octave == 3 || note_in_octave == 6 || note_in_octave == 8 || note_in_octave == 10) return 1; return 0; } static double black_key_left_shift(int key) { int note_in_octave = key % 12; switch (note_in_octave) { case 1: return 2.0/3.0; case 3: return 1.0/3.0; case 6: return 2.0/3.0; case 8: return 0.5; case 10: return 1.0/3.0; default: return 0; } return 0; } static void recompute_dimensions(PianoKeyboard *pk) { int number_of_white_keys = 0, skipped_white_keys = 0, key_width, black_key_width, useful_width, note, white_key, width, height; for (note = pk->min_note; note <= pk->max_note; ++note) if (!is_black(note)) ++number_of_white_keys; for (note = 0; note < pk->min_note; ++note) if (!is_black(note)) ++skipped_white_keys; width = GTK_WIDGET(pk)->allocation.width; 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 = -skipped_white_keys; note < NNOTES; note++) { if (is_black(note)) { /* This note is black key. */ pk->notes[note].x = pk->widget_margin + (white_key * key_width) - (black_key_width * black_key_left_shift(note)); pk->notes[note].w = black_key_width; pk->notes[note].h = (height * 2) / 3; 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; PianoKeyboard *pk; widget = gtk_type_new(piano_keyboard_get_type()); 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); pk->min_note = PIANO_MIN_NOTE; pk->max_note = PIANO_MAX_NOTE; 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, "QWERTY_REV")) { bind_keys_qwerty_rev(pk); } else if (!strcasecmp(layout, "QWERTY_UK")) { bind_keys_qwerty_uk(pk); } else if (!strcasecmp(layout, "QWERTY_UK_REV")) { bind_keys_qwerty_uk_rev(pk); } else if (!strcasecmp(layout, "QWERTZ")) { bind_keys_qwertz(pk); } else if (!strcasecmp(layout, "AZERTY")) { bind_keys_azerty(pk); } else if (!strcasecmp(layout, "DVORAK")) { bind_keys_dvorak(pk); } else { /* Unknown layout name. */ return (TRUE); } return (FALSE); } void piano_keyboard_enable_all_midi_notes(PianoKeyboard* pk) { pk->min_note = 0; pk->max_note = NNOTES-1; recompute_dimensions(pk); } jack-keyboard-2.7.1/src/pianokeyboard.h000644 003720 003720 00000007667 11676313011 022361 0ustar00hans_otherhans_other000000 000000 /*- * Copyright (c) 2007, 2008 Edward Tomasz Napierała * 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. */ #ifndef __PIANO_KEYBOARD_H__ #define __PIANO_KEYBOARD_H__ #include #include 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; /* A note about note numbers: 0 = C-1 (midi minmum) 21 = A0 (piano minimum) 60 = C4 (middle C) 108 = C7 (piano maximum) 127 = G9 (midi maximum) */ #define NNOTES 128 #define PIANO_MIN_NOTE 21 #define PIANO_MAX_NOTE 108 #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; int min_note; int max_note; 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); void piano_keyboard_enable_all_midi_notes(PianoKeyboard* pk); G_END_DECLS #endif /* __PIANO_KEYBOARD_H__ */ jack-keyboard-2.7.1/src/jack-keyboard.desktop000644 003720 003720 00000000262 11560716656 023460 0ustar00hans_otherhans_other000000 000000 [Desktop Entry] Name=JACK Keyboard Comment=Virtual keyboard for JACK MIDI Exec=jack-keyboard Icon=jack-keyboard Categories=AudioVideo;Audio;Midi; Terminal=false Type=Application jack-keyboard-2.7.1/pixmaps/jack-keyboard.png000644 003720 003720 00000000241 11720375661 023455 0ustar00hans_otherhans_other000000 000000 PNG  IHDR00`nsRGB[IDATX10 /  u8MzCSW _Y6%4yYpC@@@@@@@@@@eV'`IENDB`jack-keyboard-2.7.1/man/jack-keyboard.1000644 003720 003720 00000017640 11560753642 022137 0ustar00hans_otherhans_other000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .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, AZERTY, and DVORAK. 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. jack-keyboard-2.7.1/man/jack-keyboard-ref.sgml000644 003720 003720 00000027346 11560754150 023512 0ustar00hans_otherhans_other000000 000000 jack-keyboard 1 jack-keyboard 2.5 jack-keyboard A virtual keyboard for JACK MIDI jack-keyboard OPTIONS -C 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. -G Disable GUI. It makes jack-keyboard look like it did before version 2.0. -K Grab the keyboard. This makes jack-keyboard 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. -T Toggle titlebar, on which channel/bank/program information is shown. With \-G option, it disables titlebar; otherwise it enables it. -V Print version number to standard output and exit. -a input port Automatically connect to the named input port. Note that this may cause problems with LASH. -k Allow connecting to other instances of jack-keyboard (see PIANOLA MODE below). Without this option, jack-keyboard 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 jack-keyboard to itself, even with this option set. -r rate Set rate limit to rate, in Kbaud. Limit defined by the MIDI specification is 31.25. By default this parameter is zero, that is, rate limiting is disabled. -t 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. -u By default, jack-keyboard does not send program/bank change messages after reconnecting, so the newly connected instrument remains at previous settings. This option changes that behaviour. -c channel Set initial MIDI channel to channel; by default it's 1. -b bank Set initial MIDI bank to bank. With this option, jack-keyboard will send the bank/program change once, when it connects. -p program Set initial MIDI program to program. With this option, jack-keyboard will send the bank/program change once, when it connects. -l layout Specify the layout of computer keyboard being used. Valid arguments are QWERTY, QWERTZ, AZERTY, and DVORAK. Default is QWERTY. DESCRIPTION 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 default 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. KEY BINDINGS 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 xor 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 and parameters. To automatically send bank/program change after reconnect, use the 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 with "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. SEE ALSO jackd1, ghostess1, qjackctl1 BUGS Key grabbing is unreliable. 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. 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 jack-keyboard already uses numeric keys for different purposes. You can still directly enter channel/bank/program in a way described above. jack-keyboard-2.7.1/cmake/FindJACK.cmake000644 003720 003720 00000001171 11600413702 022140 0ustar00hans_otherhans_other000000 000000 # - Try to find Jack library # Once done this will define: # JACK_FOUND - system has JACK # JACK_INCLUDE_DIR - incude paths to use # JACK_LIBRARIES - Link these to use SET(JACK_FOUND 0) FIND_PATH(JACK_INCLUDE_DIR jack/ringbuffer.h /usr/local/include /usr/include ) FIND_LIBRARY(JACK_LIBRARIES jack /usr/local/lib /usr/lib ) # handle the QUIETLY and REQUIRED arguments and set JACK_FOUND to TRUE # if all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(JACK DEFAULT_MSG JACK_LIBRARIES JACK_INCLUDE_DIR) MARK_AS_ADVANCED( JACK_INCLUDE_DIR JACK_LIBRARIES ) jack-keyboard-2.7.1/cmake/FindLASH.cmake000644 003720 003720 00000001204 11600422203 022150 0ustar00hans_otherhans_other000000 000000 # - Try to find Lash library # Once done this will define: # LASH_FOUND - system has LASH # LASH_INCLUDE_DIR - incude paths to use # LASH_LIBRARIES - Link these to use SET(LASH_FOUND 0) FIND_PATH(LASH_INCLUDE_DIR lash/lash.h /usr/local/include/lash-1.0 /usr/include/lash-1.0 ) FIND_LIBRARY(LASH_LIBRARIES lash /usr/local/lib /usr/lib ) # handle the QUIETLY and REQUIRED arguments and set LASH_FOUND to TRUE # if all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(LASH DEFAULT_MSG LASH_LIBRARIES LASH_INCLUDE_DIR) MARK_AS_ADVANCED( LASH_INCLUDE_DIR LASH_LIBRARIES )