liblscp-1.0.1/0000755000175000017500000000000014772271215012261 5ustar drag0ndrag0nliblscp-1.0.1/ChangeLog0000644000175000017500000003162414771257264014050 0ustar drag0ndrag0nliblscp - LinuxSampler Control Protocol API ------------------------------------------- ChangeLog 1.0.1 2025-03-27 An Early Spring'25 Release. 1.0.0 2024-06-19 An Unthinkable Release. Making up the unthinkable (aka. v1.0.0) 0.9.91 2024-05-01 A Spring'24 release candidate 2. Prepping the unthinkable (aka. v1.0.0-rc2) 0.9.90 2024-04-10 A Spring'24 release candidate. Prepping the unthinkable (aka. v1.0.0-rc1) 0.9.12 2024-01-24 A Winter'24 release. 0.9.11 2023-09-09 An End-of-Summer'23 release. 0.9.10 2023-06-01 A Spring'23 release. 0.9.9 2023-03-23 An Early-Spring'23 release. 0.9.8 2022-12-28 An End-of-Year'22 release. Fixed broken pkg-config file (lscp.pc) generation (thanks to Konstantin Voinov while on OBS multimedia::proaudio). 0.9.7 2022-10-03 An Early-Autumn'22 release. 0.9.6 2022-04-02 Install doxygen (doc/html) files again (via cmake). 0.9.5 2022-01-09 Dropped autotools (autoconf, automake, etc.) build system. 0.9.4 2021-07-04 An Early-Summer'21 release. 0.9.3 2021-05-11 A Spring'21 release. Introducing cmake build option. 0.9.2 2021-03-14 An End-of-Winter'21 release. 0.9.1 2021-02-07 A Winter'21 release. 0.9.0 2020-12-17 A Winter'20 release. 0.6.2 2019-03-24 A Spring'20 release. Fixed compile errors on macOS. Expose actual system error codes via client interface instead of just -1 whenever a syscall failed. Added new client interface function: lscp_client_connection_lost(); for checking whether client lost connection to server. Fixed potential missing NULL termination of locale strings. 0.6.1 2019-12-22 The Winter'19 Release. 0.6.0 2017-12-12 An Autumn'17 release. 0.5.8 2016-11-14 A Fall'16 release. 0.5.7 2013-12-31 Use getaddrinfo() instead of deprecated gethostbyname(). Update on newer autoconf macros (m4). Fixes for building for Windows with configure and make. Makefile fixes for building in separate directory. Autoconf fix: AC_CONFIG_HEADER obsolete error (fixes #194). 0.5.6 2009-08-01 Fixed locale related parser bug (fixes #59). Added new client interface functions, for managing the global limit of maximum voices and disk streams: lscp_get_voices(), lscp_set_voices(), lscp_get_streams(), lscp_set_streams(). _lscp_device_port_info_query(): take port parameter "NAME" into the result list. Bugfix: _lscp_client_evt_proc() dropped LSCP events. Bugfix in lscp_client_subscribe(): only the first lscp_client_subscribe() call succeeded (per client), all subsequent ones failed. Added support for new (un)subscribable events: LSCP_EVENT_CHANNEL_MIDI LSCP_EVENT_DEVICE_MIDI Caution: the bitflag approach for the event variable is now abondoned, since otherwise we would soon hit the limit of the bit range. The bitflag approach will remain for events older at this point (that is all events which occupy the lower 16 bits), but this new and all following events will simply be enumared along the upper 16 bits. Added new client interface function, for renaming effect send entities: lscp_set_fxsend_name(); Added new client interface functions, for managing the global limit of maximum voices and disk streams: lscp_get_voices(); lscp_set_voices(); lscp_get_streams(); lscp_set_streams(); Bugfix: fixed buggy behavior on different locale settings (e.g. when parsing floating point numbers). 0.5.5 2007-10-12 Changed client interface function, for editing instrument, from: lscp_edit_instrument(); to: lscp_edit_channel_instrument(); 0.5.4 2007-10-02 Added new client interface function, for editing instrument: lscp_edit_instrument(); Fixed some minor bugs in: lscp_set_fxsend_midi_controller(); lscp_set_fxsend_level(); 0.5.3 2007-01-15 Added new client interface functions, for sampler channel effect sends control: lscp_set_fxsend_midi_controller(); lscp_set_fxsend_level(); Added new field member to lscp_fxsend_info_t (level). 0.5.2 2007-01-11 Added new client interface functions, for sampler channel effect sends control: lscp_create_fxsend(); lscp_destroy_fxsend(); lscp_get_fxsends(); lscp_list_fxsends(); lscp_get_fxsend_info(); lscp_set_fxsend_audio_channel(); and for global volume: lscp_get_volume(); lscp_set_volume(); Audio routing representation changed to integer array. 0.5.1 2006-12-22 Added support for new (un)subscribable events: LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO, LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT, LSCP_EVENT_MIDI_INPUT_DEVICE_INFO, LSCP_EVENT_MIDI_INSTRUMENT_MAP_COUNT, LSCP_EVENT_MIDI_INSTRUMENT_MAP_INFO, LSCP_EVENT_MIDI_INSTRUMENT_COUNT, LSCP_EVENT_MIDI_INSTRUMENT_INFO. Updated examples. 0.5.0 2006-12-17 MIDI instrument mapping, second round, according to LSCP 1.2 draft document as of December 15, 2006. New client interface functions: lscp_set_channel_midi_map(); lscp_add_midi_instrument_map(); lscp_remove_midi_instrument_map(); lscp_get_midi_instrument_maps(); lscp_list_midi_instrument_maps(); lscp_get_midi_instrument_map_name(); lscp_set_midi_instrument_map_name(); 0.4.2 2006-12-04 MIDI instrument mapping fixed, previously missing the regular ON_DEMAND load mode. Server error reporting is now effective; all server numerical error and warning codes are added to 100, thus giving a proper non-zero lscp_client_get_errno() return value. 0.4.1 2006-11-28 Fixed the flush timeout operation to be issued only once, avoiding recurrent client failure after receive timeout. Support for very long command result sets have been introduced, with the downside of strictly obeying the LSCP draft. list_midi_instruments() is now being implemented. LGPL text is now back in COPYING. 0.4.0 2006-11-27 As of the LSCP 1.2 working draft document, added some missing client interface functions: lscp_get_total_voice_count(); lscp_get_total_voice_count_max(); and for the new MIDI instrumenbt mapping features: lscp_map_midi_instrument(); lscp_unmap_midi_instrument(); lscp_get_midi_instruments(); lscp_get_midi_instrument_info(); lscp_clear_midi_instruments(); 0.3.4 2006-09-24 GPL address update. 0.3.3 2006-06-01 Fixed some compilation warnings due to suspicious type casting and unsused header macros. Changed deprecated copyright attribute to license and added ldconfig to post-(un)install steps to liblscp.spec (RPM). 0.3.2 2005-08-29 Fixed missing initialization bug on lscp_channel_info_t new struct fields (mute/solo). Include debian files into distribution. Renamed configure.in to newer configure.ac. 0.3.1 2005-08-16 [bug #21] Fixed automake support for separate build directory. Added support to sampler channel MUTE/SOLO states: lscp_set_channel_mute(); lscp_set_channel_solo(); with corresponding new lscp_channel_info_t fields. 0.3.0 2005-06-10 [bug #11] Timeout flush idiosyncrasy is now a feature; this just tries to flush the receive buffer whenever any previous transaction has failed due to a timeout. Fixed an off-by-one timeout quirk, that has been a real showstopper on Mac OS X at least, which is incidental to qsampler's default timeout setting of 1000 msecs, giving up systematically on select() due to "Invalid argument" (EINVAL). 0.2.8 2005-05-22 More LSCP command syntax changes, particularly on the event subscription ones: the LSCP_EVENT_CHANNELS event definition were renamed to LSCP_EVENT_CHANNEL_COUNT, as to be more meaningful. Added support for the newest LSCP command: GET SERVER INFO; lscp_get_server_info(). 0.2.8 2005-05-08 [bug #9] Fixed for a LSCP command syntax convention consistency, regarding the enumeration of available sampler engines, Audio and MIDI drivers; this has affected the signature of the following functions: lscp_get_available_engines(); lscp_get_available_audio_drivers(); lscp_get_available_midi_drivers(); which are now returning an integer count of engines and drivers, respectively, while the following functions are now being introduced: lscp_list_available_engines(); lscp_list_available_audio_drivers(); lscp_list_available_midi_drivers(); taking on the previous functionality, returning a comma separated list of names. 0.2.7 2005-03-10 Mini bitsy regression; a severe crash (segfault) was fixed on the device configuration functions: lscp_set_audio_device_param(); lscp_set_midi_device_param(); lscp_set_audio_channel_param(); lscp_set_midi_port_param(); 0.2.6 2005-03-01 Fixed nasty off-by-one bug on internal helpers. 0.2.5 2005-02-14 Added support for the new INSTRUMENT_NAME field of GET CHANNEL INFO command. 0.2.4 2004-10-11 Fixed lscp_set_channel_midi_channel() again, MIDI channels should be given in the range 0-15, and omni mode with the LSCP_MIDI_CHANNEL_ALL symbol (16). Fixed lscp_get_channel_info() to parse MIDI omni (ALL) channels mode. 0.2.3 2004-09-28 Fixed lscp_set_channel_midi_channel() where MIDI channels should be given in the range 1-16, and omni mode with the new LSCP_MIDI_CHANNEL_ALL symbol (0). Rearrangement on main command requester executive. 0.2.2 2004-07-29 In sync with LSCP document draf (v.12). New functions added: lscp_client_get_events() and lscp_reset_sampler(). Added support for generating Debian packages; renamed pkg-config lib name 'liblscp' -> 'lscp' as it's common practice to omit the 'lib' prefix. 0.2.1 2004-07-09 Potential cripling defects habve been fixed. 0.2.0 2004-07-06 New LSCP extension draft (v.11) initial support. (still a work in progress...) 0.1.9 2004-05-18 More fixes for MSVC++ example build. 0.1.8 2004-05-17 Fix for MSVC++ example build; snprintf replaced by buffer overflow friendlier sprintf. 0.1.7 2004-05-10 Missing version.h now included on install; WIN32 is only now conditionally defined. 0.1.6 2004-05-04 WIN32 build support; LPGL disclaimer consistency; versioning functions introduced. 0.1.5 2004-04-26 Server stuff moved into examples. 0.1.4 2004-04-24 Initial auto/libtool preparation. liblscp-1.0.1/examples/0000755000175000017500000000000014771257264014106 5ustar drag0ndrag0nliblscp-1.0.1/examples/parser.h0000644000175000017500000000422514771257264015556 0ustar drag0ndrag0n// parser.h // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __LSCP_PARSER_H #define __LSCP_PARSER_H #include #include #include #if (defined(_WIN32) || defined(__WIN32__)) #if (!defined(WIN32)) #define WIN32 #endif #endif #if defined(__cplusplus) extern "C" { #endif //------------------------------------------------------------------------- // Simple token parser. typedef struct _lscp_parser_t { char *pchBuffer; int cchBuffer; const char *pszToken; char *pch; } lscp_parser_t; const char *lscp_parser_strtok (char *pchBuffer, const char *pszDelim, char **ppch); void lscp_parser_init (lscp_parser_t *pParser, const char *pchBuffer, int cchBuffer); const char *lscp_parser_next (lscp_parser_t *pParser); int lscp_parser_nextint (lscp_parser_t *pParser); float lscp_parser_nextnum (lscp_parser_t *pParser); int lscp_parser_test (lscp_parser_t *pParser, const char *pszToken); int lscp_parser_test2 (lscp_parser_t *pParser, const char *pszToken, const char *pszToken2); void lscp_parser_free (lscp_parser_t *pParser); #if defined(__cplusplus) } #endif #endif // __LSCP_PARSER_H // end of parser.h liblscp-1.0.1/examples/CMakeLists.txt0000644000175000017500000000122514771257264016646 0ustar drag0ndrag0n# project(liblscp) set (CMAKE_INCLUDE_CURRENT_DIR ON) include_directories (${CMAKE_SOURCE_DIR}) add_executable (example_server example_server.c server.h parser.h server.c parser.c ) add_executable (example_client example_client.c ) target_link_libraries (example_server PRIVATE ${PROJECT_NAME}) target_link_libraries (example_client PRIVATE ${PROJECT_NAME}) if (MICROSOFT) target_link_libraries(example_server PRIVATE ws2_32.lib) target_link_libraries(example_client PRIVATE ws2_32.lib) elseif (MINGW) target_link_libraries(example_server PRIVATE wsock32 ws2_32) target_link_libraries(example_client PRIVATE wsock32 ws2_32) endif() liblscp-1.0.1/examples/server.c0000644000175000017500000005072014771257264015564 0ustar drag0ndrag0n// server.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "server.h" #define LSCP_SERVER_SLEEP 30 // Period in seconds for watchdog wakeup (idle loop). // Local prototypes. static lscp_connect_t *_lscp_connect_create (lscp_server_t *pServer, lscp_socket_t sock, struct sockaddr_in *pAddr, int cAddr); static lscp_status_t _lscp_connect_destroy (lscp_connect_t *pConnect); static lscp_status_t _lscp_connect_recv (lscp_connect_t *pConnect); static void _lscp_connect_list_append (lscp_connect_list_t *pList, lscp_connect_t *pItem); static void _lscp_connect_list_remove (lscp_connect_list_t *pList, lscp_connect_t *pItem); static void _lscp_connect_list_remove_safe (lscp_connect_list_t *pList, lscp_connect_t *pItem); static void _lscp_connect_list_free (lscp_connect_list_t *pList); static lscp_connect_t *_lscp_connect_list_find_sock (lscp_connect_list_t *pList, lscp_socket_t sock); static void _lscp_server_thread_proc (lscp_server_t *pServer); static void _lscp_server_select_proc (lscp_server_t *pServer); static void _lscp_server_agent_proc (void *pvServer); //------------------------------------------------------------------------- // Server-side client connection list methods. static void _lscp_connect_list_init ( lscp_connect_list_t *pList ) { // fprintf(stderr, "_lscp_connect_list_init: pList=%p.\n", pList); pList->first = NULL; pList->last = NULL; pList->count = 0; lscp_mutex_init(pList->mutex); } static void _lscp_connect_list_append ( lscp_connect_list_t *pList, lscp_connect_t *pItem ) { // fprintf(stderr, "_lscp_connect_list_append: pList=%p pItem=%p.\n", pList, pItem); lscp_mutex_lock(pList->mutex); pItem->prev = pList->last; pItem->next = NULL; if (pList->last) (pList->last)->next = pItem; else pList->first = pItem; pList->last = pItem; pList->count++; lscp_mutex_unlock(pList->mutex); } static void _lscp_connect_list_remove ( lscp_connect_list_t *pList, lscp_connect_t *pItem ) { // fprintf(stderr, "_lscp_connect_list_remove: pList=%p pItem=%p.\n", pList, pItem); if (pItem->next) (pItem->next)->prev = pItem->prev; else pList->last = pItem->prev; if (pItem->prev) (pItem->prev)->next = pItem->next; else pList->first = pItem->next; pItem->next = NULL; pItem->prev = NULL; pList->count--; } static void _lscp_connect_list_remove_safe ( lscp_connect_list_t *pList, lscp_connect_t *pItem ) { lscp_connect_t *p; // fprintf(stderr, "_lscp_connect_list_remove_safe: pList=%p pItem=%p.\n", pList, pItem); lscp_mutex_lock(pList->mutex); for (p = pList->first; p; p = p->next) { if (p == pItem) { _lscp_connect_list_remove(pList, pItem); break; } } lscp_mutex_unlock(pList->mutex); } static void _lscp_connect_list_free ( lscp_connect_list_t *pList ) { lscp_connect_t *p, *pNext; // fprintf(stderr, "_lscp_connect_list_free: pList=%p.\n", pList); lscp_mutex_lock(pList->mutex); for (p = pList->first; p; p = pNext) { pNext = p->next; _lscp_connect_list_remove(pList, p); _lscp_connect_destroy(p); } lscp_mutex_unlock(pList->mutex); lscp_mutex_destroy(pList->mutex); } static lscp_connect_t *_lscp_connect_list_find_sock ( lscp_connect_list_t *pList, lscp_socket_t sock ) { lscp_connect_t *p; // fprintf(stderr, "_lscp_connect_list_find_sock: pList=%p sock=%d.\n", pList, sock); for (p = pList->first; p; p = p->next) { if (sock == p->client.sock) return p; } return NULL; } //------------------------------------------------------------------------- // Server-side threaded client connections. static void _lscp_connect_proc ( void *pvConnect ) { lscp_connect_t *pConnect = (lscp_connect_t *) pvConnect; lscp_server_t *pServer = pConnect->server; while (pServer->agent.iState && pConnect->client.iState) { if (_lscp_connect_recv(pConnect) != LSCP_OK) pConnect->client.iState = 0; } (*pServer->pfnCallback)(pConnect, NULL, LSCP_CONNECT_CLOSE, pServer->pvData); _lscp_connect_list_remove_safe(&(pServer->connects), pConnect); closesocket(pConnect->client.sock); } static lscp_connect_t *_lscp_connect_create ( lscp_server_t *pServer, lscp_socket_t sock, struct sockaddr_in *pAddr, int cAddr ) { lscp_connect_t *pConnect; if (pServer == NULL || sock == INVALID_SOCKET || pAddr == NULL) { fprintf(stderr, "_lscp_connect_create: Invalid connection arguments.\n"); return NULL; } pConnect = (lscp_connect_t *) malloc(sizeof(lscp_connect_t)); if (pConnect == NULL) { fprintf(stderr, "_lscp_connect_create: Out of memory.\n"); closesocket(sock); return NULL; } memset(pConnect, 0, sizeof(lscp_connect_t)); pConnect->server = pServer; pConnect->events = LSCP_EVENT_NONE; #ifdef CONFIG_DEBUG fprintf(stderr, "_lscp_connect_create: pConnect=%p: sock=%d addr=%s port=%d.\n", pConnect, sock, inet_ntoa(pAddr->sin_addr), ntohs(pAddr->sin_port)); #endif lscp_socket_agent_init(&(pConnect->client), sock, pAddr, cAddr); if (pServer->mode == LSCP_SERVER_THREAD) { if (lscp_socket_agent_start(&(pConnect->client), _lscp_connect_proc, pConnect, 0) != LSCP_OK) { closesocket(sock); free(pConnect); return NULL; } } return pConnect; } static lscp_status_t _lscp_connect_destroy ( lscp_connect_t *pConnect ) { lscp_status_t ret = LSCP_FAILED; if (pConnect == NULL) return ret; #ifdef CONFIG_DEBUG fprintf(stderr, "_lscp_connect_destroy: pConnect=%p.\n", pConnect); #endif lscp_socket_agent_free(&(pConnect->client)); free(pConnect); return ret; } lscp_status_t _lscp_connect_recv ( lscp_connect_t *pConnect ) { lscp_status_t ret = LSCP_FAILED; lscp_server_t *pServer; char achBuffer[LSCP_BUFSIZ]; int cchBuffer; if (pConnect == NULL) return ret; pServer = pConnect->server; if (pServer == NULL) return ret; cchBuffer = recv(pConnect->client.sock, achBuffer, sizeof(achBuffer), 0); if (cchBuffer > 0) ret = (*pServer->pfnCallback)(pConnect, achBuffer, cchBuffer, pServer->pvData); else if (cchBuffer < 0) lscp_socket_perror("_lscp_connect_recv: recv"); return ret; } //------------------------------------------------------------------------- // Command service (stream oriented). static void _lscp_server_thread_proc ( lscp_server_t *pServer ) { lscp_socket_t sock; struct sockaddr_in addr; socklen_t cAddr; lscp_connect_t *pConnect; #ifdef CONFIG_DEBUG fprintf(stderr, "_lscp_server_thread_proc: Server listening for connections.\n"); #endif while (pServer->agent.iState) { cAddr = sizeof(struct sockaddr_in); sock = accept(pServer->agent.sock, (struct sockaddr *) &addr, &cAddr); if (sock == INVALID_SOCKET) { lscp_socket_perror("_lscp_server_thread_proc: accept"); pServer->agent.iState = 0; } else { pConnect = _lscp_connect_create(pServer, sock, &addr, cAddr); if (pConnect) { _lscp_connect_list_append(&(pServer->connects), pConnect); (*pServer->pfnCallback)(pConnect, NULL, LSCP_CONNECT_OPEN, pServer->pvData); } } } #ifdef CONFIG_DEBUG fprintf(stderr, "_lscp_server_thread_proc: Server closing.\n"); #endif } static void _lscp_server_select_proc ( lscp_server_t *pServer ) { fd_set master_fds; // Master file descriptor list. fd_set select_fds; // temp file descriptor list for select(). int fd, fdmax; // Maximum file descriptor number. struct timeval tv; // For specifying a timeout value. int iSelect; // Holds select return status. lscp_socket_t sock; struct sockaddr_in addr; socklen_t cAddr; lscp_connect_t *pConnect; #ifdef CONFIG_DEBUG fprintf(stderr, "_lscp_server_select_proc: Server listening for connections.\n"); #endif FD_ZERO(&master_fds); FD_ZERO(&select_fds); // Add the listener to the master set FD_SET((unsigned int) pServer->agent.sock, &master_fds); // Keep track of the biggest file descriptor; // So far, it's ourself, the listener. fdmax = (int) pServer->agent.sock; // Main loop... while (pServer->agent.iState) { // Use a copy of the master. select_fds = master_fds; // Use the timeout feature for watchdoggin. tv.tv_sec = LSCP_SERVER_SLEEP; tv.tv_usec = 0; // Wait for events... iSelect = select(fdmax + 1, &select_fds, NULL, NULL, &tv); if (iSelect < 0) { lscp_socket_perror("_lscp_server_select_proc: select"); pServer->agent.iState = 0; } else if (iSelect > 0) { // Run through the existing connections looking for data to read... for (fd = 0; fd < fdmax + 1; fd++) { if (FD_ISSET(fd, &select_fds)) { // We got one!! // Is it ourselves, the command listener? if (fd == (int) pServer->agent.sock) { // Accept the connection... cAddr = sizeof(struct sockaddr_in); sock = accept(pServer->agent.sock, (struct sockaddr *) &addr, &cAddr); if (sock == INVALID_SOCKET) { lscp_socket_perror("_lscp_server_select_proc: accept"); pServer->agent.iState = 0; } else { // Add to master set. FD_SET((unsigned int) sock, &master_fds); // Keep track of the maximum. if ((int) sock > fdmax) fdmax = (int) sock; // And do create the client connection entry. pConnect = _lscp_connect_create(pServer, sock, &addr, cAddr); if (pConnect) { _lscp_connect_list_append(&(pServer->connects), pConnect); (*pServer->pfnCallback)(pConnect, NULL, LSCP_CONNECT_OPEN, pServer->pvData); } } // Done with one new connection. } else { // Otherwise it's trivial transaction... lscp_mutex_lock(pServer->connects.mutex); // Find the connection on our cache... pConnect = _lscp_connect_list_find_sock(&(pServer->connects), (lscp_socket_t) fd); // Handle data from a client. if (_lscp_connect_recv(pConnect) != LSCP_OK) { // Say bye bye! if (pConnect) { (*pServer->pfnCallback)(pConnect, NULL, LSCP_CONNECT_CLOSE, pServer->pvData); _lscp_connect_list_remove(&(pServer->connects), pConnect); _lscp_connect_destroy(pConnect); } // Remove from master set. FD_CLR((unsigned int) fd, &master_fds); } lscp_mutex_unlock(pServer->connects.mutex); } } } // Done (iSelect > 0) } } #ifdef CONFIG_DEBUG fprintf(stderr, "_lscp_server_select_proc: Server closing.\n"); #endif } static void _lscp_server_agent_proc ( void *pvServer ) { lscp_server_t *pServer = (lscp_server_t *) pvServer; if (pServer->mode == LSCP_SERVER_THREAD) _lscp_server_thread_proc(pServer); else _lscp_server_select_proc(pServer); } //------------------------------------------------------------------------- // Server versioning teller fuunction. /** Retrieve the current server library version string. */ const char* lscp_server_package (void) { return LSCP_PACKAGE; } /** Retrieve the current server library version string. */ const char* lscp_server_version (void) { return LSCP_VERSION; } /** Retrieve the current server library build string. */ const char* lscp_server_build (void) { return LSCP_BUILD; } //------------------------------------------------------------------------- // Server sockets. /** * Create a server instance, listening on the given port for client * connections. A server callback function must be suplied that will * handle every and each client request. * * @param iPort Port number where the server will bind for listening. * @param pfnCallback Callback function to receive and handle client requests. * @param pvData Server context opaque data, that will be passed * to the callback function without change. * * @returns The new server instance pointer @ref lscp_server_t if successfull, * which shall be used on all subsequent server calls, NULL otherwise. */ lscp_server_t* lscp_server_create ( int iPort, lscp_server_proc_t pfnCallback, void *pvData ) { return lscp_server_create_ex(iPort, pfnCallback, pvData, LSCP_SERVER_SELECT); } /** * Create a server instance, listening on the given port for client * connections. A server callback function must be suplied that will * handle every and each client request. A server threading model * maybe specified either as multi-threaded (one thread per client) * or single thread multiplex mode (one thread serves all clients). * * @param iPort Port number where the server will bind for listening. * @param pfnCallback Callback function to receive and handle client requests. * @param pvData Server context opaque data, that will be passed * to the callback function without change. * @param mode Server mode of operation, regarding the internal * threading model, either @ref LSCP_SERVER_THREAD for * a multi-threaded server, or @ref LSCP_SERVER_SELECT * for a single-threaded multiplexed server. * * @returns The new server instance pointer if successfull, which shall be * used on all subsequent server calls, NULL otherwise. */ lscp_server_t* lscp_server_create_ex ( int iPort, lscp_server_proc_t pfnCallback, void *pvData, lscp_server_mode_t mode ) { lscp_server_t *pServer; lscp_socket_t sock; struct sockaddr_in addr; socklen_t cAddr; int iSockOpt = (-1); if (pfnCallback == NULL) { fprintf(stderr, "lscp_server_create: Invalid server callback function.\n"); return NULL; } // Allocate server descriptor... pServer = (lscp_server_t *) malloc(sizeof(lscp_server_t)); if (pServer == NULL) { fprintf(stderr, "lscp_server_create: Out of memory.\n"); return NULL; } memset(pServer, 0, sizeof(lscp_server_t)); _lscp_connect_list_init(&(pServer->connects)); pServer->mode = mode; pServer->pfnCallback = pfnCallback; pServer->pvData = pvData; #ifdef CONFIG_DEBUG fprintf(stderr, "lscp_server_create: pServer=%p: iPort=%d.\n", pServer, iPort); #endif // Prepare the command stream server socket... sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { lscp_socket_perror("lscp_server_create: socket"); free(pServer); return NULL; } if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &iSockOpt, sizeof(int)) == SOCKET_ERROR) lscp_socket_perror("lscp_server_create: setsockopt(SO_REUSEADDR)"); #if defined(WIN32) if (setsockopt(sock, SOL_SOCKET, SO_DONTLINGER, (char *) &iSockOpt, sizeof(int)) == SOCKET_ERROR) lscp_socket_perror("lscp_server_create: setsockopt(SO_DONTLINGER)"); #endif #ifdef CONFIG_DEBUG lscp_socket_getopts("lscp_server_create", sock); #endif cAddr = sizeof(struct sockaddr_in); memset((char *) &addr, 0, cAddr); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons((short) iPort); if (bind(sock, (const struct sockaddr *) &addr, cAddr) == SOCKET_ERROR) { lscp_socket_perror("lscp_server_create: bind"); closesocket(sock); free(pServer); return NULL; } if (listen(sock, 10) == SOCKET_ERROR) { lscp_socket_perror("lscp_server_create: listen"); closesocket(sock); free(pServer); return NULL; } if (iPort == 0) { if (getsockname(sock, (struct sockaddr *) &addr, &cAddr) == SOCKET_ERROR) { lscp_socket_perror("lscp_server_create: getsockname"); closesocket(sock); free(pServer); } } lscp_socket_agent_init(&(pServer->agent), sock, &addr, cAddr); #ifdef CONFIG_DEBUG fprintf(stderr, "lscp_server_create: sock=%d addr=%s port=%d.\n", pServer->agent.sock, inet_ntoa(pServer->agent.addr.sin_addr), ntohs(pServer->agent.addr.sin_port)); #endif // Now's finally time to startup threads... // Command service thread... if (lscp_socket_agent_start(&(pServer->agent), _lscp_server_agent_proc, pServer, 0) != LSCP_OK) { lscp_socket_agent_free(&(pServer->agent)); free(pServer); return NULL; } // Finally we've some success... return pServer; } /** * Wait for a server instance to terminate graciously. * * @param pServer Pointer to server instance structure. */ lscp_status_t lscp_server_join ( lscp_server_t *pServer ) { if (pServer == NULL) return LSCP_FAILED; #ifdef CONFIG_DEBUG fprintf(stderr, "lscp_server_join: pServer=%p.\n", pServer); #endif lscp_socket_agent_join(&(pServer->agent)); return LSCP_OK; } /** * Terminate and destroy a server instance. * * @param pServer Pointer to server instance structure. */ lscp_status_t lscp_server_destroy ( lscp_server_t *pServer ) { if (pServer == NULL) return LSCP_FAILED; #ifdef CONFIG_DEBUG fprintf(stderr, "lscp_server_destroy: pServer=%p.\n", pServer); #endif _lscp_connect_list_free(&(pServer->connects)); lscp_socket_agent_free(&(pServer->agent)); free(pServer); return LSCP_OK; } /** * Send an event notification message to all subscribed clients. * * @param pServer Pointer to server instance structure. * @param event Event type flag to send to all subscribed clients. * @param pchData Pointer to event data to be sent to all clients. * @param cchData Length of the event data to be sent in bytes. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_server_broadcast ( lscp_server_t *pServer, lscp_event_t event, const char *pchData, int cchData ) { lscp_connect_t *p; const char *pszEvent; char achBuffer[LSCP_BUFSIZ]; int cchBuffer; if (pServer == NULL) return LSCP_FAILED; if (pchData == NULL || cchData < 1) return LSCP_FAILED; // Which (single) event? pszEvent = lscp_event_to_text(event); if (pszEvent == NULL) return LSCP_FAILED; // Build the event message string... cchBuffer = sprintf(achBuffer, "NOTIFY:%s:", pszEvent); if (pchData) { if (cchData > LSCP_BUFSIZ - cchBuffer - 2) cchData = LSCP_BUFSIZ - cchBuffer - 2; strncpy(&achBuffer[cchBuffer], pchData, cchData); cchBuffer += cchData; } achBuffer[cchBuffer++] = '\r'; achBuffer[cchBuffer++] = '\n'; // And do the direct broadcasting... lscp_mutex_lock(pServer->connects.mutex); for (p = pServer->connects.first; p; p = p->next) { if (p->events & event) send(p->client.sock, achBuffer, cchBuffer, 0); } lscp_mutex_unlock(pServer->connects.mutex); return LSCP_OK; } /** * Send response for the current client callback request. * * @param pConnect Pointer to client connection instance structure. * @param pchBuffer Pointer to data to be sent to the client as response. * @param cchBuffer Length of the response data to be sent in bytes. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_server_result ( lscp_connect_t *pConnect, const char *pchBuffer, int cchBuffer ) { lscp_status_t ret = LSCP_FAILED; if (pConnect == NULL) return ret; if (pchBuffer == NULL || cchBuffer < 1) return ret; if (send(pConnect->client.sock, pchBuffer, cchBuffer, 0) != cchBuffer) lscp_socket_perror("lscp_server_result"); else ret = LSCP_OK; return ret; } /** * Register client as a subscriber of event broadcast messages. * * @param pConnect Pointer to client connection instance structure. * @param event Event type flag of the requesting client subscription. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_server_subscribe ( lscp_connect_t *pConnect, lscp_event_t event ) { if (pConnect == NULL) return LSCP_FAILED; if (event == LSCP_EVENT_NONE) return LSCP_FAILED; pConnect->events |= event; return LSCP_OK; } /** * Deregister client as subscriber of event broadcast messages. * * @param pConnect Pointer to client connection instance structure. * @param event Event type flag of the requesting client unsubscription. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_server_unsubscribe ( lscp_connect_t *pConnect, lscp_event_t event ) { if (pConnect == NULL) return LSCP_FAILED; if (event == LSCP_EVENT_NONE) return LSCP_FAILED; pConnect->events &= ~event; return LSCP_OK; } // end of server.c liblscp-1.0.1/examples/server.h0000644000175000017500000000726114771257264015573 0ustar drag0ndrag0n// server.h // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __LSCP_SERVER_H #define __LSCP_SERVER_H #include "lscp/socket.h" #include "lscp/event.h" #if defined(__cplusplus) extern "C" { #endif //------------------------------------------------------------------------- // Server socket modes. /** Server thread model. */ typedef enum _lscp_server_mode_t { LSCP_SERVER_THREAD = 0, LSCP_SERVER_SELECT = 1 } lscp_server_mode_t; /** Connection mode notification. */ typedef enum _lscp_connect_mode_t { LSCP_CONNECT_OPEN = 0, LSCP_CONNECT_CLOSE = 1 } lscp_connect_mode_t; //------------------------------------------------------------------------- // Server socket structures. struct _lscp_server_t; /** Client connection descriptor struct. */ typedef struct _lscp_connect_t { struct _lscp_server_t *server; lscp_socket_agent_t client; lscp_event_t events; struct _lscp_connect_t *prev; struct _lscp_connect_t *next; } lscp_connect_t; /** Client connection list struct. */ typedef struct _lscp_connect_list_t { lscp_connect_t *first; lscp_connect_t *last; unsigned int count; lscp_mutex_t mutex; } lscp_connect_list_t; /** Server callback procedure prototype. */ typedef lscp_status_t (*lscp_server_proc_t) ( lscp_connect_t *pConnect, const char *pchBuffer, int cchBuffer, void *pvData ); /** Server descriptor struct. */ typedef struct _lscp_server_t { lscp_server_mode_t mode; lscp_connect_list_t connects; lscp_server_proc_t pfnCallback; void *pvData; lscp_socket_agent_t agent; } lscp_server_t; //------------------------------------------------------------------------- // Server versioning teller fuunctions. const char * lscp_server_package (void); const char * lscp_server_version (void); const char * lscp_server_build (void); //------------------------------------------------------------------------- // Server socket functions. lscp_server_t * lscp_server_create (int iPort, lscp_server_proc_t pfnCallback, void *pvData); lscp_server_t * lscp_server_create_ex (int iPort, lscp_server_proc_t pfnCallback, void *pvData, lscp_server_mode_t mode); lscp_status_t lscp_server_join (lscp_server_t *pServer); lscp_status_t lscp_server_destroy (lscp_server_t *pServer); lscp_status_t lscp_server_broadcast (lscp_server_t *pServer, lscp_event_t event, const char *pchData, int cchData); lscp_status_t lscp_server_result (lscp_connect_t *pConnect, const char *pchBuffer, int cchBuffer); lscp_status_t lscp_server_subscribe (lscp_connect_t *pConnect, lscp_event_t event); lscp_status_t lscp_server_unsubscribe (lscp_connect_t *pConnect, lscp_event_t event); #if defined(__cplusplus) } #endif #endif // __LSCP_SERVER_H // end of server.h liblscp-1.0.1/examples/example_server.c0000644000175000017500000007074314771257264017306 0ustar drag0ndrag0n// example_server.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "server.h" #include "parser.h" #include #define SERVER_PORT 8888 #if defined(WIN32) static WSADATA _wsaData; #endif //////////////////////////////////////////////////////////////////////// lscp_status_t server_callback ( lscp_connect_t *pConnect, const char *pchBuffer, int cchBuffer, void *pvData ) { lscp_status_t ret = LSCP_OK; lscp_parser_t tok; const char *pszResult = NULL; char szTemp[4096]; int i; static int iSamplerChannel = 0; static int iAudioDevice = 0; static int iMidiDevice = 0; static int iFxSend = 0; static int iMidiMaps = 0; static int iMidiInstruments = 0; static float fVolume = 1.0f; if (pchBuffer == NULL) { fprintf(stderr, "server_callback: addr=%s port=%d: ", inet_ntoa(pConnect->client.addr.sin_addr), htons(pConnect->client.addr.sin_port)); switch (cchBuffer) { case LSCP_CONNECT_OPEN: fprintf(stderr, "New client connection.\n"); break; case LSCP_CONNECT_CLOSE: fprintf(stderr, "Connection closed.\n"); break; } return ret; } lscp_socket_trace("server_callback", &(pConnect->client.addr), pchBuffer, cchBuffer); lscp_parser_init(&tok, pchBuffer, cchBuffer); if (lscp_parser_test(&tok, "GET")) { if (lscp_parser_test(&tok, "CHANNEL")) { if (lscp_parser_test(&tok, "INFO")) { // Getting sampler channel informations: // GET CHANNEL INFO pszResult = "ENGINE_NAME: DummyEngine\r\n" "INSTRUMENT_FILE: DummyInstrument.gig\r\n" "INSTRUMENT_NR: 0\r\n" "INSTRUMENT_NAME: Dummy Instrument\r\n" "INSTRUMENT_STATUS: 100\r\n" "AUDIO_OUTPUT_DEVICE: 0\r\n" "AUDIO_OUTPUT_CHANNELS: 2\r\n" "AUDIO_OUTPUT_ROUTING: 0,1\r\n" "MIDI_INPUT_DEVICE: 0\r\n" "MIDI_INPUT_PORT: 0\r\n" "MIDI_INPUT_CHANNEL: ALL\r\n" "VOLUME: 0.5\r\n" "MUTE: FALSE\r\n" "SOLO: TRUE\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "VOICE_COUNT")) { // Current number of active voices: // GET CHANNEL VOICE_COUNT sprintf(szTemp, "%d\r\n", rand() % 100); pszResult = szTemp; } else if (lscp_parser_test(&tok, "STREAM_COUNT")) { // Current number of active disk streams: // GET CHANNEL STREAM_COUNT pszResult = "3\r\n"; } else if (lscp_parser_test(&tok, "BUFFER_FILL")) { if (lscp_parser_test(&tok, "BYTES")) { // Current fill state of disk stream buffers: // GET CHANNEL BUFFER_FILL BYTES sprintf(szTemp, "[1]%d,[2]%d,[3]%d\r\n", rand(), rand(), rand()); pszResult = szTemp; } else if (lscp_parser_test(&tok, "PERCENTAGE")) { // Current fill state of disk stream buffers: // GET CHANNEL BUFFER_FILL PERCENTAGE sprintf(szTemp, "[1]%d%%,[2]%d%%,[3]%d%%\r\n", rand() % 100, rand() % 100, rand() % 100); pszResult = szTemp; } else ret = LSCP_FAILED; } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "CHANNELS")) { // Current number of sampler channels: // GET CHANNELS sprintf(szTemp, "%d\r\n", iSamplerChannel); pszResult = szTemp; } else if (lscp_parser_test(&tok, "AVAILABLE_AUDIO_OUTPUT_DRIVERS")) { // Getting all available audio output driver count. // GET AVAILABLE_AUDIO_OUTPUT_DRIVERS pszResult = "2\r\n"; } else if (lscp_parser_test(&tok, "AVAILABLE_MIDI_INPUT_DRIVERS")) { // Getting all available MIDI input driver count. // GET AVAILABLE_MIDI_INPUT_DRIVERS pszResult = "1\r\n"; } else if (lscp_parser_test2(&tok, "AUDIO_OUTPUT_DRIVER", "INFO")) { // Getting informations about a specific audio output driver. // GET AUDIO_OUTPUT_DRIVER INFO if (lscp_parser_test(&tok, "Alsa")) { pszResult = "DESCRIPTION: 'ALSA PCM'\r\n" "VERSION: '1.0'\r\n" "PARAMETERS: channels,samplerate,active\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "Jack")) { pszResult = "DESCRIPTION: JACK Audio Connection Kit\r\n" "VERSION: 0.98.1\r\n" "PARAMETERS: channels,samplerate,active\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "MIDI_INPUT_DRIVER", "INFO")) { // Getting informations about a specific MIDI input driver. // GET MIDI_INPUT_DRIVER INFO if (lscp_parser_test(&tok, "Alsa")) { pszResult = "DESCRIPTION: ALSA Sequencer\r\n" "VERSION: 1.0\r\n" "PARAMETERS: ports,active\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "AUDIO_OUTPUT_DRIVER_PARAMETER", "INFO")) { // Getting informations about a specific audio output driver parameter. // GET AUDIO_OUTPUT_DRIVER_PARAMETER INFO if (lscp_parser_test(&tok, "Alsa")) { if (lscp_parser_test(&tok, "active")) { pszResult = "DESCRIPTION: 'ALSA PCM active state'\r\n" "TYPE: BOOL\r\n" "MANDATORY: TRUE\r\n" "FIX: TRUE\r\n" "MULTIPLICITY: FALSE\r\n" "DEPENDS: channels,samplerate,card\r\n" "DEFAULT: TRUE\r\n" "RANGE_MIN: FALSE\r\n" "RANGE_MAX: TRUE\r\n" "POSSIBILITIES: FALSE,TRUE\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "channels")) { pszResult = "DESCRIPTION: 'Number of ALSA PCM channels'\r\n" "TYPE: INT\r\n" "MANDATORY: TRUE\r\n" "FIX: TRUE\r\n" "MULTIPLICITY: FALSE\r\n" "DEFAULT: 2\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "samplerate")) { pszResult = "DESCRIPTION: 'ALSA PCM sample rate'\r\n" "TYPE: INT\r\n" "MANDATORY: TRUE\r\n" "FIX: TRUE\r\n" "MULTIPLICITY: TRUE\r\n" "DEFAULT: 44100\r\n" "POSSIBILITIES: 44100,48000,96000\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "Jack")) { if (lscp_parser_test(&tok, "active")) { pszResult = "DESCRIPTION: 'JACK active state'\r\n" "TYPE: BOOL\r\n" "MANDATORY: TRUE\r\n" "FIX: TRUE\r\n" "MULTIPLICITY: FALSE\r\n" "DEPENDS: channels,samplerate\r\n" "DEFAULT: TRUE\r\n" "RANGE_MIN: FALSE\r\n" "RANGE_MAX: TRUE\r\n" "POSSIBILITIES: FALSE,TRUE\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "channels")) { pszResult = "DESCRIPTION: 'Number of JACK audio channels'\r\n" "TYPE: INT\r\n" "MANDATORY: TRUE\r\n" "FIX: TRUE\r\n" "MULTIPLICITY: FALSE\r\n" "DEFAULT: 2\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "samplerate")) { pszResult = "DESCRIPTION: 'JACK sample rate'\r\n" "TYPE: INT\r\n" "MANDATORY: TRUE\r\n" "FIX: TRUE\r\n" "MULTIPLICITY: TRUE\r\n" "DEFAULT: 44100\r\n" "POSSIBILITIES: 44100,48000,96000\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "MIDI_INPUT_DRIVER_PARAMETER", "INFO")) { // Getting informations about a specific MIDI input driver parameter. // GET MIDI_INPUT_DRIVER_PARAMETER INFO if (lscp_parser_test(&tok, "Alsa")) { if (lscp_parser_test(&tok, "active")) { pszResult = "DESCRIPTION: 'ALSA Sequencer device active state'\r\n" "TYPE: BOOL\r\n" "MANDATORY: TRUE\r\n" "FIX: TRUE\r\n" "MULTIPLICITY: FALSE\r\n" "DEPENDS: channels,ports\r\n" "DEFAULT: TRUE\r\n" "RANGE_MIN: FALSE\r\n" "RANGE_MAX: TRUE\r\n" "POSSIBILITIES: FALSE,TRUE\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "ports")) { pszResult = "DESCRIPTION: 'Number of ALSA Sequencer ports'\r\n" "TYPE: INT\r\n" "MANDATORY: FALSE\r\n" "FIX: FALSE\r\n" "MULTIPLICITY: FALSE\r\n" "DEFAULT: 1\r\n" "RANGE_MIN: 1\r\n" "RANGE_MAX: 4\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "AUDIO_OUTPUT_DEVICE", "INFO")) { // Getting informations about a specific audio output device. // GET AUDIO_OUTPUT_DEVICE INFO if (lscp_parser_nextint(&tok) < iAudioDevice) { pszResult = "driver: Alsa\r\n" "active: TRUE\r\n" "channels: 2\r\n" "samplerate: 44100\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "MIDI_INPUT_DEVICE", "INFO")) { // Getting informations about a specific MIDI input device. // GET MIDI_INPUT_DEVICE INFO if (lscp_parser_nextint(&tok) < iMidiDevice) { pszResult = "driver: Alsa\r\n" "active: TRUE\r\n" "channels: 16\r\n" "ports: 1\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "AUDIO_OUTPUT_CHANNEL", "INFO")) { // Getting informations about an audio channel. // GET AUDIO_OUTPUT_CHANNEL INFO if (lscp_parser_nextint(&tok) < iAudioDevice) { pszResult = "name: DummyMonitor\r\n" "is_mix_channel: FALSE\r\n" "mix_channel_destination: 0\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "MIDI_INPUT_PORT", "INFO")) { // Getting informations about a MIDI port. // GET MIDI_INPUT_PORT INFO if (lscp_parser_nextint(&tok) < iMidiDevice) { pszResult = "name: DummyKeyboard\r\n" "alsa_seq_bindings: '64:0'\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "AUDIO_OUTPUT_CHANNEL_PARAMETER", "INFO")) { // Getting informations about specific audio channel parameter. // GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO if (lscp_parser_nextint(&tok) < iAudioDevice) { lscp_parser_nextint(&tok); if (lscp_parser_test(&tok, "is_mix_channel")) { pszResult = "DESCRIPTION: 'Whether this is an audio mix channel'\r\n" "TYPE: BOOL\r\n" "MANDATORY: TRUE\r\n" "FIX: FALSE\r\n" "MULTIPLICITY: FALSE\r\n" "POSSIBILITIES: FALSE,TRUE\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "mix_channel_destination")) { pszResult = "DESCRIPTION: 'Audio mix channel destination'\r\n" "TYPE: INT\r\n" "MANDATORY: TRUE\r\n" "FIX: FALSE\r\n" "MULTIPLICITY: TRUE\r\n" "POSSIBILITIES: 0,1\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "MIDI_INPUT_PORT_PARAMETER", "INFO")) { // Getting informations about specific MIDI port parameter. // GET MIDI_INPUT_PORT_PARAMETER INFO if (lscp_parser_nextint(&tok) < iMidiDevice) { lscp_parser_nextint(&tok); if (lscp_parser_test(&tok, "alsa_seq_bindings")) { pszResult = "DESCRIPTION: 'Alsa sequencer port bindings'\r\n" "TYPE: STRING\r\n" "MANDATORY: TRUE\r\n" "FIX: FALSE\r\n" "MULTIPLICITY: TRUE\r\n" "POSSIBILITIES: '64:0','68:0','68:1'\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "AUDIO_OUTPUT_DEVICES")) { // Getting all created audio output device count. // GET AUDIO_OUTPUT_DEVICES sprintf(szTemp, "%d\r\n", iAudioDevice); pszResult = szTemp; } else if (lscp_parser_test(&tok, "MIDI_INPUT_DEVICES")) { // Getting all created MID input device count. // GET MIDI_INPUT_DEVICES sprintf(szTemp, "%d\r\n", iMidiDevice); pszResult = szTemp; } else if (lscp_parser_test(&tok, "AVAILABLE_ENGINES")) { // Getting all available engine count: // GET AVAILABLE_ENGINES pszResult = "3\r\n"; } else if (lscp_parser_test2(&tok, "SERVER", "INFO")) { // Getting information about the server. // GET SERVER INFO sprintf(szTemp, "DESCRIPTION: example_server (%s) %s\r\n" "VERSION: %s\r\n.\r\n", lscp_server_package(), lscp_server_build(), lscp_server_version()); pszResult = szTemp; } else if (lscp_parser_test2(&tok, "ENGINE", "INFO")) { // Getting information about an engine. // GET ENGINE INFO if (lscp_parser_test(&tok, "GigEngine")) { pszResult = "DESCRIPTION: GigaSampler Engine\r\n" "VERSION: 0.3\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "DLSEngine")) { pszResult = "DESCRIPTION: 'DLS Generic Engine'\r\n" "VERSION: 0.2\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "AkaiEngine")) { pszResult = "DESCRIPTION: Akai Sampler Engine\r\n" "VERSION: 0.1\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "TOTAL_VOICE_COUNT")) { // Current number of active voices: // GET TOTAL_VOICE_COUNT sprintf(szTemp, "%d\r\n", rand() % 100); pszResult = szTemp; } else if (lscp_parser_test(&tok, "TOTAL_VOICE_COUNT_MAX")) { // Maximum amount of active voices: // GET TOTAL_VOICE_COUNT_MAX sprintf(szTemp, "%d\r\n", rand() % 100); pszResult = szTemp; } else if (lscp_parser_test(&tok, "VOLUME")) { // Get global volume attenuation: // GET VOLUME sprintf(szTemp, "%g\r\n", fVolume); pszResult = szTemp; } else if (lscp_parser_test2(&tok, "FX_SEND", "INFO")) { // Getting effect send informations: // GET FX_SEND INFO pszResult = "NAME: DummyFxSend\r\n" "MIDI_CONTROLLER: 99\r\n" "AUDIO_OUTPUT_ROUTING: 0,1\r\n" "LEVEL: 0.15\r\n" ".\r\n"; } else if (lscp_parser_test(&tok, "FX_SENDS")) { // Get ammount of effect sends on a sampler channel: // GET FX_SENDS sprintf(szTemp, "%d\r\n", iFxSend); pszResult = szTemp; } else if (lscp_parser_test(&tok, "MIDI_INSTRUMENTS")) { // Get the total count of MIDI instrument map entries: // GET MIDI_INSTRUMENTS sprintf(szTemp, "%d\r\n", iMidiInstruments); pszResult = szTemp; } else if (lscp_parser_test(&tok, "MIDI_INSTRUMENT_MAPS")) { // Get the total count of MIDI instrument maps: // GET MIDI_INSTRUMENT_MAPS sprintf(szTemp, "%d\r\n", iMidiMaps); pszResult = szTemp; } else if (lscp_parser_test2(&tok, "MIDI_INSTRUMENT_MAP", "INFO")) { // Getting information about a MIDI instrument map entry: // GET MIDI_INSTRUMENT_MAP INFO strcpy(szTemp, "NAME: "); switch (lscp_parser_nextint(&tok)) { case 0: strcat(szTemp, "Chromatic\r\n"); break; case 1: strcat(szTemp, "Drum Kits\r\n"); break; } strcat(szTemp, ".\r\n"); pszResult = szTemp; } else if (lscp_parser_test2(&tok, "MIDI_INSTRUMENT", "INFO")) { // Getting information about a MIDI instrument map entry: // GET MIDI_INSTRUMENT INFO pszResult = "NAME: DummyName\r\n" "ENGINE_NAME: DummyEngine\r\n" "INSTRUMENT_FILE: DummyInstrument.gig\r\n" "INSTRUMENT_NR: 0\r\n" "INSTRUMENT_NAME: Dummy Instrument\r\n" "LOAD_MODE: ON_DEMAND\r\n" "VOLUME: 0.5\r\n" ".\r\n"; } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "LIST")) { if (lscp_parser_test(&tok, "CHANNELS")) { // Getting all created sampler channel list. // LIST CHANNELS szTemp[0] = (char) 0; for (i = 0; i < iSamplerChannel && strlen(szTemp) < sizeof(szTemp) - 8; i++) { if (i > 0) strcat(szTemp, ","); sprintf(szTemp + strlen(szTemp), "%d", i); } strcat(szTemp, "\r\n"); pszResult = szTemp; } else if (lscp_parser_test(&tok, "AVAILABLE_ENGINES")) { // Getting all available engines: // LIST AVAILABLE_ENGINES pszResult = "GigEngine,DLSEngine,AkaiEngine\r\n"; } else if (lscp_parser_test(&tok, "AVAILABLE_AUDIO_OUTPUT_DRIVERS")) { // Getting all available audio output drivers. // LIST AVAILABLE_AUDIO_OUTPUT_DRIVERS pszResult = "ALSA,JACK\r\n"; } else if (lscp_parser_test(&tok, "AVAILABLE_MIDI_INPUT_DRIVERS")) { // Getting all available MIDI input drivers. // LIST AVAILABLE_MIDI_INPUT_DRIVERS pszResult = "ALSA\r\n"; } else if (lscp_parser_test(&tok, "AUDIO_OUTPUT_DEVICES")) { // Getting all created audio output device list. // LIST AUDIO_OUTPUT_DEVICES szTemp[0] = (char) 0; for (i = 0; i < iAudioDevice && strlen(szTemp) < sizeof(szTemp) - 8; i++) { if (i > 0) strcat(szTemp, ","); sprintf(szTemp + strlen(szTemp), "%d", i); } strcat(szTemp, "\r\n"); pszResult = szTemp; } else if (lscp_parser_test(&tok, "MIDI_INPUT_DEVICES")) { // Getting all created MID input device list. // LIST MIDI_INPUT_DEVICES szTemp[0] = (char) 0; for (i = 0; i < iMidiDevice && strlen(szTemp) < sizeof(szTemp) - 8; i++) { if (i > 0) strcat(szTemp, ","); sprintf(szTemp + strlen(szTemp), "%d", i); } strcat(szTemp, "\r\n"); pszResult = szTemp; } else if (lscp_parser_test(&tok, "FX_SENDS")) { // Listing all effect sends on a sampler channel: // LIST FX_SENDS szTemp[0] = (char) 0; for (i = 0; i < iFxSend && strlen(szTemp) < sizeof(szTemp) - 8; i++) { if (i > 0) strcat(szTemp, ","); sprintf(szTemp + strlen(szTemp), "%d", i); } strcat(szTemp, "\r\n"); pszResult = szTemp; } else if (lscp_parser_test(&tok, "MIDI_INSTRUMENTS")) { // Getting indeces of all MIDI instrument map entries: // LIST MIDI_INSTRUMENTS szTemp[0] = (char) 0; for (i = 0; i < iMidiInstruments && strlen(szTemp) < sizeof(szTemp) - 16; i++) { if (i > 0) strcat(szTemp, ","); sprintf(szTemp + strlen(szTemp), "{0,%d,%d}", i / 128, i % 128); } strcat(szTemp, "\r\n"); pszResult = szTemp; } else if (lscp_parser_test(&tok, "MIDI_INSTRUMENT_MAPS")) { // Getting indeces of all MIDI instrument map entries: // LIST MIDI_INSTRUMENT_MAPS szTemp[0] = (char) 0; for (i = 0; i < iMidiMaps && strlen(szTemp) < sizeof(szTemp) - 8; i++) { if (i > 0) strcat(szTemp, ","); sprintf(szTemp + strlen(szTemp), "%d", i); } strcat(szTemp, "\r\n"); pszResult = szTemp; } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "SET")) { if (lscp_parser_test(&tok, "CHANNEL")) { if (lscp_parser_test(&tok, "VOLUME")) { // Setting channel volume: // SET CHANNEL VOLUME } else if (lscp_parser_test(&tok, "MUTE")) { // Muting a sampler channel: // SET CHANNEL MUTE } else if (lscp_parser_test(&tok, "SOLO")) { // Soloing a sampler channel: // SET CHANNEL SOLO } else if (lscp_parser_test(&tok, "AUDIO_OUTPUT_TYPE")) { // Setting audio output type: // SET CHANNEL AUDIO_OUTPUT_TYPE } else if (lscp_parser_test(&tok, "AUDIO_OUTPUT_DEVICE")) { // Setting audio output device: // SET CHANNEL AUDIO_OUTPUT_DEVICE } else if (lscp_parser_test(&tok, "AUDIO_OUTPUT_CHANNEL")) { // Setting audio output channel: // SET CHANNEL AUDIO_OUTPUT_CHANNEL } else if (lscp_parser_test(&tok, "MIDI_INPUT_TYPE")) { // Setting MIDI input type: // SET CHANNEL MIDI_INPUT_TYPE } else if (lscp_parser_test(&tok, "MIDI_INPUT_DEVICE")) { // Setting MIDI input device: // SET CHANNEL MIDI_INPUT_DEVICE } else if (lscp_parser_test(&tok, "MIDI_INPUT_PORT")) { // Setting MIDI input port: // SET CHANNEL MIDI_INPUT_PORT } else if (lscp_parser_test(&tok, "MIDI_INPUT_CHANNEL")) { // Setting MIDI input channel: // SET CHANNEL MIDI_INPUT_CHANNEL } else if (lscp_parser_test(&tok, "MIDI_INSTRUMENT_MAP")) { // Setting MIDI instrument mapl: // SET CHANNEL MIDI_INSTRUMENT_MAP } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "VOLUME")) { // Setting global volume attenuation: // SET VOLUME fVolume = lscp_parser_nextnum(&tok); } else if (lscp_parser_test(&tok, "FX_SEND")) { if (lscp_parser_test(&tok, "MIDI_CONTROLLER")) { // Altering effect send MIDI controller: // SET FX_SEND MIDI_CONTROLLER } else if (lscp_parser_test(&tok, "AUDIO_OUTPUT_CHANNEL")) { // Altering effect send audio routing: // SET FX_SEND AUDIO_OUTPUT_CHANNEL } else if (lscp_parser_test(&tok, "LEVEL")) { // Altering effect send audio level: // SET FX_SEND LEVEL } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "MIDI_INSTRUMENT_MAP", "NAME")) { // Setting MIDI instrument map name: // SET MIDI_INSTRUMENT_MAP NAME } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "LOAD")) { if (lscp_parser_test(&tok, "ENGINE")) { // Loading a sampler engine: // LOAD ENGINE } else if (lscp_parser_test(&tok, "INSTRUMENT")) { // Loading an instrument: // LOAD INSTRUMENT [NON_MODAL] } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "ADD")) { if (lscp_parser_test(&tok, "CHANNEL")) { // Adding a new sampler channel: // ADD CHANNEL if (iSamplerChannel < 16) { sprintf(szTemp, "OK[%d]\r\n", iSamplerChannel++); pszResult = szTemp; } else { iSamplerChannel = 0; ret = LSCP_FAILED; } } else if (lscp_parser_test(&tok, "MIDI_INSTRUMENT_MAP")) { // Adding a new MIDI instrument map: // ADD MIDI_INSTRUMENT_MAP if (iMidiMaps < 2) { sprintf(szTemp, "OK[%d]\r\n", iMidiMaps++); pszResult = szTemp; } else { iMidiMaps = 0; ret = LSCP_FAILED; } } } else if (lscp_parser_test(&tok, "REMOVE")) { if (lscp_parser_test(&tok, "CHANNEL")) { // Removing a sampler channel: // REMOVE CHANNEL if (lscp_parser_nextint(&tok) >= 0) iSamplerChannel--; else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "MIDI_INSTRUMENT_MAP")) { // Removing a MIDI instrument map: // REMOVE MIDI_INSTRUMENT_MAP if (lscp_parser_nextint(&tok) >= 0) iMidiMaps--; else ret = LSCP_FAILED; } } else if (lscp_parser_test(&tok, "RESET")) { if (lscp_parser_test(&tok, "CHANNEL")) { // Resetting a sampler channel: // RESET CHANNEL if (lscp_parser_nextint(&tok) > iSamplerChannel) ret = LSCP_FAILED; } else { // Reset sampler: // RESET iSamplerChannel = 0; iAudioDevice = 0; iMidiDevice = 0; iMidiMaps = 0; iMidiInstruments = 0; } } else if (lscp_parser_test(&tok, "CREATE")) { if (lscp_parser_test(&tok, "AUDIO_OUTPUT_DEVICE")) { // Creating an audio output device. // CREATE AUDIO_OUTPUT_DEVICE [] if (iAudioDevice < 8) { sprintf(szTemp, "OK[%d]\r\n", iAudioDevice++); pszResult = szTemp; } else { iAudioDevice = 0; ret = LSCP_FAILED; } } else if (lscp_parser_test(&tok, "MIDI_INPUT_DEVICE")) { // Creating an MIDI input device. // CREATE MIDI_INPUT_DEVICE [] if (iMidiDevice < 8) { sprintf(szTemp, "OK[%d]\r\n", iMidiDevice++); pszResult = szTemp; } else { iMidiDevice = 0; ret = LSCP_FAILED; } } else if (lscp_parser_test(&tok, "FX_SEND")) { // Adding an effect send to a sampler channel: // CREATE FX_SEND [] if (iFxSend < 8) { sprintf(szTemp, "OK[%d]\r\n", iFxSend++); pszResult = szTemp; } else { iFxSend = 0; ret = LSCP_FAILED; } } else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "DESTROY")) { if (lscp_parser_test(&tok, "AUDIO_OUTPUT_DEVICE")) { // Destroying an audio output device. // DESTROY AUDIO_OUTPUT_DEVICE if (lscp_parser_nextint(&tok) >= 0 && iAudioDevice > 0) iAudioDevice--; else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "MIDI_INPUT_DEVICE")) { // Destroying an MIDI intput device. // DESTROY MIDI_INPUT_DEVICE if (lscp_parser_nextint(&tok) >= 0 && iMidiDevice > 0) iMidiDevice--; else ret = LSCP_FAILED; } else if (lscp_parser_test(&tok, "FX_SEND")) { // Removing an effect send from a sampler channel: // CREATE FX_SEND if (lscp_parser_nextint(&tok) >= 0 && iFxSend > 0) iFxSend--; else ret = LSCP_FAILED; } else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "MAP", "MIDI_INSTRUMENT")) { // Create or replace a MIDI instrumnet map entry: // MAP MIDI_INSTRUMENT // [] iMidiInstruments++; } else if (lscp_parser_test2(&tok, "UNMAP", "MIDI_INSTRUMENT")) { // Remove an entry from the MIDI instrument map: // UNMAP MIDI_INSTRUMENT if (iMidiInstruments > 0) iMidiInstruments--; else ret = LSCP_FAILED; } else if (lscp_parser_test2(&tok, "CLEAR", "MIDI_INSTRUMENTS")) { // Clear the MIDI instrumnet map: // CLEAR MIDI_INSTRUMENTS iMidiInstruments = 0; } else if (lscp_parser_test(&tok, "SUBSCRIBE")) { // Register frontend for receiving event notification messages: // SUBSCRIBE ret = lscp_server_subscribe(pConnect, lscp_event_from_text(lscp_parser_next(&tok))); } else if (lscp_parser_test(&tok, "UNSUBSCRIBE")) { // Deregister frontend for not receiving event notification messages anymore: // UNSUBSCRIBE ret = lscp_server_unsubscribe(pConnect, lscp_event_from_text(lscp_parser_next(&tok))); } else if (lscp_parser_test(&tok, "QUIT")) { // Close client connection: // QUIT lscp_parser_free(&tok); return LSCP_FAILED; // Disconnect. } else ret = LSCP_FAILED; lscp_parser_free(&tok); if (pszResult == NULL) pszResult = (ret == LSCP_OK ? "OK\r\n" : "ERR:1:Failed\r\n"); fprintf(stderr, "> %s", pszResult); return lscp_server_result(pConnect, pszResult, strlen(pszResult)); } //////////////////////////////////////////////////////////////////////// void server_usage (void) { printf("\n %s %s (Build: %s)\n", lscp_server_package(), lscp_server_version(), lscp_server_build()); fputs("\n Available server commands: help, exit, quit, list", stdout); fputs("\n (all else are broadcast verbatim to subscribers)\n\n", stdout); } void server_prompt (void) { fputs("lscp_server> ", stdout); } int main (int argc, char *argv[] ) { lscp_server_t *pServer; char szLine[200]; int cchLine; lscp_connect_t *p; #if defined(WIN32) if (WSAStartup(MAKEWORD(1, 1), &_wsaData) != 0) { fprintf(stderr, "lscp_server: WSAStartup failed.\n"); return -1; } #endif srand(time(NULL)); pServer = lscp_server_create(SERVER_PORT, server_callback, NULL); if (pServer == NULL) return -1; server_usage(); server_prompt(); while (fgets(szLine, sizeof(szLine), stdin)) { cchLine = strlen(szLine); while (cchLine > 0 && (szLine[cchLine - 1] == '\n' || szLine[cchLine - 1] == '\r')) cchLine--; szLine[cchLine] = '\0'; if (strcmp(szLine, "exit") == 0 || strcmp(szLine, "quit") == 0) break; else if (strcmp(szLine, "list") == 0) { for (p = pServer->connects.first; p; p = p->next) { printf("client: sock=%d addr=%s port=%d events=0x%04x.\n", p->client.sock, inet_ntoa(p->client.addr.sin_addr), ntohs(p->client.addr.sin_port), (int) p->events ); } } else if (cchLine > 0 && strcmp(szLine, "help") != 0) lscp_server_broadcast(pServer, LSCP_EVENT_MISCELLANEOUS, szLine, strlen(szLine)); else server_usage(); server_prompt(); } lscp_server_destroy(pServer); #if defined(WIN32) WSACleanup(); #endif return 0; } // end of example_server.c liblscp-1.0.1/examples/parser.c0000644000175000017500000000647514771257264015562 0ustar drag0ndrag0n// parser.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "parser.h" // Case unsensitive comparison substitutes. #if defined(WIN32) #define strcasecmp stricmp #define strncasecmp strnicmp #endif //------------------------------------------------------------------------- // Simple token parser. const char *lscp_parser_strtok ( char *pchBuffer, const char *pszDelim, char **ppch ) { const char *pszToken; if (pchBuffer == NULL) pchBuffer = *ppch; pchBuffer += strspn(pchBuffer, pszDelim); if (*pchBuffer == '\0') return NULL; pszToken = pchBuffer; pchBuffer = strpbrk(pszToken, pszDelim); if (pchBuffer == NULL) { *ppch = strchr(pszToken, '\0'); } else { *pchBuffer = '\0'; *ppch = pchBuffer + 1; while (strchr(pszDelim, **ppch)) (*ppch)++; } return pszToken; } void lscp_parser_init ( lscp_parser_t *pParser, const char *pchBuffer, int cchBuffer ) { memset(pParser, 0, sizeof(lscp_parser_t)); pParser->pchBuffer = (char *) malloc(cchBuffer + 1); if (pParser->pchBuffer) { memcpy(pParser->pchBuffer, pchBuffer, cchBuffer); pParser->pchBuffer[cchBuffer] = (char) 0; pParser->pszToken = lscp_parser_strtok(pParser->pchBuffer, " \t\r\n", &(pParser->pch)); } } const char *lscp_parser_next ( lscp_parser_t *pParser ) { const char *pszToken = pParser->pszToken; if (pParser->pszToken) pParser->pszToken = lscp_parser_strtok(NULL, " \t\r\n", &(pParser->pch)); return pszToken; } int lscp_parser_nextint ( lscp_parser_t *pParser ) { int ret = 0; if (pParser->pszToken) { ret = atoi(pParser->pszToken); lscp_parser_next(pParser); } return ret; } float lscp_parser_nextnum ( lscp_parser_t *pParser ) { float ret = 0.0f; if (pParser->pszToken) { ret = (float) atof(pParser->pszToken); lscp_parser_next(pParser); } return ret; } int lscp_parser_test ( lscp_parser_t *pParser, const char *pszToken ) { int ret = (pParser->pszToken != NULL); if (ret) ret = (strcasecmp(pParser->pszToken, pszToken) == 0); if (ret) lscp_parser_next(pParser); return ret; } int lscp_parser_test2 ( lscp_parser_t *pParser, const char *pszToken, const char *pszToken2 ) { int ret = lscp_parser_test(pParser, pszToken); if (ret) ret = lscp_parser_test(pParser, pszToken2); return ret; } void lscp_parser_free ( lscp_parser_t *pParser ) { if (pParser->pchBuffer) free(pParser->pchBuffer); pParser->pchBuffer = NULL; } // end of parser.c liblscp-1.0.1/examples/example_client.c0000644000175000017500000006123414771257264017251 0ustar drag0ndrag0n// example_client.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "lscp/client.h" #include "lscp/device.h" #include #define SERVER_PORT 8888 #if defined(WIN32) static WSADATA _wsaData; #endif //////////////////////////////////////////////////////////////////////// lscp_status_t client_callback ( lscp_client_t *pClient, lscp_event_t event, const char *pchData, int cchData, void *pvData ) { lscp_status_t ret = LSCP_FAILED; char *pszData = (char *) malloc(cchData + 1); if (pszData) { memcpy(pszData, pchData, cchData); pszData[cchData] = (char) 0; printf("client_callback: event=%s (0x%04x) [%s]\n", lscp_event_to_text(event), (int) event, pszData); free(pszData); ret = LSCP_OK; } return ret; } //////////////////////////////////////////////////////////////////////// int client_test_int ( int i ) { printf("%d\n", i); return (i >= 0 ? 0 : 1); } int client_test_status ( lscp_status_t s ) { const char *pszStatus; switch (s) { case LSCP_OK: pszStatus = "OK"; break; case LSCP_FAILED: pszStatus = "FAILED"; break; case LSCP_ERROR: pszStatus = "ERROR"; break; case LSCP_WARNING: pszStatus = "WARNING"; break; case LSCP_TIMEOUT: pszStatus = "TIMEOUT"; break; case LSCP_QUIT: pszStatus = "QUIT"; break; default: pszStatus = "NONE"; break; } printf("%s\n", pszStatus); return (s == LSCP_OK ? 0 : 1); } int client_test_isplit ( int *piSplit ) { int i; printf("{"); for (i = 0; piSplit && piSplit[i] >= 0; i++) { if (i > 0) printf(","); printf(" %d", piSplit[i]); } printf(" }\n"); return 0; } int client_test_szsplit ( char **ppszSplit ) { int i; printf("{"); for (i = 0; ppszSplit && ppszSplit[i]; i++) { if (i > 0) printf(","); printf(" %s", ppszSplit[i]); } printf(" }\n"); return 0; } int client_test_params ( lscp_param_t *pParams ) { int i; printf("{"); for (i = 0; pParams && pParams[i].key; i++) { if (i > 0) printf(","); printf(" %s='%s'", pParams[i].key, pParams[i].value); } printf(" }\n"); return 0; } int client_test_midi_instruments ( lscp_midi_instrument_t *pInstrs ) { int i; printf("{"); for (i = 0; pInstrs && pInstrs[i].prog >= 0; i++) { if (i > 0) printf(","); printf("{%d,%d,%d}", pInstrs[i].map, pInstrs[i].bank, pInstrs[i].prog); } printf(" }\n"); return 0; } int client_test_param_info ( lscp_param_info_t *pParamInfo ) { const char *pszType; if (pParamInfo == NULL) { printf("(nil)\n"); return 1; } switch (pParamInfo->type) { case LSCP_TYPE_BOOL: pszType = "BOOL"; break; case LSCP_TYPE_INT: pszType = "INT"; break; case LSCP_TYPE_FLOAT: pszType = "FLOAT"; break; case LSCP_TYPE_STRING: pszType = "STRING"; break; default: pszType = "NONE"; break; } printf("{\n"); printf(" param_info.type = %d (%s)\n", (int) pParamInfo->type, pszType); printf(" param_info.description = %s\n", pParamInfo->description); printf(" param_info.mandatory = %d\n", pParamInfo->mandatory); printf(" param_info.fix = %d\n", pParamInfo->fix); printf(" param_info.multiplicity = %d\n", pParamInfo->multiplicity); printf(" param_info.depends = "); client_test_szsplit(pParamInfo->depends); printf(" param_info.defaultv = %s\n", pParamInfo->defaultv); printf(" param_info.range_min = %s\n", pParamInfo->range_min); printf(" param_info.range_max = %s\n", pParamInfo->range_max); printf(" param_info.possibilities = "); client_test_szsplit(pParamInfo->possibilities); printf(" }\n"); return 0; } int client_test_driver_info ( lscp_driver_info_t *pDriverInfo ) { if (pDriverInfo == NULL) { printf("(nil)\n"); return 1; } printf("{\n"); printf(" driver_info.description = %s\n", pDriverInfo->description); printf(" driver_info.version = %s\n", pDriverInfo->version); printf(" driver_info.parameters = "); client_test_szsplit(pDriverInfo->parameters); printf(" }\n"); return 0; } int client_test_device_info ( lscp_device_info_t *pDeviceInfo ) { if (pDeviceInfo == NULL) { printf("(nil)\n"); return 1; } printf("{\n"); printf(" device_info.driver = %s\n", pDeviceInfo->driver); printf(" device_info.params = "); client_test_params(pDeviceInfo->params); printf(" }\n"); return 0; } int client_test_device_port_info ( lscp_device_port_info_t *pDevicePortInfo ) { if (pDevicePortInfo == NULL) { printf("(nil)\n"); return 1; } printf("{\n"); printf(" device_port_info.name = %s\n", pDevicePortInfo->name); printf(" device_port_info.params = "); client_test_params(pDevicePortInfo->params); printf(" }\n"); return 0; } int client_test_server_info ( lscp_server_info_t *pServerInfo ) { if (pServerInfo == NULL) { printf("(nil)\n"); return 1; } printf("{\n"); printf(" server_info.description = %s\n", pServerInfo->description); printf(" server_info.version = %s\n", pServerInfo->version); printf(" server_info.protocol_version = %s\n", pServerInfo->protocol_version); printf(" }\n"); return 0; } int client_test_engine_info ( lscp_engine_info_t *pEngineInfo ) { if (pEngineInfo == NULL) { printf("(nil)\n"); return 1; } printf("{\n"); printf(" engine_info.description = %s\n", pEngineInfo->description); printf(" engine_info.version = %s\n", pEngineInfo->version); printf(" }\n"); return 0; } int client_test_channel_info ( lscp_channel_info_t *pChannelInfo ) { if (pChannelInfo == NULL) { printf("(nil)\n"); return 1; } printf("{\n"); printf(" channel_info.engine_name = %s\n", pChannelInfo->engine_name); printf(" channel_info.audio_device = %d\n", pChannelInfo->audio_device); printf(" channel_info.audio_channels = %d\n", pChannelInfo->audio_channels); printf(" channel_info.audio_routing = "); client_test_isplit(pChannelInfo->audio_routing); printf(" channel_info.instrument_file = %s\n", pChannelInfo->instrument_file); printf(" channel_info.instrument_nr = %d\n", pChannelInfo->instrument_nr); printf(" channel_info.instrument_name = %s\n", pChannelInfo->instrument_name); printf(" channel_info.instrument_status = %d\n", pChannelInfo->instrument_status); printf(" channel_info.midi_device = %d\n", pChannelInfo->midi_device); printf(" channel_info.midi_port = %d\n", pChannelInfo->midi_port); printf(" channel_info.midi_channel = %d\n", pChannelInfo->midi_channel); printf(" channel_info.midi_map = %d\n", pChannelInfo->midi_map); printf(" channel_info.volume = %g\n", pChannelInfo->volume); printf(" channel_info.mute = %d\n", pChannelInfo->mute); printf(" channel_info.solo = %d\n", pChannelInfo->solo); printf(" }\n"); return 0; } int client_test_fxsend_info ( lscp_fxsend_info_t *pFxSendInfo ) { if (pFxSendInfo == NULL) { printf("(nil)\n"); return 1; } printf("{\n"); printf(" fxsend_info.engine_name = %s\n", pFxSendInfo->name); printf(" fxsend_info.midi_controller = %d\n", pFxSendInfo->midi_controller); printf(" fxsend_info.audio_routing = "); client_test_isplit(pFxSendInfo->audio_routing); printf(" fxsend_info.level = %g\n", pFxSendInfo->level); printf(" }\n"); return 0; } int client_test_buffer_fill ( lscp_buffer_fill_t *pBufferFill ) { if (pBufferFill == NULL) { printf("(nil)\n"); return 1; } printf("{ <%p> }\n", pBufferFill); return 0; } int client_test_load_mode ( lscp_load_mode_t load_mode ) { const char *pszLoadMode; switch (load_mode) { case LSCP_LOAD_ON_DEMAND: pszLoadMode = "ON_DEMAND"; break; case LSCP_LOAD_ON_DEMAND_HOLD: pszLoadMode = "ON_DEMAND_HOLD"; break; case LSCP_LOAD_PERSISTENT: pszLoadMode = "PERSISTENT"; break; default: pszLoadMode = "DEFAULT"; break; } printf("%s\n", pszLoadMode); return 0; } int client_test_midi_instrument_info ( lscp_midi_instrument_info_t *pInstrInfo ) { if (pInstrInfo == NULL) { printf("(nil)\n"); return 1; } printf("{\n"); printf(" midi_instrument_info.name = %s\n", pInstrInfo->name); printf(" midi_instrument_info.engine_name = %s\n", pInstrInfo->engine_name); printf(" midi_instrument_info.instrument_file = %s\n", pInstrInfo->instrument_file); printf(" midi_instrument_info.instrument_nr = %d\n", pInstrInfo->instrument_nr); printf(" midi_instrument_info.instrument_name = %s\n", pInstrInfo->instrument_name); printf(" midi_instrument_info.load_mode = "); client_test_load_mode(pInstrInfo->load_mode); printf(" midi_instrument_info.volume = %g\n", pInstrInfo->volume); printf(" }\n"); return 0; } //////////////////////////////////////////////////////////////////////// static int g_test_step = 0; static int g_test_count = 0; static int g_test_fails = 0; void client_test_start ( clock_t *pclk ) { *pclk = clock(); } float client_test_elapsed ( clock_t *pclk ) { return (float) ((long) clock() - *pclk) / (float) CLOCKS_PER_SEC; } typedef int * isplit; typedef char ** szsplit; typedef lscp_status_t status; typedef lscp_driver_info_t * driver_info; typedef lscp_device_info_t * device_info; typedef lscp_device_port_info_t * device_port_info; typedef lscp_param_info_t * param_info; typedef lscp_server_info_t * server_info; typedef lscp_engine_info_t * engine_info; typedef lscp_channel_info_t * channel_info; typedef lscp_buffer_fill_t * buffer_fill; typedef lscp_fxsend_info_t * fxsend_info; typedef lscp_midi_instrument_t * midi_instruments; typedef lscp_midi_instrument_info_t *midi_instrument_info; #define CLIENT_TEST(p, t, x) { clock_t c; g_test_count++; \ printf("\n" #x ":\n"); client_test_start(&c); \ printf(" elapsed=%gs errno=%d result='%s...' ret=", \ client_test_elapsed(&c), \ lscp_client_get_errno(p), \ lscp_client_get_result(p)); \ if (client_test_##t((t)(x))) { g_test_fails++; getchar(); } \ else if (g_test_step) getchar(); } void client_test_engine ( lscp_client_t *pClient, const char *pszEngine, const char *pszAudioDriver, int iAudioDevice, const char *pszMidiDriver, int iMidiDevice ) { int iSamplerChannel; int iFxSend; printf("\n--- pszEngine=\"%s\" pszAudioDevice=\"%s\" iAudioDevice=%d pszMidiDevice=\"%s\" iMidiDevice=%d ---\n", pszEngine, pszAudioDriver, iAudioDevice, pszMidiDriver, iMidiDevice); CLIENT_TEST(pClient, engine_info, lscp_get_engine_info(pClient, pszEngine)); CLIENT_TEST(pClient, int, lscp_get_total_voice_count(pClient)); CLIENT_TEST(pClient, int, lscp_get_total_voice_count_max(pClient)); CLIENT_TEST(pClient, int, lscp_get_channels(pClient)); CLIENT_TEST(pClient, isplit, lscp_list_channels(pClient)); CLIENT_TEST(pClient, int, iSamplerChannel = lscp_add_channel(pClient)); printf("\n--- iSamplerChannel=%d ---\n", iSamplerChannel); CLIENT_TEST(pClient, channel_info, lscp_get_channel_info(pClient, iSamplerChannel)); CLIENT_TEST(pClient, status, lscp_load_engine(pClient, pszEngine, iSamplerChannel)); CLIENT_TEST(pClient, status, lscp_load_instrument(pClient, "DefaultInstrument.gig", 0, iSamplerChannel)); CLIENT_TEST(pClient, int, lscp_get_channel_voice_count(pClient, iSamplerChannel)); CLIENT_TEST(pClient, int, lscp_get_channel_stream_count(pClient, iSamplerChannel)); CLIENT_TEST(pClient, int, lscp_get_channel_stream_usage(pClient, iSamplerChannel)); CLIENT_TEST(pClient, buffer_fill, lscp_get_channel_buffer_fill(pClient, LSCP_USAGE_BYTES, iSamplerChannel)); CLIENT_TEST(pClient, buffer_fill, lscp_get_channel_buffer_fill(pClient, LSCP_USAGE_PERCENTAGE, iSamplerChannel)); CLIENT_TEST(pClient, status, lscp_set_channel_audio_type(pClient, iSamplerChannel, pszAudioDriver)); CLIENT_TEST(pClient, status, lscp_set_channel_audio_device(pClient, iSamplerChannel, 0)); CLIENT_TEST(pClient, status, lscp_set_channel_audio_channel(pClient, iSamplerChannel, 0, 1)); CLIENT_TEST(pClient, status, lscp_set_channel_midi_type(pClient, iSamplerChannel, pszMidiDriver)); CLIENT_TEST(pClient, status, lscp_set_channel_midi_device(pClient, iSamplerChannel, 0)); CLIENT_TEST(pClient, status, lscp_set_channel_midi_channel(pClient, iSamplerChannel, 0)); CLIENT_TEST(pClient, status, lscp_set_channel_midi_port(pClient, iSamplerChannel, 0)); CLIENT_TEST(pClient, status, lscp_set_channel_volume(pClient, iSamplerChannel, 0.5)); CLIENT_TEST(pClient, status, lscp_set_channel_mute(pClient, iSamplerChannel, 1)); CLIENT_TEST(pClient, status, lscp_set_channel_solo(pClient, iSamplerChannel, 1)); CLIENT_TEST(pClient, int, iFxSend = lscp_create_fxsend(pClient, iSamplerChannel, 90, "DummyFxSend")); CLIENT_TEST(pClient, int, lscp_get_fxsends(pClient, iSamplerChannel)); CLIENT_TEST(pClient, isplit, lscp_list_fxsends(pClient, iSamplerChannel)); CLIENT_TEST(pClient, fxsend_info, lscp_get_fxsend_info(pClient, iSamplerChannel, iFxSend)); CLIENT_TEST(pClient, status, lscp_set_fxsend_midi_controller(pClient, iSamplerChannel, iFxSend, 99)); CLIENT_TEST(pClient, status, lscp_set_fxsend_audio_channel(pClient, iSamplerChannel, iFxSend, 0, 1)); CLIENT_TEST(pClient, status, lscp_set_fxsend_level(pClient, iSamplerChannel, iFxSend, 0.12f)); CLIENT_TEST(pClient, status, lscp_destroy_fxsend(pClient, iSamplerChannel, iFxSend)); CLIENT_TEST(pClient, channel_info, lscp_get_channel_info(pClient, iSamplerChannel)); CLIENT_TEST(pClient, status, lscp_reset_channel(pClient, iSamplerChannel)); CLIENT_TEST(pClient, status, lscp_remove_channel(pClient, iSamplerChannel)); } void client_test_midi_port ( lscp_client_t *pClient, int iMidiDevice, int iMidiPort ) { lscp_device_port_info_t *pMidiPortInfo; const char *pszParam; int i; printf("\n--- iMidiDevice=%d iMidiPort=%d ---\n", iMidiDevice, iMidiPort); CLIENT_TEST(pClient, device_port_info, pMidiPortInfo = lscp_get_midi_port_info(pClient, iMidiDevice, iMidiPort)); if (pMidiPortInfo && pMidiPortInfo->params) { for (i = 0; pMidiPortInfo->params[i].key; i++) { pszParam = pMidiPortInfo->params[i].key; printf("\n--- iMidiDevice=%d iMidiPort=%d pszParam=\"%s\" ---\n", iMidiDevice, iMidiPort, pszParam); CLIENT_TEST(pClient, param_info, lscp_get_midi_port_param_info(pClient, iMidiDevice, iMidiPort, pszParam)); } } } void client_test_audio_channel ( lscp_client_t *pClient, int iAudioDevice, int iAudioChannel ) { lscp_device_port_info_t *pAudioChannelInfo; const char *pszParam; int i; printf("\n--- iAudioDevice=%d iAudioChannel=%d ---\n", iAudioDevice, iAudioChannel); CLIENT_TEST(pClient, device_port_info, pAudioChannelInfo = lscp_get_audio_channel_info(pClient, iAudioDevice, iAudioChannel)); if (pAudioChannelInfo && pAudioChannelInfo->params) { for (i = 0; pAudioChannelInfo->params[i].key; i++) { pszParam = pAudioChannelInfo->params[i].key; printf("\n--- iAudioDevice=%d iAudioChannel=%d pszParam=\"%s\" ---\n", iAudioDevice, iAudioChannel, pszParam); CLIENT_TEST(pClient, param_info, lscp_get_audio_channel_param_info(pClient, iAudioDevice, iAudioChannel, pszParam)); } } } void client_test_midi_device ( lscp_client_t *pClient, int iMidiDevice ) { lscp_device_info_t *pDeviceInfo; const char *pszValue; int iMidiPort, iMidiPorts; printf("\n--- iMidiDevice=%d ---\n", iMidiDevice); CLIENT_TEST(pClient, device_info, pDeviceInfo = lscp_get_midi_device_info(pClient, iMidiDevice)); if (pDeviceInfo && pDeviceInfo->params) { pszValue = lscp_get_param_value(pDeviceInfo->params, "ports"); if (pszValue) { iMidiPorts = atoi(pszValue); for (iMidiPort = 0; iMidiPort < iMidiPorts; iMidiPort++) client_test_midi_port(pClient, iMidiDevice, iMidiPort); } } } void client_test_audio_device ( lscp_client_t *pClient, int iAudioDevice ) { lscp_device_info_t *pDeviceInfo; const char *pszValue; int iAudioChannel, iAudioChannels; printf("\n--- iAudioDevice=%d ---\n", iAudioDevice); CLIENT_TEST(pClient, device_info, pDeviceInfo = lscp_get_audio_device_info(pClient, iAudioDevice)); if (pDeviceInfo && pDeviceInfo->params) { pszValue = lscp_get_param_value(pDeviceInfo->params, "channels"); if (pszValue) { iAudioChannels = atoi(pszValue); for (iAudioChannel = 0; iAudioChannel < iAudioChannels; iAudioChannel++) client_test_audio_channel(pClient, iAudioDevice, iAudioChannel); } } } void client_test_midi_driver ( lscp_client_t *pClient, const char *pszMidiDriver ) { lscp_driver_info_t *pDriverInfo; const char *pszParam; int i; printf("\n--- pszMidiDriver=\"%s\" ---\n", pszMidiDriver); CLIENT_TEST(pClient, driver_info, pDriverInfo = lscp_get_midi_driver_info(pClient, pszMidiDriver)); if (pDriverInfo && pDriverInfo->parameters) { for (i = 0; pDriverInfo->parameters[i]; i++) { pszParam = pDriverInfo->parameters[i]; printf("\n--- pszMidiDriver=\"%s\" pszParam=\"%s\" ---\n", pszMidiDriver, pszParam); CLIENT_TEST(pClient, param_info, lscp_get_midi_driver_param_info(pClient, pszMidiDriver, pszParam, NULL)); } } } void client_test_audio_driver ( lscp_client_t *pClient, const char *pszAudioDriver ) { lscp_driver_info_t *pDriverInfo; const char *pszParam; int i; printf("\n--- pszAudioDriver=\"%s\" ---\n", pszAudioDriver); CLIENT_TEST(pClient, driver_info, pDriverInfo = lscp_get_audio_driver_info(pClient, pszAudioDriver)); if (pDriverInfo && pDriverInfo->parameters) { for (i = 0; pDriverInfo->parameters[i]; i++) { pszParam = pDriverInfo->parameters[i]; printf("\n--- pszAudioDriver=\"%s\" pszParam=\"%s\" ---\n", pszAudioDriver, pszParam); CLIENT_TEST(pClient, param_info, lscp_get_audio_driver_param_info(pClient, pszAudioDriver, pszParam, NULL)); } } } void client_test_all ( lscp_client_t *pClient, int step ) { const char **ppszAudioDrivers, **ppszMidiDrivers, **ppszEngines; const char *pszAudioDriver, *pszMidiDriver, *pszEngine = 0; int iAudioDriver, iMidiDriver, iEngine; int iAudio, iAudioDevice, iMidi, iMidiDevice; int iNewAudioDevice, iNewMidiDevice; int *piAudioDevices, *piMidiDevices; lscp_midi_instrument_t midi_instr; int i, j, k; g_test_step = step; g_test_count = 0; g_test_fails = 0; CLIENT_TEST(pClient, server_info, lscp_get_server_info(pClient)); CLIENT_TEST(pClient, int, lscp_get_available_audio_drivers(pClient)); CLIENT_TEST(pClient, szsplit, ppszAudioDrivers = lscp_list_available_audio_drivers(pClient)); if (ppszAudioDrivers == NULL) { fprintf(stderr, "client_test: No audio drivers available.\n"); return; } CLIENT_TEST(pClient, int, lscp_get_available_midi_drivers(pClient)); CLIENT_TEST(pClient, szsplit, ppszMidiDrivers = lscp_list_available_midi_drivers(pClient)); if (ppszMidiDrivers == NULL) { fprintf(stderr, "client_test: No MIDI drivers available.\n"); return; } CLIENT_TEST(pClient, int, lscp_get_available_engines(pClient)); CLIENT_TEST(pClient, szsplit, ppszEngines = lscp_list_available_engines(pClient)); if (ppszEngines == NULL) { fprintf(stderr, "client_test: No engines available.\n"); return; } for (iAudioDriver = 0; ppszAudioDrivers[iAudioDriver]; iAudioDriver++) { pszAudioDriver = ppszAudioDrivers[iAudioDriver]; client_test_audio_driver(pClient, pszAudioDriver); CLIENT_TEST(pClient, int, iNewAudioDevice = lscp_create_audio_device(pClient, pszAudioDriver, NULL)); CLIENT_TEST(pClient, int, lscp_get_audio_devices(pClient)); CLIENT_TEST(pClient, isplit, piAudioDevices = lscp_list_audio_devices(pClient)); for (iAudio = 0; piAudioDevices && piAudioDevices[iAudio] >= 0; iAudio++) { iAudioDevice = piAudioDevices[iAudio]; client_test_audio_device(pClient, iAudioDevice); for (iMidiDriver = 0; ppszMidiDrivers[iMidiDriver]; iMidiDriver++) { pszMidiDriver = ppszMidiDrivers[iMidiDriver]; client_test_midi_driver(pClient, pszMidiDriver); CLIENT_TEST(pClient, int, iNewMidiDevice = lscp_create_midi_device(pClient, pszMidiDriver, NULL)); CLIENT_TEST(pClient, int, lscp_get_midi_devices(pClient)); CLIENT_TEST(pClient, isplit, piMidiDevices = lscp_list_midi_devices(pClient)); for (iMidi = 0; piMidiDevices && piMidiDevices[iMidi] >= 0; iMidi++) { iMidiDevice = piMidiDevices[iMidi]; client_test_midi_device(pClient, iMidiDevice); for (iEngine = 0; ppszEngines[iEngine]; iEngine++) { pszEngine = ppszEngines[iEngine]; client_test_engine(pClient, pszEngine, pszAudioDriver, iAudioDevice, pszMidiDriver, iMidiDevice); } } CLIENT_TEST(pClient, status, lscp_destroy_midi_device(pClient, iNewMidiDevice)); } } CLIENT_TEST(pClient, status, lscp_destroy_audio_device(pClient, iNewAudioDevice)); } for (i = 0; i < 2; i++) { CLIENT_TEST(pClient, int, lscp_add_midi_instrument_map(pClient, NULL)); for (j = 0; j < 4; j++) { for (k = 0; k < 8; k++) { midi_instr.map = i; midi_instr.bank = j; midi_instr.prog = k; CLIENT_TEST(pClient, status, lscp_map_midi_instrument(pClient, &midi_instr, pszEngine, "DefaultInstrument.gig", 0, 1.0f, LSCP_LOAD_ON_DEMAND, "DummyName")); } } CLIENT_TEST(pClient, status, lscp_set_midi_instrument_map_name(pClient, i, "DummyMapName")); } CLIENT_TEST(pClient, int, lscp_get_midi_instruments(pClient, LSCP_MIDI_MAP_ALL)); CLIENT_TEST(pClient, midi_instruments, lscp_list_midi_instruments(pClient, LSCP_MIDI_MAP_ALL)); for (i = 0; i < 2; i++) { for (j = 0; j < 4; j++) { for (k = 0; k < 8; k++) { midi_instr.map = i; midi_instr.bank = j; midi_instr.prog = k; CLIENT_TEST(pClient, midi_instrument_info, lscp_get_midi_instrument_info(pClient, &midi_instr)); CLIENT_TEST(pClient, status, lscp_unmap_midi_instrument(pClient, &midi_instr)); } } CLIENT_TEST(pClient, int, lscp_remove_midi_instrument_map(pClient, i)); } CLIENT_TEST(pClient, status, lscp_clear_midi_instruments(pClient, LSCP_MIDI_MAP_ALL)); CLIENT_TEST(pClient, status, lscp_set_volume(pClient, 0.123f)); CLIENT_TEST(pClient, int, (int) (100.0f * lscp_get_volume(pClient))); CLIENT_TEST(pClient, status, lscp_reset_sampler(pClient)); printf("\n\n"); printf(" Total: %d tests, %d failed.\n\n", g_test_count, g_test_fails); } //////////////////////////////////////////////////////////////////////// void client_usage (void) { printf("\n %s %s (Build: %s)\n", lscp_client_package(), lscp_client_version(), lscp_client_build()); fputs("\n Available commands: help, test[step], exit, quit, subscribe, unsubscribe", stdout); fputs("\n (all else are sent verbatim to server)\n\n", stdout); } void client_prompt (void) { fputs("lscp_client> ", stdout); } int main (int argc, char *argv[] ) { lscp_client_t *pClient; char *pszHost = "localhost"; char szLine[1024]; int cchLine; lscp_status_t ret; #if defined(WIN32) if (WSAStartup(MAKEWORD(1, 1), &_wsaData) != 0) { fprintf(stderr, "lscp_client: WSAStartup failed.\n"); return -1; } #endif if (argc > 1) pszHost = argv[1]; pClient = lscp_client_create(pszHost, SERVER_PORT, client_callback, NULL); if (pClient == NULL) return -1; client_usage(); client_prompt(); while (fgets(szLine, sizeof(szLine) - 3, stdin)) { cchLine = strlen(szLine); while (cchLine > 0 && (szLine[cchLine - 1] == '\n' || szLine[cchLine - 1] == '\r')) cchLine--; szLine[cchLine] = '\0'; if (strcmp(szLine, "exit") == 0 || strcmp(szLine, "quit") == 0) break; else if (strcmp(szLine, "subscribe") == 0) lscp_client_subscribe(pClient, LSCP_EVENT_MISCELLANEOUS); else if (strcmp(szLine, "unsubscribe") == 0) lscp_client_unsubscribe(pClient, LSCP_EVENT_MISCELLANEOUS); else if (strcmp(szLine, "test") == 0) client_test_all(pClient, 0); else if (strcmp(szLine, "teststep") == 0 || strcmp(szLine, "test step") == 0) client_test_all(pClient, 1); else if (cchLine > 0 && strcmp(szLine, "help") != 0) { szLine[cchLine++] = '\r'; szLine[cchLine++] = '\n'; szLine[cchLine] = '\0'; ret = lscp_client_query(pClient, szLine); printf("%s\n(errno = %d)\n", lscp_client_get_result(pClient), lscp_client_get_errno(pClient)); if (ret == LSCP_QUIT) break; } else client_usage(); client_prompt(); } lscp_client_destroy(pClient); #if defined(WIN32) WSACleanup(); #endif return 0; } // end of example_client.c liblscp-1.0.1/src/0000755000175000017500000000000014771257264013057 5ustar drag0ndrag0nliblscp-1.0.1/src/event.c0000644000175000017500000001162014771257264014344 0ustar drag0ndrag0n// event.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "lscp/event.h" // Case unsensitive comparison substitutes. #if defined(WIN32) #define strcasecmp stricmp #define strncasecmp strnicmp #endif //------------------------------------------------------------------------- // Simple event helper functions. /** * Getting the text string representation of a single event. * * @param event Event to convert to text string. * * @returns The text string representation of the event. */ const char *lscp_event_to_text ( lscp_event_t event ) { const char *pszText = NULL; switch (event) { case LSCP_EVENT_CHANNEL_COUNT: pszText = "CHANNEL_COUNT"; break; case LSCP_EVENT_VOICE_COUNT: pszText = "VOICE_COUNT"; break; case LSCP_EVENT_STREAM_COUNT: pszText = "STREAM_COUNT"; break; case LSCP_EVENT_BUFFER_FILL: pszText = "BUFFER_FILL"; break; case LSCP_EVENT_CHANNEL_INFO: pszText = "CHANNEL_INFO"; break; case LSCP_EVENT_TOTAL_VOICE_COUNT: pszText = "TOTAL_VOICE_COUNT"; break; case LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT: pszText = "AUDIO_OUTPUT_DEVICE_COUNT"; break; case LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO: pszText = "AUDIO_OUTPUT_DEVICE_INFO"; break; case LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT: pszText = "MIDI_INPUT_DEVICE_COUNT"; break; case LSCP_EVENT_MIDI_INPUT_DEVICE_INFO: pszText = "MIDI_INPUT_DEVICE_INFO"; break; case LSCP_EVENT_MIDI_INSTRUMENT_MAP_COUNT: pszText = "MIDI_INSTRUMENT_MAP_COUNT"; break; case LSCP_EVENT_MIDI_INSTRUMENT_MAP_INFO: pszText = "MIDI_INSTRUMENT_MAP_INFO"; break; case LSCP_EVENT_MIDI_INSTRUMENT_COUNT: pszText = "MIDI_INSTRUMENT_COUNT"; break; case LSCP_EVENT_MIDI_INSTRUMENT_INFO: pszText = "MIDI_INSTRUMENT_INFO"; break; case LSCP_EVENT_MISCELLANEOUS: pszText = "MISCELLANEOUS"; break; case LSCP_EVENT_CHANNEL_MIDI: pszText = "CHANNEL_MIDI"; break; case LSCP_EVENT_DEVICE_MIDI: pszText = "DEVICE_MIDI"; break; case LSCP_EVENT_NONE: default: break; } return pszText; } /** * Getting an event from a text string. * * @param pszText Text string to convert to event. * * @returns The event correponding to the text string. */ lscp_event_t lscp_event_from_text ( const char *pszText ) { lscp_event_t event = LSCP_EVENT_NONE; if (pszText) { if (strcasecmp(pszText, "CHANNEL_COUNT") == 0) event = LSCP_EVENT_CHANNEL_COUNT; else if (strcasecmp(pszText, "VOICE_COUNT") == 0) event = LSCP_EVENT_VOICE_COUNT; else if (strcasecmp(pszText, "STREAM_COUNT") == 0) event = LSCP_EVENT_STREAM_COUNT; else if (strcasecmp(pszText, "BUFFER_FILL") == 0) event = LSCP_EVENT_BUFFER_FILL; else if (strcasecmp(pszText, "CHANNEL_INFO") == 0) event = LSCP_EVENT_CHANNEL_INFO; else if (strcasecmp(pszText, "TOTAL_VOICE_COUNT") == 0) event = LSCP_EVENT_TOTAL_VOICE_COUNT; else if (strcasecmp(pszText, "AUDIO_OUTPUT_DEVICE_COUNT") == 0) event = LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT; else if (strcasecmp(pszText, "AUDIO_OUTPUT_DEVICE_INFO") == 0) event = LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO; else if (strcasecmp(pszText, "MIDI_INPUT_DEVICE_COUNT") == 0) event = LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT; else if (strcasecmp(pszText, "MIDI_INPUT_DEVICE_INFO") == 0) event = LSCP_EVENT_MIDI_INPUT_DEVICE_INFO; else if (strcasecmp(pszText, "MIDI_INSTRUMENT_MAP_COUNT") == 0) event = LSCP_EVENT_MIDI_INSTRUMENT_MAP_COUNT; else if (strcasecmp(pszText, "MIDI_INSTRUMENT_MAP_INFO") == 0) event = LSCP_EVENT_MIDI_INSTRUMENT_MAP_INFO; else if (strcasecmp(pszText, "MIDI_INSTRUMENT_COUNT") == 0) event = LSCP_EVENT_MIDI_INSTRUMENT_COUNT; else if (strcasecmp(pszText, "MIDI_INSTRUMENT_INFO") == 0) event = LSCP_EVENT_MIDI_INSTRUMENT_INFO; else if (strcasecmp(pszText, "MISCELLANEOUS") == 0) event = LSCP_EVENT_MISCELLANEOUS; else if (strcasecmp(pszText, "CHANNEL_MIDI") == 0) event = LSCP_EVENT_CHANNEL_MIDI; else if (strcasecmp(pszText, "DEVICE_MIDI") == 0) event = LSCP_EVENT_DEVICE_MIDI; } return event; } // end of event.c liblscp-1.0.1/src/thread.c0000644000175000017500000001052014771257264014470 0ustar drag0ndrag0n// thread.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "lscp/thread.h" //------------------------------------------------------------------------- // Threads. struct _lscp_thread_t { #if defined(WIN32) HANDLE hThread; DWORD dwThreadID; #else pthread_t pthread; #endif lscp_thread_proc_t pfnProc; void *pvData; int iDetach; }; #if defined(WIN32) static DWORD WINAPI _lscp_thread_start ( LPVOID pvThread ) #else static void *_lscp_thread_start ( void *pvThread ) #endif { lscp_thread_t *pThread = (lscp_thread_t *) pvThread; if (pThread) { // fprintf(stderr, "_lscp_thread_start: pThread=%p started.\n", pThread); pThread->pfnProc(pThread->pvData); // fprintf(stderr, "_lscp_thread_start: pThread=%p terminated.\n", pThread); if (pThread->iDetach) free(pThread); } #if defined(WIN32) return 0; #else return NULL; #endif } lscp_thread_t *lscp_thread_create ( lscp_thread_proc_t pfnProc, void *pvData, int iDetach ) { lscp_thread_t *pThread; #if !defined(WIN32) pthread_attr_t attr; #endif if (pfnProc == NULL) { fprintf(stderr, "lcsp_thread_create: Invalid thread function.\n"); return NULL; } pThread = (lscp_thread_t *) malloc(sizeof(lscp_thread_t)); if (pThread == NULL) { fprintf(stderr, "lcsp_thread_create: Out of memory.\n"); return NULL; } memset(pThread, 0, sizeof(lscp_thread_t)); pThread->pvData = pvData; pThread->pfnProc = pfnProc; pThread->iDetach = iDetach; // fprintf(stderr, "lscp_thread_create: pThread=%p.\n", pThread); #if defined(WIN32) pThread->hThread = CreateThread(NULL, 0, _lscp_thread_start, (LPVOID) pThread, 0, &(pThread->dwThreadID)); if (pThread->hThread == NULL) { fprintf(stderr, "lcsp_thread_create: Failed to create thread.\n"); free(pThread); return NULL; } #else pthread_attr_init(&attr); if (iDetach) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (pthread_create(&pThread->pthread, &attr, _lscp_thread_start, pThread)) { fprintf(stderr, "lcsp_thread_create: Failed to create thread.\n"); free(pThread); return NULL; } #endif return pThread; } lscp_status_t lscp_thread_join( lscp_thread_t *pThread ) { lscp_status_t ret = LSCP_FAILED; if (pThread == NULL) return ret; // fprintf(stderr, "lscp_thread_join: pThread=%p.\n", pThread); #if defined(WIN32) if (pThread->hThread && WaitForSingleObject(pThread->hThread, INFINITE) == WAIT_OBJECT_0) { pThread->hThread = NULL; ret = LSCP_OK; } #else if (pThread->pthread && pthread_join(pThread->pthread, NULL) == 0) { pThread->pthread = 0; ret = LSCP_OK; } #endif return ret; } lscp_status_t lscp_thread_cancel ( lscp_thread_t *pThread ) { lscp_status_t ret = LSCP_FAILED; if (pThread == NULL) return ret; // fprintf(stderr, "lscp_thread_cancel: pThread=%p.\n", pThread); #if defined(WIN32) if (pThread->hThread) { // Should we TerminateThread(pThread->hThread, 0) ? /* pThread->hThread = NULL; */ ret = LSCP_OK; } #else if (pThread->pthread && pthread_cancel(pThread->pthread) == 0) { pThread->pthread = 0; ret = LSCP_OK; } #endif return ret; } lscp_status_t lscp_thread_destroy ( lscp_thread_t *pThread ) { lscp_status_t ret = lscp_thread_cancel(pThread); if (ret == LSCP_OK) ret = lscp_thread_join(pThread); // fprintf(stderr, "lscp_thread_destroy: pThread=%p.\n", pThread); if (ret == LSCP_OK) free(pThread); return ret; } // end of thread.c liblscp-1.0.1/src/CMakeLists.txt0000644000175000017500000000325214771257264015621 0ustar drag0ndrag0n# project(liblscp) set (CMAKE_INCLUDE_CURRENT_DIR ON) include_directories (${CMAKE_SOURCE_DIR}) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/config.h) file (REMOVE ${CMAKE_CURRENT_SOURCE_DIR}/config.h) endif () configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) if (EXISTS ${CMAKE_SOURCE_DIR}/lscp/version.h) file(REMOVE ${CMAKE_SOURCE_DIR}/lscp/version.h) endif() configure_file (${CMAKE_SOURCE_DIR}/lscp/version.h.cmake ${CMAKE_SOURCE_DIR}/lscp/version.h) if (EXISTS ${CMAKE_SOURCE_DIR}/lscp.pc) file(REMOVE ${CMAKE_SOURCE_DIR}/lscp.pc) endif() configure_file (lscp.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/lscp.pc IMMEDIATE @ONLY) set (PUBLIC_HEADERS ${CMAKE_SOURCE_DIR}/lscp/client.h ${CMAKE_SOURCE_DIR}/lscp/device.h ${CMAKE_SOURCE_DIR}/lscp/event.h ${CMAKE_SOURCE_DIR}/lscp/socket.h ${CMAKE_SOURCE_DIR}/lscp/thread.h ${CMAKE_SOURCE_DIR}/lscp/version.h ) set (HEADERS common.h ) set (SOURCES client.c common.c device.c event.c socket.c thread.c ) add_library (${PROJECT_NAME} SHARED ${HEADERS} ${SOURCES} ) target_link_libraries (${PROJECT_NAME} PUBLIC pthread) if (MICROSOFT) target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32.lib) elseif (MINGW) target_link_libraries(${PROJECT_NAME} PRIVATE wsock32 ws2_32) endif() set_target_properties (${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}" PREFIX "lib" OUTPUT_NAME "lscp" VERSION ${SHARED_VERSION_INFO} SOVERSION ${SHARED_VERSION_CURRENT}) install (TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CONFIG_LIBDIR} PUBLIC_HEADER DESTINATION ${CONFIG_INCLUDEDIR}/lscp) install (FILES ${CMAKE_CURRENT_BINARY_DIR}/lscp.pc DESTINATION ${CONFIG_LIBDIR}/pkgconfig) liblscp-1.0.1/src/config.h.cmake0000644000175000017500000000124114771257264015552 0ustar drag0ndrag0n#ifndef CONFIG_H #define CONFIG_H /* Define to the name of this package. */ #cmakedefine PROJECT_NAME "@PROJECT_NAME@" /* Define to the version of this package. */ #cmakedefine PROJECT_VERSION "@PROJECT_VERSION@" /* Define to the description of this package. */ #cmakedefine PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@" /* Define to the homepage of this package. */ #cmakedefine PROJECT_HOMEPAGE_URL "@PROJECT_HOMEPAGE_URL@" /* Default installation prefix. */ #cmakedefine CONFIG_PREFIX "@CONFIG_PREFIX@" /* Define to target installation dirs. */ #cmakedefine CONFIG_LIBDIR "@CONFIG_LIBDIR@" #cmakedefine CONFIG_INCLUDEDIR "@CONFIG_INCLUDEDIR@" #endif /* CONFIG_H */ liblscp-1.0.1/src/device.c0000644000175000017500000007552614771257264014501 0ustar drag0ndrag0n// device.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "common.h" // Local prototypes. static lscp_driver_info_t *_lscp_driver_info_query (lscp_client_t *pClient, lscp_driver_info_t *pDriverInfo, char *pszQuery); static lscp_device_info_t *_lscp_device_info_query (lscp_client_t *pClient, lscp_device_info_t *pDeviceInfo, char *pszQuery); static lscp_param_info_t *_lscp_param_info_query (lscp_client_t *pClient, lscp_param_info_t *pParamInfo, char *pszQuery, int cchMaxQuery, lscp_param_t *pDepList); static lscp_device_port_info_t *_lscp_device_port_info_query (lscp_client_t *pClient, lscp_device_port_info_t *pDevicePortInfo, char *pszQuery); //------------------------------------------------------------------------- // Local funtions. // Common driver type query command. static lscp_driver_info_t *_lscp_driver_info_query ( lscp_client_t *pClient, lscp_driver_info_t *pDriverInfo, char *pszQuery ) { const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; // Lock this section up. lscp_mutex_lock(pClient->mutex); lscp_driver_info_reset(pDriverInfo); if (lscp_client_call(pClient, pszQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "DESCRIPTION") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pDriverInfo->description), &pszToken); } else if (strcasecmp(pszToken, "VERSION") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pDriverInfo->version), &pszToken); } else if (strcasecmp(pszToken, "PARAMETERS") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { if (pDriverInfo->parameters) lscp_szsplit_destroy(pDriverInfo->parameters); pDriverInfo->parameters = lscp_szsplit_create(pszToken, ","); } } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pDriverInfo = NULL; // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pDriverInfo; } // Common device info query command. static lscp_device_info_t *_lscp_device_info_query ( lscp_client_t *pClient, lscp_device_info_t *pDeviceInfo, char *pszQuery ) { const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; char *pszKey; // Lock this section up. lscp_mutex_lock(pClient->mutex); lscp_device_info_reset(pDeviceInfo); if (lscp_client_call(pClient, pszQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "DRIVER") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pDeviceInfo->driver), &pszToken); } else { pszKey = pszToken; pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_plist_append(&(pDeviceInfo->params), pszKey, lscp_unquote(&pszToken, 0)); } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pDeviceInfo = NULL; // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pDeviceInfo; } // Common device channel/port info query command. static lscp_device_port_info_t *_lscp_device_port_info_query ( lscp_client_t *pClient, lscp_device_port_info_t *pDevicePortInfo, char *pszQuery ) { const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; char *pszKey; char *pszVal; // Lock this section up. lscp_mutex_lock(pClient->mutex); lscp_device_port_info_reset(pDevicePortInfo); if (lscp_client_call(pClient, pszQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { pszKey = pszToken; pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszKey && pszToken) { pszVal = lscp_unquote(&pszToken, 0); lscp_plist_append(&(pDevicePortInfo->params), pszKey, pszVal); if (strcasecmp(pszKey, "NAME") == 0) { // Free desteny string, if already there. if (pDevicePortInfo->name) free(pDevicePortInfo->name); pDevicePortInfo->name = NULL; if (pszVal) pDevicePortInfo->name = strdup(pszVal); } } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pDevicePortInfo = NULL; // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pDevicePortInfo; } // Common parameter info query command. static lscp_param_info_t *_lscp_param_info_query ( lscp_client_t *pClient, lscp_param_info_t *pParamInfo, char *pszQuery, int cchMaxQuery, lscp_param_t *pDepList ) { const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; // Lock this section up. lscp_mutex_lock(pClient->mutex); lscp_param_info_reset(pParamInfo); lscp_param_concat(pszQuery, cchMaxQuery, pDepList); if (lscp_client_call(pClient, pszQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "TYPE") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { pszToken = lscp_unquote(&pszToken, 0); if (strcasecmp(pszToken, "BOOL") == 0) pParamInfo->type = LSCP_TYPE_BOOL; else if (strcasecmp(pszToken, "INT") == 0) pParamInfo->type = LSCP_TYPE_INT; else if (strcasecmp(pszToken, "FLOAT") == 0) pParamInfo->type = LSCP_TYPE_FLOAT; else if (strcasecmp(pszToken, "STRING") == 0) pParamInfo->type = LSCP_TYPE_STRING; } } else if (strcasecmp(pszToken, "DESCRIPTION") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pParamInfo->description), &pszToken); } else if (strcasecmp(pszToken, "MANDATORY") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pParamInfo->mandatory = (strcasecmp(lscp_unquote(&pszToken, 0), "TRUE") == 0); } else if (strcasecmp(pszToken, "FIX") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pParamInfo->fix = (strcasecmp(lscp_unquote(&pszToken, 0), "TRUE") == 0); } else if (strcasecmp(pszToken, "MULTIPLICITY") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pParamInfo->multiplicity = (strcasecmp(lscp_unquote(&pszToken, 0), "TRUE") == 0); } else if (strcasecmp(pszToken, "DEPENDS") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { if (pParamInfo->depends) lscp_szsplit_destroy(pParamInfo->depends); pParamInfo->depends = lscp_szsplit_create(pszToken, ","); } } else if (strcasecmp(pszToken, "DEFAULT") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pParamInfo->defaultv), &pszToken); } else if (strcasecmp(pszToken, "RANGE_MIN") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pParamInfo->range_min), &pszToken); } else if (strcasecmp(pszToken, "RANGE_MAX") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pParamInfo->range_max), &pszToken); } else if (strcasecmp(pszToken, "POSSIBILITIES") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { if (pParamInfo->possibilities) lscp_szsplit_destroy(pParamInfo->possibilities); pParamInfo->possibilities = lscp_szsplit_create(pszToken, ","); } } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pParamInfo = NULL; // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pParamInfo; } //------------------------------------------------------------------------- // Audio driver control functions. /** * Getting all available audio output driver count. * GET AVAILABLE_AUDIO_OUTPUT_DRIVERS * * @param pClient Pointer to client instance structure. * * @returns The current total number of audio output drivers on success, * -1 otherwise. */ int lscp_get_available_audio_drivers ( lscp_client_t *pClient ) { int iAudioDrivers = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET AVAILABLE_AUDIO_OUTPUT_DRIVERS\r\n", 0) == LSCP_OK) iAudioDrivers = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iAudioDrivers; } /** * Getting all available audio output drivers. * LIST AVAILABLE_AUDIO_OUTPUT_DRIVERS * * @param pClient Pointer to client instance structure. * * @returns A NULL terminated array of audio output driver type * name strings, or NULL in case of failure. */ const char ** lscp_list_available_audio_drivers ( lscp_client_t *pClient ) { const char *pszSeps = ","; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->audio_drivers) { lscp_szsplit_destroy(pClient->audio_drivers); pClient->audio_drivers = NULL; } if (lscp_client_call(pClient, "LIST AVAILABLE_AUDIO_OUTPUT_DRIVERS\r\n", 0) == LSCP_OK) pClient->audio_drivers = lscp_szsplit_create(lscp_client_get_result(pClient), pszSeps); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return (const char **) pClient->audio_drivers; } /** * Getting informations about a specific audio output driver. * GET AUDIO_OUTPUT_DRIVER INFO * * @param pClient Pointer to client instance structure. * @param pszAudioDriver Audio driver type string (e.g. "ALSA"). * * @returns A pointer to a @ref lscp_driver_info_t structure, with * the given audio driver information, or NULL in case of failure. */ lscp_driver_info_t* lscp_get_audio_driver_info ( lscp_client_t *pClient, const char *pszAudioDriver ) { char szQuery[LSCP_BUFSIZ]; if (pszAudioDriver == NULL) return NULL; sprintf(szQuery, "GET AUDIO_OUTPUT_DRIVER INFO %s\r\n", pszAudioDriver); return _lscp_driver_info_query(pClient, &(pClient->audio_driver_info), szQuery); } /** * Getting informations about specific audio output driver parameter. * GET AUDIO_OUTPUT_DRIVER_PARAMETER INFO [] * * @param pClient Pointer to client instance structure. * @param pszAudioDriver Audio driver type string (e.g. "ALSA"). * @param pszParam Audio driver parameter name. * @param pDepList Pointer to specific dependencies parameter list. * * @returns A pointer to a @ref lscp_param_info_t structure, with * the given audio driver parameter information, or NULL in case of failure. */ lscp_param_info_t *lscp_get_audio_driver_param_info ( lscp_client_t *pClient, const char *pszAudioDriver, const char *pszParam, lscp_param_t *pDepList ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; if (pszAudioDriver == NULL) return NULL; if (pszParam == NULL) return NULL; sprintf(szQuery, "GET AUDIO_OUTPUT_DRIVER_PARAMETER INFO %s %s", pszAudioDriver, pszParam); return _lscp_param_info_query(pClient, &(pClient->audio_param_info), szQuery, sizeof(szQuery), pDepList); } //------------------------------------------------------------------------- // Audio device control functions. /** * Creating an audio output device. * CREATE AUDIO_OUTPUT_DEVICE [] * * @param pClient Pointer to client instance structure. * @param pszAudioDriver Audio driver type string (e.g. "ALSA"). * @param pParams Pointer to specific parameter list. * * @returns The new audio device number identifier on success, * or -1 in case of failure. */ int lscp_create_audio_device ( lscp_client_t *pClient, const char *pszAudioDriver, lscp_param_t *pParams ) { char szQuery[LSCP_BUFSIZ]; int iAudioDevice = -1; if (pClient == NULL) return -1; if (pszAudioDriver == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); sprintf(szQuery, "CREATE AUDIO_OUTPUT_DEVICE %s", pszAudioDriver); lscp_param_concat(szQuery, sizeof(szQuery), pParams); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) iAudioDevice = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iAudioDevice; } /** * Destroying an audio output device. * DESTROY AUDIO_OUTPUT_DEVICE * * @param pClient Pointer to client instance structure. * @param iAudioDevice Audio device number identifier. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_destroy_audio_device ( lscp_client_t *pClient, int iAudioDevice ) { lscp_status_t ret = LSCP_FAILED; char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return ret; if (iAudioDevice < 0) return ret; sprintf(szQuery, "DESTROY AUDIO_OUTPUT_DEVICE %d\r\n", iAudioDevice); return lscp_client_query(pClient, szQuery); } /** * Getting all created audio output device count. * GET AUDIO_OUTPUT_DEVICES * * @param pClient Pointer to client instance structure. * * @returns The current total number of audio devices on success, * -1 otherwise. */ int lscp_get_audio_devices ( lscp_client_t *pClient ) { int iAudioDevices = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET AUDIO_OUTPUT_DEVICES\r\n", 0) == LSCP_OK) iAudioDevices = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iAudioDevices; } /** * Getting all created audio output device list. * LIST AUDIO_OUTPUT_DEVICES * * @param pClient Pointer to client instance structure. * * @returns An array of audio device number identifiers, * terminated with -1 on success, or NULL in case of failure. */ int *lscp_list_audio_devices ( lscp_client_t *pClient ) { const char *pszSeps = ","; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->audio_devices) { lscp_isplit_destroy(pClient->audio_devices); pClient->audio_devices = NULL; } if (lscp_client_call(pClient, "LIST AUDIO_OUTPUT_DEVICES\r\n", 0) == LSCP_OK) pClient->audio_devices = lscp_isplit_create(lscp_client_get_result(pClient), pszSeps); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pClient->audio_devices; } /** * Getting current settings of an audio output device. * GET AUDIO_OUTPUT_DEVICE INFO * * @param pClient Pointer to client instance structure. * @param iAudioDevice Audio device number identifier. * * @returns A pointer to a @ref lscp_device_info_t structure, with * the given audio device information, or NULL in case of failure. */ lscp_device_info_t *lscp_get_audio_device_info ( lscp_client_t *pClient, int iAudioDevice ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; if (iAudioDevice < 0) return NULL; sprintf(szQuery, "GET AUDIO_OUTPUT_DEVICE INFO %d\r\n", iAudioDevice); return _lscp_device_info_query(pClient, &(pClient->audio_device_info), szQuery); } /** * Changing settings of audio output devices. * SET AUDIO_OUTPUT_DEVICE_PARAMETER = * * @param pClient Pointer to client instance structure. * @param iAudioDevice Audio device number identifier. * @param pParam Pointer to a key-valued audio device parameter. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_audio_device_param ( lscp_client_t *pClient, int iAudioDevice, lscp_param_t *pParam ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return LSCP_FAILED; if (iAudioDevice < 0) return LSCP_FAILED; if (pParam == NULL) return LSCP_FAILED; sprintf(szQuery, "SET AUDIO_OUTPUT_DEVICE_PARAMETER %d %s='%s'\r\n", iAudioDevice, pParam->key, pParam->value); return lscp_client_query(pClient, szQuery); } /** * Getting informations about an audio channel. * GET AUDIO_OUTPUT_CHANNEL INFO * * @param pClient Pointer to client instance structure. * @param iAudioDevice Audio device number identifier. * @param iAudioChannel Audio channel number. * * @returns A pointer to a @ref lscp_device_port_info_t structure, * with the given audio channel information, or NULL in case of failure. */ lscp_device_port_info_t* lscp_get_audio_channel_info ( lscp_client_t *pClient, int iAudioDevice, int iAudioChannel ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; if (iAudioDevice < 0) return NULL; if (iAudioChannel < 0) return NULL; sprintf(szQuery, "GET AUDIO_OUTPUT_CHANNEL INFO %d %d\r\n", iAudioDevice, iAudioChannel); return _lscp_device_port_info_query(pClient, &(pClient->audio_channel_info), szQuery); } /** * Getting informations about specific audio channel parameter. * GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO * * @param pClient Pointer to client instance structure. * @param iAudioDevice Audio device number identifier. * @param iAudioChannel Audio channel number. * @param pszParam Audio channel parameter name. * * @returns A pointer to a @ref lscp_param_info_t structure, with * the given audio channel parameter information, or NULL in case of failure. */ lscp_param_info_t* lscp_get_audio_channel_param_info ( lscp_client_t *pClient, int iAudioDevice, int iAudioChannel, const char *pszParam ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; if (iAudioDevice < 0) return NULL; if (iAudioChannel < 0) return NULL; if (pszParam == NULL) return NULL; sprintf(szQuery, "GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO %d %d %s", iAudioDevice, iAudioChannel, pszParam); return _lscp_param_info_query(pClient, &(pClient->audio_channel_param_info), szQuery, sizeof(szQuery), NULL); } /** * Changing settings of audio output channels. * SET AUDIO_OUTPUT_CHANNEL_PARAMETER * * @param pClient Pointer to client instance structure. * @param iAudioDevice Audio device number identifier. * @param iAudioChannel Audio channel number. * @param pParam Pointer to a key-valued audio channel parameter. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_audio_channel_param ( lscp_client_t *pClient, int iAudioDevice, int iAudioChannel, lscp_param_t *pParam ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return LSCP_FAILED; if (iAudioDevice < 0) return LSCP_FAILED; if (iAudioChannel < 0) return LSCP_FAILED; if (pParam == NULL) return LSCP_FAILED; sprintf(szQuery, "SET AUDIO_OUTPUT_CHANNEL_PARAMETER %d %d %s='%s'\r\n", iAudioDevice, iAudioChannel, pParam->key, pParam->value); return lscp_client_query(pClient, szQuery); } //------------------------------------------------------------------------- // MIDI driver control functions. /** * Getting all available MIDI input driver count. * GET AVAILABLE_MIDI_INPUT_DRIVERS * * @param pClient Pointer to client instance structure. * * @returns The current total number of MIDI input drivers on success, * -1 otherwise. */ int lscp_get_available_midi_drivers ( lscp_client_t *pClient ) { int iMidiDrivers = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET AVAILABLE_MIDI_INPUT_DRIVERS\r\n", 0) == LSCP_OK) iMidiDrivers = atoi(lscp_client_get_result(pClient)); // Unlock this section up. lscp_mutex_unlock(pClient->mutex); return iMidiDrivers; } /** * Getting all available MIDI input drivers. * LIST AVAILABLE_MIDI_INPUT_DRIVERS * * @param pClient Pointer to client instance structure. * * @returns A NULL terminated array of MIDI input driver type * name strings, or NULL in case of failure. */ const char** lscp_list_available_midi_drivers ( lscp_client_t *pClient ) { const char *pszSeps = ","; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->midi_drivers) { lscp_szsplit_destroy(pClient->midi_drivers); pClient->midi_drivers = NULL; } if (lscp_client_call(pClient, "LIST AVAILABLE_MIDI_INPUT_DRIVERS\r\n", 0) == LSCP_OK) pClient->midi_drivers = lscp_szsplit_create(lscp_client_get_result(pClient), pszSeps); // Unlock this section up. lscp_mutex_unlock(pClient->mutex); return (const char **) pClient->midi_drivers; } /** * Getting informations about a specific MIDI input driver. * GET MIDI_INPUT_DRIVER INFO * * @param pClient Pointer to client instance structure. * @param pszMidiDriver MIDI driver type string (e.g. "ALSA"). * * @returns A pointer to a @ref lscp_driver_info_t structure, with * the given MIDI driver information, or NULL in case of failure. */ lscp_driver_info_t* lscp_get_midi_driver_info ( lscp_client_t *pClient, const char *pszMidiDriver ) { char szQuery[LSCP_BUFSIZ]; if (pszMidiDriver == NULL) return NULL; sprintf(szQuery, "GET MIDI_INPUT_DRIVER INFO %s\r\n", pszMidiDriver); return _lscp_driver_info_query(pClient, &(pClient->midi_driver_info), szQuery); } /** * Getting informations about specific MIDI input driver parameter. * GET MIDI_INPUT_DRIVER_PARAMETER INFO [] * * @param pClient Pointer to client instance structure. * @param pszMidiDriver MIDI driver type string (e.g. "ALSA"). * @param pszParam MIDI driver parameter name. * @param pDepList Pointer to specific dependencies parameter list. * * @returns A pointer to a @ref lscp_param_info_t structure, with * the given MIDI driver parameter information, or NULL in case of failure. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_param_info_t *lscp_get_midi_driver_param_info ( lscp_client_t *pClient, const char *pszMidiDriver, const char *pszParam, lscp_param_t *pDepList ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; if (pszMidiDriver == NULL) return NULL; if (pszParam == NULL) return NULL; sprintf(szQuery, "GET MIDI_INPUT_DRIVER_PARAMETER INFO %s %s", pszMidiDriver, pszParam); return _lscp_param_info_query(pClient, &(pClient->midi_param_info), szQuery, sizeof(szQuery), pDepList); } //------------------------------------------------------------------------- // MIDI device control functions. /** * Creating a MIDI input device. * CREATE MIDI_INPUT_DEVICE [] * * @param pClient Pointer to client instance structure. * @param pszMidiDriver MIDI driver type string (e.g. "ALSA"). * @param pParams Pointer to specific parameter list. * * @returns The new audio device number identifier on success, * or -1 in case of failure. */ int lscp_create_midi_device ( lscp_client_t *pClient, const char *pszMidiDriver, lscp_param_t *pParams ) { char szQuery[LSCP_BUFSIZ]; int iMidiDevice = -1; if (pClient == NULL) return -1; if (pszMidiDriver == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); sprintf(szQuery, "CREATE MIDI_INPUT_DEVICE %s", pszMidiDriver); lscp_param_concat(szQuery, sizeof(szQuery), pParams); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) iMidiDevice = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iMidiDevice; } /** * Destroying a MIDI input device. * DESTROY MIDI_INPUT_DEVICE * * @param pClient Pointer to client instance structure. * @param iMidiDevice MIDI device number identifier. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_destroy_midi_device ( lscp_client_t *pClient, int iMidiDevice ) { lscp_status_t ret = LSCP_FAILED; char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return ret; if (iMidiDevice < 0) return ret; sprintf(szQuery, "DESTROY MIDI_INPUT_DEVICE %d\r\n", iMidiDevice); return lscp_client_query(pClient, szQuery); } /** * Getting all created MIDI intput device count. * GET MIDI_INPUT_DEVICES * * @param pClient Pointer to client instance structure. * * @returns The current total number of MIDI devices on success, * -1 otherwise. */ int lscp_get_midi_devices ( lscp_client_t *pClient ) { int iMidiDevices = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET MIDI_INPUT_DEVICES\r\n", 0) == LSCP_OK) iMidiDevices = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iMidiDevices; } /** * Getting all created MIDI intput device list. * LIST MIDI_INPUT_DEVICES * * @param pClient Pointer to client instance structure. * * @returns An array of MIDI device number identifiers, * terminated with -1 on success, or NULL in case of failure. */ int *lscp_list_midi_devices ( lscp_client_t *pClient ) { const char *pszSeps = ","; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->midi_devices) { lscp_isplit_destroy(pClient->midi_devices); pClient->midi_devices = NULL; } if (lscp_client_call(pClient, "LIST MIDI_INPUT_DEVICES\r\n", 0) == LSCP_OK) pClient->midi_devices = lscp_isplit_create(lscp_client_get_result(pClient), pszSeps); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pClient->midi_devices; } /** * Getting current settings of a MIDI input device. * GET MIDI_INPUT_DEVICE INFO * * @param pClient Pointer to client instance structure. * @param iMidiDevice MIDI device number identifier. * * @returns A pointer to a @ref lscp_device_info_t structure, with * the given MIDI device information, or NULL in case of failure. */ lscp_device_info_t* lscp_get_midi_device_info ( lscp_client_t *pClient, int iMidiDevice ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; if (iMidiDevice < 0) return NULL; sprintf(szQuery, "GET MIDI_INPUT_DEVICE INFO %d\r\n", iMidiDevice); return _lscp_device_info_query(pClient, &(pClient->midi_device_info), szQuery); } /** * Changing settings of MIDI input devices. * SET MIDI_INPUT_DEVICE_PARAMETER = * * @param pClient Pointer to client instance structure. * @param iMidiDevice MIDI device number identifier. * @param pParam Pointer to a key-valued MIDI device parameter. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_midi_device_param ( lscp_client_t *pClient, int iMidiDevice, lscp_param_t *pParam ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return LSCP_FAILED; if (iMidiDevice < 0) return LSCP_FAILED; if (pParam == NULL) return LSCP_FAILED; sprintf(szQuery, "SET MIDI_INPUT_DEVICE_PARAMETER %d %s='%s'\r\n", iMidiDevice, pParam->key, pParam->value); return lscp_client_query(pClient, szQuery); } /** * Getting informations about a MIDI port. * GET MIDI_INPUT_PORT INFO * * @param pClient Pointer to client instance structure. * @param iMidiDevice MIDI device number identifier. * @param iMidiPort MIDI port number. * * @returns A pointer to a @ref lscp_device_port_info_t structure, * with the given MIDI port information, or NULL in case of failure. */ lscp_device_port_info_t* lscp_get_midi_port_info ( lscp_client_t *pClient, int iMidiDevice, int iMidiPort ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; if (iMidiDevice < 0) return NULL; if (iMidiPort < 0) return NULL; sprintf(szQuery, "GET MIDI_INPUT_PORT INFO %d %d\r\n", iMidiDevice, iMidiPort); return _lscp_device_port_info_query(pClient, &(pClient->midi_port_info), szQuery); } /** * Getting informations about specific MIDI port parameter. * GET MIDI_INPUT_PORT_PARAMETER INFO * * @param pClient Pointer to client instance structure. * @param iMidiDevice MIDI device number identifier. * @param iMidiPort MIDI port number. * @param pszParam MIDI port parameter name. * * @returns A pointer to a @ref lscp_param_info_t structure, with * the given MIDI port parameter information, or NULL in case of failure. */ lscp_param_info_t* lscp_get_midi_port_param_info ( lscp_client_t *pClient, int iMidiDevice, int iMidiPort, const char *pszParam ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; if (iMidiDevice < 0) return NULL; if (iMidiPort < 0) return NULL; if (pszParam == NULL) return NULL; sprintf(szQuery, "GET MIDI_INPUT_PORT_PARAMETER INFO %d %d %s", iMidiDevice, iMidiPort, pszParam); return _lscp_param_info_query(pClient, &(pClient->midi_port_param_info), szQuery, sizeof(szQuery), NULL); } /** * Changing settings of MIDI input ports. * SET MIDI_INPUT_PORT_PARAMETER * * @param pClient Pointer to client instance structure. * @param iMidiDevice MIDI device number identifier. * @param iMidiPort MIDI port number. * @param pParam Pointer to a key-valued MIDI port parameter. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_midi_port_param ( lscp_client_t *pClient, int iMidiDevice, int iMidiPort, lscp_param_t *pParam ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return LSCP_FAILED; if (iMidiDevice < 0) return LSCP_FAILED; if (iMidiPort < 0) return LSCP_FAILED; if (pParam == NULL) return LSCP_FAILED; sprintf(szQuery, "SET MIDI_INPUT_PORT_PARAMETER %d %d %s='%s'\r\n", iMidiDevice, iMidiPort, pParam->key, pParam->value); return lscp_client_query(pClient, szQuery); } //------------------------------------------------------------------------- // Generic parameter list functions. const char *lscp_get_param_value ( lscp_param_t *pParams, const char *pszParam ) { int i; for (i = 0; pParams && pParams[i].key; i++) { if (strcasecmp(pParams[i].key, pszParam) == 0) return (const char *) pParams[i].value; } return NULL; } // end of device.c liblscp-1.0.1/src/common.h0000644000175000017500000002147514771257264014531 0ustar drag0ndrag0n// common.h // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __LSCP_COMMON_H #define __LSCP_COMMON_H #include "lscp/client.h" #include "lscp/device.h" // Case unsensitive comparison substitutes. #if defined(WIN32) #define strcasecmp stricmp #define strncasecmp strnicmp #endif //------------------------------------------------------------------------- // Client opaque descriptor struct. struct _lscp_client_t { // Client socket stuff. lscp_client_proc_t pfnCallback; void * pvData; lscp_socket_agent_t cmd; lscp_socket_agent_t evt; // Subscribed events. lscp_event_t events; // Client struct persistent caches. char ** audio_drivers; char ** midi_drivers; int * audio_devices; int * midi_devices; char ** engines; int * channels; int * fxsends; lscp_midi_instrument_t *midi_instruments; int * midi_maps; char * midi_map_name; // Client struct volatile caches. lscp_driver_info_t audio_driver_info; lscp_driver_info_t midi_driver_info; lscp_device_info_t audio_device_info; lscp_device_info_t midi_device_info; lscp_param_info_t audio_param_info; lscp_param_info_t midi_param_info; lscp_device_port_info_t audio_channel_info; lscp_device_port_info_t midi_port_info; lscp_param_info_t audio_channel_param_info; lscp_param_info_t midi_port_param_info; lscp_server_info_t server_info; lscp_engine_info_t engine_info; lscp_channel_info_t channel_info; lscp_fxsend_info_t fxsend_info; lscp_midi_instrument_info_t midi_instrument_info; // Result and error status. char * pszResult; int iErrno; // Stream buffers status. lscp_buffer_fill_t *buffer_fill; int iStreamCount; // Transaction call timeout (msecs). int iTimeout; lscp_mutex_t mutex; lscp_cond_t cond; // Flag last transaction timedout. int iTimeoutCount; }; //------------------------------------------------------------------------- // Local client request executive. lscp_status_t lscp_client_recv (lscp_client_t *pClient, char *pchBuffer, int *pcchBuffer, int iTimeout); lscp_status_t lscp_client_call (lscp_client_t *pClient, const char *pszQuery, int iResult); void lscp_client_set_result (lscp_client_t *pClient, char *pszResult, int iErrno); //------------------------------------------------------------------------- // General utility function prototypes. char * lscp_strtok (char *pchBuffer, const char *pszSeps, char **ppch); char * lscp_ltrim (char *psz); char * lscp_unquote (char **ppsz, int dup); void lscp_unquote_dup (char **ppszDst, char **ppszSrc); char ** lscp_szsplit_create (const char *pszCsv, const char *pszSeps); void lscp_szsplit_destroy (char **ppszSplit); #ifdef LSCP_SZSPLIT_COUNT int lscp_szsplit_count (char **ppszSplit); int lscp_szsplit_size (char **ppszSplit); #endif int * lscp_isplit_create (const char *pszCsv, const char *pszSeps); void lscp_isplit_destroy (int *piSplit); #ifdef LSCP_ISPLIT_COUNT int lscp_isplit_count (int *piSplit); int lscp_isplit_size (int *piSplit); #endif lscp_param_t * lscp_psplit_create (const char *pszCsv, const char *pszSep1, const char *pszSep2); void lscp_psplit_destroy (lscp_param_t *ppSplit); #ifdef LSCP_PSPLIT_COUNT int lscp_psplit_count (lscp_param_t *ppSplit); int lscp_psplit_size (lscp_param_t *ppSplit); #endif void lscp_plist_alloc (lscp_param_t **ppList); void lscp_plist_free (lscp_param_t **ppList); void lscp_plist_append (lscp_param_t **ppList, const char *pszKey, const char *pszValue); #ifdef LSCP_PLIST_COUNT int lscp_plist_count (lscp_param_t **ppList); int lscp_plist_size (lscp_param_t **ppList); #endif lscp_midi_instrument_t *lscp_midi_instruments_create (const char *pszCsv); void lscp_midi_instruments_destroy (lscp_midi_instrument_t *pInstrs); #ifdef LSCP_MIDI_INSTRUMENTS_COUNT int lscp_midi_instruments_count (lscp_midi_instrument_t *pInstrs); int lscp_midi_instruments_size (lscp_midi_instrument_t *pInstrs); #endif //------------------------------------------------------------------------- // Server struct helper functions. void lscp_server_info_init (lscp_server_info_t *pServerInfo); void lscp_server_info_free (lscp_server_info_t *pServerInfo); void lscp_server_info_reset (lscp_server_info_t *pServerInfo); //------------------------------------------------------------------------- // Engine struct helper functions. void lscp_engine_info_init (lscp_engine_info_t *pEngineInfo); void lscp_engine_info_free (lscp_engine_info_t *pEngineInfo); void lscp_engine_info_reset (lscp_engine_info_t *pEngineInfo); //------------------------------------------------------------------------- // Channel struct helper functions. void lscp_channel_info_init (lscp_channel_info_t *pChannelInfo); void lscp_channel_info_free (lscp_channel_info_t *pChannelInfo); void lscp_channel_info_reset (lscp_channel_info_t *pChannelInfo); //------------------------------------------------------------------------- // Driver struct helper functions. void lscp_driver_info_init (lscp_driver_info_t *pDriverInfo); void lscp_driver_info_free (lscp_driver_info_t *pDriverInfo); void lscp_driver_info_reset (lscp_driver_info_t *pDriverInfo); //------------------------------------------------------------------------- // Device struct helper functions. void lscp_device_info_init (lscp_device_info_t *pDeviceInfo); void lscp_device_info_free (lscp_device_info_t *pDeviceInfo); void lscp_device_info_reset (lscp_device_info_t *pDeviceInfo); //------------------------------------------------------------------------- // Device channel/port struct helper functions. void lscp_device_port_info_init (lscp_device_port_info_t *pDevicePortInfo); void lscp_device_port_info_free (lscp_device_port_info_t *pDevicePortInfo); void lscp_device_port_info_reset (lscp_device_port_info_t *pDevicePortInfo); //------------------------------------------------------------------------- // Parameter struct helper functions. void lscp_param_info_init (lscp_param_info_t *pParamInfo); void lscp_param_info_free (lscp_param_info_t *pParamInfo); void lscp_param_info_reset (lscp_param_info_t *pParamInfo); //------------------------------------------------------------------------- // Concatenate a parameter list (key='value'...) into a string. int lscp_param_concat (char *pszBuffer, int cchMaxBuffer, lscp_param_t *pParams); //------------------------------------------------------------------------- // Effect struct helper functions. void lscp_fxsend_info_init (lscp_fxsend_info_t *pFxSendInfo); void lscp_fxsend_info_free (lscp_fxsend_info_t *pFxSendInfo); void lscp_fxsend_info_reset (lscp_fxsend_info_t *pFxSendInfo); //------------------------------------------------------------------------- // MIDI instrument info struct helper functions. void lscp_midi_instrument_info_init (lscp_midi_instrument_info_t *pInstrInfo); void lscp_midi_instrument_info_free (lscp_midi_instrument_info_t *pInstrInfo); void lscp_midi_instrument_info_reset (lscp_midi_instrument_info_t *pInstrInfo); #endif // __LSCP_COMMON_H // end of common.h liblscp-1.0.1/src/lscp.pc.cmake0000644000175000017500000000036014771257264015422 0ustar drag0ndrag0nprefix=@CONFIG_PREFIX@ exec_prefix=@CONFIG_PREFIX@ libdir=@CONFIG_LIBDIR@ includedir=@CONFIG_INCLUDEDIR@ Name: lscp Description: LinuxSampler control protocol API Version: @PROJECT_VERSION@ Libs: -L${libdir} -llscp Cflags: -I${includedir} liblscp-1.0.1/src/client.c0000644000175000017500000026125014771257264014507 0ustar drag0ndrag0n// client.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include #include "common.h" #include #ifdef WIN32 # include #else # include #endif // Default timeout value (in milliseconds). #define LSCP_TIMEOUT_MSECS 500 // Whether to use getaddrinfo() instead // of deprecated gethostbyname() #if !defined(WIN32) #define USE_GETADDRINFO 1 #endif // Local prototypes. static void _lscp_client_evt_proc (void *pvClient); static lscp_status_t _lscp_client_evt_connect (lscp_client_t *pClient); static lscp_status_t _lscp_client_evt_request (lscp_client_t *pClient, int iSubscribe, lscp_event_t event); //------------------------------------------------------------------------- // General helper functions. struct _locale_t { char numeric[32+1]; char ctype[32+1]; }; // we need to ensure a constant locale setting e.g. for parsing // floating point numbers with atof(), as the floating point separator // character varies by the invidual locale settings static void _save_and_set_c_locale(struct _locale_t* locale) { locale->numeric[32] = locale->ctype[32] = 0; strncpy(locale->numeric, setlocale(LC_NUMERIC, NULL), 32); strncpy(locale->ctype, setlocale(LC_CTYPE, NULL), 32); setlocale(LC_NUMERIC, "C"); setlocale(LC_CTYPE, "C"); } // restore the original locale setting as nothing happened static void _restore_locale(struct _locale_t* locale) { setlocale(LC_NUMERIC, locale->numeric); setlocale(LC_CTYPE, locale->ctype); } // seems the standard atof() function doesnt care much about locale // runtime modifications, so we use this workaround static float _atof(const char* txt) { float f; sscanf(txt, "%f", &f); // yeah, you're a good boy sscanf() return f; } //------------------------------------------------------------------------- // Event service (datagram oriented). static void _lscp_client_evt_proc ( void *pvClient ) { lscp_client_t *pClient = (lscp_client_t *) pvClient; fd_set fds; // File descriptor list for select(). int fd, fdmax; // Maximum file descriptor number. struct timeval tv; // For specifying a timeout value. int iSelect; // Holds select return status. int iTimeout; char achBuffer[LSCP_BUFSIZ]; int cchBuffer; const char *pszSeps = ":\r\n"; char *pszToken; char *pch; int cchToken; lscp_event_t event; #ifdef CONFIG_DEBUG fprintf(stderr, "_lscp_client_evt_proc: Client waiting for events.\n"); #endif while (pClient->evt.iState) { // Prepare for waiting on select... fd = (int) pClient->evt.sock; FD_ZERO(&fds); FD_SET((unsigned int) fd, &fds); fdmax = fd; // Use the timeout (x10) select feature ... iTimeout = 10 * pClient->iTimeout; if (iTimeout >= 1000) { tv.tv_sec = iTimeout / 1000; iTimeout -= tv.tv_sec * 1000; } else tv.tv_sec = 0; tv.tv_usec = iTimeout * 1000; // Wait for event... iSelect = select(fdmax + 1, &fds, NULL, NULL, &tv); if (iSelect > 0 && FD_ISSET(fd, &fds)) { // May recv now... cchBuffer = recv(pClient->evt.sock, achBuffer, sizeof(achBuffer), 0); if (cchBuffer > 0) { // Make sure received buffer it's null terminated. achBuffer[cchBuffer] = (char) 0; pch = achBuffer; do { // Parse for the notification event message... pszToken = lscp_strtok(NULL, pszSeps, &(pch)); // Have "NOTIFY" if (strcasecmp(pszToken, "NOTIFY") == 0) { pszToken = lscp_strtok(NULL, pszSeps, &(pch)); event = lscp_event_from_text(pszToken); // And pick the rest of data... pszToken = lscp_strtok(NULL, pszSeps, &(pch)); cchToken = (pszToken == NULL ? 0 : strlen(pszToken)); // Double-check if we're really up to it... if (pClient->events & event) { // Invoke the client event callback... if ((*pClient->pfnCallback)( pClient, event, pszToken, cchToken, pClient->pvData) != LSCP_OK) { pClient->evt.iState = 0; } } } } while (*pch); } else { lscp_socket_perror("_lscp_client_evt_proc: recv"); pClient->evt.iState = 0; pClient->iErrno = -errno; } } // Check if select has in error. else if (iSelect < 0) { lscp_socket_perror("_lscp_client_evt_proc: select"); pClient->evt.iState = 0; pClient->iErrno = -errno; } // Finally, always signal the event. lscp_cond_signal(pClient->cond); } #ifdef CONFIG_DEBUG fprintf(stderr, "_lscp_client_evt_proc: Client closing.\n"); #endif } //------------------------------------------------------------------------- // Event subscription helpers. // Open the event service socket connection. static lscp_status_t _lscp_client_evt_connect ( lscp_client_t *pClient ) { lscp_socket_t sock; struct sockaddr_in addr; int cAddr; #if defined(WIN32) int iSockOpt = (-1); #endif // Prepare the event connection socket... sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { lscp_socket_perror("_lscp_client_evt_connect: socket"); return LSCP_FAILED; } #if defined(WIN32) if (setsockopt(sock, SOL_SOCKET, SO_DONTLINGER, (char *) &iSockOpt, sizeof(int)) == SOCKET_ERROR) lscp_socket_perror("lscp_client_evt_connect: setsockopt(SO_DONTLINGER)"); #endif #ifdef CONFIG_DEBUG lscp_socket_getopts("_lscp_client_evt_connect:", sock); #endif // Use same address of the command connection. cAddr = sizeof(struct sockaddr_in); memmove((char *) &addr, &(pClient->cmd.addr), cAddr); // Start the connection... if (connect(sock, (struct sockaddr *) &addr, cAddr) == SOCKET_ERROR) { lscp_socket_perror("_lscp_client_evt_connect: connect"); closesocket(sock); return LSCP_FAILED; } // Set our socket agent struct... lscp_socket_agent_init(&(pClient->evt), sock, &addr, cAddr); // And finally the service thread... return lscp_socket_agent_start(&(pClient->evt), _lscp_client_evt_proc, pClient, 0); } // Subscribe to a single event. static lscp_status_t _lscp_client_evt_request ( lscp_client_t *pClient, int iSubscribe, lscp_event_t event ) { const char *pszEvent; char szQuery[LSCP_BUFSIZ]; int cchQuery; if (pClient == NULL) return LSCP_FAILED; // Which (single) event? pszEvent = lscp_event_to_text(event); if (pszEvent == NULL) return LSCP_FAILED; // Build the query string... cchQuery = sprintf(szQuery, "%sSUBSCRIBE %s\n\n", (iSubscribe == 0 ? "UN" : ""), pszEvent); // Just send data, forget result... if (send(pClient->evt.sock, szQuery, cchQuery, 0) < cchQuery) { lscp_socket_perror("_lscp_client_evt_request: send"); return LSCP_FAILED; } // Wait on response. lscp_cond_wait(pClient->cond, pClient->mutex); // Update as naively as we can... if (iSubscribe) pClient->events |= event; else pClient->events &= ~event; return LSCP_OK; } //------------------------------------------------------------------------- // Client versioning teller fuunction. /** Retrieve the current client library version string. */ const char* lscp_client_package (void) { return LSCP_PACKAGE; } /** Retrieve the current client library version string. */ const char* lscp_client_version (void) { return LSCP_VERSION; } /** Retrieve the current client library build string. */ const char* lscp_client_build (void) { return LSCP_BUILD; } //------------------------------------------------------------------------- // Client socket functions. /** * Create a client instance, estabilishing a connection to a server hostname, * which must be listening on the given port. A client callback function is * also supplied for server notification event handling. * * @param pszHost Hostname of the linuxsampler listening server. * @param iPort Port number of the linuxsampler listening server. * @param pfnCallback Callback function to receive event notifications. * @param pvData User context opaque data, that will be passed * to the callback function. * * @returns The new client instance pointer if successfull, which shall be * used on all subsequent client calls, NULL otherwise. */ lscp_client_t* lscp_client_create ( const char *pszHost, int iPort, lscp_client_proc_t pfnCallback, void *pvData ) { lscp_client_t *pClient; #if defined(USE_GETADDRINFO) char szPort[33]; struct addrinfo hints; struct addrinfo *result, *res; #else struct hostent *pHost; struct sockaddr_in addr; int cAddr; #endif /* !USE_GETADDRINFO */ lscp_socket_t sock; #if defined(WIN32) int iSockOpt = (-1); #endif if (pfnCallback == NULL) { fprintf(stderr, "lscp_client_create: Invalid client callback function.\n"); return NULL; } #if defined(USE_GETADDRINFO) // Convert port number to string/name... snprintf(szPort, sizeof(szPort), "%d", iPort); // Obtain address(es) matching host/port... memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; result = NULL; if (getaddrinfo(pszHost, szPort, &hints, &result)) { lscp_socket_herror("lscp_client_create: getaddrinfo"); return NULL; } #else // Obtain host matching name... pHost = gethostbyname(pszHost); if (pHost == NULL) { lscp_socket_herror("lscp_client_create: gethostbyname"); return NULL; } #endif /* !USE_GETADDRINFO */ // Allocate client descriptor... pClient = (lscp_client_t *) malloc(sizeof(lscp_client_t)); if (pClient == NULL) { fprintf(stderr, "lscp_client_create: Out of memory.\n"); return NULL; } memset(pClient, 0, sizeof(lscp_client_t)); pClient->pfnCallback = pfnCallback; pClient->pvData = pvData; #ifdef CONFIG_DEBUG fprintf(stderr, "lscp_client_create: pClient=%p: pszHost=%s iPort=%d.\n", pClient, pszHost, iPort); #endif // Prepare the command connection socket... #if defined(USE_GETADDRINFO) // getaddrinfo() returns a list of address structures; // try each address until we successfully connect(2); // if socket or connect fails, we close the socket and // try the next address... sock = INVALID_SOCKET; for (res = result; res; res = res->ai_next) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock == INVALID_SOCKET) continue; #if defined(WIN32) if (setsockopt(sock, SOL_SOCKET, SO_DONTLINGER, (char *) &iSockOpt, sizeof(int)) == SOCKET_ERROR) lscp_socket_perror("lscp_client_create: cmd: setsockopt(SO_DONTLINGER)"); #endif #ifdef CONFIG_DEBUG lscp_socket_getopts("lscp_client_create: cmd", sock); #endif if (connect(sock, res->ai_addr, res->ai_addrlen) != SOCKET_ERROR) break; closesocket(sock); } if (sock == INVALID_SOCKET) { lscp_socket_perror("lscp_client_create: cmd: socket"); free(pClient); return NULL; } if (res == NULL) { lscp_socket_perror("lscp_client_create: cmd: connect"); free(pClient); return NULL; } // Initialize the command socket agent struct... lscp_socket_agent_init(&(pClient->cmd), sock, (struct sockaddr_in *) res->ai_addr, res->ai_addrlen); // No longer needed... freeaddrinfo(result); #else sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { lscp_socket_perror("lscp_client_create: cmd: socket"); free(pClient); return NULL; } #if defined(WIN32) if (setsockopt(sock, SOL_SOCKET, SO_DONTLINGER, (char *) &iSockOpt, sizeof(int)) == SOCKET_ERROR) lscp_socket_perror("lscp_client_create: cmd: setsockopt(SO_DONTLINGER)"); #endif #ifdef CONFIG_DEBUG lscp_socket_getopts("lscp_client_create: cmd", sock); #endif cAddr = sizeof(struct sockaddr_in); memset((char *) &addr, 0, cAddr); addr.sin_family = pHost->h_addrtype; memmove((char *) &(addr.sin_addr), pHost->h_addr, pHost->h_length); addr.sin_port = htons((short) iPort); if (connect(sock, (struct sockaddr *) &addr, cAddr) == SOCKET_ERROR) { lscp_socket_perror("lscp_client_create: cmd: connect"); closesocket(sock); free(pClient); return NULL; } // Initialize the command socket agent struct... lscp_socket_agent_init(&(pClient->cmd), sock, &addr, cAddr); #endif /* !USE_GETADDRINFO */ #ifdef CONFIG_DEBUG fprintf(stderr, "lscp_client_create: cmd: pClient=%p: sock=%d addr=%s port=%d.\n", pClient, pClient->cmd.sock, inet_ntoa(pClient->cmd.addr.sin_addr), ntohs(pClient->cmd.addr.sin_port)); #endif // Initialize the event service socket struct... lscp_socket_agent_init(&(pClient->evt), INVALID_SOCKET, NULL, 0); // No events subscribed, yet. pClient->events = LSCP_EVENT_NONE; // Initialize cached members. pClient->audio_drivers = NULL; pClient->midi_drivers = NULL; pClient->audio_devices = NULL; pClient->midi_devices = NULL; pClient->engines = NULL; pClient->channels = NULL; pClient->fxsends = NULL; pClient->midi_instruments = NULL; pClient->midi_maps = NULL; pClient->midi_map_name = NULL; lscp_driver_info_init(&(pClient->audio_driver_info)); lscp_driver_info_init(&(pClient->midi_driver_info)); lscp_device_info_init(&(pClient->audio_device_info)); lscp_device_info_init(&(pClient->midi_device_info)); lscp_param_info_init(&(pClient->audio_param_info)); lscp_param_info_init(&(pClient->midi_param_info)); lscp_device_port_info_init(&(pClient->audio_channel_info)); lscp_device_port_info_init(&(pClient->midi_port_info)); lscp_param_info_init(&(pClient->audio_channel_param_info)); lscp_param_info_init(&(pClient->midi_port_param_info)); lscp_server_info_init(&(pClient->server_info)); lscp_engine_info_init(&(pClient->engine_info)); lscp_channel_info_init(&(pClient->channel_info)); lscp_fxsend_info_init(&(pClient->fxsend_info)); lscp_midi_instrument_info_init(&(pClient->midi_instrument_info)); // Initialize error stuff. pClient->pszResult = NULL; pClient->iErrno = -1; // Stream usage stuff. pClient->buffer_fill = NULL; pClient->iStreamCount = 0; // Default timeout value. pClient->iTimeout = LSCP_TIMEOUT_MSECS; pClient->iTimeoutCount = 0; // Initialize the transaction mutex. lscp_mutex_init(pClient->mutex); lscp_cond_init(pClient->cond); // Finally we've some success... return pClient; } /** * Wait for a client instance to terminate graciously. * * @param pClient Pointer to client instance structure. */ lscp_status_t lscp_client_join ( lscp_client_t *pClient ) { if (pClient == NULL) return LSCP_FAILED; #ifdef CONFIG_DEBUG fprintf(stderr, "lscp_client_join: pClient=%p.\n", pClient); #endif // lscp_socket_agent_join(&(pClient->evt)); lscp_socket_agent_join(&(pClient->cmd)); return LSCP_OK; } /** * Terminate and destroy a client instance. * * @param pClient Pointer to client instance structure. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_client_destroy ( lscp_client_t *pClient ) { if (pClient == NULL) return LSCP_FAILED; #ifdef CONFIG_DEBUG fprintf(stderr, "lscp_client_destroy: pClient=%p.\n", pClient); #endif // Lock this section up. lscp_mutex_lock(pClient->mutex); // Free up all cached members. lscp_midi_instrument_info_free(&(pClient->midi_instrument_info)); lscp_fxsend_info_free(&(pClient->fxsend_info)); lscp_channel_info_free(&(pClient->channel_info)); lscp_engine_info_free(&(pClient->engine_info)); lscp_server_info_free(&(pClient->server_info)); lscp_param_info_free(&(pClient->midi_port_param_info)); lscp_param_info_free(&(pClient->audio_channel_param_info)); lscp_device_port_info_free(&(pClient->midi_port_info)); lscp_device_port_info_free(&(pClient->audio_channel_info)); lscp_param_info_free(&(pClient->midi_param_info)); lscp_param_info_free(&(pClient->audio_param_info)); lscp_device_info_free(&(pClient->midi_device_info)); lscp_device_info_free(&(pClient->audio_device_info)); lscp_driver_info_free(&(pClient->midi_driver_info)); lscp_driver_info_free(&(pClient->audio_driver_info)); // Free available engine table. lscp_szsplit_destroy(pClient->audio_drivers); lscp_szsplit_destroy(pClient->midi_drivers); lscp_isplit_destroy(pClient->audio_devices); lscp_isplit_destroy(pClient->midi_devices); lscp_szsplit_destroy(pClient->engines); lscp_isplit_destroy(pClient->channels); lscp_isplit_destroy(pClient->fxsends); lscp_midi_instruments_destroy(pClient->midi_instruments); lscp_isplit_destroy(pClient->midi_maps); if (pClient->midi_map_name) free(pClient->midi_map_name); // Make them null. pClient->audio_drivers = NULL; pClient->midi_drivers = NULL; pClient->audio_devices = NULL; pClient->midi_devices = NULL; pClient->engines = NULL; pClient->channels = NULL; pClient->fxsends = NULL; pClient->midi_instruments = NULL; pClient->midi_maps = NULL; pClient->midi_map_name = NULL; // Free result error stuff. lscp_client_set_result(pClient, NULL, 0); // Free stream usage stuff. if (pClient->buffer_fill) free(pClient->buffer_fill); pClient->buffer_fill = NULL; pClient->iStreamCount = 0; pClient->iTimeout = 0; // Free socket agents. lscp_socket_agent_free(&(pClient->evt)); lscp_socket_agent_free(&(pClient->cmd)); // Last but not least, free good ol'transaction mutex. lscp_mutex_unlock(pClient->mutex); lscp_mutex_destroy(pClient->mutex); lscp_cond_destroy(pClient->cond); free(pClient); return LSCP_OK; } /** * Set the client transaction timeout interval. * * @param pClient Pointer to client instance structure. * @param iTimeout Transaction timeout in milliseconds. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_client_set_timeout ( lscp_client_t *pClient, int iTimeout ) { if (pClient == NULL) return LSCP_FAILED; if (iTimeout < 0) return LSCP_FAILED; pClient->iTimeout = iTimeout; return LSCP_OK; } /** * Get the client transaction timeout interval. * * @param pClient Pointer to client instance structure. * * @returns The current timeout value milliseconds, -1 in case of failure. */ int lscp_client_get_timeout ( lscp_client_t *pClient ) { if (pClient == NULL) return -1; return pClient->iTimeout; } /** * Check whether connection to server is lost. * * @param pClient Pointer to client instance structure. * * @returns @c true if client lost connection to server, @c false otherwise. */ bool lscp_client_connection_lost ( lscp_client_t *pClient ) { int err = lscp_client_get_errno(pClient); if (err >= 0) return false; return err == -EPIPE || err == -ECONNRESET || err == -ECONNABORTED; } //------------------------------------------------------------------------- // Client common protocol functions. /** * Submit a command query line string to the server. The query string * must be cr/lf and null terminated. Besides the return code, the * specific server response to the command request is made available * by the @ref lscp_client_get_result and @ref lscp_client_get_errno * function calls. * * @param pClient Pointer to client instance structure. * @param pszQuery Command request line to be sent to server, * must be cr/lf and null terminated. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_client_query ( lscp_client_t *pClient, const char *pszQuery ) { lscp_status_t ret; if (pClient == NULL) return LSCP_FAILED; // Lock this section up. lscp_mutex_lock(pClient->mutex); // Just make the now guarded call. ret = lscp_client_call(pClient, pszQuery, 0); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return ret; } /** * Get the last received result string. In case of error or warning, * this is the text of the error or warning message issued. * * @param pClient Pointer to client instance structure. * * @returns A pointer to the literal null-terminated result string as * of the last command request. */ const char *lscp_client_get_result ( lscp_client_t *pClient ) { if (pClient == NULL) return NULL; return pClient->pszResult; } /** * Get the last error/warning number received. * * @param pClient Pointer to client instance structure. * * @returns The numerical value of the last error or warning * response code received. */ int lscp_client_get_errno ( lscp_client_t *pClient ) { if (pClient == NULL) return -1; return pClient->iErrno; } //------------------------------------------------------------------------- // Client registration protocol functions. /** * Register frontend for receiving event messages by the sampler backend. * @e Caution: since liblscp v0.5.5.4 you have to call lscp_client_subscribe() * for @e each event you want to subscribe. That is the old bitflag approach * was abondoned at this point. You can however still register all older * events with one lscp_client_subscribe() call at once. Thus, the old * behavior of this functions was not broken. Those older events are namely: * @code * SUBSCRIBE CHANNEL_COUNT | VOICE_COUNT | STREAM_COUNT * | BUFFER_FILL | CHANNEL_INFO | TOTAL_VOICE_COUNT * | AUDIO_OUTPUT_DEVICE_COUNT | AUDIO_OUTPUT_DEVICE_INFO * | MIDI_INPUT_DEVICE_COUNT | MIDI_INPUT_DEVICE_INFO * | MIDI_INSTRUMENT_MAP_COUNT | MIDI_INSTRUMENT_MAP_INFO * | MIDI_INSTRUMENT_COUNT | MIDI_INSTRUMENT_INFO * | MISCELLANEOUS * @endcode * The old events occupy the lower 16 bits (as bit flags), and all younger * events enumerate the whole upper 16 bits range. The new, enumerated * events are namely: * @code * SUBSCRIBE CHANNEL_MIDI * @endcode * * @param pClient Pointer to client instance structure. * @param events LSCP event to subscribe. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_client_subscribe ( lscp_client_t *pClient, lscp_event_t events ) { lscp_status_t ret = LSCP_OK; lscp_event_t currentEvent; if (pClient == NULL) return LSCP_FAILED; // Lock this section up. lscp_mutex_lock(pClient->mutex); // If applicable, start the alternate connection... if (pClient->events == LSCP_EVENT_NONE) ret = _lscp_client_evt_connect(pClient); // Send the subscription commands. if (ret == LSCP_OK && (events & LSCP_EVENT_CHANNEL_COUNT)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_CHANNEL_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_VOICE_COUNT)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_VOICE_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_STREAM_COUNT)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_STREAM_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_BUFFER_FILL)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_BUFFER_FILL); if (ret == LSCP_OK && (events & LSCP_EVENT_CHANNEL_INFO)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_CHANNEL_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_TOTAL_VOICE_COUNT)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_TOTAL_VOICE_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INPUT_DEVICE_INFO)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_MIDI_INPUT_DEVICE_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INSTRUMENT_MAP_COUNT)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_MIDI_INSTRUMENT_MAP_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INSTRUMENT_MAP_INFO)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_MIDI_INSTRUMENT_MAP_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INSTRUMENT_COUNT)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_MIDI_INSTRUMENT_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INSTRUMENT_INFO)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_MIDI_INSTRUMENT_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_MISCELLANEOUS)) ret = _lscp_client_evt_request(pClient, 1, LSCP_EVENT_MISCELLANEOUS); // Caution: for the upper 16 bits, we don't use bit flags anymore ... currentEvent = events & 0xffff0000; if (ret == LSCP_OK && currentEvent) { switch (currentEvent) { case LSCP_EVENT_CHANNEL_MIDI: case LSCP_EVENT_DEVICE_MIDI: ret = _lscp_client_evt_request(pClient, 1, currentEvent); break; default: // unknown "upper" event type ret = LSCP_FAILED; break; } } // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return ret; } /** * Deregister frontend from receiving UDP event messages anymore. * @e Caution: since liblscp v0.5.5.4 you have to call * lscp_client_unsubscribe() for @e each event you want to unsubscribe. * That is the old bitflag approach was abondoned at this point. You can * however still register all older events with one lscp_client_subscribe() * call at once. Thus, the old behavior of this functions was not broken. * Those older events are namely: * @code * UNSUBSCRIBE CHANNEL_COUNT | VOICE_COUNT | STREAM_COUNT * | BUFFER_FILL | CHANNEL_INFO | TOTAL_VOICE_COUNT * | AUDIO_OUTPUT_DEVICE_COUNT | AUDIO_OUTPUT_DEVICE_INFO * | MIDI_INPUT_DEVICE_COUNT | MIDI_INPUT_DEVICE_INFO * | MIDI_INSTRUMENT_MAP_COUNT | MIDI_INSTRUMENT_MAP_INFO * | MIDI_INSTRUMENT_COUNT | MIDI_INSTRUMENT_INFO * | MISCELLANEOUS * @endcode * The old events occupy the lower 16 bits (as bit flags), and all younger * events enumerate the whole upper 16 bits range. The new, enumerated * events are namely: * @code * UNSUBSCRIBE CHANNEL_MIDI * @endcode * * @param pClient Pointer to client instance structure. * @param events LSCP event to unsubscribe. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_client_unsubscribe ( lscp_client_t *pClient, lscp_event_t events ) { lscp_status_t ret = LSCP_OK; lscp_event_t currentEvent; if (pClient == NULL) return LSCP_FAILED; // Lock this section up. lscp_mutex_lock(pClient->mutex); // Send the unsubscription commands. if (ret == LSCP_OK && (events & LSCP_EVENT_CHANNEL_COUNT)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_CHANNEL_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_VOICE_COUNT)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_VOICE_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_STREAM_COUNT)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_STREAM_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_BUFFER_FILL)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_BUFFER_FILL); if (ret == LSCP_OK && (events & LSCP_EVENT_CHANNEL_INFO)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_CHANNEL_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_TOTAL_VOICE_COUNT)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_TOTAL_VOICE_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INPUT_DEVICE_INFO)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_MIDI_INPUT_DEVICE_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INSTRUMENT_MAP_COUNT)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_MIDI_INSTRUMENT_MAP_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INSTRUMENT_MAP_INFO)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_MIDI_INSTRUMENT_MAP_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INSTRUMENT_COUNT)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_MIDI_INSTRUMENT_COUNT); if (ret == LSCP_OK && (events & LSCP_EVENT_MIDI_INSTRUMENT_INFO)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_MIDI_INSTRUMENT_INFO); if (ret == LSCP_OK && (events & LSCP_EVENT_MISCELLANEOUS)) ret = _lscp_client_evt_request(pClient, 0, LSCP_EVENT_MISCELLANEOUS); // Caution: for the upper 16 bits, we don't use bit flags anymore ... currentEvent = events & 0xffff0000; if (ret == LSCP_OK && currentEvent) { switch (currentEvent) { case LSCP_EVENT_CHANNEL_MIDI: case LSCP_EVENT_DEVICE_MIDI: ret = _lscp_client_evt_request(pClient, 0, currentEvent); break; default: // unknown "upper" event type ret = LSCP_FAILED; break; } } // If necessary, close the alternate connection... if (pClient->events == LSCP_EVENT_NONE) lscp_socket_agent_free(&(pClient->evt)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return ret; } /** * Getting current subscribed events. * * @param pClient Pointer to client instance structure. * * @returns The current subscrived bit-wise OR'ed event flags. */ lscp_event_t lscp_client_get_events ( lscp_client_t *pClient ) { if (pClient == NULL) return LSCP_EVENT_NONE; return pClient->events; } //------------------------------------------------------------------------- // Client command protocol functions. /** * Loading an instrument: * LOAD INSTRUMENT * * @param pClient Pointer to client instance structure. * @param pszFileName Instrument file name. * @param iInstrIndex Instrument index number. * @param iSamplerChannel Sampler Channel. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_load_instrument ( lscp_client_t *pClient, const char *pszFileName, int iInstrIndex, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; if (pszFileName == NULL || iSamplerChannel < 0) return LSCP_FAILED; sprintf(szQuery, "LOAD INSTRUMENT '%s' %d %d\r\n", pszFileName, iInstrIndex, iSamplerChannel); return lscp_client_query(pClient, szQuery); } /** * Loading an instrument in the background (non modal): * LOAD INSTRUMENT NON_MODAL * * @param pClient Pointer to client instance structure. * @param pszFileName Instrument file name. * @param iInstrIndex Instrument index number. * @param iSamplerChannel Sampler Channel. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_load_instrument_non_modal ( lscp_client_t *pClient, const char *pszFileName, int iInstrIndex, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; if (pszFileName == NULL || iSamplerChannel < 0) return LSCP_FAILED; sprintf(szQuery, "LOAD INSTRUMENT NON_MODAL '%s' %d %d\r\n", pszFileName, iInstrIndex, iSamplerChannel); return lscp_client_query(pClient, szQuery); } /** * Loading a sampler engine: * LOAD ENGINE * * @param pClient Pointer to client instance structure. * @param pszEngineName Engine name. * @param iSamplerChannel Sampler channel number. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_load_engine ( lscp_client_t *pClient, const char *pszEngineName, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; if (pszEngineName == NULL || iSamplerChannel < 0) return LSCP_FAILED; sprintf(szQuery, "LOAD ENGINE %s %d\r\n", pszEngineName, iSamplerChannel); return lscp_client_query(pClient, szQuery); } /** * Current number of sampler channels: * GET CHANNELS * * @param pClient Pointer to client instance structure. * * @returns The current total number of sampler channels on success, * -1 otherwise. */ int lscp_get_channels ( lscp_client_t *pClient ) { int iChannels = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET CHANNELS\r\n", 0) == LSCP_OK) iChannels = atoi(lscp_client_get_result(pClient)); // Unlock this section doen. lscp_mutex_unlock(pClient->mutex); return iChannels; } /** * List current sampler channels number identifiers: * LIST CHANNELS * * @param pClient Pointer to client instance structure. * * @returns An array of the sampler channels identifiers as positive integers, * terminated with -1 on success, NULL otherwise. */ int *lscp_list_channels ( lscp_client_t *pClient ) { const char *pszSeps = ","; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->channels) { lscp_isplit_destroy(pClient->channels); pClient->channels = NULL; } if (lscp_client_call(pClient, "LIST CHANNELS\r\n", 0) == LSCP_OK) pClient->channels = lscp_isplit_create(lscp_client_get_result(pClient), pszSeps); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pClient->channels; } /** * Adding a new sampler channel: * ADD CHANNEL * * @param pClient Pointer to client instance structure. * * @returns The new sampler channel number identifier, * or -1 in case of failure. */ int lscp_add_channel ( lscp_client_t *pClient ) { int iSamplerChannel = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "ADD CHANNEL\r\n", 0) == LSCP_OK) iSamplerChannel = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iSamplerChannel; } /** * Removing a sampler channel: * REMOVE CHANNEL * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_remove_channel ( lscp_client_t *pClient, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0) return LSCP_FAILED; sprintf(szQuery, "REMOVE CHANNEL %d\r\n", iSamplerChannel); return lscp_client_query(pClient, szQuery); } /** * Getting all available engines count: * GET AVAILABLE_ENGINES * * @param pClient Pointer to client instance structure. * * @returns The current total number of sampler engines on success, * -1 otherwise. */ int lscp_get_available_engines ( lscp_client_t *pClient ) { int iAvailableEngines = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET AVAILABLE_ENGINES\r\n", 0) == LSCP_OK) iAvailableEngines = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iAvailableEngines; } /** * Getting all available engines: * LIST AVAILABLE_ENGINES * * @param pClient Pointer to client instance structure. * * @returns A NULL terminated array of engine name strings, * or NULL in case of failure. */ const char **lscp_list_available_engines ( lscp_client_t *pClient ) { const char *pszSeps = ","; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->engines) { lscp_szsplit_destroy(pClient->engines); pClient->engines = NULL; } if (lscp_client_call(pClient, "LIST AVAILABLE_ENGINES\r\n", 0) == LSCP_OK) pClient->engines = lscp_szsplit_create(lscp_client_get_result(pClient), pszSeps); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return (const char **) pClient->engines; } /** * Getting information about an engine. * GET ENGINE INFO * * @param pClient Pointer to client instance structure. * @param pszEngineName Engine name. * * @returns A pointer to a @ref lscp_engine_info_t structure, with all the * information of the given sampler engine, or NULL in case of failure. */ lscp_engine_info_t *lscp_get_engine_info ( lscp_client_t *pClient, const char *pszEngineName ) { lscp_engine_info_t *pEngineInfo; char szQuery[LSCP_BUFSIZ]; const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; if (pClient == NULL) return NULL; if (pszEngineName == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); pEngineInfo = &(pClient->engine_info); lscp_engine_info_reset(pEngineInfo); sprintf(szQuery, "GET ENGINE INFO %s\r\n", pszEngineName); if (lscp_client_call(pClient, szQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "DESCRIPTION") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pEngineInfo->description), &pszToken); } else if (strcasecmp(pszToken, "VERSION") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pEngineInfo->version), &pszToken); } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pEngineInfo = NULL; // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pEngineInfo; } /** * Getting sampler channel informations: * GET CHANNEL INFO * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * * @returns A pointer to a @ref lscp_channel_info_t structure, with all the * information of the given sampler channel, or NULL in case of failure. */ lscp_channel_info_t *lscp_get_channel_info ( lscp_client_t *pClient, int iSamplerChannel ) { lscp_channel_info_t *pChannelInfo; char szQuery[LSCP_BUFSIZ]; const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; struct _locale_t locale; if (pClient == NULL) return NULL; if (iSamplerChannel < 0) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); pChannelInfo = &(pClient->channel_info); lscp_channel_info_reset(pChannelInfo); _save_and_set_c_locale(&locale); sprintf(szQuery, "GET CHANNEL INFO %d\r\n", iSamplerChannel); if (lscp_client_call(pClient, szQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "ENGINE_NAME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pChannelInfo->engine_name), &pszToken); } else if (strcasecmp(pszToken, "AUDIO_OUTPUT_DEVICE") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->audio_device = atoi(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "AUDIO_OUTPUT_CHANNELS") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->audio_channels = atoi(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "AUDIO_OUTPUT_ROUTING") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { if (pChannelInfo->audio_routing) lscp_isplit_destroy(pChannelInfo->audio_routing); pChannelInfo->audio_routing = lscp_isplit_create(pszToken, ","); } } else if (strcasecmp(pszToken, "INSTRUMENT_FILE") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pChannelInfo->instrument_file), &pszToken); } else if (strcasecmp(pszToken, "INSTRUMENT_NR") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->instrument_nr = atoi(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "INSTRUMENT_NAME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pChannelInfo->instrument_name), &pszToken); } else if (strcasecmp(pszToken, "INSTRUMENT_STATUS") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->instrument_status = atoi(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "MIDI_INPUT_DEVICE") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->midi_device = atoi(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "MIDI_INPUT_PORT") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->midi_port = atoi(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "MIDI_INPUT_CHANNEL") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { pszToken = lscp_ltrim(pszToken); if (strcasecmp(pszToken, "ALL") == 0) pChannelInfo->midi_channel = LSCP_MIDI_CHANNEL_ALL; else pChannelInfo->midi_channel = atoi(pszToken); } } else if (strcasecmp(pszToken, "MIDI_INSTRUMENT_MAP") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { pszToken = lscp_ltrim(pszToken); if (strcasecmp(pszToken, "NONE") == 0) pChannelInfo->midi_map = LSCP_MIDI_MAP_NONE; else if (strcasecmp(pszToken, "DEFAULT") == 0) pChannelInfo->midi_map = LSCP_MIDI_MAP_DEFAULT; else pChannelInfo->midi_map = atoi(pszToken); } } else if (strcasecmp(pszToken, "VOLUME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->volume = _atof(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "MUTE") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->mute = (strcasecmp(lscp_unquote(&pszToken, 0), "TRUE") == 0); } else if (strcasecmp(pszToken, "SOLO") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pChannelInfo->solo = (strcasecmp(lscp_unquote(&pszToken, 0), "TRUE") == 0); } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pChannelInfo = NULL; _restore_locale(&locale); // Unlock this section up. lscp_mutex_unlock(pClient->mutex); return pChannelInfo; } /** * Current number of active voices: * GET CHANNEL VOICE_COUNT * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * * @returns The number of voices currently active, -1 in case of failure. */ int lscp_get_channel_voice_count ( lscp_client_t *pClient, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; int iVoiceCount = -1; if (pClient == NULL) return -1; if (iSamplerChannel < 0) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); sprintf(szQuery, "GET CHANNEL VOICE_COUNT %d\r\n", iSamplerChannel); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) iVoiceCount = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iVoiceCount; } /** * Current number of active disk streams: * GET CHANNEL STREAM_COUNT * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * * @returns The number of active disk streams on success, -1 otherwise. */ int lscp_get_channel_stream_count ( lscp_client_t *pClient, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; int iStreamCount = -1; if (pClient == NULL) return -1; if (iSamplerChannel < 0) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); sprintf(szQuery, "GET CHANNEL STREAM_COUNT %d\r\n", iSamplerChannel); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) iStreamCount = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iStreamCount; } /** * Current least usage of active disk streams. * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * * @returns The usage percentage of the least filled active disk stream * on success, -1 otherwise. */ int lscp_get_channel_stream_usage ( lscp_client_t *pClient, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; int iStreamUsage = -1; const char *pszResult; const char *pszSeps = "[]%,"; char *pszToken; char *pch; int iStream; int iPercent; if (pClient == NULL) return -1; if (iSamplerChannel < 0) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); iStream = 0; sprintf(szQuery, "GET CHANNEL BUFFER_FILL PERCENTAGE %d\r\n", iSamplerChannel); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (*pszToken) { // Skip stream id. pszToken = lscp_strtok(NULL, pszSeps, &(pch)); if (pszToken == NULL) break; // Get least buffer fill percentage. iPercent = atol(pszToken); if (iStreamUsage > iPercent || iStream == 0) iStreamUsage = iPercent; iStream++; } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iStreamUsage; } /** * Current fill state of disk stream buffers: * GET CHANNEL BUFFER_FILL {BYTES|PERCENTAGE} * * @param pClient Pointer to client instance structure. * @param usage_type Usage type to be returned, either * @ref LSCP_USAGE_BYTES, or * @ref LSCP_USAGE_PERCENTAGE. * @param iSamplerChannel Sampler channel number. * * @returns A pointer to a @ref lscp_buffer_fill_t structure, with the * information of the current disk stream buffer fill usage, for the given * sampler channel, or NULL in case of failure. */ lscp_buffer_fill_t *lscp_get_channel_buffer_fill ( lscp_client_t *pClient, lscp_usage_t usage_type, int iSamplerChannel ) { lscp_buffer_fill_t *pBufferFill; char szQuery[LSCP_BUFSIZ]; int iStreamCount; const char *pszUsageType = (usage_type == LSCP_USAGE_BYTES ? "BYTES" : "PERCENTAGE"); const char *pszResult; const char *pszSeps = "[]%,"; char *pszToken; char *pch; int iStream; // Retrieve a channel stream estimation. iStreamCount = lscp_get_channel_stream_count(pClient, iSamplerChannel); if (iStreamCount < 0) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); // Check if we need to reallocate the stream usage array. if (pClient->iStreamCount != iStreamCount) { if (pClient->buffer_fill) free(pClient->buffer_fill); if (iStreamCount > 0) pClient->buffer_fill = (lscp_buffer_fill_t *) malloc(iStreamCount * sizeof(lscp_buffer_fill_t)); else pClient->buffer_fill = NULL; pClient->iStreamCount = iStreamCount; } // Get buffer fill usage... pBufferFill = pClient->buffer_fill; if (pBufferFill && iStreamCount > 0) { iStream = 0; pBufferFill = pClient->buffer_fill; sprintf(szQuery, "GET CHANNEL BUFFER_FILL %s %d\r\n", pszUsageType, iSamplerChannel); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken && iStream < pClient->iStreamCount) { if (*pszToken) { pBufferFill[iStream].stream_id = atol(pszToken); pszToken = lscp_strtok(NULL, pszSeps, &(pch)); if (pszToken == NULL) break; pBufferFill[iStream].stream_usage = atol(pszToken); iStream++; } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } // Reset the usage, whatever it was before. else while (iStream < pClient->iStreamCount) pBufferFill[iStream++].stream_usage = 0; } // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pBufferFill; } /** * Setting audio output type: * SET CHANNEL AUDIO_OUTPUT_TYPE * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param pszAudioDriver Audio output driver type (e.g. "ALSA" or "JACK"). * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_audio_type ( lscp_client_t *pClient, int iSamplerChannel, const char *pszAudioDriver ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || pszAudioDriver == NULL) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL AUDIO_OUTPUT_TYPE %d %s\r\n", iSamplerChannel, pszAudioDriver); return lscp_client_query(pClient, szQuery); } /** * Setting audio output device: * SET CHANNEL AUDIO_OUTPUT_DEVICE * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iAudioDevice Audio output device number identifier. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_audio_device ( lscp_client_t *pClient, int iSamplerChannel, int iAudioDevice ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iAudioDevice < 0) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL AUDIO_OUTPUT_DEVICE %d %d\r\n", iSamplerChannel, iAudioDevice); return lscp_client_query(pClient, szQuery); } /** * Setting audio output channel: * SET CHANNEL AUDIO_OUTPUT_CHANNEL * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iAudioOut Audio output device channel to be routed from. * @param iAudioIn Audio output device channel to be routed into. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_audio_channel ( lscp_client_t *pClient, int iSamplerChannel, int iAudioOut, int iAudioIn ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iAudioOut < 0 || iAudioIn < 0) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL AUDIO_OUTPUT_CHANNEL %d %d %d\r\n", iSamplerChannel, iAudioOut, iAudioIn); return lscp_client_query(pClient, szQuery); } /** * Setting MIDI input type: * SET CHANNEL MIDI_INPUT_TYPE * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param pszMidiDriver MIDI input driver type (e.g. "ALSA"). * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_midi_type ( lscp_client_t *pClient, int iSamplerChannel, const char *pszMidiDriver ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || pszMidiDriver == NULL) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL MIDI_INPUT_TYPE %d %s\r\n", iSamplerChannel, pszMidiDriver); return lscp_client_query(pClient, szQuery); } /** * Setting MIDI input device: * SET CHANNEL MIDI_INPUT_DEVICE * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iMidiDevice MIDI input device number identifier. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_midi_device ( lscp_client_t *pClient, int iSamplerChannel, int iMidiDevice ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iMidiDevice < 0) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL MIDI_INPUT_DEVICE %d %d\r\n", iSamplerChannel, iMidiDevice); return lscp_client_query(pClient, szQuery); } /** * Setting MIDI input port: * SET CHANNEL MIDI_INPUT_PORT * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iMidiPort MIDI input driver virtual port number. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_midi_port ( lscp_client_t *pClient, int iSamplerChannel, int iMidiPort ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iMidiPort < 0) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL MIDI_INPUT_PORT %d %d\r\n", iSamplerChannel, iMidiPort); return lscp_client_query(pClient, szQuery); } /** * Setting MIDI input channel: * SET CHANNEL MIDI_INPUT_CHANNEL * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iMidiChannel MIDI channel address number to listen (0-15) or * @ref LSCP_MIDI_CHANNEL_ALL (16) to listen on all channels. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_midi_channel ( lscp_client_t *pClient, int iSamplerChannel, int iMidiChannel ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iMidiChannel < 0 || iMidiChannel > 16) return LSCP_FAILED; if (iMidiChannel == LSCP_MIDI_CHANNEL_ALL) sprintf(szQuery, "SET CHANNEL MIDI_INPUT_CHANNEL %d ALL\r\n", iSamplerChannel); else sprintf(szQuery, "SET CHANNEL MIDI_INPUT_CHANNEL %d %d\r\n", iSamplerChannel, iMidiChannel); return lscp_client_query(pClient, szQuery); } /** * Setting MIDI instrument map: * SET CHANNEL MIDI_INSTRUMENT_MAP * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iMidiMap MIDI instrument map number, or either * @ref LSCP_MIDI_MAP_NONE or * @ref LSCP_MIDI_MAP_DEFAULT . * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_midi_map ( lscp_client_t *pClient, int iSamplerChannel, int iMidiMap ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL MIDI_INSTRUMENT_MAP %d ", iSamplerChannel); if (iMidiMap == LSCP_MIDI_MAP_NONE) strcat(szQuery , "NONE"); else if (iMidiMap == LSCP_MIDI_MAP_DEFAULT) strcat(szQuery , "DEFAULT"); else sprintf(szQuery + strlen(szQuery), "%d", iMidiMap); strcat(szQuery, "\r\n"); return lscp_client_query(pClient, szQuery); } /** * Setting channel volume: * SET CHANNEL VOLUME * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param fVolume Sampler channel volume as a positive floating point * number, where a value less than 1.0 for attenuation, * and greater than 1.0 for amplification. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_volume ( lscp_client_t *pClient, int iSamplerChannel, float fVolume ) { char szQuery[LSCP_BUFSIZ]; struct _locale_t locale; if (iSamplerChannel < 0 || fVolume < 0.0f) return LSCP_FAILED; _save_and_set_c_locale(&locale); sprintf(szQuery, "SET CHANNEL VOLUME %d %g\r\n", iSamplerChannel, fVolume); _restore_locale(&locale); return lscp_client_query(pClient, szQuery); } /** * Muting a sampler channel: * SET CHANNEL MUTE * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iMute Sampler channel mute state as a boolean value, * either 1 (one) to mute the channel or 0 (zero) * to unmute the channel. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_mute ( lscp_client_t *pClient, int iSamplerChannel, int iMute ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iMute < 0 || iMute > 1) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL MUTE %d %d\r\n", iSamplerChannel, iMute); return lscp_client_query(pClient, szQuery); } /** * Soloing a sampler channel: * SET CHANNEL SOLO * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iSolo Sampler channel solo state as a boolean value, * either 1 (one) to solo the channel or 0 (zero) * to unsolo the channel. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_channel_solo ( lscp_client_t *pClient, int iSamplerChannel, int iSolo ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iSolo < 0 || iSolo > 1) return LSCP_FAILED; sprintf(szQuery, "SET CHANNEL SOLO %d %d\r\n", iSamplerChannel, iSolo); return lscp_client_query(pClient, szQuery); } /** * Resetting a sampler channel: * RESET CHANNEL * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_reset_channel ( lscp_client_t *pClient, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0) return LSCP_FAILED; sprintf(szQuery, "RESET CHANNEL %d\r\n", iSamplerChannel); return lscp_client_query(pClient, szQuery); } /** * Resetting the sampler: * RESET * * @param pClient Pointer to client instance structure. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_reset_sampler ( lscp_client_t *pClient ) { // Do actual whole sampler reset... return lscp_client_query(pClient, "RESET\r\n"); } /** * Getting information about the server. * GET SERVER INFO * * @param pClient Pointer to client instance structure. * * @returns A pointer to a @ref lscp_server_info_t structure, with all the * information of the current connected server, or NULL in case of failure. */ lscp_server_info_t *lscp_get_server_info ( lscp_client_t *pClient ) { lscp_server_info_t *pServerInfo; const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); pServerInfo = &(pClient->server_info); lscp_server_info_reset(pServerInfo); if (lscp_client_call(pClient, "GET SERVER INFO\r\n", 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "DESCRIPTION") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pServerInfo->description), &pszToken); } else if (strcasecmp(pszToken, "VERSION") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pServerInfo->version), &pszToken); } else if (strcasecmp(pszToken, "PROTOCOL_VERSION") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pServerInfo->protocol_version), &pszToken); } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pServerInfo = NULL; // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pServerInfo; } /** * Current total number of active voices: * GET TOTAL_VOICE_COUNT * * @param pClient Pointer to client instance structure. * * @returns The total number of voices currently active, * -1 in case of failure. */ int lscp_get_total_voice_count ( lscp_client_t *pClient ) { int iVoiceCount = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET TOTAL_VOICE_COUNT\r\n", 0) == LSCP_OK) iVoiceCount = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iVoiceCount; } /** * Maximum amount of active voices: * GET TOTAL_VOICE_COUNT_MAX * * @param pClient Pointer to client instance structure. * * @returns The maximum amount of voices currently active, * -1 in case of failure. */ int lscp_get_total_voice_count_max ( lscp_client_t *pClient ) { int iVoiceCount = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET TOTAL_VOICE_COUNT_MAX\r\n", 0) == LSCP_OK) iVoiceCount = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iVoiceCount; } /** * Get global volume attenuation: * GET VOLUME * * @param pClient Pointer to client instance structure. * * @returns The global volume as positive floating point value usually in * the range between 0.0 and 1.0; in case of failure 0.0 is returned. */ float lscp_get_volume ( lscp_client_t *pClient ) { float fVolume = 0.0f; struct _locale_t locale; if (pClient == NULL) return 0.0f; // Lock this section up. lscp_mutex_lock(pClient->mutex); _save_and_set_c_locale(&locale); if (lscp_client_call(pClient, "GET VOLUME\r\n", 0) == LSCP_OK) fVolume = _atof(lscp_client_get_result(pClient)); _restore_locale(&locale); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return fVolume; } /** * Setting global volume attenuation: * SET VOLUME * * @param pClient Pointer to client instance structure. * @param fVolume Global volume parameter as positive floating point * value usually be in the range between 0.0 and 1.0, * that is for attenuating the overall volume. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_volume ( lscp_client_t *pClient, float fVolume ) { char szQuery[LSCP_BUFSIZ]; struct _locale_t locale; if (fVolume < 0.0f) return LSCP_FAILED; _save_and_set_c_locale(&locale); sprintf(szQuery, "SET VOLUME %g\r\n", fVolume); _restore_locale(&locale); return lscp_client_query(pClient, szQuery); } /** * Get global voice limit setting: * @code * GET VOICES * @endcode * This value reflects the maximum amount of voices a sampler engine * processes simultaniously before voice stealing kicks in. * * @param pClient Pointer to client instance structure. * * @returns The current global maximum amount of voices limit or a * negative value on error (e.g. if sampler doesn't support * this command). */ int lscp_get_voices ( lscp_client_t *pClient ) { int iVoices = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET VOICES\r\n", 0) == LSCP_OK) iVoices = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iVoices; } /** * Setting global voice limit setting: * @code * SET VOICES * @endcode * This value reflects the maximum amount of voices a sampler engine * processes simultaniously before voice stealing kicks in. Note that * this value will be passed to all sampler engine instances, that is * the total amount of maximum voices on the running system is thus * @param iMaxVoices multiplied with the current amount of sampler * engine instances. * * @param pClient Pointer to client instance structure. * @param iMaxVoices Global voice limit setting as integer value larger * or equal to 1. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_voices ( lscp_client_t *pClient, int iMaxVoices ) { char szQuery[LSCP_BUFSIZ]; if (iMaxVoices < 1) return LSCP_FAILED; sprintf(szQuery, "SET VOICES %d\r\n", iMaxVoices); return lscp_client_query(pClient, szQuery); } /** * Get global disk streams limit setting: * @code * GET STREAMS * @endcode * This value reflects the maximum amount of disk streams a sampler * engine processes simultaniously. * * @param pClient Pointer to client instance structure. * * @returns The current global maximum amount of disk streams limit * or a negative value on error (e.g. if sampler doesn't * support this command). */ int lscp_get_streams ( lscp_client_t *pClient ) { int iStreams = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET STREAMS\r\n", 0) == LSCP_OK) iStreams = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iStreams; } /** * Setting global disk streams limit setting: * @code * SET STREAMS * @endcode * This value reflects the maximum amount of dist streams a sampler * engine instance processes simultaniously. Note that this value will * be passed to all sampler engine instances, that is the total amount * of maximum disk streams on the running system is thus * @param iMaxStreams multiplied with the current amount of sampler * engine instances. * * @param pClient Pointer to client instance structure. * @param iMaxStreams Global streams limit setting as positive integer * value (larger or equal to 0). * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_streams ( lscp_client_t *pClient, int iMaxStreams ) { char szQuery[LSCP_BUFSIZ]; if (iMaxStreams < 0) return LSCP_FAILED; sprintf(szQuery, "SET STREAMS %d\r\n", iMaxStreams); return lscp_client_query(pClient, szQuery); } /** * Add an effect send to a sampler channel: * CREATE FX_SEND [] * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iMidiController MIDI controller used to alter the effect, * usually a number between 0 and 127. * @param pszFxName Optional name for the effect send entity, * does not have to be unique. * * @returns The new effect send number identifier, or -1 in case of failure. */ int lscp_create_fxsend ( lscp_client_t *pClient, int iSamplerChannel, int iMidiController, const char *pszFxName ) { int iFxSend = -1; char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return -1; if (iSamplerChannel < 0 || iMidiController < 0 || iMidiController > 127) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); sprintf(szQuery, "CREATE FX_SEND %d %d", iSamplerChannel, iMidiController); if (pszFxName) sprintf(szQuery + strlen(szQuery), " '%s'", pszFxName); strcat(szQuery, "\r\n"); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) iFxSend = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iFxSend; } /** * Remove an effect send from a sampler channel: * DESTROY FX_SEND * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iFxSend Effect send number. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_destroy_fxsend ( lscp_client_t *pClient, int iSamplerChannel, int iFxSend ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iFxSend < 0) return LSCP_FAILED; sprintf(szQuery, "DESTROY FX_SEND %d %d\r\n", iSamplerChannel, iFxSend); return lscp_client_query(pClient, szQuery); } /** * Get amount of effect sends on a sampler channel: * GET FX_SENDS * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * * @returns The current total number of effect sends of the sampler channel * on success, -1 otherwise. */ int lscp_get_fxsends ( lscp_client_t *pClient, int iSamplerChannel ) { int iFxSends = -1; char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return -1; if (iSamplerChannel < 0) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); sprintf(szQuery, "GET FX_SENDS %d\r\n", iSamplerChannel); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) iFxSends = atoi(lscp_client_get_result(pClient)); // Unlock this section doen. lscp_mutex_unlock(pClient->mutex); return iFxSends; } /** * List all effect sends on a sampler channel: * LIST FX_SENDS * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * * @returns An array of the effect sends identifiers as positive integers, * terminated with -1 on success, NULL otherwise. */ int *lscp_list_fxsends ( lscp_client_t *pClient, int iSamplerChannel ) { const char *pszSeps = ","; char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->fxsends) { lscp_isplit_destroy(pClient->fxsends); pClient->fxsends = NULL; } sprintf(szQuery, "LIST FX_SENDS %d\r\n", iSamplerChannel); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) pClient->fxsends = lscp_isplit_create(lscp_client_get_result(pClient), pszSeps); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pClient->fxsends; } /** * Getting effect send information * GET FX_SEND INFO * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iFxSend Effect send number. * * @returns A pointer to a @ref lscp_fxsend_info_t structure, with the * information of the given FX send, or NULL in case of failure. */ lscp_fxsend_info_t *lscp_get_fxsend_info ( lscp_client_t *pClient, int iSamplerChannel, int iFxSend ) { lscp_fxsend_info_t *pFxSendInfo; char szQuery[LSCP_BUFSIZ]; const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; struct _locale_t locale; if (pClient == NULL) return NULL; if (iSamplerChannel < 0 || iFxSend < 0) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); _save_and_set_c_locale(&locale); pFxSendInfo = &(pClient->fxsend_info); lscp_fxsend_info_reset(pFxSendInfo); sprintf(szQuery, "GET FX_SEND INFO %d %d\r\n", iSamplerChannel, iFxSend); if (lscp_client_call(pClient, szQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "NAME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pFxSendInfo->name), &pszToken); } else if (strcasecmp(pszToken, "MIDI_CONTROLLER") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pFxSendInfo->midi_controller = atoi(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "AUDIO_OUTPUT_ROUTING") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { if (pFxSendInfo->audio_routing) lscp_isplit_destroy(pFxSendInfo->audio_routing); pFxSendInfo->audio_routing = lscp_isplit_create(pszToken, ","); } } else if (strcasecmp(pszToken, "LEVEL") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pFxSendInfo->level = _atof(lscp_ltrim(pszToken)); } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pFxSendInfo = NULL; _restore_locale(&locale); // Unlock this section up. lscp_mutex_unlock(pClient->mutex); return pFxSendInfo; } /** * Alter effect send's name: * @code * SET FX_SEND NAME * @endcode * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iFxSend Effect send number. * @param pszFxName Effect send's new name. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_fxsend_name ( lscp_client_t *pClient, int iSamplerChannel, int iFxSend, const char *pszFxName ) { char szQuery[LSCP_BUFSIZ]; if (!pClient || iSamplerChannel < 0 || iFxSend < 0 || !pszFxName) return LSCP_FAILED; snprintf(szQuery, LSCP_BUFSIZ, "SET FX_SEND NAME %d %d '%s'\r\n", iSamplerChannel, iFxSend, pszFxName); return lscp_client_query(pClient, szQuery); } /** * Alter effect send's audio routing: * SET FX_SEND AUDIO_OUTPUT_CHANNEL * * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iFxSend Effect send number. * @param iAudioSrc Audio output device channel to be routed from. * @param iAudioDst Audio output device channel to be routed into. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_fxsend_audio_channel ( lscp_client_t *pClient, int iSamplerChannel, int iFxSend, int iAudioSrc, int iAudioDst ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iFxSend < 0 || iAudioSrc < 0 || iAudioDst < 0) return LSCP_FAILED; sprintf(szQuery, "SET FX_SEND AUDIO_OUTPUT_CHANNEL %d %d %d %d\r\n", iSamplerChannel, iFxSend, iAudioSrc, iAudioDst); return lscp_client_query(pClient, szQuery); } /** * Alter effect send's MIDI controller: * SET FX_SEND MIDI_CONTROLLER * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iFxSend Effect send number. * @param iMidiController MIDI controller used to alter the effect, * usually a number between 0 and 127. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_fxsend_midi_controller ( lscp_client_t *pClient, int iSamplerChannel, int iFxSend, int iMidiController ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0 || iFxSend < 0 || iMidiController < 0 || iMidiController > 127) return LSCP_FAILED; sprintf(szQuery, "SET FX_SEND MIDI_CONTROLLER %d %d %d\r\n", iSamplerChannel, iFxSend, iMidiController); return lscp_client_query(pClient, szQuery); } /** * Alter effect send's audio level: * SET FX_SEND LEVEL * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler channel number. * @param iFxSend Effect send number. * @param fLevel Effect send volume level. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_fxsend_level ( lscp_client_t *pClient, int iSamplerChannel, int iFxSend, float fLevel ) { char szQuery[LSCP_BUFSIZ]; struct _locale_t locale; if (iSamplerChannel < 0 || iFxSend < 0 || fLevel < 0.0f) return LSCP_FAILED; _save_and_set_c_locale(&locale); sprintf(szQuery, "SET FX_SEND LEVEL %d %d %f\r\n", iSamplerChannel, iFxSend, fLevel); _restore_locale(&locale); return lscp_client_query(pClient, szQuery); } /** * Create a new MIDI instrument map: * ADD MIDI_INSTRUMENT_MAP [] * * @param pClient Pointer to client instance structure. * @param pszMapName MIDI instrument map name (optional) * * @returns The new MIDI instrument map number identifier, * or -1 in case of failure. */ int lscp_add_midi_instrument_map ( lscp_client_t *pClient, const char *pszMapName ) { int iMidiMap = -1; char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); strcpy(szQuery, "ADD MIDI_INSTRUMENT_MAP"); if (pszMapName) sprintf(szQuery + strlen(szQuery), " '%s'", pszMapName); strcat(szQuery, "\r\n"); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) iMidiMap = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iMidiMap; } /** * Delete one particular or all MIDI instrument maps: * REMOVE MIDI_INSTRUMENT_MAP * * @param pClient Pointer to client instance structure. * @param iMidiMap MIDI instrument map number. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_remove_midi_instrument_map ( lscp_client_t *pClient, int iMidiMap ) { char szQuery[LSCP_BUFSIZ]; if (iMidiMap < 0) return LSCP_FAILED; sprintf(szQuery, "REMOVE MIDI_INSTRUMENT_MAP %d\r\n", iMidiMap); return lscp_client_query(pClient, szQuery); } /** * Get amount of existing MIDI instrument maps: * GET MIDI_INSTRUMENT_MAPS * * @param pClient Pointer to client instance structure. * * @returns The current total number of MIDI instrument maps * on success, -1 otherwise. */ int lscp_get_midi_instrument_maps ( lscp_client_t *pClient ) { int iMidiMaps = -1; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (lscp_client_call(pClient, "GET MIDI_INSTRUMENT_MAPS\r\n", 0) == LSCP_OK) iMidiMaps = atoi(lscp_client_get_result(pClient)); // Unlock this section doen. lscp_mutex_unlock(pClient->mutex); return iMidiMaps; } /** * Getting all created MIDI instrument maps: * LIST MIDI_INSTRUMENT_MAPS * * @param pClient Pointer to client instance structure. * * @returns An array of the MIDI instrument map identifiers as positive * integers, terminated with -1 on success, NULL otherwise. */ int *lscp_list_midi_instrument_maps ( lscp_client_t *pClient ) { const char *pszSeps = ","; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->midi_maps) { lscp_isplit_destroy(pClient->midi_maps); pClient->midi_maps = NULL; } if (lscp_client_call(pClient, "LIST MIDI_INSTRUMENT_MAPS\r\n", 0) == LSCP_OK) pClient->midi_maps = lscp_isplit_create(lscp_client_get_result(pClient), pszSeps); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pClient->midi_maps; } /** * Getting a MIDI instrument map name: * GET MIDI_INSTRUMENT_MAP INFO * * @param pClient Pointer to client instance structure. * @param iMidiMap MIDI instrument map number. * * @returns The MIDI instrument map name on success, NULL on failure. */ const char *lscp_get_midi_instrument_map_name ( lscp_client_t *pClient, int iMidiMap ) { char szQuery[LSCP_BUFSIZ]; const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; if (pClient == NULL) return NULL; if (iMidiMap < 0) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->midi_map_name) { free(pClient->midi_map_name); pClient->midi_map_name = NULL; } sprintf(szQuery, "GET MIDI_INSTRUMENT_MAP INFO %d\r\n", iMidiMap); if (lscp_client_call(pClient, szQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "NAME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pClient->midi_map_name), &pszToken); } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pClient->midi_map_name; } /** * Renaming a MIDI instrument map: * SET MIDI_INSTRUMENT_MAP NAME * * @param pClient Pointer to client instance structure. * @param iMidiMap MIDI instrument map number. * @param pszMapName MIDI instrument map name. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_set_midi_instrument_map_name ( lscp_client_t *pClient, int iMidiMap, const char *pszMapName ) { char szQuery[LSCP_BUFSIZ]; if (iMidiMap < 0) return LSCP_FAILED; if (pszMapName == NULL) return LSCP_FAILED; sprintf(szQuery, "SET MIDI_INSTRUMENT_MAP NAME %d '%s'\r\n", iMidiMap, pszMapName); return lscp_client_query(pClient, szQuery); } /** * Create or replace a MIDI instrumnet map entry: * MAP MIDI_INSTRUMENT * [ []} * * @param pClient Pointer to client instance structure. * @param pMidiInstr MIDI instrument bank and program parameter key. * @param pszEngineName Engine name. * @param pszFileName Instrument file name. * @param iInstrIndex Instrument index number. * @param fVolume Reflects the master volume of the instrument as * a positive floating point number, where a value * less than 1.0 for attenuation, and greater than * 1.0 for amplification. * @param load_mode Instrument load life-time strategy, either * @ref LSCP_LOAD_DEFAULT, or * @ref LSCP_LOAD_ON_DEMAND, or * @ref LSCP_LOAD_ON_DEMAND_HOLD, or * @ref LSCP_LOAD_PERSISTENT. * @param pszName Instrument custom name for the map entry (optional). * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_map_midi_instrument ( lscp_client_t *pClient, lscp_midi_instrument_t *pMidiInstr, const char *pszEngineName, const char *pszFileName, int iInstrIndex, float fVolume, lscp_load_mode_t load_mode, const char *pszName ) { char szQuery[LSCP_BUFSIZ]; struct _locale_t locale; if (pMidiInstr->map < 0) return LSCP_FAILED; if (pMidiInstr->bank < 0 || pMidiInstr->bank > 16383) return LSCP_FAILED; if (pMidiInstr->prog < 0 || pMidiInstr->prog > 127) return LSCP_FAILED; if (pszEngineName == NULL || pszFileName == NULL) return LSCP_FAILED; if (fVolume < 0.0f) fVolume = 1.0f; _save_and_set_c_locale(&locale); sprintf(szQuery, "MAP MIDI_INSTRUMENT %d %d %d %s '%s' %d %g", pMidiInstr->map, pMidiInstr->bank, pMidiInstr->prog, pszEngineName, pszFileName, iInstrIndex, fVolume); _restore_locale(&locale); switch (load_mode) { case LSCP_LOAD_PERSISTENT: strcat(szQuery, " PERSISTENT"); break; case LSCP_LOAD_ON_DEMAND_HOLD: strcat(szQuery, " ON_DEMAND_HOLD"); break; case LSCP_LOAD_ON_DEMAND: strcat(szQuery, " ON_DEMAND"); break; case LSCP_LOAD_DEFAULT: default: break; } if (pszName) sprintf(szQuery + strlen(szQuery), " '%s'", pszName); strcat(szQuery, "\r\n"); return lscp_client_query(pClient, szQuery); } /** * Remove an entry from the MIDI instrument map: * UNMAP MIDI_INSTRUMENT * * @param pClient Pointer to client instance structure. * @param pMidiInstr MIDI instrument bank and program parameter key. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_unmap_midi_instrument ( lscp_client_t *pClient, lscp_midi_instrument_t *pMidiInstr ) { char szQuery[LSCP_BUFSIZ]; if (pMidiInstr->map < 0) return LSCP_FAILED; if (pMidiInstr->bank < 0 || pMidiInstr->bank > 16383) return LSCP_FAILED; if (pMidiInstr->prog < 0 || pMidiInstr->prog > 127) return LSCP_FAILED; sprintf(szQuery, "UNMAP MIDI_INSTRUMENT %d %d %d\r\n", pMidiInstr->map, pMidiInstr->bank, pMidiInstr->prog); return lscp_client_query(pClient, szQuery); } /** * Get the total count of MIDI instrument map entries: * GET MIDI_INSTRUMENTS ALL| * * @param pClient Pointer to client instance structure. * @param iMidiMap MIDI instrument map number, or @ref LSCP_MIDI_MAP_ALL . * * @returns The current total number of MIDI instrument map entries * on success, -1 otherwise. */ int lscp_get_midi_instruments ( lscp_client_t *pClient, int iMidiMap ) { int iInstruments = -1; char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return -1; // Lock this section up. lscp_mutex_lock(pClient->mutex); strcpy(szQuery, "GET MIDI_INSTRUMENTS "); if (iMidiMap < 0) strcat(szQuery, "ALL"); else sprintf(szQuery + strlen(szQuery), "%d", iMidiMap); strcat(szQuery, "\r\n"); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) iInstruments = atoi(lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return iInstruments; } /** * Getting indeces of all MIDI instrument map entries: * LIST MIDI_INSTRUMENTS ALL| * * @param pClient Pointer to client instance structure. * @param iMidiMap MIDI instrument map number, or @ref LSCP_MIDI_MAP_ALL . * * @returns An array of @ref lscp_midi_instrument_t, terminated with the * {-1,-1,-1} triplet, NULL otherwise. */ lscp_midi_instrument_t *lscp_list_midi_instruments ( lscp_client_t *pClient, int iMidiMap ) { char szQuery[LSCP_BUFSIZ]; if (pClient == NULL) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); if (pClient->midi_instruments) { lscp_midi_instruments_destroy(pClient->midi_instruments); pClient->midi_instruments = NULL; } strcpy(szQuery, "LIST MIDI_INSTRUMENTS "); if (iMidiMap < 0) strcat(szQuery, "ALL"); else sprintf(szQuery + strlen(szQuery), "%d", iMidiMap); strcat(szQuery, "\r\n"); if (lscp_client_call(pClient, szQuery, 0) == LSCP_OK) pClient->midi_instruments = lscp_midi_instruments_create( lscp_client_get_result(pClient)); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pClient->midi_instruments; } /** * Getting information about a MIDI instrument map entry: * GET MIDI_INSTRUMENT INFO * * @param pClient Pointer to client instance structure. * @param pMidiInstr MIDI instrument bank and program parameter key. * * @returns A pointer to a @ref lscp_midi_instrument_info_t structure, * with all the information of the given MIDI instrument map entry, * or NULL in case of failure. */ lscp_midi_instrument_info_t *lscp_get_midi_instrument_info ( lscp_client_t *pClient, lscp_midi_instrument_t *pMidiInstr ) { lscp_midi_instrument_info_t *pInstrInfo; char szQuery[LSCP_BUFSIZ]; const char *pszResult; const char *pszSeps = ":"; const char *pszCrlf = "\r\n"; char *pszToken; char *pch; struct _locale_t locale; if (pClient == NULL) return NULL; if (pMidiInstr->map < 0) return NULL; if (pMidiInstr->bank < 0 || pMidiInstr->bank > 16383) return NULL; if (pMidiInstr->prog < 0 || pMidiInstr->prog > 127) return NULL; // Lock this section up. lscp_mutex_lock(pClient->mutex); _save_and_set_c_locale(&locale); pInstrInfo = &(pClient->midi_instrument_info); lscp_midi_instrument_info_reset(pInstrInfo); sprintf(szQuery, "GET MIDI_INSTRUMENT INFO %d %d %d\r\n", pMidiInstr->map, pMidiInstr->bank, pMidiInstr->prog); if (lscp_client_call(pClient, szQuery, 1) == LSCP_OK) { pszResult = lscp_client_get_result(pClient); pszToken = lscp_strtok((char *) pszResult, pszSeps, &(pch)); while (pszToken) { if (strcasecmp(pszToken, "NAME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pInstrInfo->name), &pszToken); } else if (strcasecmp(pszToken, "ENGINE_NAME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pInstrInfo->engine_name), &pszToken); } else if (strcasecmp(pszToken, "INSTRUMENT_FILE") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pInstrInfo->instrument_file), &pszToken); } else if (strcasecmp(pszToken, "INSTRUMENT_NR") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pInstrInfo->instrument_nr = atoi(lscp_ltrim(pszToken)); } else if (strcasecmp(pszToken, "INSTRUMENT_NAME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) lscp_unquote_dup(&(pInstrInfo->instrument_name), &pszToken); } else if (strcasecmp(pszToken, "LOAD_MODE") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) { pszToken = lscp_ltrim(pszToken); if (strcasecmp(pszToken, "ON_DEMAND") == 0) pInstrInfo->load_mode = LSCP_LOAD_ON_DEMAND; else if (strcasecmp(pszToken, "ON_DEMAND_HOLD") == 0) pInstrInfo->load_mode = LSCP_LOAD_ON_DEMAND_HOLD; else if (strcasecmp(pszToken, "PERSISTENT") == 0) pInstrInfo->load_mode = LSCP_LOAD_PERSISTENT; else pInstrInfo->load_mode = LSCP_LOAD_DEFAULT; } } else if (strcasecmp(pszToken, "VOLUME") == 0) { pszToken = lscp_strtok(NULL, pszCrlf, &(pch)); if (pszToken) pInstrInfo->volume = _atof(lscp_ltrim(pszToken)); } pszToken = lscp_strtok(NULL, pszSeps, &(pch)); } } else pInstrInfo = NULL; _restore_locale(&locale); // Unlock this section down. lscp_mutex_unlock(pClient->mutex); return pInstrInfo; } /** * Clear the MIDI instrumnet map: * CLEAR MIDI_INSTRUMENTS ALL| * * @param pClient Pointer to client instance structure. * @param iMidiMap MIDI instrument map number, or @ref LSCP_MIDI_MAP_ALL . * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_clear_midi_instruments ( lscp_client_t *pClient, int iMidiMap ) { char szQuery[LSCP_BUFSIZ]; strcpy(szQuery, "CLEAR MIDI_INSTRUMENTS "); if (iMidiMap < 0) strcat(szQuery, "ALL"); else sprintf(szQuery + strlen(szQuery), "%d", iMidiMap); strcat(szQuery, "\r\n"); return lscp_client_query(pClient, szQuery); } /** * Open an instrument editor application for the instrument * on the given sampler channel: * EDIT CHANNEL INSTRUMENT * * @param pClient Pointer to client instance structure. * @param iSamplerChannel Sampler Channel. * * @returns LSCP_OK on success, LSCP_FAILED otherwise. */ lscp_status_t lscp_edit_channel_instrument ( lscp_client_t *pClient, int iSamplerChannel ) { char szQuery[LSCP_BUFSIZ]; if (iSamplerChannel < 0) return LSCP_FAILED; sprintf(szQuery, "EDIT CHANNEL INSTRUMENT %d\r\n", iSamplerChannel); return lscp_client_query(pClient, szQuery); } // end of client.c liblscp-1.0.1/src/socket.c0000644000175000017500000001762114771257264014522 0ustar drag0ndrag0n// socket.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "lscp/socket.h" //------------------------------------------------------------------------- // Socket info debugging. #if defined(WIN32) static struct { int iError; const char *pszError; } _wsaErrorCodes[] = { { 0, "No error" }, { WSAEINTR, "Interrupted system call" }, { WSAEBADF, "Bad file number" }, { WSAEACCES, "Permission denied" }, { WSAEFAULT, "Bad address" }, { WSAEINVAL, "Invalid argument" }, { WSAEMFILE, "Too many open sockets" }, { WSAEWOULDBLOCK, "Operation would block" }, { WSAEINPROGRESS, "Operation now in progress" }, { WSAEALREADY, "Operation already in progress" }, { WSAENOTSOCK, "Socket operation on non-socket" }, { WSAEDESTADDRREQ, "Destination address required" }, { WSAEMSGSIZE, "Message too long" }, { WSAEPROTOTYPE, "Protocol wrong type for socket" }, { WSAENOPROTOOPT, "Bad protocol option" }, { WSAEPROTONOSUPPORT, "Protocol not supported" }, { WSAESOCKTNOSUPPORT, "Socket type not supported" }, { WSAEOPNOTSUPP, "Operation not supported on socket" }, { WSAEPFNOSUPPORT, "Protocol family not supported" }, { WSAEAFNOSUPPORT, "Address family not supported" }, { WSAEADDRINUSE, "Address already in use" }, { WSAEADDRNOTAVAIL, "Can't assign requested address" }, { WSAENETDOWN, "Network is down" }, { WSAENETUNREACH, "Network is unreachable" }, { WSAENETRESET, "Net connection reset" }, { WSAECONNABORTED, "Software caused connection abort" }, { WSAECONNRESET, "Connection reset by peer" }, { WSAENOBUFS, "No buffer space available" }, { WSAEISCONN, "Socket is already connected" }, { WSAENOTCONN, "Socket is not connected" }, { WSAESHUTDOWN, "Can't send after socket shutdown" }, { WSAETOOMANYREFS, "Too many references, can't splice" }, { WSAETIMEDOUT, "Connection timed out" }, { WSAECONNREFUSED, "Connection refused" }, { WSAELOOP, "Too many levels of symbolic links" }, { WSAENAMETOOLONG, "File name too long" }, { WSAEHOSTDOWN, "Host is down" }, { WSAEHOSTUNREACH, "No route to host" }, { WSAENOTEMPTY, "Directory not empty" }, { WSAEPROCLIM, "Too many processes" }, { WSAEUSERS, "Too many users" }, { WSAEDQUOT, "Disc quota exceeded" }, { WSAESTALE, "Stale NFS file handle" }, { WSAEREMOTE, "Too many levels of remote in path" }, { WSASYSNOTREADY, "Network system is unavailable" }, { WSAVERNOTSUPPORTED, "Winsock version out of range" }, { WSANOTINITIALISED, "WSAStartup not yet called" }, { WSAEDISCON, "Graceful shutdown in progress" }, { WSAHOST_NOT_FOUND, "Host not found" }, { WSANO_DATA, "No host data of that type was found" }, { 0, NULL } }; void lscp_socket_perror ( const char *pszPrefix ) { int iError = WSAGetLastError(); const char *pszError = "Unknown error code"; int i; for (i = 0; _wsaErrorCodes[i].pszError; i++) { if (_wsaErrorCodes[i].iError == iError) { pszError = _wsaErrorCodes[i].pszError; break; } } fprintf(stderr, "%s: %s (%d)\n", pszPrefix, pszError, iError); } void lscp_socket_herror ( const char *pszPrefix ) { lscp_socket_perror(pszPrefix); } #else void lscp_socket_perror ( const char *pszPrefix ) { perror(pszPrefix); } void lscp_socket_herror ( const char *pszPrefix ) { herror(pszPrefix); } #endif static void _lscp_socket_getopt_bool ( lscp_socket_t sock, const char *pszOptName, int iOptName ) { int iSockOpt; socklen_t cSockLen = sizeof(int); char szPrefix[33]; sprintf(szPrefix, " %s\t", pszOptName); if (getsockopt(sock, SOL_SOCKET, iOptName, (char *) &iSockOpt, &cSockLen) == SOCKET_ERROR) lscp_socket_perror(szPrefix); else fprintf(stderr, "%s: %s\n", szPrefix, (iSockOpt ? "ON" : "OFF")); } static void _lscp_socket_getopt_int ( lscp_socket_t sock, const char *pszOptName, int iOptName ) { int iSockOpt; socklen_t cSockLen = sizeof(int); char szPrefix[33]; sprintf(szPrefix, " %s\t", pszOptName); if (getsockopt(sock, SOL_SOCKET, iOptName, (char *) &iSockOpt, &cSockLen) == SOCKET_ERROR) lscp_socket_perror(szPrefix); else fprintf(stderr, "%s: %d\n", szPrefix, iSockOpt); } void lscp_socket_getopts ( const char *pszPrefix, lscp_socket_t sock ) { fprintf(stderr, "%s: sock=%d:\n", pszPrefix, sock); _lscp_socket_getopt_bool(sock, "SO_BROADCAST", SO_BROADCAST); _lscp_socket_getopt_bool(sock, "SO_DEBUG", SO_DEBUG); #if defined(WIN32) _lscp_socket_getopt_bool(sock, "SO_DONTLINGER", SO_DONTLINGER); #endif _lscp_socket_getopt_bool(sock, "SO_DONTROUTE", SO_DONTROUTE); _lscp_socket_getopt_bool(sock, "SO_KEEPALIVE", SO_KEEPALIVE); _lscp_socket_getopt_bool(sock, "SO_OOBINLINE", SO_OOBINLINE); _lscp_socket_getopt_int (sock, "SO_RCVBUF", SO_RCVBUF); _lscp_socket_getopt_bool(sock, "SO_REUSEADDR", SO_REUSEADDR); _lscp_socket_getopt_int (sock, "SO_SNDBUF", SO_SNDBUF); } void lscp_socket_trace ( const char *pszPrefix, struct sockaddr_in *pAddr, const char *pchBuffer, int cchBuffer ) { char *pszBuffer; fprintf(stderr, "%s: addr=%s port=%d:\n", pszPrefix, inet_ntoa(pAddr->sin_addr), htons(pAddr->sin_port) ); if (pchBuffer && cchBuffer > 0) { pszBuffer = (char *) malloc(cchBuffer + 1); if (pszBuffer) { memcpy(pszBuffer, pchBuffer, cchBuffer); while (cchBuffer > 0 && (pszBuffer[cchBuffer - 1] == '\n' || pszBuffer[cchBuffer- 1] == '\r')) cchBuffer--; pszBuffer[cchBuffer] = (char) 0; fprintf(stderr, "< %s\n", pszBuffer); free(pszBuffer); } } else fprintf(stderr, "< (null)\n"); } //------------------------------------------------------------------------- // Threaded socket agent struct helpers. void lscp_socket_agent_init ( lscp_socket_agent_t *pAgent, lscp_socket_t sock, struct sockaddr_in *pAddr, int cAddr ) { memset(pAgent, 0, sizeof(lscp_socket_agent_t)); pAgent->sock = sock; pAgent->pThread = NULL; pAgent->iState = 0; if (pAddr) memmove((char *) &(pAgent->addr), pAddr, cAddr); } lscp_status_t lscp_socket_agent_start ( lscp_socket_agent_t *pAgent, lscp_thread_proc_t pfnProc, void *pvData, int iDetach ) { if (pAgent->iState) pAgent->iState = 0; if (pAgent->pThread) lscp_thread_destroy(pAgent->pThread); pAgent->iState = 1; pAgent->pThread = lscp_thread_create(pfnProc, pvData, iDetach); return (pAgent->pThread == NULL ? LSCP_FAILED : LSCP_OK); } lscp_status_t lscp_socket_agent_join ( lscp_socket_agent_t *pAgent ) { lscp_status_t ret = LSCP_FAILED; if (pAgent->pThread) ret = lscp_thread_join(pAgent->pThread); return ret; } lscp_status_t lscp_socket_agent_free ( lscp_socket_agent_t *pAgent ) { lscp_status_t ret = LSCP_FAILED; if (pAgent->iState) pAgent->iState = 0; if (pAgent->sock != INVALID_SOCKET) closesocket(pAgent->sock); pAgent->sock = INVALID_SOCKET; if (pAgent->pThread) ret = lscp_thread_destroy(pAgent->pThread); pAgent->pThread = NULL; return ret; } // end of socket.c liblscp-1.0.1/src/common.c0000644000175000017500000006741314771257264014526 0ustar drag0ndrag0n// common.c // /**************************************************************************** liblscp - LinuxSampler Control Protocol API Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "common.h" #include #include #ifdef WIN32 # include #else # include #endif // Split chunk size magic: // LSCP_SPLIT_CHUNK1 := 2 ^ LSCP_SPLIT_CHUNK2 #define LSCP_SPLIT_CHUNK1 4 #define LSCP_SPLIT_CHUNK2 2 // Chunk size legal calculator. #define LSCP_SPLIT_SIZE(n) ((((n) >> LSCP_SPLIT_CHUNK2) + 1) << LSCP_SPLIT_CHUNK2) //------------------------------------------------------------------------- // Local client request executive. // Result buffer internal settler. void lscp_client_set_result ( lscp_client_t *pClient, char *pszResult, int iErrno ) { if (pClient->pszResult) free(pClient->pszResult); pClient->pszResult = NULL; pClient->iErrno = iErrno; if (pszResult) pClient->pszResult = strdup(lscp_ltrim(pszResult)); } // The common client receiver executive. lscp_status_t lscp_client_recv ( lscp_client_t *pClient, char *pchBuffer, int *pcchBuffer, int iTimeout ) { fd_set fds; // File descriptor list for select(). int fd, fdmax; // Maximum file descriptor number. struct timeval tv; // For specifying a timeout value. int iSelect; // Holds select return status. lscp_status_t ret = LSCP_FAILED; if (pClient == NULL) return ret; // Prepare for waiting on select... fd = (int) pClient->cmd.sock; FD_ZERO(&fds); FD_SET((unsigned int) fd, &fds); fdmax = fd; // Use the timeout select feature... if (iTimeout < 1) iTimeout = pClient->iTimeout; if (iTimeout >= 1000) { tv.tv_sec = iTimeout / 1000; iTimeout -= tv.tv_sec * 1000; } else tv.tv_sec = 0; tv.tv_usec = iTimeout * 1000; // Wait for event... iSelect = select(fdmax + 1, &fds, NULL, NULL, &tv); if (iSelect > 0 && FD_ISSET(fd, &fds)) { // May recv now... *pcchBuffer = recv(pClient->cmd.sock, pchBuffer, *pcchBuffer, 0); if (*pcchBuffer > 0) ret = LSCP_OK; else if (*pcchBuffer < 0) lscp_socket_perror("lscp_client_recv: recv"); else if (*pcchBuffer == 0) { // Damn, server probably disconnected, // we better free everything down here. lscp_socket_agent_free(&(pClient->evt)); lscp_socket_agent_free(&(pClient->cmd)); // Fake a result message. ret = LSCP_QUIT; } } // Check if select has timed out. else if (iSelect == 0) ret = LSCP_TIMEOUT; else lscp_socket_perror("lscp_client_recv: select"); return ret; } // The main client requester call executive. lscp_status_t lscp_client_call ( lscp_client_t *pClient, const char *pszQuery, int iResult ) { int cchQuery; char achBuffer[LSCP_BUFSIZ]; int cchBuffer; const char *pszSeps = ":[]"; char *pszBuffer; char *pszToken; char *pch; int iErrno; char *pszResult; int cchResult; ssize_t sz; lscp_status_t ret = LSCP_FAILED; if (pClient == NULL) return ret; iErrno = -1; cchResult = 0; pszResult = NULL; pszBuffer = NULL; // Check if command socket socket is still valid. if (pClient->cmd.sock == INVALID_SOCKET) { pszResult = "Connection closed or no longer valid"; lscp_client_set_result(pClient, pszResult, iErrno); return ret; } // Check if last transaction has timed out, in which case // we'll retry wait and flush for some pending garbage... if (pClient->iTimeoutCount > 0) { // We'll hope to get rid of timeout trouble... pClient->iTimeoutCount = 0; cchBuffer = sizeof(achBuffer); ret = lscp_client_recv(pClient, achBuffer, &cchBuffer, pClient->iTimeout); if (ret != LSCP_OK) { // Things seems to be unresolved. Fake a result message. iErrno = (int) ret; pszResult = "Failure during flush timeout operation"; lscp_client_set_result(pClient, pszResult, iErrno); return ret; } } // Send data, and then, wait for the result... cchQuery = strlen(pszQuery); sz = send(pClient->cmd.sock, pszQuery, cchQuery, 0); if (sz < cchQuery) { lscp_socket_perror("lscp_client_call: send"); pszResult = "Failure during send operation"; if (sz < 0) iErrno = -errno; lscp_client_set_result(pClient, pszResult, iErrno); return ret; } // Keep receiving while result is not the expected one: // single-line result (iResult = 0) : one single CRLF ends the receipt; // multi-line result (iResult > 0) : one "." followed by a last CRLF; while (pszResult == NULL) { // Wait for receive event... cchBuffer = sizeof(achBuffer) - 1; ret = lscp_client_recv(pClient, achBuffer, &cchBuffer, pClient->iTimeout); switch (ret) { case LSCP_OK: // Always force the result to be null terminated. achBuffer[cchBuffer] = (char) 0; // Check if the response it's an error or warning message. if (strncasecmp(achBuffer, "WRN:", 4) == 0) ret = LSCP_WARNING; else if (strncasecmp(achBuffer, "ERR:", 4) == 0) ret = LSCP_ERROR; // So we got a result... if (ret == LSCP_OK) { // Reset errno in case of success. iErrno = 0; // Is it a special successful response? if (iResult < 1 && strncasecmp(achBuffer, "OK[", 3) == 0) { // Parse the OK message, get the return string under brackets... pszToken = lscp_strtok(achBuffer, pszSeps, &(pch)); if (pszToken) pszResult = lscp_strtok(NULL, pszSeps, &(pch)); } else { // It can be specially long response... cchResult += sizeof(achBuffer); pszResult = malloc(cchResult + 1); pszResult[0] = (char) 0; if (pszBuffer) { strcat(pszResult, pszBuffer); free(pszBuffer); } strcat(pszResult, achBuffer); pszBuffer = pszResult; pszResult = NULL; // Check for correct end-of-transmission... // Depending whether its single or multi-line we'll // flag end-of-transmission... cchBuffer = strlen(pszBuffer); if (cchBuffer >= 2 && pszBuffer[cchBuffer - 1] == '\n' && pszBuffer[cchBuffer - 2] == '\r' && (iResult < 1 || (cchBuffer >= 3 && pszBuffer[cchBuffer - 3] == '.'))) { // Get rid of the trailling dot and CRLF anyway... while (cchBuffer > 0 && ( pszBuffer[cchBuffer - 1] == '\r' || pszBuffer[cchBuffer - 1] == '\n' || pszBuffer[cchBuffer - 1] == '.')) cchBuffer--; pszBuffer[cchBuffer] = (char) 0; pszResult = pszBuffer; } } // The result string is now set to the command response, if any. } else { // Get rid of the CRLF anyway... while (cchBuffer > 0 && ( achBuffer[cchBuffer - 1] == '\r' || achBuffer[cchBuffer - 1] == '\n')) achBuffer[--cchBuffer] = (char) 0; // Parse the error/warning message, skip first colon... pszToken = lscp_strtok(achBuffer, pszSeps, &(pch)); if (pszToken) { // Get the error number... pszToken = lscp_strtok(NULL, pszSeps, &(pch)); if (pszToken) { iErrno = atoi(pszToken) + 100; // And make the message text our final result. pszResult = lscp_strtok(NULL, pszSeps, &(pch)); } } // The result string is set to the error/warning message text. } break; case LSCP_TIMEOUT: // We have trouble... pClient->iTimeoutCount++; // Fake a result message. pszResult = "Timeout during receive operation"; iErrno = (int) ret; break; case LSCP_QUIT: // Fake a result message. pszResult = "Server terminated the connection"; iErrno = (int) ret; break; case LSCP_FAILED: default: // What's down? pszResult = "Failure during receive operation"; break; } } // Make the result official... lscp_client_set_result(pClient, pszResult, iErrno); // Free long-buffer, if any... if (pszBuffer) free(pszBuffer); return ret; } //------------------------------------------------------------------------- // Other general utility functions. // Trimming left spaces... char *lscp_ltrim ( char *psz ) { while (isspace(*psz)) psz++; return psz; } // Unquote an in-split string. char *lscp_unquote ( char **ppsz, int dup ) { char chQuote; char *psz = *ppsz; while (isspace(*psz)) ++psz; if (*psz == '\"' || *psz == '\'') { chQuote = *psz++; while (isspace(*psz)) ++psz; if (dup) psz = strdup(psz); *ppsz = psz; if (*ppsz) { while (**ppsz && **ppsz != chQuote) ++(*ppsz); if (**ppsz) { while (isspace(*(*ppsz - 1)) && *ppsz > psz) --(*ppsz); *(*ppsz)++ = (char) 0; } } } else if (dup) { psz = strdup(psz); *ppsz = psz; } return psz; } // Unquote and make a duplicate of an in-split string. void lscp_unquote_dup ( char **ppszDst, char **ppszSrc ) { // Free desteny string, if already there. if (*ppszDst) free(*ppszDst); *ppszDst = NULL; // Unquote and duplicate. if (*ppszSrc) *ppszDst = lscp_unquote(ppszSrc, 1); } // Custom tokenizer. char *lscp_strtok ( char *pchBuffer, const char *pszSeps, char **ppch ) { char *pszToken; if (pchBuffer == NULL) pchBuffer = *ppch; pchBuffer += strspn(pchBuffer, pszSeps); if (*pchBuffer == '\0') return NULL; pszToken = pchBuffer; pchBuffer = strpbrk(pszToken, pszSeps); if (pchBuffer == NULL) { *ppch = strchr(pszToken, '\0'); } else { *pchBuffer = '\0'; *ppch = pchBuffer + 1; while (**ppch && strchr(pszSeps, **ppch)) (*ppch)++; } return pszToken; } // Split a comma separated string into a null terminated array of strings. char **lscp_szsplit_create ( const char *pszCsv, const char *pszSeps ) { char *pszHead, *pch; int iSize, i, j, cchSeps; char **ppszSplit, **ppszNewSplit; // Initial size is one chunk away. iSize = LSCP_SPLIT_CHUNK1; // Allocate and split... ppszSplit = (char **) malloc(iSize * sizeof(char *)); if (ppszSplit == NULL) return NULL; // Make a copy of the original string. i = 0; pszHead = (char *) pszCsv; if ((ppszSplit[i++] = lscp_unquote(&pszHead, 1)) == NULL) { free(ppszSplit); return NULL; } // Go on for it... cchSeps = strlen(pszSeps); while ((pch = strpbrk(pszHead, pszSeps)) != NULL) { // Pre-advance to next item. pszHead = pch + cchSeps; // Trim and null terminate current item. while (isspace(*(pch - 1)) && pch > ppszSplit[0]) --pch; *pch = (char) 0; // Make it official. ppszSplit[i] = lscp_unquote(&pszHead, 0); // Do we need to grow? if (++i >= iSize) { // Yes, but only grow in chunks. iSize += LSCP_SPLIT_CHUNK1; // Allocate and copy to new split array. ppszNewSplit = (char **) malloc(iSize * sizeof(char *)); if (ppszNewSplit) { for (j = 0; j < i; j++) ppszNewSplit[j] = ppszSplit[j]; free(ppszSplit); ppszSplit = ppszNewSplit; } } } // NULL terminate split array. for ( ; i < iSize; i++) ppszSplit[i] = NULL; return ppszSplit; } // Free allocated memory of a legal null terminated array of strings. void lscp_szsplit_destroy ( char **ppszSplit ) { // Our split string is always the first item, if any. if (ppszSplit && ppszSplit[0]) free(ppszSplit[0]); // Now free the array itself. if (ppszSplit) free(ppszSplit); } #ifdef LSCP_SZSPLIT_COUNT // Return the number of items of a null terminated array of strings. int lscp_szsplit_count ( char **ppszSplit ) { int i = 0; while (ppszSplit && ppszSplit[i]) i++; return i; } // Return the allocated number of items of a splitted string array. int lscp_szsplit_size ( char **ppszSplit ) { return LSCP_SPLIT_SIZE(lscp_szsplit_count(ppszSplit)); } #endif // LSCP_SZSPLIT_COUNT // Split a comma separated string into a -1 terminated array of positive integers. int *lscp_isplit_create ( const char *pszCsv, const char *pszSeps ) { char *pchHead, *pch; int iSize, i, j, cchSeps; int *piSplit, *piNewSplit; // Get it clean first. pchHead = lscp_ltrim((char *) pszCsv); if (*pchHead == (char) 0) return NULL; // Initial size is one chunk away. iSize = LSCP_SPLIT_CHUNK1; // Allocate and split... piSplit = (int *) malloc(iSize * sizeof(int)); if (piSplit == NULL) return NULL; // Make a copy of the original string. i = 0; if ((piSplit[i++] = atoi(pchHead)) < 0) { free(piSplit); return NULL; } // Go on for it... cchSeps = strlen(pszSeps); while ((pch = strpbrk(pchHead, pszSeps)) != NULL) { // Pre-advance to next item. pchHead = pch + cchSeps; // Make it official. piSplit[i] = atoi(pchHead); // Do we need to grow? if (++i >= iSize) { // Yes, but only grow in chunks. iSize += LSCP_SPLIT_CHUNK1; // Allocate and copy to new split array. piNewSplit = (int *) malloc(iSize * sizeof(int)); if (piNewSplit) { for (j = 0; j < i; j++) piNewSplit[j] = piSplit[j]; free(piSplit); piSplit = piNewSplit; } } } // NULL terminate split array. for ( ; i < iSize; i++) piSplit[i] = -1; return piSplit; } // Destroy a integer splitted array. void lscp_isplit_destroy ( int *piSplit ) { if (piSplit) free(piSplit); } #ifdef LSCP_ISPLIT_COUNT // Compute a string list valid item count. int lscp_isplit_count ( int *piSplit ) { int i = 0; while (piSplit && piSplit[i] >= 0) i++; return i; } // Compute a string list size. int lscp_isplit_size ( int *piSplit ) { return LSCP_SPLIT_SIZE(lscp_isplit_count(piSplit)); } #endif // LSCP_ISPLIT_COUNT // Split a string into a null terminated array of parameter items. lscp_param_t *lscp_psplit_create ( const char *pszCsv, const char *pszSeps1, const char *pszSeps2 ) { char *pszHead, *pch; int iSize, i, j, cchSeps1, cchSeps2; lscp_param_t *ppSplit, *ppNewSplit; pszHead = strdup(pszCsv); if (pszHead == NULL) return NULL; iSize = LSCP_SPLIT_CHUNK1; ppSplit = (lscp_param_t *) malloc(iSize * sizeof(lscp_param_t)); if (ppSplit == NULL) { free(pszHead); return NULL; } cchSeps1 = strlen(pszSeps1); cchSeps2 = strlen(pszSeps2); i = 0; while ((pch = strpbrk(pszHead, pszSeps1)) != NULL) { ppSplit[i].key = pszHead; pszHead = pch + cchSeps1; *pch = (char) 0; ppSplit[i].value = lscp_unquote(&pszHead, 0); if ((pch = strpbrk(pszHead, pszSeps2)) != NULL) { pszHead = pch + cchSeps2; *pch = (char) 0; } if (++i >= iSize) { iSize += LSCP_SPLIT_CHUNK1; ppNewSplit = (lscp_param_t *) malloc(iSize * sizeof(lscp_param_t)); if (ppNewSplit) { for (j = 0; j < i; j++) { ppNewSplit[j].key = ppSplit[j].key; ppNewSplit[j].value = ppSplit[j].value; } free(ppSplit); ppSplit = ppNewSplit; } } } if (i < 1) free(pszHead); for ( ; i < iSize; i++) { ppSplit[i].key = NULL; ppSplit[i].value = NULL; } return ppSplit; } // Destroy a parameter list array. void lscp_psplit_destroy ( lscp_param_t *ppSplit ) { if (ppSplit && ppSplit[0].key) free(ppSplit[0].key); if (ppSplit) free(ppSplit); } #ifdef LSCP_PSPLIT_COUNT // Compute a parameter list valid item count. int lscp_psplit_count ( lscp_param_t *ppSplit ) { int i = 0; while (ppSplit && ppSplit[i].key) i++; return i; } // Compute a parameter list size. int lscp_psplit_size ( lscp_param_t *ppSplit ) { return LSCP_SPLIT_SIZE(lscp_psplit_count(ppSplit)); } #endif // LSCP_PSPLIT_COUNT // Allocate a parameter list, optionally copying an existing one. void lscp_plist_alloc (lscp_param_t **ppList) { lscp_param_t *pParams; int iSize, i; if (ppList) { iSize = LSCP_SPLIT_CHUNK1; pParams = (lscp_param_t *) malloc(iSize * sizeof(lscp_param_t)); if (pParams) { for (i = 0 ; i < iSize; i++) { pParams[i].key = NULL; pParams[i].value = NULL; } } *ppList = pParams; } } // Destroy a parameter list, including all it's contents. void lscp_plist_free ( lscp_param_t **ppList ) { lscp_param_t *pParams; int i; if (ppList) { if (*ppList) { pParams = *ppList; for (i = 0; pParams && pParams[i].key; i++) { free(pParams[i].key); free(pParams[i].value); } free(pParams); } *ppList = NULL; } } // Add an item to a parameter list, growing it as fit. void lscp_plist_append ( lscp_param_t **ppList, const char *pszKey, const char *pszValue ) { lscp_param_t *pParams; lscp_param_t *pNewParams; int iSize, iNewSize; int i = 0; if (ppList && *ppList) { pParams = *ppList; while (pParams[i].key) { if (strcasecmp(pParams[i].key, pszKey) == 0) { if (pParams[i].value) free(pParams[i].value); pParams[i].value = strdup(pszValue); return; } i++; } iSize = LSCP_SPLIT_SIZE(i); pParams[i].key = strdup(pszKey); pParams[i].value = strdup(pszValue); if (++i >= iSize) { iNewSize = iSize + LSCP_SPLIT_CHUNK1; pNewParams = (lscp_param_t *) malloc(iNewSize * sizeof(lscp_param_t)); for (i = 0; i < iSize; i++) { pNewParams[i].key = pParams[i].key; pNewParams[i].value = pParams[i].value; } for ( ; i < iNewSize; i++) { pNewParams[i].key = NULL; pNewParams[i].value = NULL; } free(pParams); *ppList = pNewParams; } } } #ifdef LSCP_PLIST_COUNT // Compute a parameter list valid item count. int lscp_plist_count ( lscp_param_t **ppList ) { lscp_param_t *pParams; int i = 0; if (ppList && *ppList) { pParams = *ppList; while (pParams[i].key) i++; } return i; } // Compute the legal parameter list size. int lscp_plist_size ( lscp_param_t **ppList ) { return LSCP_SPLIT_SIZE(lscp_plist_count(ppList)); } #endif // LSCP_PLIST_COUNT // Split a string into an array of MIDI instrument triplets. lscp_midi_instrument_t *lscp_midi_instruments_create ( const char *pszCsv ) { char *pchHead, *pch; int iSize, i, j, k; lscp_midi_instrument_t *pInstrs; lscp_midi_instrument_t *pNewInstrs; // Get it clean first. pchHead = lscp_ltrim((char *) pszCsv); if (*pchHead == (char) 0) return NULL; // Initial size is one chunk away. iSize = LSCP_SPLIT_CHUNK1; // Allocate and split... pInstrs = (lscp_midi_instrument_t *) malloc(iSize * sizeof(lscp_midi_instrument_t)); if (pInstrs == NULL) return NULL; // Go on for it... i = 0; k = 0; while ((pch = strpbrk(pchHead, "{,}")) != NULL) { // Pre-advance to next item. switch (*pch) { case '{': pchHead = pch + 1; if (k == 0) { pInstrs[i].map = atoi(pchHead); k++; } break; case ',': pchHead = pch + 1; if (k == 1) { pInstrs[i].bank = atoi(pchHead); k++; } else if (k == 2) { pInstrs[i].prog = atoi(pchHead); k++; } break; case '}': pchHead = pch + 1; k = 0; break; } // Do we need to grow? if (k == 3 && ++i >= iSize) { // Yes, but only grow in chunks. iSize += LSCP_SPLIT_CHUNK1; // Allocate and copy to new split array. pNewInstrs = (lscp_midi_instrument_t *) malloc(iSize * sizeof(lscp_midi_instrument_t)); if (pNewInstrs) { for (j = 0; j < i; j++) { pNewInstrs[j].map = pInstrs[j].map; pNewInstrs[j].bank = pInstrs[j].bank; pNewInstrs[j].prog = pInstrs[j].prog; } free(pInstrs); pInstrs = pNewInstrs; } } } // Special terminate split array. for ( ; i < iSize; i++) { pInstrs[i].map = -1; pInstrs[i].bank = -1; pInstrs[i].prog = -1; } return pInstrs; } // Destroy a MIDI instrument triplet array. void lscp_midi_instruments_destroy ( lscp_midi_instrument_t *pInstrs ) { if (pInstrs) free(pInstrs); } #ifdef LSCP_MIDI_INSTRUMENTS_COUNT // Compute a MIDI instrument array item count. int lscp_midi_instruments_count ( lscp_midi_instrument_t *pInstrs ) { int i = 0; while (pInstrs && pInstrs[i].program >= 0) i++; return i; } // Compute a MIDI instrument array size. int lscp_midi_instruments_size ( lscp_midi_instrument_t *pInstrs ) { return LSCP_SPLIT_SIZE(lscp_midi_instruments_count(pInstrs)); } #endif // LSCP_MIDI_INSTRUMENTS_COUNT //------------------------------------------------------------------------- // Server info struct helper functions. void lscp_server_info_init ( lscp_server_info_t *pServerInfo ) { pServerInfo->description = NULL; pServerInfo->version = NULL; pServerInfo->protocol_version = NULL; } void lscp_server_info_free ( lscp_server_info_t *pServerInfo ) { if (pServerInfo->description) free(pServerInfo->description); if (pServerInfo->version) free(pServerInfo->version); if (pServerInfo->protocol_version) free(pServerInfo->protocol_version); } void lscp_server_info_reset ( lscp_server_info_t *pServerInfo ) { lscp_server_info_free(pServerInfo); lscp_server_info_init(pServerInfo); } //------------------------------------------------------------------------- // Engine info struct helper functions. void lscp_engine_info_init ( lscp_engine_info_t *pEngineInfo ) { pEngineInfo->description = NULL; pEngineInfo->version = NULL; } void lscp_engine_info_free ( lscp_engine_info_t *pEngineInfo ) { if (pEngineInfo->description) free(pEngineInfo->description); if (pEngineInfo->version) free(pEngineInfo->version); } void lscp_engine_info_reset ( lscp_engine_info_t *pEngineInfo ) { lscp_engine_info_free(pEngineInfo); lscp_engine_info_init(pEngineInfo); } //------------------------------------------------------------------------- // Channel info struct helper functions. void lscp_channel_info_init ( lscp_channel_info_t *pChannelInfo ) { pChannelInfo->engine_name = NULL; pChannelInfo->audio_device = 0; pChannelInfo->audio_channels = 0; pChannelInfo->audio_routing = NULL; pChannelInfo->instrument_file = NULL; pChannelInfo->instrument_nr = 0; pChannelInfo->instrument_name = NULL; pChannelInfo->instrument_status = 0; pChannelInfo->midi_device = 0; pChannelInfo->midi_port = 0; pChannelInfo->midi_channel = 0; pChannelInfo->midi_map = 0; pChannelInfo->volume = 0.0; pChannelInfo->mute = 0; pChannelInfo->solo = 0; } void lscp_channel_info_free ( lscp_channel_info_t *pChannelInfo ) { if (pChannelInfo->engine_name) free(pChannelInfo->engine_name); if (pChannelInfo->audio_routing) lscp_isplit_destroy(pChannelInfo->audio_routing); if (pChannelInfo->instrument_file) free(pChannelInfo->instrument_file); if (pChannelInfo->instrument_name) free(pChannelInfo->instrument_name); } void lscp_channel_info_reset ( lscp_channel_info_t *pChannelInfo ) { lscp_channel_info_free(pChannelInfo); lscp_channel_info_init(pChannelInfo); } //------------------------------------------------------------------------- // Driver info struct functions. void lscp_driver_info_init ( lscp_driver_info_t *pDriverInfo ) { pDriverInfo->description = NULL; pDriverInfo->version = NULL; pDriverInfo->parameters = NULL; } void lscp_driver_info_free ( lscp_driver_info_t *pDriverInfo ) { if (pDriverInfo->description) free(pDriverInfo->description); if (pDriverInfo->version) free(pDriverInfo->version); lscp_szsplit_destroy(pDriverInfo->parameters); } void lscp_driver_info_reset ( lscp_driver_info_t *pDriverInfo ) { lscp_driver_info_free(pDriverInfo); lscp_driver_info_init(pDriverInfo); } //------------------------------------------------------------------------- // Device info struct functions. void lscp_device_info_init ( lscp_device_info_t *pDeviceInfo ) { pDeviceInfo->driver = NULL; lscp_plist_alloc(&(pDeviceInfo->params)); } void lscp_device_info_free ( lscp_device_info_t *pDeviceInfo ) { if (pDeviceInfo->driver) free(pDeviceInfo->driver); lscp_plist_free(&(pDeviceInfo->params)); } void lscp_device_info_reset ( lscp_device_info_t *pDeviceInfo ) { lscp_device_info_free(pDeviceInfo); lscp_device_info_init(pDeviceInfo); } //------------------------------------------------------------------------- // Device channel/port info struct functions. void lscp_device_port_info_init ( lscp_device_port_info_t *pDevicePortInfo ) { pDevicePortInfo->name = NULL; lscp_plist_alloc(&(pDevicePortInfo->params)); } void lscp_device_port_info_free ( lscp_device_port_info_t *pDevicePortInfo ) { if (pDevicePortInfo->name) free(pDevicePortInfo->name); lscp_plist_free(&(pDevicePortInfo->params)); } void lscp_device_port_info_reset ( lscp_device_port_info_t *pDevicePortInfo ) { lscp_device_port_info_free(pDevicePortInfo); lscp_device_port_info_init(pDevicePortInfo); } //------------------------------------------------------------------------- // Parameter struct helper functions. void lscp_param_info_init ( lscp_param_info_t *pParamInfo ) { pParamInfo->type = LSCP_TYPE_NONE; pParamInfo->description = NULL; pParamInfo->mandatory = 0; pParamInfo->fix = 0; pParamInfo->multiplicity = 0; pParamInfo->depends = NULL; pParamInfo->defaultv = NULL; pParamInfo->range_min = NULL; pParamInfo->range_max = NULL; pParamInfo->possibilities = NULL; } void lscp_param_info_free ( lscp_param_info_t *pParamInfo ) { if (pParamInfo->description) free(pParamInfo->description); lscp_szsplit_destroy(pParamInfo->depends); if (pParamInfo->defaultv) free(pParamInfo->defaultv); if (pParamInfo->range_min) free(pParamInfo->range_min); if (pParamInfo->range_max) free(pParamInfo->range_max); lscp_szsplit_destroy(pParamInfo->possibilities); } void lscp_param_info_reset ( lscp_param_info_t *pParamInfo ) { lscp_param_info_free(pParamInfo); lscp_param_info_init(pParamInfo); } //------------------------------------------------------------------------- // Concatenate a parameter list (key='value'...) into a string, // appending a crlf terminator. int lscp_param_concat ( char *pszBuffer, int cchMaxBuffer, lscp_param_t *pParams ) { int cchBuffer, cchParam, i; if (pszBuffer == NULL) return 0; cchBuffer = strlen(pszBuffer); for (i = 0; pParams && pParams[i].key && pParams[i].value; i++) { cchParam = strlen(pParams[i].key) + strlen(pParams[i].value) + 4; if (cchBuffer + cchParam + 2 < cchMaxBuffer) { sprintf(pszBuffer + cchBuffer, " %s='%s'", pParams[i].key, pParams[i].value); cchBuffer += cchParam; } } if (cchBuffer + 2 < cchMaxBuffer) { pszBuffer[cchBuffer++] = '\r'; pszBuffer[cchBuffer++] = '\n'; pszBuffer[cchBuffer ] = (char) 0; } return cchBuffer; } //------------------------------------------------------------------------- // Effect struct helper functions. void lscp_fxsend_info_init ( lscp_fxsend_info_t *pFxSendInfo ) { pFxSendInfo->name = NULL; pFxSendInfo->midi_controller = 0; pFxSendInfo->audio_routing = NULL; pFxSendInfo->level = 0.0f; } void lscp_fxsend_info_free ( lscp_fxsend_info_t *pFxSendInfo ) { if (pFxSendInfo->name) free(pFxSendInfo->name); if (pFxSendInfo->audio_routing) lscp_isplit_destroy(pFxSendInfo->audio_routing); } void lscp_fxsend_info_reset (lscp_fxsend_info_t *pFxSendInfo ) { lscp_fxsend_info_free(pFxSendInfo); lscp_fxsend_info_init(pFxSendInfo); } //------------------------------------------------------------------------- // MIDI instrument info struct helper functions. void lscp_midi_instrument_info_init ( lscp_midi_instrument_info_t *pInstrInfo ) { pInstrInfo->name = NULL; pInstrInfo->engine_name = NULL; pInstrInfo->instrument_file = NULL; pInstrInfo->instrument_nr = 0; pInstrInfo->instrument_name = NULL; pInstrInfo->load_mode = LSCP_LOAD_DEFAULT; pInstrInfo->volume = 0.0; } void lscp_midi_instrument_info_free ( lscp_midi_instrument_info_t *pInstrInfo ) { if (pInstrInfo->name) free(pInstrInfo->name); if (pInstrInfo->engine_name) free(pInstrInfo->engine_name); if (pInstrInfo->instrument_file) free(pInstrInfo->instrument_file); if (pInstrInfo->instrument_name) free(pInstrInfo->instrument_name); } void lscp_midi_instrument_info_reset ( lscp_midi_instrument_info_t *pInstrInfo ) { lscp_midi_instrument_info_free(pInstrInfo); lscp_midi_instrument_info_init(pInstrInfo); } // end of common.c liblscp-1.0.1/CMakeLists.txt0000644000175000017500000001006414771257264015031 0ustar drag0ndrag0ncmake_minimum_required (VERSION 3.13) project (liblscp VERSION 1.0.1 DESCRIPTION "LinuxSampler Control Protocol API library" HOMEPAGE_URL "https://qsampler.sourceforge.io" LANGUAGES C) set (PROJECT_COPYRIGHT "Copyright (C) 2004-2025, rncbc aka Rui Nuno Capela. All rights reserved.") set (PROJECT_DOMAIN "linuxsampler.org") execute_process ( COMMAND git describe --tags --dirty --abbrev=6 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_DESCRIBE_OUTPUT RESULT_VARIABLE GIT_DESCRIBE_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) if (GIT_DESCRIBE_RESULT EQUAL 0) set (GIT_VERSION "${GIT_DESCRIBE_OUTPUT}") string (REGEX REPLACE "^[^0-9]+" "" GIT_VERSION "${GIT_VERSION}") string (REGEX REPLACE "-g" "git." GIT_VERSION "${GIT_VERSION}") string (REGEX REPLACE "[_|-]" "." GIT_VERSION "${GIT_VERSION}") execute_process ( COMMAND git rev-parse --abbrev-ref HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_REVPARSE_OUTPUT RESULT_VARIABLE GIT_REVPARSE_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) if (GIT_REVPARSE_RESULT EQUAL 0 AND NOT GIT_REVPARSE_OUTPUT STREQUAL "main") set (GIT_VERSION "${GIT_VERSION} [${GIT_REVPARSE_OUTPUT}]") endif () set (PROJECT_VERSION "${GIT_VERSION}") endif () set (CONFIG_PREFIX "${CMAKE_INSTALL_PREFIX}") include (GNUInstallDirs) set (CONFIG_LIBDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_LIBDIR}") set (CONFIG_INCLUDEDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}") #------------------------------------------------------------------------------------ # Rules for library version information: # # 1. Start with version information of `0:0:0' for each libtool library. # 2. Update the version information only immediately before a public release of # your software. More frequent updates are unnecessary, and only guarantee # that the current interface number gets larger faster. # 3. If the library source code has changed at all since the last update, then # increment revision (`c:r:a' becomes `c:r+1:a'). # 4. If any interfaces have been added, removed, or changed since the last update, # increment current, and set revision to 0. # 5. If any interfaces have been added since the last public release, then increment # age. # 6. If any interfaces have been removed since the last public release, then set age # to 0. # #set (BUILD_SHARED_LIBS ON) set (SHARED_VERSION_CURRENT 6) set (SHARED_VERSION_AGE 0) set (SHARED_VERSION_REVISION 6) set (SHARED_VERSION_INFO "${SHARED_VERSION_CURRENT}.${SHARED_VERSION_AGE}.${SHARED_VERSION_REVISION}") if (CMAKE_BUILD_TYPE MATCHES "Debug") set (CONFIG_DEBUG 1) set (CONFIG_BUILD_TYPE "debug") else () set (CONFIG_DEBUG 0) set (CONFIG_BUILD_TYPE "release") set (CMAKE_BUILD_TYPE "Release") endif () set (CONFIG_PREFIX "${CMAKE_INSTALL_PREFIX}") include (GNUInstallDirs) set (CONFIG_BINDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_BINDIR}") set (CONFIG_LIBDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_LIBDIR}") set (CONFIG_DATADIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_DATADIR}") set (CONFIG_INCLUDEDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}") # # No special build options... # # Fix for new CMAKE_REQUIRED_LIBRARIES policy. if (POLICY CMP0075) cmake_policy (SET CMP0075 NEW) endif () include (CheckIncludeFiles) include (CheckFunctionExists) # Checks for standard header files. if (UNIX AND NOT APPLE) check_include_files ("stdlib.h;string.h;netdb.h;arpa/inet.h;netinet/tcp.h;netinet/in.h;sys/socket.h;unistd.h" HAVE_HEADERS_H) if (NOT HAVE_HEADERS_H) message (FATAL_ERROR "*** Standard headers not found.") endif () endif () # Checks for standard functions. if (UNIX AND NOT APPLE) set (FUNCS "strdup;strtok_r;memset;memmove;socket;connect;bind;listen;setsockopt;getsockopt;getsockname;gethostbyname") foreach (FUNC ${FUNCS}) check_function_exists (${FUNC} HAVE_${FUNC}_C) if (NOT HAVE_${FUNC}_C) message (FATAL_ERROR "*** Standard functions not found.") endif () endforeach () endif () # # No additional packages or libraries... # add_subdirectory (src) add_subdirectory (doc) add_subdirectory (examples) liblscp-1.0.1/README0000644000175000017500000000126214771257264013151 0ustar drag0ndrag0nliblscp - LinuxSampler Control Protocol API ------------------------------------------- liblscp is an implementation of the LinuxSampler control protocol, proposed as a C language API. Homepage: https://www.linuxsampler.org License: GNU Lesser General Public License (LGPL) version 2 or later. Installation ------------ Unpack the tarball and in the extracted source directory: cmake [-DCMAKE_INSTALL_PREFIX=] -B build cmake --build build [--parallel ] and optionally, as root: [sudo] cmake --install build Note that the default installation path () is /usr/local . Enjoy. -- rncbc aka Rui Nuno Capela rncbc at rncbc.org https://www.rncbc.org liblscp-1.0.1/doc/0000755000175000017500000000000014771257264013035 5ustar drag0ndrag0nliblscp-1.0.1/doc/CMakeLists.txt0000644000175000017500000000064714771257264015604 0ustar drag0ndrag0n# project(liblscp) find_package (Doxygen) if (DOXYGEN_FOUND) set (top_srcdir "${CMAKE_CURRENT_SOURCE_DIR}/..") configure_file (liblscp.doxygen.in liblscp.doxygen IMMEDIATE @ONLY) add_custom_target (doxygen ALL COMMAND ${DOXYGEN_EXECUTABLE} liblscp.doxygen WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR}) endif () liblscp-1.0.1/doc/liblscp.doxygen.in0000644000175000017500000033421414771257264016500 0ustar drag0ndrag0n# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = @PROJECT_NAME@ # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = @PROJECT_VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = . # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all generated output in the proper direction. # Possible values are: None, LTR, RTL and Context. # The default value is: None. OUTPUT_TEXT_DIRECTION = None # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # By default Python docstrings are displayed as preformatted text and doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the # doxygen's special commands can be used and the contents of the docstring # documentation blocks is shown as doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines (in the resulting output). You can put ^^ in the value part of an # alias to insert a newline as if a physical newline was in the original file. # When you need a literal { or } or , in the value part of an alias you have to # escape them by means of a backslash (\), this can lead to conflicts with the # commands \{ and \} for these it is advised to use the version @{ and @} or use # a double escape (\\{ and \\}) ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 5 # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, # which efficively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If this flag is set to YES, the name of an unnamed parameter in a declaration # will be determined by the corresponding definition. By default unnamed # parameters remain unnamed in the output. # The default value is: YES. RESOLVE_UNNAMED_PARAMS = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that # are not case sensitive the option should be be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. If # EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. # Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = @top_srcdir@ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), # *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, # *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.dox \ *.h \ *.c # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = *common.h *common.c *examples/* *CMakeFiles* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = @top_srcdir@/examples # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = *.h \ *.c # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = NO # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To # create a documentation set, doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: # https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path # including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = YES # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. # Possible values are: png (the default) and svg (looks nicer but requires the # pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. # The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /