diff options
41 files changed, 7781 insertions, 26 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 571044c..b1abd96 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,8 +5,8 @@ stages: .variables_template: &variables_definition variables: - BASE_NAME: "timely.lv2" - PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig" + BASE_NAME: "monitors.lv2" + PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig" .common_template: &common_definition <<: *variables_definition @@ -19,12 +19,12 @@ stages: .build_template: &build_definition <<: *common_definition script: - - mkdir build - - pushd build - - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR} -DPLUGIN_DEST="${BASE_NAME}-$(cat ../VERSION)/${CI_BUILD_NAME}/${BASE_NAME}" -DCMAKE_CI_BUILD_NAME=${CI_BUILD_NAME} .. - - cmake .. # needed for darwin - - make - - make install + - meson --prefix="/opt/${CI_BUILD_NAME}" --libdir="lib" --cross-file "${CI_BUILD_NAME}" build + - sed -i -e '/framework/s/-Wl,-O1//g' -e '/framework/s/-Wl,--start-group//g' -e '/framework/s/-Wl,--end-group//g' -e '/framework/s/-Wl,-soname,.*dylib//g' build/build.ninja + - ninja -C build + - ninja -C build install + - mkdir -p "${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/${BASE_NAME}" + - cp -r "/opt/${CI_BUILD_NAME}/lib/lv2/${BASE_NAME}/" "${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/" .universal_linux_template: &universal_linux_definition image: ventosus/universal-linux-gnu @@ -1,10 +1,31 @@ -# Timely.lv2 +# Monitors.lv2 -## Utility header for time-based LV2 plugins +## LV2 monitors plugins + +### Webpage + +Get more information at: [http://open-music-kontrollers.ch/lv2/monitors](http://open-music-kontrollers.ch/lv2/monitors) + +### Build status + +[](https://gitlab.com/OpenMusicKontrollers/monitors.lv2/commits/master) + +### Dependencies + +* [LV2](http://lv2plug.in) (LV2 Plugin Standard) + +### Build / install + + git clone https://git.open-music-kontrollers.ch/lv2/monitors.lv2 + cd monitors.lv2 + meson build + cd build + ninja -j4 + sudo ninja install ### License -Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) +Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch) This is free software: you can redistribute it and/or modify it under the terms of the Artistic License 2.0 as published by @@ -1 +1 @@ -0.1.49 +0.1.267 diff --git a/canvas.lv2/COPYING b/canvas.lv2/COPYING new file mode 100644 index 0000000..ddb9a46 --- /dev/null +++ b/canvas.lv2/COPYING @@ -0,0 +1,201 @@ + The Artistic License 2.0 + + Copyright (c) 2000-2006, The Perl Foundation. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +Preamble + +This license establishes the terms under which a given free software +Package may be copied, modified, distributed, and/or redistributed. +The intent is that the Copyright Holder maintains some artistic +control over the development of that Package while still keeping the +Package available as open source and free software. + +You are always permitted to make arrangements wholly outside of this +license directly with the Copyright Holder of a given Package. If the +terms of this license do not permit the full use that you propose to +make of the Package, you should contact the Copyright Holder and seek +a different licensing arrangement. + +Definitions + + "Copyright Holder" means the individual(s) or organization(s) + named in the copyright notice for the entire Package. + + "Contributor" means any party that has contributed code or other + material to the Package, in accordance with the Copyright Holder's + procedures. + + "You" and "your" means any person who would like to copy, + distribute, or modify the Package. + + "Package" means the collection of files distributed by the + Copyright Holder, and derivatives of that collection and/or of + those files. A given Package may consist of either the Standard + Version, or a Modified Version. + + "Distribute" means providing a copy of the Package or making it + accessible to anyone else, or in the case of a company or + organization, to others outside of your company or organization. + + "Distributor Fee" means any fee that you charge for Distributing + this Package or providing support for this Package to another + party. It does not mean licensing fees. + + "Standard Version" refers to the Package if it has not been + modified, or has been modified only in ways explicitly requested + by the Copyright Holder. + + "Modified Version" means the Package, if it has been changed, and + such changes were not explicitly requested by the Copyright + Holder. + + "Original License" means this Artistic License as Distributed with + the Standard Version of the Package, in its current version or as + it may be modified by The Perl Foundation in the future. + + "Source" form means the source code, documentation source, and + configuration files for the Package. + + "Compiled" form means the compiled bytecode, object code, binary, + or any other form resulting from mechanical transformation or + translation of the Source form. + + +Permission for Use and Modification Without Distribution + +(1) You are permitted to use the Standard Version and create and use +Modified Versions for any purpose without restriction, provided that +you do not Distribute the Modified Version. + + +Permissions for Redistribution of the Standard Version + +(2) You may Distribute verbatim copies of the Source form of the +Standard Version of this Package in any medium without restriction, +either gratis or for a Distributor Fee, provided that you duplicate +all of the original copyright notices and associated disclaimers. At +your discretion, such verbatim copies may or may not include a +Compiled form of the Package. + +(3) You may apply any bug fixes, portability changes, and other +modifications made available from the Copyright Holder. The resulting +Package will still be considered the Standard Version, and as such +will be subject to the Original License. + + +Distribution of Modified Versions of the Package as Source + +(4) You may Distribute your Modified Version as Source (either gratis +or for a Distributor Fee, and with or without a Compiled form of the +Modified Version) provided that you clearly document how it differs +from the Standard Version, including, but not limited to, documenting +any non-standard features, executables, or modules, and provided that +you do at least ONE of the following: + + (a) make the Modified Version available to the Copyright Holder + of the Standard Version, under the Original License, so that the + Copyright Holder may include your modifications in the Standard + Version. + + (b) ensure that installation of your Modified Version does not + prevent the user installing or running the Standard Version. In + addition, the Modified Version must bear a name that is different + from the name of the Standard Version. + + (c) allow anyone who receives a copy of the Modified Version to + make the Source form of the Modified Version available to others + under + + (i) the Original License or + + (ii) a license that permits the licensee to freely copy, + modify and redistribute the Modified Version using the same + licensing terms that apply to the copy that the licensee + received, and requires that the Source form of the Modified + Version, and of any works derived from it, be made freely + available in that license fees are prohibited but Distributor + Fees are allowed. + + +Distribution of Compiled Forms of the Standard Version +or Modified Versions without the Source + +(5) You may Distribute Compiled forms of the Standard Version without +the Source, provided that you include complete instructions on how to +get the Source of the Standard Version. Such instructions must be +valid at the time of your distribution. If these instructions, at any +time while you are carrying out such distribution, become invalid, you +must provide new instructions on demand or cease further distribution. +If you provide valid instructions or cease distribution within thirty +days after you become aware that the instructions are invalid, then +you do not forfeit any of your rights under this license. + +(6) You may Distribute a Modified Version in Compiled form without +the Source, provided that you comply with Section 4 with respect to +the Source of the Modified Version. + + +Aggregating or Linking the Package + +(7) You may aggregate the Package (either the Standard Version or +Modified Version) with other packages and Distribute the resulting +aggregation provided that you do not charge a licensing fee for the +Package. Distributor Fees are permitted, and licensing fees for other +components in the aggregation are permitted. The terms of this license +apply to the use and Distribution of the Standard or Modified Versions +as included in the aggregation. + +(8) You are permitted to link Modified and Standard Versions with +other works, to embed the Package in a larger work of your own, or to +build stand-alone binary or bytecode versions of applications that +include the Package, and Distribute the result without restriction, +provided the result does not expose a direct interface to the Package. + + +Items That are Not Considered Part of a Modified Version + +(9) Works (including, but not limited to, modules and scripts) that +merely extend or make use of the Package, do not, by themselves, cause +the Package to be a Modified Version. In addition, such works are not +considered parts of the Package itself, and are not subject to the +terms of this license. + + +General Provisions + +(10) Any use, modification, and distribution of the Standard or +Modified Versions is governed by this Artistic License. By using, +modifying or distributing the Package, you accept this license. Do not +use, modify, or distribute the Package, if you do not accept this +license. + +(11) If your Modified Version has been derived from a Modified +Version made by someone other than you, you are nevertheless required +to ensure that your Modified Version complies with the requirements of +this license. + +(12) This license does not grant you the right to use any trademark, +service mark, tradename, or logo of the Copyright Holder. + +(13) This license includes the non-exclusive, worldwide, +free-of-charge patent license to make, have made, use, offer to sell, +sell, import and otherwise transfer the Package with respect to any +patent claims licensable by the Copyright Holder that are necessarily +infringed by the Package. If you institute patent litigation +(including a cross-claim or counterclaim) against any party alleging +that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the +date that such litigation is filed. + +(14) Disclaimer of Warranty: +THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS +IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL +LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/canvas.lv2/README.md b/canvas.lv2/README.md new file mode 100644 index 0000000..06b87f2 --- /dev/null +++ b/canvas.lv2/README.md @@ -0,0 +1,18 @@ +# Canvas LV2 plugin extension + +### License + +Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + +This is free software: you can redistribute it and/or modify +it under the terms of the Artistic License 2.0 as published by +The Perl Foundation. + +This source 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 +Artistic License 2.0 for more details. + +You should have received a copy of the Artistic License 2.0 +along the source as a COPYING file. If not, obtain it from +<http://www.perlfoundation.org/artistic_license_2_0>. diff --git a/canvas.lv2/canvas.lv2/canvas.h b/canvas.lv2/canvas.lv2/canvas.h new file mode 100644 index 0000000..7cca0fa --- /dev/null +++ b/canvas.lv2/canvas.lv2/canvas.h @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _LV2_CANVAS_H +#define _LV2_CANVAS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <lv2/lv2plug.in/ns/lv2core/lv2.h> +#include <lv2/lv2plug.in/ns/ext/atom/atom.h> +#include <lv2/lv2plug.in/ns/ext/atom/forge.h> + +#define CANVAS_URI "http://open-music-kontrollers.ch/lv2/canvas" +#define CANVAS_PREFIX CANVAS_URI"#" + +#define CANVAS__graph CANVAS_PREFIX"graph" +#define CANVAS__body CANVAS_PREFIX"body" +#define CANVAS__aspectRatio CANVAS_PREFIX"aspectRatio" + +// Graph properties and attributes +#define CANVAS__BeginPath CANVAS_PREFIX"BeginPath" +#define CANVAS__ClosePath CANVAS_PREFIX"ClosePath" +#define CANVAS__Arc CANVAS_PREFIX"Arc" +#define CANVAS__CurveTo CANVAS_PREFIX"CurveTo" +#define CANVAS__LineTo CANVAS_PREFIX"LineTo" +#define CANVAS__MoveTo CANVAS_PREFIX"MoveTo" +#define CANVAS__Rectangle CANVAS_PREFIX"Rectangle" +#define CANVAS__PolyLine CANVAS_PREFIX"PolyLine" +#define CANVAS__Style CANVAS_PREFIX"Style" +#define CANVAS__LineWidth CANVAS_PREFIX"LineWidth" +#define CANVAS__LineDash CANVAS_PREFIX"LineDash" +#define CANVAS__LineCap CANVAS_PREFIX"LineCap" +#define CANVAS__LineJoin CANVAS_PREFIX"LineJoin" +#define CANVAS__MiterLimit CANVAS_PREFIX"MiterLimig" +#define CANVAS__Stroke CANVAS_PREFIX"Stroke" +#define CANVAS__Fill CANVAS_PREFIX"Fill" +#define CANVAS__Clip CANVAS_PREFIX"Clip" +#define CANVAS__Save CANVAS_PREFIX"Save" +#define CANVAS__Restore CANVAS_PREFIX"Restore" +#define CANVAS__Translate CANVAS_PREFIX"Translate" +#define CANVAS__Scale CANVAS_PREFIX"Scale" +#define CANVAS__Rotate CANVAS_PREFIX"Rotate" +#define CANVAS__Transform CANVAS_PREFIX"Transform" +#define CANVAS__Reset CANVAS_PREFIX"Reset" +#define CANVAS__FontSize CANVAS_PREFIX"FontSize" +#define CANVAS__FillText CANVAS_PREFIX"FillText" + +#define CANVAS__lineCapButt CANVAS_PREFIX"lineCapButt" +#define CANVAS__lineCapRound CANVAS_PREFIX"lineCapRound" +#define CANVAS__lineCapSquare CANVAS_PREFIX"lineCapSquare" + +#define CANVAS__lineJoinMiter CANVAS_PREFIX"lineJoinMiter" +#define CANVAS__lineJoinRound CANVAS_PREFIX"lineJoinRound" +#define CANVAS__lineJoinBevel CANVAS_PREFIX"lineJoinBevel" + +// Input properties and attributes + +#define CANVAS__mouseButtonLeft CANVAS_PREFIX"mouseButtonLeft" +#define CANVAS__mouseButtonMiddle CANVAS_PREFIX"mouseButtonMiddle" +#define CANVAS__mouseButtonRight CANVAS_PREFIX"mouseButtonRight" +#define CANVAS__mouseWheelX CANVAS_PREFIX"mouseWheelX" +#define CANVAS__mouseWheelY CANVAS_PREFIX"mouseWheelY" +#define CANVAS__mousePositionX CANVAS_PREFIX"mousePositionX" +#define CANVAS__mousePositionY CANVAS_PREFIX"mousePositionY" +#define CANVAS__mouseFocus CANVAS_PREFIX"mouseFocus" + +typedef struct _LV2_Canvas_URID LV2_Canvas_URID; + +struct _LV2_Canvas_URID { + LV2_URID Canvas_graph; + LV2_URID Canvas_body; + LV2_URID Canvas_aspectRatio; + + LV2_URID Canvas_BeginPath; + LV2_URID Canvas_ClosePath; + LV2_URID Canvas_Arc; + LV2_URID Canvas_CurveTo; + LV2_URID Canvas_LineTo; + LV2_URID Canvas_MoveTo; + LV2_URID Canvas_Rectangle; + LV2_URID Canvas_PolyLine; + LV2_URID Canvas_Style; + LV2_URID Canvas_LineWidth; + LV2_URID Canvas_LineDash; + LV2_URID Canvas_LineCap; + LV2_URID Canvas_LineJoin; + LV2_URID Canvas_MiterLimit; + LV2_URID Canvas_Stroke; + LV2_URID Canvas_Fill; + LV2_URID Canvas_Clip; + LV2_URID Canvas_Save; + LV2_URID Canvas_Restore; + LV2_URID Canvas_Translate; + LV2_URID Canvas_Scale; + LV2_URID Canvas_Rotate; + LV2_URID Canvas_Transform; + LV2_URID Canvas_Reset; + LV2_URID Canvas_FontSize; + LV2_URID Canvas_FillText; + + LV2_URID Canvas_lineCapButt; + LV2_URID Canvas_lineCapRound; + LV2_URID Canvas_lineCapSquare; + + LV2_URID Canvas_lineJoinMiter; + LV2_URID Canvas_lineJoinRound; + LV2_URID Canvas_lineJoinBevel; + + LV2_URID Canvas_mouseButtonLeft; + LV2_URID Canvas_mouseButtonMiddle; + LV2_URID Canvas_mouseButtonRight; + LV2_URID Canvas_mouseWheelX; + LV2_URID Canvas_mouseWheelY; + LV2_URID Canvas_mousePositionX; + LV2_URID Canvas_mousePositionY; + LV2_URID Canvas_mouseFocus; + + LV2_Atom_Forge forge; +}; + +static inline void +lv2_canvas_urid_init(LV2_Canvas_URID *urid, LV2_URID_Map *map) +{ + urid->Canvas_graph = map->map(map->handle, CANVAS__graph); + urid->Canvas_body = map->map(map->handle, CANVAS__body); + urid->Canvas_aspectRatio = map->map(map->handle, CANVAS__aspectRatio); + + urid->Canvas_BeginPath = map->map(map->handle, CANVAS__BeginPath); + urid->Canvas_ClosePath = map->map(map->handle, CANVAS__ClosePath); + urid->Canvas_Arc = map->map(map->handle, CANVAS__Arc); + urid->Canvas_CurveTo = map->map(map->handle, CANVAS__CurveTo); + urid->Canvas_LineTo = map->map(map->handle, CANVAS__LineTo); + urid->Canvas_MoveTo = map->map(map->handle, CANVAS__MoveTo); + urid->Canvas_Rectangle = map->map(map->handle, CANVAS__Rectangle); + urid->Canvas_PolyLine = map->map(map->handle, CANVAS__PolyLine); + urid->Canvas_Style = map->map(map->handle, CANVAS__Style); + urid->Canvas_LineWidth = map->map(map->handle, CANVAS__LineWidth); + urid->Canvas_LineDash = map->map(map->handle, CANVAS__LineDash); + urid->Canvas_LineCap = map->map(map->handle, CANVAS__LineCap); + urid->Canvas_LineJoin = map->map(map->handle, CANVAS__LineJoin); + urid->Canvas_MiterLimit = map->map(map->handle, CANVAS__MiterLimit); + urid->Canvas_Stroke = map->map(map->handle, CANVAS__Stroke); + urid->Canvas_Fill = map->map(map->handle, CANVAS__Fill); + urid->Canvas_Clip = map->map(map->handle, CANVAS__Clip); + urid->Canvas_Save = map->map(map->handle, CANVAS__Save); + urid->Canvas_Restore = map->map(map->handle, CANVAS__Restore); + urid->Canvas_Translate = map->map(map->handle, CANVAS__Translate); + urid->Canvas_Scale = map->map(map->handle, CANVAS__Scale); + urid->Canvas_Rotate = map->map(map->handle, CANVAS__Rotate); + urid->Canvas_Transform = map->map(map->handle, CANVAS__Transform); + urid->Canvas_Reset = map->map(map->handle, CANVAS__Reset); + urid->Canvas_FontSize = map->map(map->handle, CANVAS__FontSize); + urid->Canvas_FillText = map->map(map->handle, CANVAS__FillText); + + urid->Canvas_lineCapButt = map->map(map->handle, CANVAS__lineCapButt); + urid->Canvas_lineCapRound = map->map(map->handle, CANVAS__lineCapRound); + urid->Canvas_lineCapSquare = map->map(map->handle, CANVAS__lineCapSquare); + + urid->Canvas_lineJoinMiter = map->map(map->handle, CANVAS__lineJoinMiter); + urid->Canvas_lineJoinRound = map->map(map->handle, CANVAS__lineJoinRound); + urid->Canvas_lineJoinBevel = map->map(map->handle, CANVAS__lineJoinBevel); + + urid->Canvas_mouseButtonLeft = map->map(map->handle, CANVAS__mouseButtonLeft); + urid->Canvas_mouseButtonMiddle = map->map(map->handle, CANVAS__mouseButtonMiddle); + urid->Canvas_mouseButtonRight = map->map(map->handle, CANVAS__mouseButtonRight); + urid->Canvas_mouseWheelX = map->map(map->handle, CANVAS__mouseWheelX); + urid->Canvas_mouseWheelY = map->map(map->handle, CANVAS__mouseWheelY); + urid->Canvas_mousePositionX = map->map(map->handle, CANVAS__mousePositionX); + urid->Canvas_mousePositionY = map->map(map->handle, CANVAS__mousePositionY); + urid->Canvas_mouseFocus = map->map(map->handle, CANVAS__mouseFocus); + + lv2_atom_forge_init(&urid->forge, map); +} + +#ifdef __cplusplus +} +#endif + +#endif // _LV2_CANVAS_H diff --git a/canvas.lv2/canvas.lv2/forge.h b/canvas.lv2/canvas.lv2/forge.h new file mode 100644 index 0000000..c5ce5fe --- /dev/null +++ b/canvas.lv2/canvas.lv2/forge.h @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _LV2_CANVAS_FORGE_H +#define _LV2_CANVAS_FORGE_H + +#include <canvas.lv2/canvas.h> + +#ifdef __cplusplus +extern "C" { +#endif + +static inline LV2_Atom_Forge_Ref +_lv2_canvas_forge_simple(LV2_Atom_Forge *forge, LV2_URID otype) +{ + LV2_Atom_Forge_Ref ref; + LV2_Atom_Forge_Frame frame; + + ref = lv2_atom_forge_object(forge, &frame, 0, otype); + if(ref) + lv2_atom_forge_pop(forge, &frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_lv2_canvas_forge_vec(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + LV2_URID otype, uint32_t n, const float *vec) +{ + LV2_Atom_Forge_Ref ref; + LV2_Atom_Forge_Frame frame; + + ref = lv2_atom_forge_object(forge, &frame, 0, otype); + if(ref) + ref = lv2_atom_forge_key(forge, urid->Canvas_body); + if(ref) + ref = lv2_atom_forge_vector(forge, sizeof(float), forge->Float, n, vec); + if(ref) + lv2_atom_forge_pop(forge, &frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_lv2_canvas_forge_flt(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + LV2_URID otype, float flt) +{ + LV2_Atom_Forge_Ref ref; + LV2_Atom_Forge_Frame frame; + + ref = lv2_atom_forge_object(forge, &frame, 0, otype); + if(ref) + ref = lv2_atom_forge_key(forge, urid->Canvas_body); + if(ref) + ref = lv2_atom_forge_float(forge, flt); + if(ref) + lv2_atom_forge_pop(forge, &frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_lv2_canvas_forge_lng(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + LV2_URID otype, int64_t lng) +{ + LV2_Atom_Forge_Ref ref; + LV2_Atom_Forge_Frame frame; + + ref = lv2_atom_forge_object(forge, &frame, 0, otype); + if(ref) + ref = lv2_atom_forge_key(forge, urid->Canvas_body); + if(ref) + ref = lv2_atom_forge_long(forge, lng); + if(ref) + lv2_atom_forge_pop(forge, &frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_lv2_canvas_forge_prp(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + LV2_URID otype, LV2_URID prop) +{ + LV2_Atom_Forge_Ref ref; + LV2_Atom_Forge_Frame frame; + + ref = lv2_atom_forge_object(forge, &frame, 0, otype); + if(ref) + ref = lv2_atom_forge_key(forge, urid->Canvas_body); + if(ref) + ref = lv2_atom_forge_urid(forge, prop); + if(ref) + lv2_atom_forge_pop(forge, &frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_lv2_canvas_forge_str(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + LV2_URID otype, const char *text) +{ + LV2_Atom_Forge_Ref ref; + LV2_Atom_Forge_Frame frame; + + ref = lv2_atom_forge_object(forge, &frame, 0, otype); + if(ref) + ref = lv2_atom_forge_key(forge, urid->Canvas_body); + if(ref) + ref = lv2_atom_forge_string(forge, text, strlen(text)); + if(ref) + lv2_atom_forge_pop(forge, &frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_beginPath(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid) +{ + return _lv2_canvas_forge_simple(forge, urid->Canvas_BeginPath); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_closePath(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid) +{ + return _lv2_canvas_forge_simple(forge, urid->Canvas_ClosePath); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_arc(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float x, float y, float r, float a1, float a2) +{ + const float vec [5] = {x, y, r, a1, a2}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_Arc, 5, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_curveTo(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float x1, float y1, float x2, float y2, float x3, float y3) +{ + const float vec [6] = {x1, y1, x2, y2, x3, y3}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_CurveTo, 6, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_lineTo(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float x, float y) +{ + const float vec [2] = {x, y}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_LineTo, 2, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_moveTo(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float x, float y) +{ + const float vec [2] = {x, y}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_MoveTo, 2, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_rectangle(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float x, float y, float w, float h) +{ + const float vec [4] = {x, y, w, h}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_Rectangle, 4, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_polyLine(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + uint32_t n, const float *vec) +{ + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_PolyLine, n, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_style(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + uint32_t style) +{ + return _lv2_canvas_forge_lng(forge, urid, urid->Canvas_Style, style); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_lineWidth(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float line_width) +{ + return _lv2_canvas_forge_flt(forge, urid, urid->Canvas_LineWidth, line_width); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_lineDash(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float dash_length, float separator_length) +{ + const float vec [2] = {dash_length, separator_length}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_LineDash, 2, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_lineCap(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + LV2_URID line_cap) +{ + return _lv2_canvas_forge_prp(forge, urid, urid->Canvas_LineCap, line_cap); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_lineJoin(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + LV2_URID line_join) +{ + return _lv2_canvas_forge_prp(forge, urid, urid->Canvas_LineJoin, line_join); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_miterLimit(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float miter_limit) +{ + return _lv2_canvas_forge_flt(forge, urid, urid->Canvas_MiterLimit, miter_limit); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_stroke(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid) +{ + return _lv2_canvas_forge_simple(forge, urid->Canvas_Stroke); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_fill(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid) +{ + return _lv2_canvas_forge_simple(forge, urid->Canvas_Fill); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_clip(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid) +{ + return _lv2_canvas_forge_simple(forge, urid->Canvas_Clip); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_save(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid) +{ + return _lv2_canvas_forge_simple(forge, urid->Canvas_Save); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_restore(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid) +{ + return _lv2_canvas_forge_simple(forge, urid->Canvas_Restore); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_translate(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float x, float y) +{ + const float vec [2] = {x, y}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_Translate, 2, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_scale(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float w, float h) +{ + const float vec [2] = {w, h}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_Scale, 2, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_rotate(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float a) +{ + return _lv2_canvas_forge_flt(forge, urid, urid->Canvas_Rotate, a); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_transform(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float xx, float xy, float x0, float yy, float yx, float y0) +{ + const float vec [6] = {xx, xy, x0, yy, yx, y0}; + + return _lv2_canvas_forge_vec(forge, urid, urid->Canvas_Transform, 6, vec); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_reset(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid) +{ + return _lv2_canvas_forge_simple(forge, urid->Canvas_Reset); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_fontSize(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + float size) +{ + return _lv2_canvas_forge_flt(forge, urid, urid->Canvas_FontSize, size); +} + +static inline LV2_Atom_Forge_Ref +lv2_canvas_forge_fillText(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid, + const char *text) +{ + return _lv2_canvas_forge_str(forge, urid, urid->Canvas_FillText, text); +} + +#ifdef __cplusplus +} +#endif + +#endif // _LV2_CANVAS_FORGE_H diff --git a/canvas.lv2/canvas.lv2/idisp.h b/canvas.lv2/canvas.lv2/idisp.h new file mode 100644 index 0000000..099ab2c --- /dev/null +++ b/canvas.lv2/canvas.lv2/idisp.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _LV2_CANVAS_IDISP_H +#define _LV2_CANVAS_IDISP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define LV2_CANVAS_RENDER_CAIRO +#include <canvas.lv2/render.h> +#include <canvas.lv2/lv2_extensions.h> + +typedef struct _LV2_Canvas_Idisp LV2_Canvas_Idisp; + +struct _LV2_Canvas_Idisp { + LV2_Inline_Display *queue_draw; + LV2_Canvas canvas; + LV2_Inline_Display_Image_Surface image_surface; + struct { + cairo_surface_t *surface; + cairo_t *ctx; + } cairo; +}; + +static inline LV2_Inline_Display_Image_Surface * +_lv2_canvas_idisp_surf_init(LV2_Canvas_Idisp *idisp, int w, int h) +{ + LV2_Inline_Display_Image_Surface *surf = &idisp->image_surface; + + surf->width = w; + surf->height = h; + surf->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, surf->width); + surf->data = realloc(surf->data, surf->stride * surf->height); + if(!surf->data) + { + return NULL; + } + + idisp->cairo.surface = cairo_image_surface_create_for_data( + surf->data, CAIRO_FORMAT_ARGB32, surf->width, surf->height, surf->stride); + + if(idisp->cairo.surface) + { + cairo_surface_set_device_scale(idisp->cairo.surface, surf->width, surf->height); + + idisp->cairo.ctx = cairo_create(idisp->cairo.surface); + if(idisp->cairo.ctx) + { + cairo_select_font_face(idisp->cairo.ctx, "cairo:monospace", + CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + } + } + + return surf; +} + +static inline void +_lv2_canvas_idisp_surf_deinit(LV2_Canvas_Idisp *idisp) +{ + LV2_Inline_Display_Image_Surface *surf = &idisp->image_surface; + + if(idisp->cairo.ctx) + { + cairo_destroy(idisp->cairo.ctx); + idisp->cairo.ctx = NULL; + } + + if(idisp->cairo.surface) + { + cairo_surface_finish(idisp->cairo.surface); + cairo_surface_destroy(idisp->cairo.surface); + idisp->cairo.surface = NULL; + } + + if(surf->data) + { + free(surf->data); + surf->data = NULL; + } +} + +static inline LV2_Inline_Display_Image_Surface * +lv2_canvas_idisp_surf_configure(LV2_Canvas_Idisp *idisp, + uint32_t w, uint32_t h, float aspect_ratio) +{ + LV2_Inline_Display_Image_Surface *surf = &idisp->image_surface; + int W; + int H; + + if(aspect_ratio < 1.f) + { + W = h * aspect_ratio; + H = h; + } + else if(aspect_ratio > 1.f) + { + W = w; + H = w / aspect_ratio; + } + else // aspect_ratio == 1.f + { + W = w; + H = h; + } + + if( (surf->width != W) || (surf->height != H) || !surf->data) + { + _lv2_canvas_idisp_surf_deinit(idisp); + surf = _lv2_canvas_idisp_surf_init(idisp, W, H); + } + + return surf; +} + +static inline void +lv2_canvas_idisp_init(LV2_Canvas_Idisp *idisp, LV2_Inline_Display *queue_draw, + LV2_URID_Map *map) +{ + lv2_canvas_init(&idisp->canvas, map); + idisp->queue_draw = queue_draw; +} + +static inline void +lv2_canvas_idisp_deinit(LV2_Canvas_Idisp *idisp) +{ + _lv2_canvas_idisp_surf_deinit(idisp); +} + +static inline void +lv2_canvas_idisp_queue_draw(LV2_Canvas_Idisp *idisp) +{ + if(idisp->queue_draw) + { + idisp->queue_draw->queue_draw(idisp->queue_draw->handle); + } +} + +static inline bool +lv2_canvas_idisp_render_body(LV2_Canvas_Idisp *idisp, uint32_t type, + uint32_t size, const LV2_Atom *body) +{ + return lv2_canvas_render_body(&idisp->canvas, idisp->cairo.ctx, + type, size, body); +} + +#ifdef __cplusplus +} +#endif + +#endif // _LV2_CANVAS_IDISP_H diff --git a/canvas.lv2/canvas.lv2/lv2_extensions.h b/canvas.lv2/canvas.lv2/lv2_extensions.h new file mode 100644 index 0000000..64fc3bc --- /dev/null +++ b/canvas.lv2/canvas.lv2/lv2_extensions.h @@ -0,0 +1,174 @@ +/* + Copyright 2016 Robin Gareus <robin@gareus.org> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef _ardour_lv2_extensions_h_ +#define _ardour_lv2_extensions_h_ + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +/** + @defgroup inlinedisplay Inline-Display + + Support for displaying a miniaturized generic view + directly in the host's Mixer Window. + + @{ +*/ + +#define LV2_INLINEDISPLAY_URI "http://harrisonconsoles.com/lv2/inlinedisplay" +#define LV2_INLINEDISPLAY_PREFIX LV2_INLINEDISPLAY_URI "#" +#define LV2_INLINEDISPLAY__interface LV2_INLINEDISPLAY_PREFIX "interface" +#define LV2_INLINEDISPLAY__queue_draw LV2_INLINEDISPLAY_PREFIX "queue_draw" + +/** Opaque handle for LV2_Inline_Display::queue_draw() */ +typedef void* LV2_Inline_Display_Handle; + +/** raw image pixmap format is ARGB32, + * the data pointer is owned by the plugin and must be valid + * from the first call to render until cleanup. + */ +typedef struct { + unsigned char *data; + int width; + int height; + int stride; +} LV2_Inline_Display_Image_Surface; + +/** a LV2 Feature provided by the Host to the plugin */ +typedef struct { + /** Opaque host data */ + LV2_Inline_Display_Handle handle; + /** Request from run() that the host should call render() at a later time + * to update the inline display */ + void (*queue_draw)(LV2_Inline_Display_Handle handle); +} LV2_Inline_Display; + +/** + * Plugin Inline-Display Interface. + */ +typedef struct { + /** + * The render method. This is called by the host in a non-realtime context, + * usually the main GUI thread. + * The data pointer is owned by the plugin and must be valid + * from the first call to render until cleanup. + * + * @param instance The LV2 instance + * @param w the max available width + * @param h the max available height + * @return pointer to a LV2_Inline_Display_Image_Surface or NULL + */ + LV2_Inline_Display_Image_Surface* (*render)(LV2_Handle instance, uint32_t w, uint32_t h); +} LV2_Inline_Display_Interface; + +/** + @} +*/ + +/** + @defgroup automate Self-Automation + + Support for plugins to write automation data via Atom Events + + @{ +*/ + +#define LV2_AUTOMATE_URI "http://ardour.org/lv2/automate" +#define LV2_AUTOMATE_URI_PREFIX LV2_AUTOMATE_URI "#" +/** an lv2:optionalFeature */ +#define LV2_AUTOMATE_URI__can_write LV2_AUTOMATE_URI_PREFIX "canWriteAutomatation" +/** atom:supports */ +#define LV2_AUTOMATE_URI__control LV2_AUTOMATE_URI_PREFIX "automationControl" +/** lv2:portProperty */ +#define LV2_AUTOMATE_URI__controlled LV2_AUTOMATE_URI_PREFIX "automationControlled" +#define LV2_AUTOMATE_URI__controller LV2_AUTOMATE_URI_PREFIX "automationController" + +/** atom messages */ +#define LV2_AUTOMATE_URI__event LV2_AUTOMATE_URI_PREFIX "event" +#define LV2_AUTOMATE_URI__setup LV2_AUTOMATE_URI_PREFIX "setup" +#define LV2_AUTOMATE_URI__finalize LV2_AUTOMATE_URI_PREFIX "finalize" +#define LV2_AUTOMATE_URI__start LV2_AUTOMATE_URI_PREFIX "start" +#define LV2_AUTOMATE_URI__end LV2_AUTOMATE_URI_PREFIX "end" +#define LV2_AUTOMATE_URI__parameter LV2_AUTOMATE_URI_PREFIX "parameter" +#define LV2_AUTOMATE_URI__value LV2_AUTOMATE_URI_PREFIX "value" + +/** + @} +*/ + +/** + @defgroup license License-Report + + Allow for commercial LV2 to report their + licensing status. + + @{ +*/ + +#define LV2_PLUGINLICENSE_URI "http://harrisonconsoles.com/lv2/license" +#define LV2_PLUGINLICENSE_PREFIX LV2_PLUGINLICENSE_URI "#" +#define LV2_PLUGINLICENSE__interface LV2_PLUGINLICENSE_PREFIX "interface" + +typedef struct _LV2_License_Interface { + /* @return -1 if no license is needed; 0 if unlicensed, 1 if licensed */ + int (*is_licensed)(LV2_Handle instance); + /* @return a string copy of the licensee name if licensed, or NULL, the caller needs to free this */ + char* (*licensee)(LV2_Handle instance); + /* @return a URI identifying the plugin-bundle or plugin for which a given license is valid */ + const char* (*product_uri)(LV2_Handle instance); + /* @return human readable product name for the URI */ + const char* (*product_name)(LV2_Handle instance); + /* @return link to website or webstore */ + const char* (*store_url)(LV2_Handle instance); +} LV2_License_Interface; + +/** + @} +*/ + +/** + @defgroup plugin provided bypass + + A port with the designation "processing#enable" must + control a plugin's internal bypass mode. + + If the port value is larger than zero the plugin processes + normally. + + If the port value is zero, the plugin is expected to bypass + all signals unmodified. + + The plugin is responsible for providing a click-free transition + between the states. + + (values less than zero are reserved for future use: + e.g click-free insert/removal of latent plugins. + Generally values <= 0 are to be treated as bypassed.) + + lv2:designation <http://ardour.org/lv2/processing#enable> ; + + @{ +*/ + +#define LV2_PROCESSING_URI "http://ardour.org/lv2/processing" +#define LV2_PROCESSING_URI_PREFIX LV2_PROCESSING_URI "#" +#define LV2_PROCESSING_URI__enable LV2_PROCESSING_URI_PREFIX "enable" + +/** + @} +*/ + +#endif diff --git a/canvas.lv2/canvas.lv2/render.h b/canvas.lv2/canvas.lv2/render.h new file mode 100644 index 0000000..b809804 --- /dev/null +++ b/canvas.lv2/canvas.lv2/render.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _LV2_CANVAS_RENDER_H +#define _LV2_CANVAS_RENDER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <canvas.lv2/canvas.h> + +#define LV2_CANVAS_NUM_METHODS 26 + +typedef struct _LV2_Canvas_Meth LV2_Canvas_Meth; +typedef struct _LV2_Canvas LV2_Canvas; +typedef void (*LV2_Canvas_Func)(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body); + +struct _LV2_Canvas_Meth { + LV2_URID command; + LV2_Canvas_Func func; +}; + +struct _LV2_Canvas { + LV2_Canvas_URID urid; + LV2_Canvas_Meth methods [LV2_CANVAS_NUM_METHODS]; +}; + +static inline const float * +_lv2_canvas_render_get_float_vecs(LV2_Canvas_URID *urid, const LV2_Atom *body, + uint32_t *n) +{ + const LV2_Atom_Vector *vec = (const LV2_Atom_Vector *)body; + const float *flt = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Vector, vec); + *n = (vec->atom.type == urid->forge.Vector) + && (vec->body.child_type == urid->forge.Float) + && (vec->body.child_size == sizeof(float)) + ? (vec->atom.size - sizeof(LV2_Atom_Vector_Body)) / vec->body.child_size + : 0; + + return flt; +} + +static inline const float * +_lv2_canvas_render_get_float_vec(LV2_Canvas_URID *urid, const LV2_Atom *body, + uint32_t n) +{ + uint32_t N; + const float *flt = _lv2_canvas_render_get_float_vecs(urid, body, &N); + + return n == N ? flt : NULL; +} + +static inline const void * +_lv2_canvas_render_get_type(const LV2_Atom *body, LV2_URID type) +{ + return body->type == type + ? LV2_ATOM_BODY_CONST(body) + : NULL; +} + +#ifdef __cplusplus +} +#endif + +#if defined(LV2_CANVAS_RENDER_NANOVG) +# include <canvas.lv2/render_nanovg.h> +#else +# include <canvas.lv2/render_cairo.h> +#endif + +#endif // _LV2_CANVAS_RENDER_H diff --git a/canvas.lv2/canvas.lv2/render_cairo.h b/canvas.lv2/canvas.lv2/render_cairo.h new file mode 100644 index 0000000..a41c72c --- /dev/null +++ b/canvas.lv2/canvas.lv2/render_cairo.h @@ -0,0 +1,584 @@ +/* + * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _LV2_CANVAS_RENDER_CAIRO_H +#define _LV2_CANVAS_RENDER_CAIRO_H + +#include <assert.h> + +#include <canvas.lv2/canvas.h> + +#include <cairo/cairo.h> + +#ifdef __cplusplus +extern "C" { +#endif + +static inline void +_lv2_canvas_render_beginPath(void *data, + LV2_Canvas_URID *urid __attribute__((unused)), + const LV2_Atom *body __attribute__((unused))) +{ + cairo_t *ctx = data; + cairo_new_sub_path(ctx); +} + +static inline void +_lv2_canvas_render_closePath(void *data, + LV2_Canvas_URID *urid __attribute__((unused)), + const LV2_Atom *body __attribute__((unused))) +{ + cairo_t *ctx = data; + cairo_close_path(ctx); +} + +static inline void +_lv2_canvas_render_arc(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 5); + + if(v) + { + cairo_arc(ctx, v[0], v[1], v[2], v[3], v[4]); + } +} + +static inline void +_lv2_canvas_render_curveTo(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 6); + + if(v) + { + cairo_curve_to(ctx, v[0], v[1], v[2], v[3], v[4], v[5]); + } +} + +static inline void +_lv2_canvas_render_lineTo(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + cairo_line_to(ctx, v[0], v[1]); + } +} + +static inline void +_lv2_canvas_render_moveTo(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + cairo_move_to(ctx, v[0], v[1]); + } +} + +static inline void +_lv2_canvas_render_rectangle(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 4); + + if(v) + { + cairo_rectangle(ctx, v[0], v[1], v[2], v[3]); + } +} + +static inline void +_lv2_canvas_render_polyline(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + uint32_t N; + const float *v = _lv2_canvas_render_get_float_vecs(urid, body, &N); + + if(v) + { + for(uint32_t i = 0; i < 2; i += 2) + { + cairo_move_to(ctx, v[i], v[i+1]); + } + + for(uint32_t i = 2; i < N; i += 2) + { + cairo_line_to(ctx, v[i], v[i+1]); + } + } +} + +static inline void +_lv2_canvas_render_style(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const int64_t *v = _lv2_canvas_render_get_type(body, urid->forge.Long); + + if(v) + { + const float r = (float)((*v >> 24) & 0xff) / 0xff; + const float g = (float)((*v >> 16) & 0xff) / 0xff; + const float b = (float)((*v >> 8) & 0xff) / 0xff; + const float a = (float)((*v >> 0) & 0xff) / 0xff; + + cairo_set_source_rgba(ctx, r, g, b, a); + } +} + +static inline void +_lv2_canvas_render_lineWidth(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_type(body, urid->forge.Float); + + if(v) + { + cairo_set_line_width(ctx, *v); + } +} + +static inline void +_lv2_canvas_render_lineDash(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + const double d[2] = {v[0], v[1]}; + cairo_set_dash(ctx, d, 2, 0); + } +} + +static inline void +_lv2_canvas_render_lineCap(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const LV2_URID *v = _lv2_canvas_render_get_type(body, urid->forge.URID); + + if(v) + { + cairo_line_cap_t cap = CAIRO_LINE_CAP_BUTT; + + if(*v == urid->Canvas_lineCapButt) + cap = CAIRO_LINE_CAP_BUTT; + else if(*v == urid->Canvas_lineCapRound) + cap = CAIRO_LINE_CAP_ROUND; + else if(*v == urid->Canvas_lineCapSquare) + cap = CAIRO_LINE_CAP_SQUARE; + + cairo_set_line_cap(ctx, cap); + } +} + +static inline void +_lv2_canvas_render_lineJoin(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const LV2_URID *v = _lv2_canvas_render_get_type(body, urid->forge.URID); + + if(v) + { + cairo_line_join_t join = CAIRO_LINE_JOIN_MITER; + + if(*v == urid->Canvas_lineJoinMiter) + join = CAIRO_LINE_JOIN_MITER; + else if(*v == urid->Canvas_lineJoinRound) + join = CAIRO_LINE_JOIN_ROUND; + else if(*v == urid->Canvas_lineJoinBevel) + join = CAIRO_LINE_JOIN_BEVEL; + + cairo_set_line_join(ctx, join); + } +} + +static inline void +_lv2_canvas_render_miterLimit(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_type(body, urid->forge.Float); + + if(v) + { + cairo_set_miter_limit(ctx, *v); + } +} + +static inline void +_lv2_canvas_render_stroke(void *data, + LV2_Canvas_URID *urid __attribute__((unused)), + const LV2_Atom *body __attribute__((unused))) +{ + cairo_t *ctx = data; + cairo_stroke(ctx); +} + +static inline void +_lv2_canvas_render_fill(void *data, + LV2_Canvas_URID *urid __attribute__((unused)), + const LV2_Atom *body __attribute__((unused))) +{ + cairo_t *ctx = data; + cairo_fill(ctx); +} + +static inline void +_lv2_canvas_render_clip(void *data, + LV2_Canvas_URID *urid __attribute__((unused)), + const LV2_Atom *body __attribute__((unused))) +{ + cairo_t *ctx = data; + cairo_clip(ctx); +} + +static inline void +_lv2_canvas_render_save(void *data, + LV2_Canvas_URID *urid __attribute__((unused)), + const LV2_Atom *body __attribute__((unused))) +{ + cairo_t *ctx = data; + cairo_save(ctx); +} + +static inline void +_lv2_canvas_render_restore(void *data, + LV2_Canvas_URID *urid __attribute__((unused)), + const LV2_Atom *body __attribute__((unused))) +{ + cairo_t *ctx = data; + cairo_restore(ctx); +} + +static inline void +_lv2_canvas_render_translate(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + cairo_translate(ctx, v[0], v[1]); + } +} + +static inline void +_lv2_canvas_render_scale(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + cairo_scale(ctx, v[0], v[1]); + } +} + +static inline void +_lv2_canvas_render_rotate(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_type(body, urid->forge.Float); + + if(v) + { + cairo_rotate(ctx, *v); + } +} + +static inline void +_lv2_canvas_render_transform(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 6); + + if(v) + { + const cairo_matrix_t matrix = { + .xx = v[0], + .xy = v[1], + .x0 = v[2], + .yy = v[3], + .yx = v[4], + .y0 = v[5] + }; + + cairo_transform(ctx, &matrix); + } +} + +static inline void +_lv2_canvas_render_reset(void *data, + LV2_Canvas_URID *urid __attribute__((unused)), + const LV2_Atom *body __attribute__((unused))) +{ + cairo_t *ctx = data; + cairo_identity_matrix(ctx); +} + +static inline void +_lv2_canvas_render_fontSize(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const float *v = _lv2_canvas_render_get_type(body, urid->forge.Float); + + if(v) + { + cairo_set_font_size(ctx, *v); + } +} + +static inline void +_lv2_canvas_render_fillText(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + cairo_t *ctx = data; + const char *v = _lv2_canvas_render_get_type(body, urid->forge.String); + + if(v) + { + cairo_text_extents_t extents; + cairo_text_extents(ctx, v, &extents); + const float dx = extents.width/2 + extents.x_bearing; + const float dy = extents.height/2 + extents.y_bearing; + cairo_rel_move_to(ctx, -dx, -dy); + cairo_show_text(ctx, v); + } +} + +static inline void +_lv2_canvas_qsort(LV2_Canvas_Meth *A, int n) +{ + if(n < 2) + return; + + LV2_Canvas_Meth *p = A; + + int i = -1; + int j = n; + + while(true) + { + do { + i += 1; + } while(A[i].command < p->command); + + do { + j -= 1; + } while(A[j].command > p->command); + + if(i >= j) + break; + + const LV2_Canvas_Meth tmp = A[i]; + A[i] = A[j]; + A[j] = tmp; + } + + _lv2_canvas_qsort(A, j + 1); + _lv2_canvas_qsort(A + j + 1, n - j - 1); +} + +static inline LV2_Canvas_Meth * +_lv2_canvas_bsearch(LV2_URID p, LV2_Canvas_Meth *a, int n) +{ + LV2_Canvas_Meth *base = a; + + for(int N = n, half; N > 1; N -= half) + { + half = N/2; + LV2_Canvas_Meth *dst = &base[half]; + base = (dst->command > p) ? base : dst; + } + + return (base->command == p) ? base : NULL; +} + +static inline void +lv2_canvas_init(LV2_Canvas *canvas, LV2_URID_Map *map) +{ + lv2_canvas_urid_init(&canvas->urid, map); + + unsigned ptr = 0; + + canvas->methods[ptr].command = canvas->urid.Canvas_BeginPath; + canvas->methods[ptr++].func = _lv2_canvas_render_beginPath; + + canvas->methods[ptr].command = canvas->urid.Canvas_ClosePath; + canvas->methods[ptr++].func = _lv2_canvas_render_closePath; + + canvas->methods[ptr].command = canvas->urid.Canvas_Arc; + canvas->methods[ptr++].func = _lv2_canvas_render_arc; + + canvas->methods[ptr].command = canvas->urid.Canvas_CurveTo; + canvas->methods[ptr++].func = _lv2_canvas_render_curveTo; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineTo; + canvas->methods[ptr++].func = _lv2_canvas_render_lineTo; + + canvas->methods[ptr].command = canvas->urid.Canvas_MoveTo; + canvas->methods[ptr++].func = _lv2_canvas_render_moveTo; + + canvas->methods[ptr].command = canvas->urid.Canvas_Rectangle; + canvas->methods[ptr++].func = _lv2_canvas_render_rectangle; + + canvas->methods[ptr].command = canvas->urid.Canvas_PolyLine; + canvas->methods[ptr++].func = _lv2_canvas_render_polyline; + + canvas->methods[ptr].command = canvas->urid.Canvas_Style; + canvas->methods[ptr++].func = _lv2_canvas_render_style; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineWidth; + canvas->methods[ptr++].func = _lv2_canvas_render_lineWidth; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineDash; + canvas->methods[ptr++].func = _lv2_canvas_render_lineDash; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineCap; + canvas->methods[ptr++].func = _lv2_canvas_render_lineCap; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineJoin; + canvas->methods[ptr++].func = _lv2_canvas_render_lineJoin; + + canvas->methods[ptr].command = canvas->urid.Canvas_MiterLimit; + canvas->methods[ptr++].func = _lv2_canvas_render_miterLimit; + + canvas->methods[ptr].command = canvas->urid.Canvas_Stroke; + canvas->methods[ptr++].func = _lv2_canvas_render_stroke; + + canvas->methods[ptr].command = canvas->urid.Canvas_Fill; + canvas->methods[ptr++].func = _lv2_canvas_render_fill; + + canvas->methods[ptr].command = canvas->urid.Canvas_Clip; + canvas->methods[ptr++].func = _lv2_canvas_render_clip; + + canvas->methods[ptr].command = canvas->urid.Canvas_Save; + canvas->methods[ptr++].func = _lv2_canvas_render_save; + + canvas->methods[ptr].command = canvas->urid.Canvas_Restore; + canvas->methods[ptr++].func = _lv2_canvas_render_restore; + + canvas->methods[ptr].command = canvas->urid.Canvas_Translate; + canvas->methods[ptr++].func = _lv2_canvas_render_translate; + + canvas->methods[ptr].command = canvas->urid.Canvas_Scale; + canvas->methods[ptr++].func = _lv2_canvas_render_scale; + + canvas->methods[ptr].command = canvas->urid.Canvas_Rotate; + canvas->methods[ptr++].func = _lv2_canvas_render_rotate; + + canvas->methods[ptr].command = canvas->urid.Canvas_Transform; + canvas->methods[ptr++].func = _lv2_canvas_render_transform; + + canvas->methods[ptr].command = canvas->urid.Canvas_Reset; + canvas->methods[ptr++].func = _lv2_canvas_render_reset; + + canvas->methods[ptr].command = canvas->urid.Canvas_FontSize; + canvas->methods[ptr++].func = _lv2_canvas_render_fontSize; + + canvas->methods[ptr].command = canvas->urid.Canvas_FillText; + canvas->methods[ptr++].func = _lv2_canvas_render_fillText; + + assert(ptr == LV2_CANVAS_NUM_METHODS); + + _lv2_canvas_qsort(canvas->methods, LV2_CANVAS_NUM_METHODS); +} + +static inline bool +lv2_canvas_render_body(LV2_Canvas *canvas, cairo_t *ctx, uint32_t type, + uint32_t size, const LV2_Atom *body) +{ + LV2_Canvas_URID *urid = &canvas->urid; + + if(!body || (type != urid->forge.Tuple) ) + return false; + + // save state + cairo_save(ctx); + + // clear surface + cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR); + cairo_paint(ctx); + + // default attributes + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + cairo_set_font_size(ctx, 0.1); + cairo_set_line_width(ctx, 0.01); + cairo_set_source_rgba(ctx, 1.0, 1.0, 1.0, 1.0); + + LV2_ATOM_TUPLE_BODY_FOREACH(body, size, itm) + { + if(lv2_atom_forge_is_object_type(&urid->forge, itm->type)) + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)itm; + const LV2_Atom *body = NULL; + + lv2_atom_object_get(obj, urid->Canvas_body, &body, 0); + + LV2_Canvas_Meth *meth = _lv2_canvas_bsearch(obj->body.otype, + canvas->methods, LV2_CANVAS_NUM_METHODS); + + if(meth) + { + meth->func(ctx, urid, body); + } + } + } + + // save state + cairo_restore(ctx); + + // flush + cairo_surface_t *surface = cairo_get_target(ctx); + cairo_surface_flush(surface); + + return true; +} + +static inline bool +lv2_canvas_render(LV2_Canvas *canvas, cairo_t *ctx, const LV2_Atom_Tuple *tup) +{ + return lv2_canvas_render_body(canvas, ctx, tup->atom.type, tup->atom.size, + LV2_ATOM_BODY_CONST(&tup->atom)); +} + +#ifdef __cplusplus +} +#endif + +#endif // LV2_CANVAS_RENDER_CAIRO_H diff --git a/canvas.lv2/canvas.lv2/render_nanovg.h b/canvas.lv2/canvas.lv2/render_nanovg.h new file mode 100644 index 0000000..a98bc95 --- /dev/null +++ b/canvas.lv2/canvas.lv2/render_nanovg.h @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _LV2_CANVAS_RENDER_NANOVG_H +#define _LV2_CANVAS_RENDER_NANOVG_H + +#include <assert.h> + +#include <nanovg.h> + +#if defined(__APPLE__) +# include <OpenGL/gl.h> +# include <OpenGL/glext.h> +#else +# include <GL/glew.h> +#endif + +#define NANOVG_GL2_IMPLEMENTATION +#include <nanovg_gl.h> + +#if defined(NANOVG_GL2_IMPLEMENTATION) +# define nvgCreate nvgCreateGL2 +# define nvgDelete nvgDeleteGL2 +#elif defined(NANOVG_GL3_IMPLEMENTATION) +# define nvgCreate nvgCreateGL3 +# define nvgDelete nvgDeleteGL3 +#elif defined(NANOVG_GLES2_IMPLEMENTATION) +# define nvgCreate nvgCreateGLES2 +# define nvgDelete nvgDeleteGLES2 +#elif defined(NANOVG_GLES3_IMPLEMENTATION) +# define nvgCreate nvgCreateGLES3 +# define nvgDelete nvgDeleteGLES3 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +static inline void +_lv2_canvas_render_beginPath(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + nvgBeginPath(ctx); +} + +static inline void +_lv2_canvas_render_closePath(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + nvgClosePath(ctx); +} + +static inline void +_lv2_canvas_render_arc(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 5); + + if(v) + { + nvgArc(ctx, v[0], v[1], v[2], v[3], v[4], NVG_CCW); + } +} + +static inline void +_lv2_canvas_render_curveTo(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 6); + + if(v) + { + nvgBezierTo(ctx, v[0], v[1], v[2], v[3], v[4], v[5]); + } +} + +static inline void +_lv2_canvas_render_lineTo(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + nvgLineTo(ctx, v[0], v[1]); + } +} + +static inline void +_lv2_canvas_render_moveTo(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + nvgMoveTo(ctx, v[0], v[1]); + } +} + +static inline void +_lv2_canvas_render_rectangle(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 4); + + if(v) + { + nvgRect(ctx, v[0], v[1], v[2], v[3]); + } +} + +static inline void +_lv2_canvas_render_polyline(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + uint32_t N; + const float *v = _lv2_canvas_render_get_float_vecs(urid, body, &N); + + if(v) + { + for(uint32_t i = 0; i < 2; i += 2) + { + nvgMoveTo(ctx, v[i], v[i+1]); + } + + for(uint32_t i = 2; i < N; i += 2) + { + nvgLineTo(ctx, v[i], v[i+1]); + } + } +} + +static inline void +_lv2_canvas_render_style(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const int64_t *v = _lv2_canvas_render_get_type(body, urid->forge.Long); + + if(v) + { + const uint8_t r = (*v >> 24) & 0xff; + const uint8_t g = (*v >> 16) & 0xff; + const uint8_t b = (*v >> 8) & 0xff; + const uint8_t a = (*v >> 0) & 0xff; + + nvgStrokeColor(ctx, nvgRGBA(r, g, b, a)); + nvgFillColor(ctx, nvgRGBA(r, g, b, a)); + } +} + +static inline void +_lv2_canvas_render_lineWidth(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_type(body, urid->forge.Float); + + if(v) + { + nvgStrokeWidth(ctx, *v); + } +} + +static inline void +_lv2_canvas_render_lineDash(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + (void)ctx; //FIXME + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + //const double d[2] = {v[0], v[1]}; + //FIXME cairo_set_dash(ctx, d, 2, 0); + } +} + +static inline void +_lv2_canvas_render_lineCap(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const LV2_URID *v = _lv2_canvas_render_get_type(body, urid->forge.URID); + + if(v) + { + int cap = NVG_BUTT; + + if(*v == urid->Canvas_lineCapButt) + cap = NVG_BUTT; + else if(*v == urid->Canvas_lineCapRound) + cap = NVG_ROUND; + else if(*v == urid->Canvas_lineCapSquare) + cap = NVG_SQUARE; + + nvgLineCap(ctx, cap); + } +} + +static inline void +_lv2_canvas_render_lineJoin(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const LV2_URID *v = _lv2_canvas_render_get_type(body, urid->forge.URID); + + if(v) + { + int join = NVG_MITER; + + if(*v == urid->Canvas_lineJoinMiter) + join = NVG_MITER; + else if(*v == urid->Canvas_lineJoinRound) + join = NVG_ROUND; + else if(*v == urid->Canvas_lineJoinBevel) + join = NVG_BEVEL; + + nvgLineJoin(ctx, join); + } +} + +static inline void +_lv2_canvas_render_miterLimit(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_type(body, urid->forge.Float); + + if(v) + { + nvgMiterLimit(ctx, *v); + } +} + +static inline void +_lv2_canvas_render_stroke(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + nvgStroke(ctx); +} + +static inline void +_lv2_canvas_render_fill(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + nvgFill(ctx); +} + +static inline void +_lv2_canvas_render_clip(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + (void)ctx; //FIXME + //FIXME cairo_clip(ctx); +} + +static inline void +_lv2_canvas_render_save(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + nvgSave(ctx); +} + +static inline void +_lv2_canvas_render_restore(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + nvgRestore(ctx); +} + +static inline void +_lv2_canvas_render_translate(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + nvgTranslate(ctx, v[0], v[1]); + } +} + +static inline void +_lv2_canvas_render_scale(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 2); + + if(v) + { + nvgScale(ctx, v[0], v[1]); + } +} + +static inline void +_lv2_canvas_render_rotate(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_type(body, urid->forge.Float); + + if(v) + { + nvgRotate(ctx, *v); + } +} + +static inline void +_lv2_canvas_render_transform(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_float_vec(urid, body, 6); + + if(v) + { + nvgTransform(ctx, v[0], v[1], v[2], v[3], v[4], v[5]); + } +} + +static inline void +_lv2_canvas_render_reset(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + nvgReset(ctx); +} + +static inline void +_lv2_canvas_render_fontSize(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + const float *v = _lv2_canvas_render_get_type(body, urid->forge.Float); + + if(v) + { + nvgFontSize(ctx, *v); + } +} + +static inline void +_lv2_canvas_render_fillText(void *data, + LV2_Canvas_URID *urid, const LV2_Atom *body) +{ + NVGcontext *ctx = data; + (void)ctx; //FIXME + const char *v = _lv2_canvas_render_get_type(body, urid->forge.String); + + if(v) + { + /* FIXME + NVGcontextext_extents_t extents; + NVGcontextext_extents(ctx, v, &extents); + const float dx = extents.width/2 + extents.x_bearing; + const float dy = extents.height/2 + extents.y_bearing; + cairo_rel_move_to(ctx, -dx, -dy); + cairo_show_text(ctx, v); + */ + /* + float bounds [4]; + const float adv_x = nvgTextBounds(ctx, 0.f, 0.f, v, NULL, bounds); + nvgText(ctx, float x, float y, const char* string, const char* end); + */ + } +} + +static inline void +_lv2_canvas_qsort(LV2_Canvas_Meth *A, int n) +{ + if(n < 2) + return; + + LV2_Canvas_Meth *p = A; + + int i = -1; + int j = n; + + while(true) + { + do { + i += 1; + } while(A[i].command < p->command); + + do { + j -= 1; + } while(A[j].command > p->command); + + if(i >= j) + break; + + const LV2_Canvas_Meth tmp = A[i]; + A[i] = A[j]; + A[j] = tmp; + } + + _lv2_canvas_qsort(A, j + 1); + _lv2_canvas_qsort(A + j + 1, n - j - 1); +} + +static inline LV2_Canvas_Meth * +_lv2_canvas_bsearch(LV2_URID p, LV2_Canvas_Meth *a, int n) +{ + LV2_Canvas_Meth *base = a; + + for(int N = n, half; N > 1; N -= half) + { + half = N/2; + LV2_Canvas_Meth *dst = &base[half]; + base = (dst->command > p) ? base : dst; + } + + return (base->command == p) ? base : NULL; +} + +static inline void +lv2_canvas_init(LV2_Canvas *canvas, LV2_URID_Map *map) +{ + lv2_canvas_urid_init(&canvas->urid, map); + + unsigned ptr = 0; + + canvas->methods[ptr].command = canvas->urid.Canvas_BeginPath; + canvas->methods[ptr++].func = _lv2_canvas_render_beginPath; + + canvas->methods[ptr].command = canvas->urid.Canvas_ClosePath; + canvas->methods[ptr++].func = _lv2_canvas_render_closePath; + + canvas->methods[ptr].command = canvas->urid.Canvas_Arc; + canvas->methods[ptr++].func = _lv2_canvas_render_arc; + + canvas->methods[ptr].command = canvas->urid.Canvas_CurveTo; + canvas->methods[ptr++].func = _lv2_canvas_render_curveTo; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineTo; + canvas->methods[ptr++].func = _lv2_canvas_render_lineTo; + + canvas->methods[ptr].command = canvas->urid.Canvas_MoveTo; + canvas->methods[ptr++].func = _lv2_canvas_render_moveTo; + + canvas->methods[ptr].command = canvas->urid.Canvas_Rectangle; + canvas->methods[ptr++].func = _lv2_canvas_render_rectangle; + + canvas->methods[ptr].command = canvas->urid.Canvas_PolyLine; + canvas->methods[ptr++].func = _lv2_canvas_render_polyline; + + canvas->methods[ptr].command = canvas->urid.Canvas_Style; + canvas->methods[ptr++].func = _lv2_canvas_render_style; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineWidth; + canvas->methods[ptr++].func = _lv2_canvas_render_lineWidth; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineDash; + canvas->methods[ptr++].func = _lv2_canvas_render_lineDash; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineCap; + canvas->methods[ptr++].func = _lv2_canvas_render_lineCap; + + canvas->methods[ptr].command = canvas->urid.Canvas_LineJoin; + canvas->methods[ptr++].func = _lv2_canvas_render_lineJoin; + + canvas->methods[ptr].command = canvas->urid.Canvas_MiterLimit; + canvas->methods[ptr++].func = _lv2_canvas_render_miterLimit; + + canvas->methods[ptr].command = canvas->urid.Canvas_Stroke; + canvas->methods[ptr++].func = _lv2_canvas_render_stroke; + + canvas->methods[ptr].command = canvas->urid.Canvas_Fill; + canvas->methods[ptr++].func = _lv2_canvas_render_fill; + + canvas->methods[ptr].command = canvas->urid.Canvas_Clip; + canvas->methods[ptr++].func = _lv2_canvas_render_clip; + + canvas->methods[ptr].command = canvas->urid.Canvas_Save; + canvas->methods[ptr++].func = _lv2_canvas_render_save; + + canvas->methods[ptr].command = canvas->urid.Canvas_Restore; + canvas->methods[ptr++].func = _lv2_canvas_render_restore; + + canvas->methods[ptr].command = canvas->urid.Canvas_Translate; + canvas->methods[ptr++].func = _lv2_canvas_render_translate; + + canvas->methods[ptr].command = canvas->urid.Canvas_Scale; + canvas->methods[ptr++].func = _lv2_canvas_render_scale; + + canvas->methods[ptr].command = canvas->urid.Canvas_Rotate; + canvas->methods[ptr++].func = _lv2_canvas_render_rotate; + + canvas->methods[ptr].command = canvas->urid.Canvas_Transform; + canvas->methods[ptr++].func = _lv2_canvas_render_transform; + + canvas->methods[ptr].command = canvas->urid.Canvas_Reset; + canvas->methods[ptr++].func = _lv2_canvas_render_reset; + + canvas->methods[ptr].command = canvas->urid.Canvas_FontSize; + canvas->methods[ptr++].func = _lv2_canvas_render_fontSize; + + canvas->methods[ptr].command = canvas->urid.Canvas_FillText; + canvas->methods[ptr++].func = _lv2_canvas_render_fillText; + + assert(ptr == LV2_CANVAS_NUM_METHODS); + + _lv2_canvas_qsort(canvas->methods, LV2_CANVAS_NUM_METHODS); +} + +static inline bool +lv2_canvas_render_body(LV2_Canvas *canvas, NVGcontext *ctx, uint32_t type, + uint32_t size, const LV2_Atom *body) +{ + LV2_Canvas_URID *urid = &canvas->urid; + + if(!body || (type != urid->forge.Tuple) ) + return false; + + // save state + nvgSave(ctx); + + // clear surface + nvgBeginPath(ctx); + nvgRect(ctx, 0, 0, 1.f, 1.f); + nvgFillColor(ctx, nvgRGBA(0x1e, 0x1e, 0x1e, 0xff)); + nvgFill(ctx); + + nvgFontSize(ctx, 0.1); + nvgStrokeWidth(ctx, 0.01); + nvgStrokeColor(ctx, nvgRGBA(0xff, 0xff, 0xff, 0xff)); + nvgFillColor(ctx, nvgRGBA(0xff, 0xff, 0xff, 0xff)); + + LV2_ATOM_TUPLE_BODY_FOREACH(body, size, itm) + { + if(lv2_atom_forge_is_object_type(&urid->forge, itm->type)) + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)itm; + const LV2_Atom *body = NULL; + + lv2_atom_object_get(obj, urid->Canvas_body, &body, 0); + + LV2_Canvas_Meth *meth = _lv2_canvas_bsearch(obj->body.otype, + canvas->methods, LV2_CANVAS_NUM_METHODS); + + if(meth) + { + meth->func(ctx, urid, body); + } + } + } + + // save state + nvgRestore(ctx); + + return true; +} + +static inline bool +lv2_canvas_render(LV2_Canvas *canvas, NVGcontext *ctx, const LV2_Atom_Tuple *tup) +{ + return lv2_canvas_render_body(canvas, ctx, tup->atom.type, tup->atom.size, + LV2_ATOM_BODY_CONST(&tup->atom)); +} + +#ifdef __cplusplus +} +#endif + +#endif // LV2_CANVAS_RENDER_NANOVG_H diff --git a/manifest.ttl.in b/manifest.ttl.in new file mode 100644 index 0000000..9cf24e5 --- /dev/null +++ b/manifest.ttl.in @@ -0,0 +1,80 @@ +# Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch) +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the Artistic License 2.0 as published by +# The Perl Foundation. +# +# This source 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 +# Artistic License 2.0 for more details. +# +# You should have received a copy of the Artistic License 2.0 +# along the source as a COPYING file. If not, obtain it from +# http://www.perlfoundation.org/artistic_license_2_0. + +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . +@prefix atom: <http://lv2plug.in/ns/ext/atom#> . + +@prefix canvas: <http://open-music-kontrollers.ch/lv2/canvas#> . +@prefix monitors: <http://open-music-kontrollers.ch/lv2/monitors#> . + +# Audio Wave Mono +monitors:audio_wave_mono + a lv2:Plugin ; + lv2:minorVersion @MINOR_VERSION@ ; + lv2:microVersion @MICRO_VERSION@ ; + lv2:binary <monitors@MODULE_SUFFIX@> ; + ui:ui canvas:display_ui ; + rdfs:seeAlso <monitors.ttl> . + +# Audio Wave Stereo +monitors:audio_wave_stereo + a lv2:Plugin ; + lv2:minorVersion @MINOR_VERSION@ ; + lv2:microVersion @MICRO_VERSION@ ; + lv2:binary <monitors@MODULE_SUFFIX@> ; + ui:ui canvas:display_ui ; + rdfs:seeAlso <monitors.ttl> . + +# MIDI Pianoroll +monitors:midi_pianoroll + a lv2:Plugin ; + lv2:minorVersion @MINOR_VERSION@ ; + lv2:microVersion @MICRO_VERSION@ ; + lv2:binary <monitors@MODULE_SUFFIX@> ; + ui:ui canvas:display_ui ; + rdfs:seeAlso <monitors.ttl> . + +# Time Metronom +monitors:time_metronom + a lv2:Plugin ; + lv2:minorVersion @MINOR_VERSION@ ; + lv2:microVersion @MICRO_VERSION@ ; + lv2:binary <monitors@MODULE_SUFFIX@> ; + ui:ui canvas:display_ui ; + rdfs:seeAlso <monitors.ttl> . + +# UI +canvas:display_ui + a ui:@UI_TYPE@ ; + ui:portNotification [ + ui:plugin monitors:audio_wave_mono ; + lv2:symbol "notify" ; + ui:protocol atom:eventTransfer ; + ] , [ + ui:plugin monitors:audio_wave_stereo ; + lv2:symbol "notify" ; + ui:protocol atom:eventTransfer ; + ] , [ + ui:plugin monitors:midi_pianoroll ; + lv2:symbol "notify" ; + ui:protocol atom:eventTransfer ; + ] , [ + ui:plugin monitors:time_metronom ; + lv2:symbol "notify" ; + ui:protocol atom:eventTransfer ; + ] . diff --git a/meson.build b/meson.build index df8d684..0f354bd 100644 --- a/meson.build +++ b/meson.build @@ -1,15 +1,25 @@ -project('timely.lv2', 'c', default_options : [ +project('monitors.lv2', 'c', default_options : [ 'buildtype=release', 'warning_level=3', 'werror=false', 'b_lto=false', 'c_std=c11']) +build_root = meson.build_root() add_project_arguments('-D_GNU_SOURCE', language : 'c') conf_data = configuration_data() cc = meson.get_compiler('c') +build_idisp= get_option('build-inline-display') + +if build_idisp + add_project_arguments('-DBUILD_IDISP', language: 'c') + conf_data.set('BUILD_IDISP', '') +else + conf_data.set('BUILD_IDISP', '#') +endif + cp = find_program('cp') lv2_validate = find_program('lv2_validate', native : true, required : false) sord_validate = find_program('sord_validate', native : true, required : false) @@ -18,21 +28,36 @@ clone = [cp, '@INPUT@', '@OUTPUT@'] m_dep = cc.find_library('m') lv2_dep = dependency('lv2', version : '>=1.14.0') +cairo_dep = dependency('cairo', version : '>=1.0.0', + static : meson.is_cross_build() and false, #FIXME + required: build_idisp) -inc_dir = [] +canvas_inc = include_directories('canvas.lv2') +timely_inc = include_directories('timely.lv2') inst_dir = join_paths(get_option('libdir'), 'lv2', meson.project_name()) -dsp_srcs = [join_paths('test', 'timely.c')] - +srcs = ['monitors.c', + 'monitors_audio_wave.c', + 'monitors_midi_pianoroll.c', + 'monitors_time_metronom.c'] + c_args = ['-fvisibility=hidden', '-ffast-math'] -mod = shared_module('timely', dsp_srcs, +if host_machine.system() == 'windows' + conf_data.set('UI_TYPE', 'WindowsUI') +elif host_machine.system() == 'darwin' + conf_data.set('UI_TYPE', 'CocoaUI') +else + conf_data.set('UI_TYPE', 'X11UI') +endif + +mod = shared_module('monitors', srcs, c_args : c_args, - include_directories : inc_dir, name_prefix : '', - dependencies : [m_dep, lv2_dep], + include_directories : [canvas_inc, timely_inc], + dependencies : [m_dep, lv2_dep, cairo_dep], install : true, install_dir : inst_dir) @@ -44,14 +69,13 @@ conf_data.set('MICRO_VERSION', version[2]) suffix = mod.full_path().strip().split('.')[-1] conf_data.set('MODULE_SUFFIX', '.' + suffix) -manifest_ttl = configure_file(input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl', +manifest_ttl = configure_file(input : 'manifest.ttl.in', output : 'manifest.ttl', configuration : conf_data, install : true, install_dir : inst_dir) -dsp_ttl = custom_target('timely_ttl', - input : join_paths('test', 'timely.ttl'), - output : 'timely.ttl', - command : clone, + +dsp_ttl = configure_file(input : 'monitors.ttl.in', output : 'monitors.ttl', + configuration : conf_data, install : true, install_dir : inst_dir) @@ -62,6 +86,9 @@ endif if lv2lint.found() test('LV2 lint', lv2lint, - args : ['-Ewarn', - 'http://open-music-kontrollers.ch/lv2/timely#test']) + args : ['-Ewarn', '-I', join_paths(build_root, ''), + 'http://open-music-kontrollers.ch/lv2/monitors#audio_wave_mono', + 'http://open-music-kontrollers.ch/lv2/monitors#audio_wave_stereo', + 'http://open-music-kontrollers.ch/lv2/monitors#midi_pianoroll', + 'http://open-music-kontrollers.ch/lv2/monitors#time_metronom']) endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..dca36c1 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('build-inline-display', type : 'boolean', value : false) diff --git a/monitors.c b/monitors.c new file mode 100644 index 0000000..160b471 --- /dev/null +++ b/monitors.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <monitors.h> + +LV2_SYMBOL_EXPORT const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch(index) + { + case 0: + return &monitors_audio_wave_mono; + case 1: + return &monitors_audio_wave_stereo; + case 2: + return &monitors_midi_pianoroll; + case 3: + return &monitors_time_metronom; + + default: + return NULL; + } +} diff --git a/monitors.h b/monitors.h new file mode 100644 index 0000000..320cdd1 --- /dev/null +++ b/monitors.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _MONITORS_LV2_H +#define _MONITORS_LV2_H + +#include <stdint.h> +#if !defined(_WIN32) +# include <sys/mman.h> +#else +# define mlock(...) +# define munlock(...) +#endif + +#include <lv2/lv2plug.in/ns/ext/atom/atom.h> +#include <lv2/lv2plug.in/ns/ext/atom/forge.h> +#include <lv2/lv2plug.in/ns/ext/urid/urid.h> +#include <lv2/lv2plug.in/ns/ext/state/state.h> +#include <lv2/lv2plug.in/ns/ext/log/log.h> +#include <lv2/lv2plug.in/ns/ext/log/logger.h> +#include <lv2/lv2plug.in/ns/ext/options/options.h> +#include <lv2/lv2plug.in/ns/ext/midi/midi.h> +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> +#include <lv2/lv2plug.in/ns/lv2core/lv2.h> + +#define MONITORS_URI "http://open-music-kontrollers.ch/lv2/monitors" +#define MONITORS_PREFIX MONITORS_URI"#" + +// plugin uris +#define MONITORS__audio_wave_mono MONITORS_PREFIX"audio_wave_mono" +#define MONITORS__audio_wave_stereo MONITORS_PREFIX"audio_wave_stereo" +#define MONITORS__midi_pianoroll MONITORS_PREFIX"midi_pianoroll" +#define MONITORS__time_metronom MONITORS_PREFIX"time_metronom" + +// paramer uris +#define MONITORS__window MONITORS_PREFIX"window" +#define MONITORS__channel MONITORS_PREFIX"channel" +#define MONITORS__rotation MONITORS_PREFIX"rotation" +#define MONITORS__horizontalFlip MONITORS_PREFIX"horizontalFlip" +#define MONITORS__verticalFlip MONITORS_PREFIX"verticalFlip" + +extern const LV2_Descriptor monitors_audio_wave_mono; +extern const LV2_Descriptor monitors_audio_wave_stereo; +extern const LV2_Descriptor monitors_midi_pianoroll; +extern const LV2_Descriptor monitors_time_metronom; + +typedef struct _craft_t craft_t; + +struct _craft_t { + union { + LV2_Atom_Sequence *seq; + uint8_t *buf; + }; + LV2_Atom_Forge forge; + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge_Ref ref; +}; + +static inline void +_craft_init(craft_t *craft, LV2_URID_Map *map) +{ + lv2_atom_forge_init(&craft->forge, map); +} + +static inline void +_craft_connect(craft_t *craft, LV2_Atom_Sequence *seq) +{ + craft->seq = seq; +} + +static inline void +_craft_in(craft_t *craft) +{ + const uint32_t capacity = craft->seq->atom.size; + lv2_atom_forge_set_buffer(&craft->forge, craft->buf, capacity); + craft->ref = lv2_atom_forge_sequence_head(&craft->forge, &craft->frame, 0); +} + +static inline void +_craft_clear(craft_t *craft) +{ + craft->ref = 0; + lv2_atom_sequence_clear(craft->seq); +} + +static inline void +_craft_out(craft_t *craft) +{ + if(craft->ref) + lv2_atom_forge_pop(&craft->forge, &craft->frame); + else + _craft_clear(craft); +} + +#endif // _MONITORS_LV2_H diff --git a/monitors.ttl.in b/monitors.ttl.in new file mode 100644 index 0000000..e28a406 --- /dev/null +++ b/monitors.ttl.in @@ -0,0 +1,361 @@ +# Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch) +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the Artistic License 2.0 as published by +# The Perl Foundation. +# +# This source 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 +# Artistic License 2.0 for more details. +# +# You should have received a copy of the Artistic License 2.0 +# along the source as a COPYING file. If not, obtain it from +# http://www.perlfoundation.org/artistic_license_2_0. + +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix atom: <http://lv2plug.in/ns/ext/atom#> . +@prefix urid: <http://lv2plug.in/ns/ext/urid#> . +@prefix log: <http://lv2plug.in/ns/ext/log#> . +@prefix state: <http://lv2plug.in/ns/ext/state#> . +@prefix patch: <http://lv2plug.in/ns/ext/patch#> . +@prefix bufsz: <http://lv2plug.in/ns/ext/buf-size#> . +@prefix rsz: <http://lv2plug.in/ns/ext/resize-port#> . +@prefix opts: <http://lv2plug.in/ns/ext/options#> . +@prefix midi: <http://lv2plug.in/ns/ext/midi#> . +@prefix ui: <http://lv2plug.in/ns/extentions/ui#> . +@prefix units: <http://lv2plug.in/ns/extensions/units#> . +@prefix idisp: <http://harrisonconsoles.com/lv2/inlinedisplay#> . + +@prefix lic: <http://opensource.org/licenses/> . +@prefix omk: <http://open-music-kontrollers.ch/ventosus#> . +@prefix proj: <http://open-music-kontrollers.ch/lv2/> . +@prefix monitors: <http://open-music-kontrollers.ch/lv2/monitors#> . +@prefix canvas: <http://open-music-kontrollers.ch/lv2/canvas#> . + +# to please sord_validate +idisp:queue_draw + a lv2:Feature . +idisp:interface + a lv2:ExtensionData . + +ui:updateRate + a opts:Option . + +# Maintainer +omk:me + a foaf:Person ; + foaf:name "Hanspeter Portner" ; + foaf:mbox <mailto:dev@open-music-kontrollers.ch> ; + foaf:homepage <http://open-music-kontrollers.ch> . + +# Project +proj:monitors + a doap:Project ; + doap:maintainer omk:me ; + doap:name "Router Bundle" . + +# Parameters +canvas:graph + a lv2:Parameter ; + rdfs:label "Graph" ; + rdfs:comment "set canvas graph" ; + rdfs:range atom:Tuple . +canvas:aspectRatio + a lv2:Parameter ; + rdfs:label "Aspect ratio" ; + rdfs:comment "set aspect ratio" ; + rdfs:range atom:Float ; + lv2:minimum 0.25 ; + lv2:maximum 4.0 . +monitors:rotation + a lv2:Parameter ; + rdfs:label "Rotation" ; + rdfs:comment "set rotation" ; + rdfs:range atom:Float ; + lv2:minimum 0.0 ; + lv2:maximum 360.0 ; + units:unit units:degree . +monitors:horizontalFlip + a lv2:Parameter ; + rdfs:label "Horizontal flip" ; + rdfs:comment "toggle horizontal flip" ; + rdfs:range atom:Bool . +monitors:verticalFlip + a lv2:Parameter ; + rdfs:label "Vertical flip" ; + rdfs:comment "toggle vertical flip" ; + rdfs:range atom:Bool . +monitors:window + a lv2:Parameter ; + rdfs:label "Window" ; + rdfs:comment "set window" ; + rdfs:range atom:Int ; + lv2:minimum 100 ; + lv2:maximum 10000 ; + units:unit units:ms . +monitors:channel + a lv2:Parameter ; + rdfs:label "Channel" ; + rdfs:comment "set channel" ; + rdfs:range atom:Int ; + lv2:minimum 0 ; + lv2:maximum 15 . + +# Audio Wave Mono +monitors:audio_wave_mono + a lv2:Plugin , + lv2:AnalyserPlugin ; + doap:name "Monitors Audio Wave Mono" ; + doap:license lic:Artistic-2.0 ; + lv2:project proj:monitors ; + lv2:requiredFeature urid:map, state:loadDefaultState, opts:options ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ; + lv2:extensionData state:interface ; + @BUILD_IDISP@lv2:optionalFeature idisp:queue_draw ; + @BUILD_IDISP@lv2:extensionData idisp:interface ; + opts:requiredOption ui:updateRate ; + + lv2:port [ + a lv2:InputPort , + lv2:AudioPort ; + lv2:index 0 ; + lv2:symbol "audio_in" ; + lv2:name "Audio In" ; + ] , [ + a lv2:OutputPort, + lv2:AudioPort ; + lv2:index 1 ; + lv2:symbol "audio_out" ; + lv2:name "Audio Out" ; + ] , [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:index 2 ; + lv2:symbol "control" ; + lv2:name "Control" ; + lv2:designation lv2:control ; + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:index 3 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + lv2:designation lv2:control ; + rsz:minimumSize 131072 ; + ] ; + + patch:readable + canvas:graph ; + + patch:writable + canvas:aspectRatio , + monitors:rotation , + monitors:horizontalFlip , + monitors:verticalFlip , + monitors:window ; + + state:state [ + canvas:aspectRatio "1.6"^^xsd:float ; + monitors:rotation "0.0"^^xsd:float ; + monitors:horizontalFlip false ; + monitors:verticalFlip false ; + monitors:window "1000"^^xsd:int ; + ] . + +# Audio Wave Stereo +monitors:audio_wave_stereo + a lv2:Plugin , + lv2:AnalyserPlugin ; + doap:name "Monitors Audio Wave Stereo" ; + doap:license lic:Artistic-2.0 ; + lv2:project proj:monitors ; + lv2:requiredFeature urid:map, state:loadDefaultState, opts:options ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ; + lv2:extensionData state:interface ; + @BUILD_IDISP@lv2:optionalFeature idisp:queue_draw ; + @BUILD_IDISP@lv2:extensionData idisp:interface ; + opts:requiredOption ui:updateRate ; + + lv2:port [ + a lv2:InputPort , + lv2:AudioPort ; + lv2:index 0 ; + lv2:symbol "audio_in_1" ; + lv2:name "Audio In 1" ; + ] , [ + a lv2:InputPort , + lv2:AudioPort ; + lv2:index 1 ; + lv2:symbol "audio_in_2" ; + lv2:name "Audio In 2" ; + ] , [ + a lv2:OutputPort, + lv2:AudioPort ; + lv2:index 2 ; + lv2:symbol "audio_out_1" ; + lv2:name "Audio Out 1" ; + ] , [ + a lv2:OutputPort, + lv2:AudioPort ; + lv2:index 3 ; + lv2:symbol "audio_out_2" ; + lv2:name "Audio Out 2" ; + ] , [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:index 4 ; + lv2:symbol "control" ; + lv2:name "Control" ; + lv2:designation lv2:control ; + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:index 5 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + lv2:designation lv2:control ; + rsz:minimumSize 131072 ; + ] ; + + patch:readable + canvas:graph ; + + patch:writable + canvas:aspectRatio , + monitors:rotation , + monitors:horizontalFlip , + monitors:verticalFlip , + monitors:window ; + + state:state [ + canvas:aspectRatio "1.6"^^xsd:float ; + monitors:rotation "0.0"^^xsd:float ; + monitors:horizontalFlip false ; + monitors:verticalFlip false ; + monitors:window "1000"^^xsd:int ; + ] . + +# MIDI Pianoroll +monitors:midi_pianoroll + a lv2:Plugin , + lv2:AnalyserPlugin ; + doap:name "Monitors MIDI Pianoroll" ; + doap:license lic:Artistic-2.0 ; + lv2:project proj:monitors ; + lv2:requiredFeature urid:map, state:loadDefaultState, opts:options ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ; + lv2:extensionData state:interface ; + @BUILD_IDISP@lv2:optionalFeature idisp:queue_draw ; + @BUILD_IDISP@lv2:extensionData idisp:interface ; + opts:requiredOption ui:updateRate ; + + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message , + midi:MidiEvent ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" ; + lv2:designation lv2:control ; + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message , + midi:MidiEvent ; + lv2:index 1 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + lv2:designation lv2:control ; + rsz:minimumSize 131072 ; + ] ; + + patch:readable + canvas:graph ; + + patch:writable + canvas:aspectRatio , + monitors:rotation , + monitors:horizontalFlip , + monitors:verticalFlip , + monitors:window , + monitors:channel ; + + state:state [ + canvas:aspectRatio "1.0"^^xsd:float ; + monitors:rotation "0.0"^^xsd:float ; + monitors:horizontalFlip false ; + monitors:verticalFlip false ; + monitors:window "1000"^^xsd:int ; + monitors:channel 0 ; + ] . + +# Time Metronom +monitors:time_metronom + a lv2:Plugin , + lv2:AnalyserPlugin ; + doap:name "Monitors Time Metronom" ; + doap:license lic:Artistic-2.0 ; + lv2:project proj:monitors ; + lv2:requiredFeature urid:map, state:loadDefaultState, opts:options ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ; + lv2:extensionData state:interface ; + @BUILD_IDISP@lv2:optionalFeature idisp:queue_draw ; + @BUILD_IDISP@lv2:extensionData idisp:interface ; + opts:requiredOption ui:updateRate ; + + lv2:port [ + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message , + midi:MidiEvent ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Control" ; + lv2:designation lv2:control ; + ] , [ + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message , + midi:MidiEvent ; + lv2:index 1 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + lv2:designation lv2:control ; + rsz:minimumSize 131072 ; + ] ; + + patch:readable + canvas:graph ; + + patch:writable + canvas:aspectRatio , + monitors:rotation , + monitors:horizontalFlip , + monitors:verticalFlip ; + + state:state [ + canvas:aspectRatio "1.0"^^xsd:float ; + monitors:rotation "0.0"^^xsd:float ; + monitors:horizontalFlip false ; + monitors:verticalFlip false ; + ] . diff --git a/monitors_audio_wave.c b/monitors_audio_wave.c new file mode 100644 index 0000000..bb263c9 --- /dev/null +++ b/monitors_audio_wave.c @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#include <monitors.h> + +#include <props.lv2/props.h> +#include <canvas.lv2/canvas.h> +#include <canvas.lv2/forge.h> + +#ifdef BUILD_IDISP +# include <canvas.lv2/idisp.h> +#endif + +#define MAX_CHANNELS 2 +#define MAX_SAMPLES 512 +#define MAX_GRAPH 0x20000 //FIXME actually measure this +#define MAX_NPROPS 6 + +typedef struct _plugstate_t plugstate_t; +typedef struct _plughandle_t plughandle_t; +typedef struct _point_t point_t; +typedef struct _item_t item_t; +typedef struct _wave_t wave_t; + +struct _plugstate_t { + int32_t window; + float rotation; + int32_t hflip; + int32_t vflip; + float aspect_ratio; + uint8_t graph [MAX_GRAPH]; +}; + +struct _point_t { + float x; + float y; +} __attribute__((packed)); + + +struct _item_t { + point_t tail; + point_t samples [MAX_SAMPLES]; + point_t head; +} __attribute__((packed)); + +struct _wave_t { + item_t upper; + item_t lower; +} __attribute__((packed)); + +struct _plughandle_t { + LV2_URID_Map *map; + + LV2_Log_Log *log; + LV2_Log_Logger logger; + + const float *audio_in [MAX_CHANNELS]; + float *audio_out [MAX_CHANNELS]; + const LV2_Atom_Sequence *control; + craft_t notify; + +#ifdef BUILD_IDISP + LV2_Canvas_Idisp idisp; +#endif + LV2_Canvas_URID urid; + double sample_rate; + float update_rate; + uint32_t graph_size; + + uint32_t max_window; + uint32_t spf; + uint32_t thresh; + uint32_t cnt; + + unsigned nchannels; + wave_t wave [MAX_CHANNELS]; + float min [MAX_CHANNELS]; + float max [MAX_CHANNELS]; + + plugstate_t state; + plugstate_t stash; + + PROPS_T(props, MAX_NPROPS); +}; + +static void +_fill(plughandle_t *handle) +{ + const float dx = 1.f / MAX_SAMPLES; + const float dy = 1.f / handle->nchannels; + + for(unsigned idx = 0; idx < handle->nchannels; idx++) + { + //FIXME fix for nchannels + wave_t *wave = &handle->wave[idx]; + item_t *upper = &wave->upper; + item_t *lower = &wave->lower; + + const float y0 = (0.5f + idx) * dy; + + upper->tail.x = 0.f; + upper->tail.y = y0; + + lower->tail.x = 0.f; + lower->tail.y = y0; + + for(unsigned i = 0; i < MAX_SAMPLES; i++) + { + point_t *pu = &upper->samples[i]; + point_t *pl = &lower->samples[i]; + + pu->x = pl->x = i * dx; + pu->y = pl->y = y0; + } + + upper->head.x = 1.f; + upper->head.y = y0; + + lower->head.x = 1.f; + lower->head.y = y0; + } +} + +static inline void +_out_of_memory(plughandle_t *handle) +{ + if(handle->log) + { + lv2_log_trace(&handle->logger, "out-of-memory\n"); + } + + _craft_clear(&handle->notify); +} + +static void +_render(plughandle_t *handle) +{ + LV2_Atom_Forge _forge = handle->notify.forge; // clone forge object + LV2_Atom_Forge *forge = &_forge; + LV2_Canvas_URID *urid = &handle->urid; + LV2_Atom_Forge_Frame frame; + + lv2_atom_forge_set_buffer(forge, handle->state.graph, MAX_GRAPH); + + if( !lv2_atom_forge_tuple(forge, &frame) + || !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_rectangle(forge, urid, 0.f, 0.f, 1.f, 1.f) + || !lv2_canvas_forge_style(forge, urid, 0x0000003f) + || !lv2_canvas_forge_fill(forge, urid) + || !lv2_canvas_forge_save(forge, urid) ) + { + _out_of_memory(handle); + return; + } + + if(handle->state.hflip || handle->state.vflip) + { + const float xx = handle->state.hflip ? -1.f : 1.f; + const float xy = 0.f; + const float x0 = handle->state.hflip ? 1.f : 0.f; + + const float yy = handle->state.vflip ? -1.f : 1.f; + const float yx = 0.f; + const float y0 = handle->state.vflip ? 1.f : 0.f; + + if( !lv2_canvas_forge_transform(forge, urid, xx, xy, x0, yy, yx, y0) ) + { + _out_of_memory(handle); + return; + } + } + + if( (fmod(handle->state.rotation, 360.f) != 0.f) ) + { + const float rot = handle->state.rotation / 180.f * M_PI; + + if( !lv2_canvas_forge_translate(forge, urid, 0.5f, 0.5f) + || !lv2_canvas_forge_rotate(forge, urid, rot) + || !lv2_canvas_forge_translate(forge, urid, -0.5f, -0.5f) ) + { + _out_of_memory(handle); + return; + } + } + + const float dy = 1.f / handle->nchannels; + + for(unsigned idx = 0; idx < handle->nchannels; idx++) + { + wave_t *wave = &handle->wave[idx]; + + const float y0 = (0.5f + idx) * dy; + + const float base [4] = { + 0.f, y0, + 1.f, y0 + }; + + if( !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_polyLine(forge, urid, sizeof(item_t)/sizeof(float), &wave->upper.tail.x) + || !lv2_canvas_forge_style(forge, urid, 0xff7f00ff) + || !lv2_canvas_forge_closePath(forge, urid) + || !lv2_canvas_forge_fill(forge, urid) + + || !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_polyLine(forge, urid, sizeof(item_t)/sizeof(float), &wave->lower.tail.x) + || !lv2_canvas_forge_style(forge, urid, 0xff7f009f) + || !lv2_canvas_forge_closePath(forge, urid) + || !lv2_canvas_forge_fill(forge, urid) + + || !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_polyLine(forge, urid, 4, base) + || !lv2_canvas_forge_lineWidth(forge, urid, 2.f / MAX_SAMPLES) + || !lv2_canvas_forge_style(forge, urid, 0xffffffff) + || !lv2_canvas_forge_stroke(forge, urid) ) + { + _out_of_memory(handle); + return; + } + } + + if( !lv2_canvas_forge_restore(forge, urid) ) + { + _out_of_memory(handle); + return; + } + + lv2_atom_forge_pop(forge, &frame); + + props_impl_t *impl = _props_impl_get(&handle->props, handle->urid.Canvas_graph); + if(impl) + { + const LV2_Atom *value= (const LV2_Atom *)handle->state.graph; + + _props_impl_set(&handle->props, impl, value->type, value->size, + LV2_ATOM_BODY_CONST(value)); + } + +#ifdef BUILD_IDISP + lv2_canvas_idisp_queue_draw(&handle->idisp); +#endif +} + +static void +_intercept_window(void *data, int64_t frames __attribute__((unused)), + props_impl_t *impl __attribute__((unused))) +{ + plughandle_t *handle = data; + + const double spw = 1e-3 * handle->sample_rate * handle->state.window; + handle->max_window = ceil(spw / MAX_SAMPLES); + + _fill(handle); + _render(handle); +} + +static const props_def_t defs [MAX_NPROPS] = { + { + .property = MONITORS__window, + .offset = offsetof(plugstate_t, window), + .type = LV2_ATOM__Int, + .event_cb = _intercept_window + }, + { + .property = MONITORS__rotation, + .offset = offsetof(plugstate_t, rotation), + .type = LV2_ATOM__Float + }, + { + .property = MONITORS__horizontalFlip, + .offset = offsetof(plugstate_t, hflip), + .type = LV2_ATOM__Bool + }, + { + .property = MONITORS__verticalFlip, + .offset = offsetof(plugstate_t, vflip), + .type = LV2_ATOM__Bool + }, + { + .access = LV2_PATCH__readable, + .property = CANVAS__graph, + .offset = offsetof(plugstate_t, graph), + .type = LV2_ATOM__Tuple, + .max_size = MAX_GRAPH + }, + { + .property = CANVAS__aspectRatio, + .offset = offsetof(plugstate_t, aspect_ratio), + .type = LV2_ATOM__Float + } +}; + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, double rate, + const char *bundle_path __attribute__((unused)), + const LV2_Feature *const *features) +{ + plughandle_t *handle = calloc(1, sizeof(plughandle_t)); + if(!handle) + return NULL; + mlock(handle, sizeof(plughandle_t)); + + if(!strcmp(descriptor->URI, MONITORS__audio_wave_mono)) + handle->nchannels = 1; + else if(!strcmp(descriptor->URI, MONITORS__audio_wave_stereo)) + handle->nchannels = 2; + + LV2_Options_Option *opts = NULL; +#ifdef BUILD_IDISP + LV2_Inline_Display *queue_draw = NULL; +#endif + for(unsigned i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_OPTIONS__options)) + opts = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_LOG__log)) + handle->log = features[i]->data; +#ifdef BUILD_IDISP + else if(!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) + queue_draw = features[i]->data; +#endif + } + + if(!handle->map) + { + fprintf(stderr, + "%s: Host does not support urid:map\n", descriptor->URI); + free(handle); + return NULL; + } + + if(!opts) + { + fprintf(stderr, + "%s: Host does not support opts:options\n", descriptor->URI); + free(handle); + return NULL; + } + + if(handle->log) + { + lv2_log_logger_init(&handle->logger, handle->map, handle->log); + } + + _craft_init(&handle->notify, handle->map); + lv2_canvas_urid_init(&handle->urid, handle->map); +#ifdef BUILD_IDISP + lv2_canvas_idisp_init(&handle->idisp, queue_draw, handle->map); +#endif + + const LV2_URID ui_update_rate= handle->map->map(handle->map->handle, + LV2_UI__updateRate); + + handle->update_rate = 60.f; // fall-back + handle->sample_rate = rate; + + for(LV2_Options_Option *opt = opts; + opt && (opt->key != 0) && (opt->value != NULL); + opt++) + { + if( (opt->key == ui_update_rate) && (opt->type == handle->notify.forge.Float) ) + handle->update_rate = *(float*)opt->value; + } + + handle->spf = handle->sample_rate / handle->update_rate; + + if(!props_init(&handle->props, descriptor->URI, + defs, MAX_NPROPS, &handle->state, &handle->stash, + handle->map, handle)) + { + fprintf(stderr, "failed to initialize property structure\n"); + free(handle); + return NULL; + } + + _fill(handle); + _render(handle); + + return handle; +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void *data) +{ + plughandle_t *handle = (plughandle_t *)instance; + + switch(handle->nchannels) + { + case 1: + { + switch(port) + { + case 0: + handle->audio_in[0] = (const void *)data; + break; + + case 1: + handle->audio_out[0] = data; + break; + + case 2: + handle->control = (const void *)data; + break; + case 3: + _craft_connect(&handle->notify, data); + break; + } + } break; + case 2: + { + switch(port) + { + case 0: + handle->audio_in[0] = (const void *)data; + break; + case 1: + handle->audio_in[1] = (const void *)data; + break; + + case 2: + handle->audio_out[0] = data; + break; + case 3: + handle->audio_out[1] = data; + break; + + case 4: + handle->control = (const void *)data; + break; + case 5: + _craft_connect(&handle->notify, data); + break; + } + } break; + } +} + +static void +_insert(plughandle_t *handle, unsigned idx, float min, float max) +{ + wave_t *wave = &handle->wave[idx]; + + for(uint32_t i = 0; i < MAX_SAMPLES - 1; i++) + { + wave->upper.samples[i].y = wave->upper.samples[i+1].y; + wave->lower.samples[i].y = wave->lower.samples[i+1].y; + } + + const float dy = 1.f / handle->nchannels; + + wave->upper.samples[MAX_SAMPLES-1].y = (0.5f + idx + min)*dy; + wave->lower.samples[MAX_SAMPLES-1].y = (0.5f + idx + max)*dy; +} + +static void +_run(plughandle_t *handle, uint32_t from, uint32_t to) +{ + const uint32_t num = (to - from); + const size_t sz = num * sizeof(float); + + for(unsigned idx = 0; idx < handle->nchannels; idx++) + { + memmove(&handle->audio_out[idx][from], &handle->audio_in[idx][from], sz); + + for(uint32_t i = 0; i < num; i++, handle->cnt++, handle->thresh++) + { + const float val = handle->audio_in[idx][from + i]; + + if(val < handle->min[idx]) + { + handle->min[idx]= val; + } + else if(val > handle->max[idx]) + { + handle->max[idx] = val; + } + + if(handle->cnt >= handle->max_window) + { + _insert(handle, idx, handle->min[idx], handle->max[idx]); + + handle->min[idx] = 0.f; + handle->max[idx] = 0.f; + handle->cnt = 0; + } + + if(handle->thresh >= handle->spf) + { + handle->thresh = 0; + + _render(handle); + props_set(&handle->props, &handle->notify.forge, from + i, + handle->urid.Canvas_graph, &handle->notify.ref); + } + } + } +} + +static void +run(LV2_Handle instance, uint32_t nsamples) +{ + plughandle_t *handle = instance; + + _craft_in(&handle->notify); + + props_idle(&handle->props, &handle->notify.forge, 0, &handle->notify.ref); + + int64_t from = 0; + + LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev) + { + const LV2_Atom *atom = &ev->body; + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom; + + props_advance(&handle->props, &handle->notify.forge, ev->time.frames, obj, &handle->notify.ref); + + const int64_t to = ev->time.frames; + + _run(handle, from, to); + from = to; + } + + { + const int64_t to = nsamples; + + _run(handle, from, to); + } + + _craft_out(&handle->notify); +} + +static LV2_State_Status +_state_save(LV2_Handle instance, LV2_State_Store_Function store, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = instance; + + return props_save(&handle->props, store, state, flags, features); +} + +static LV2_State_Status +_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = instance; + + return props_restore(&handle->props, retrieve, state, flags, features); +} + +static const LV2_State_Interface state_iface = { + .save = _state_save, + .restore = _state_restore +}; + +#ifdef BUILD_IDISP +static LV2_Inline_Display_Image_Surface * +_idisp_render(LV2_Handle instance, uint32_t w, uint32_t h) +{ + plughandle_t *handle = instance; + + float aspect_ratio = 1.f; + + { + props_impl_t *impl = _props_impl_get(&handle->props, + handle->urid.Canvas_aspectRatio); + + if(impl) + { + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + memcpy(&aspect_ratio, impl->stash.body, impl->stash.size); + + _props_impl_unlock(impl, PROP_STATE_NONE); + } + } + + LV2_Inline_Display_Image_Surface *surf = + lv2_canvas_idisp_surf_configure(&handle->idisp, w, h, aspect_ratio); + + if(surf) + { + props_impl_t *impl = _props_impl_get(&handle->props, + handle->urid.Canvas_graph); + + if(impl) + { + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + lv2_canvas_idisp_render_body(&handle->idisp, impl->type, impl->stash.size, + impl->stash.body); + + _props_impl_unlock(impl, PROP_STATE_NONE); + } + } + + return surf; +} + +static const LV2_Inline_Display_Interface idisp_iface = { + .render = _idisp_render +}; +#endif + +static void +cleanup(LV2_Handle instance) +{ + plughandle_t *handle = instance; + + munlock(handle, sizeof(plughandle_t)); +#ifdef BUILD_IDISP + lv2_canvas_idisp_deinit(&handle->idisp); +#endif + free(handle); +} + +static const void* +extension_data(const char* uri) +{ + if(!strcmp(uri, LV2_STATE__interface)) + return &state_iface; +#ifdef BUILD_IDISP + else if(!strcmp(uri, LV2_INLINEDISPLAY__interface)) + return &idisp_iface; +#endif + + return NULL; +} + +const LV2_Descriptor monitors_audio_wave_mono = { + .URI = MONITORS__audio_wave_mono, + .instantiate = instantiate, + .connect_port = connect_port, + .activate = NULL, + .run = run, + .deactivate = NULL, + .cleanup = cleanup, + .extension_data = extension_data +}; + +const LV2_Descriptor monitors_audio_wave_stereo = { + .URI = MONITORS__audio_wave_stereo, + .instantiate = instantiate, + .connect_port = connect_port, + .activate = NULL, + .run = run, + .deactivate = NULL, + .cleanup = cleanup, + .extension_data = extension_data +}; diff --git a/monitors_midi_pianoroll.c b/monitors_midi_pianoroll.c new file mode 100644 index 0000000..04cd841 --- /dev/null +++ b/monitors_midi_pianoroll.c @@ -0,0 +1,729 @@ +/* + * Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#include <monitors.h> + +#include <props.lv2/props.h> +#include <canvas.lv2/canvas.h> +#include <canvas.lv2/forge.h> + +#ifdef BUILD_IDISP +# include <canvas.lv2/idisp.h> +#endif + +#define MAX_GRAPH 0x20000 //FIXME actually measure this +#define MAX_NPROPS 7 +#define MAX_KEYS 0x80 +#define MAX_NOTES 0x200 +#define MAX_VELOCITY 0x80 +#define MASK_KEYS (MAX_KEYS - 1) +#define MASK_NOTES (MAX_NOTES - 1) + +typedef struct _plugstate_t plugstate_t; +typedef struct _plughandle_t plughandle_t; +typedef struct _active_note_t active_note_t; +typedef struct _passive_note_t passive_note_t; + +struct _active_note_t { + int64_t start; + uint32_t style; +}; + +struct _passive_note_t { + int64_t start; + int64_t end; + uint32_t style; +}; + +struct _plugstate_t { + int32_t window; + float rotation; + int32_t hflip; + int32_t vflip; + float aspect_ratio; + uint8_t graph [MAX_GRAPH]; + int32_t channel; +}; + +struct _plughandle_t { + LV2_URID_Map *map; + + LV2_Log_Log *log; + LV2_Log_Logger logger; + + const LV2_Atom_Sequence *control; + craft_t notify; + + LV2_URID midi_MidiEvent; + +#ifdef BUILD_IDISP + LV2_Canvas_Idisp idisp; +#endif + LV2_Canvas_URID urid; + double sample_rate; + float update_rate; + uint32_t graph_size; + unsigned range; + float range_1; + + unsigned spf; + unsigned thresh; + int64_t cnt; + + plugstate_t state; + plugstate_t stash; + + active_note_t actives [MAX_KEYS]; + passive_note_t passives [MAX_KEYS][MAX_NOTES]; + unsigned offsets [MAX_KEYS]; + uint32_t palette [MAX_VELOCITY]; + + PROPS_T(props, MAX_NPROPS); +}; + +static const uint32_t palette_colors [] = { + 0x00ffffff, + 0x00ff00ff, + 0xffff00ff, + 0xff0000ff +}; + +static inline void +_out_of_memory(plughandle_t *handle) +{ + if(handle->log) + { + lv2_log_trace(&handle->logger, "out-of-memory\n"); + } + + _craft_clear(&handle->notify); +} + +static inline void +_clear_state(plughandle_t *handle) +{ + memset(handle->actives, 0x0, sizeof(handle->actives)); + memset(handle->passives, 0x0, sizeof(handle->passives)); +} + +static void +_render(plughandle_t *handle, int64_t frames) +{ + LV2_Atom_Forge _forge = handle->notify.forge; // clone forge object + LV2_Atom_Forge *forge = &_forge; + LV2_Canvas_URID *urid = &handle->urid; + LV2_Atom_Forge_Frame frame; + + const float dy = 1.f / MASK_KEYS; + lv2_atom_forge_set_buffer(forge, handle->state.graph, MAX_GRAPH); + + if( !lv2_atom_forge_tuple(forge, &frame) + || !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_rectangle(forge, urid, 0.f, 0.f, 1.f, 1.f) + || !lv2_canvas_forge_style(forge, urid, 0x0000003f) + || !lv2_canvas_forge_fill(forge, urid) + || !lv2_canvas_forge_lineWidth(forge, urid, dy) + || !lv2_canvas_forge_save(forge, urid) ) + { + _out_of_memory(handle); + return; + } + + if(handle->state.hflip || handle->state.vflip) + { + const float xx = handle->state.hflip ? -1.f : 1.f; + const float xy = 0.f; + const float x0 = handle->state.hflip ? 1.f : 0.f; + + const float yy = handle->state.vflip ? -1.f : 1.f; + const float yx = 0.f; + const float y0 = handle->state.vflip ? 1.f : 0.f; + + if( !lv2_canvas_forge_transform(forge, urid, xx, xy, x0, yy, yx, y0) ) + { + _out_of_memory(handle); + return; + } + } + + if( (fmod(handle->state.rotation, 360.f) != 0.f) ) + { + const float rot = handle->state.rotation / 180.f * M_PI; + + if( !lv2_canvas_forge_translate(forge, urid, 0.5f, 0.5f) + || !lv2_canvas_forge_rotate(forge, urid, rot) + || !lv2_canvas_forge_translate(forge, urid, -0.5f, -0.5f) ) + { + _out_of_memory(handle); + return; + } + } + + const unsigned range = handle->range; + const float range_1 = handle->range_1; + const int64_t head = handle->cnt + frames; + const int64_t tail = head - range; + + float y = 1.f - dy*0.5f; + for(unsigned i = 0; i < MAX_KEYS; i++, y -= dy) + { + active_note_t *active = &handle->actives[i]; + passive_note_t *passives = handle->passives[i]; + + if(active->start != 0) + { + const bool start_in_window = (active->start >= tail) && (active->start <= head); + const float x0 = start_in_window + ? range_1 * (active->start - tail) + : 0.f; + const float x1 = 1.f; + const float line [] = { + x0, y, + x1, y + }; + + if( !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_polyLine(forge, urid, 4, line) + || !lv2_canvas_forge_style(forge, urid, active->style) + || !lv2_canvas_forge_stroke(forge, urid) ) + { + _out_of_memory(handle); + return; + } + } + + for(unsigned j = 0; j < MAX_NOTES; j++) + { + const unsigned idx = (handle->offsets[i] + j) & MASK_NOTES; + passive_note_t *passive = &passives[idx]; + + if(passive->start == 0) + { + break; // skip this key + } + + const bool end_in_window = (passive->end >= tail) && (passive->end <= head); + + if(end_in_window) + { + const bool start_in_window = (passive->start >= tail) && (passive->start <= head); + const float x0 = start_in_window + ? range_1 * (passive->start - tail) + : 0.f; + const float x1 = end_in_window + ? range_1 * (passive->end - tail) + : 1.f; + const float line [] = { + x0, y, + x1, y + }; + + if( !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_polyLine(forge, urid, 4, line) + || !lv2_canvas_forge_style(forge, urid, passive->style) + || !lv2_canvas_forge_stroke(forge, urid) ) + { + _out_of_memory(handle); + return; + } + } + else + { + passive->start = 0; // invalidate + } + } + } + + if( !lv2_canvas_forge_restore(forge, urid) ) + { + _out_of_memory(handle); + return; + } + + lv2_atom_forge_pop(forge, &frame); + + props_impl_t *impl = _props_impl_get(&handle->props, handle->urid.Canvas_graph); + if(impl) + { + const LV2_Atom *value= (const LV2_Atom *)handle->state.graph; + + _props_impl_set(&handle->props, impl, value->type, value->size, + LV2_ATOM_BODY_CONST(value)); + } + +#ifdef BUILD_IDISP + lv2_canvas_idisp_queue_draw(&handle->idisp); +#endif +} + +static void +_intercept_window(void *data, int64_t frames, + props_impl_t *impl __attribute__((unused))) +{ + plughandle_t *handle = data; + + handle->range = 1e-3 * handle->sample_rate * handle->state.window; + handle->range_1 = 1.f / handle->range; + + _clear_state(handle); + _render(handle, frames); +} + +static void +_intercept_channel(void *data, int64_t frames, + props_impl_t *impl __attribute__((unused))) +{ + plughandle_t *handle = data; + + _clear_state(handle); + _render(handle, frames); +} + +static const props_def_t defs [MAX_NPROPS] = { + { + .property = MONITORS__window, + .offset = offsetof(plugstate_t, window), + .type = LV2_ATOM__Int, + .event_cb = _intercept_window + }, + { + .property = MONITORS__rotation, + .offset = offsetof(plugstate_t, rotation), + .type = LV2_ATOM__Float + }, + { + .property = MONITORS__horizontalFlip, + .offset = offsetof(plugstate_t, hflip), + .type = LV2_ATOM__Bool + }, + { + .property = MONITORS__verticalFlip, + .offset = offsetof(plugstate_t, vflip), + .type = LV2_ATOM__Bool + }, + { + .access = LV2_PATCH__readable, + .property = CANVAS__graph, + .offset = offsetof(plugstate_t, graph), + .type = LV2_ATOM__Tuple, + .max_size = MAX_GRAPH + }, + { + .property = CANVAS__aspectRatio, + .offset = offsetof(plugstate_t, aspect_ratio), + .type = LV2_ATOM__Float + }, + { + .property = MONITORS__channel, + .offset = offsetof(plugstate_t, channel), + .type = LV2_ATOM__Int, + .event_cb = _intercept_channel + } +}; + +static void +_fill_palette(uint32_t *palette) +{ + const unsigned n = sizeof(palette_colors) / sizeof(uint32_t); + unsigned I = 0; + unsigned J = ceilf((float)MAX_VELOCITY / (n-1)); + + for(unsigned i = 0; i < (n-1); i++) + { + const uint32_t c1 = palette_colors[i]; + const uint32_t c2 = palette_colors[i+1]; + + const uint8_t r1 = (c1 >> 24) & 0xff; + const uint8_t r2 = (c2 >> 24) & 0xff; + const uint8_t g1 = (c1 >> 16) & 0xff; + const uint8_t g2 = (c2 >> 16) & 0xff; + const uint8_t b1 = (c1 >> 8) & 0xff; + const uint8_t b2 = (c2 >> 8) & 0xff; + const uint8_t a1 = (c1 >> 0) & 0xff; + const uint8_t a2 = (c2 >> 0) & 0xff; + + for(unsigned j = 0; j < J; j++) + { + const float p = (float)j / J; + + const uint8_t r = r1 + floorf((r2 - r1)*p); + const uint8_t g = g1 + floorf((g2 - g1)*p); + const uint8_t b = b1 + floorf((b2 - b1)*p); + const uint8_t a = a1 + floorf((a2 - a1)*p); + + if(I + j < MAX_VELOCITY) + { + palette[I + j] = (r << 24) | (g << 16) | (b << 8) | a; + } + } + + I += J; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, double rate, + const char *bundle_path __attribute__((unused)), + const LV2_Feature *const *features) +{ + plughandle_t *handle = calloc(1, sizeof(plughandle_t)); + if(!handle) + return NULL; + mlock(handle, sizeof(plughandle_t)); + + LV2_Options_Option *opts = NULL; +#ifdef BUILD_IDISP + LV2_Inline_Display *queue_draw = NULL; +#endif + for(unsigned i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_OPTIONS__options)) + opts = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_LOG__log)) + handle->log = features[i]->data; +#ifdef BUILD_IDISP + else if(!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) + queue_draw = features[i]->data; +#endif + } + + if(!handle->map) + { + fprintf(stderr, + "%s: Host does not support urid:map\n", descriptor->URI); + free(handle); + return NULL; + } + + if(!opts) + { + fprintf(stderr, + "%s: Host does not support opts:options\n", descriptor->URI); + free(handle); + return NULL; + } + + if(handle->log) + { + lv2_log_logger_init(&handle->logger, handle->map, handle->log); + } + + _craft_init(&handle->notify, handle->map); + lv2_canvas_urid_init(&handle->urid, handle->map); +#ifdef BUILD_IDISP + lv2_canvas_idisp_init(&handle->idisp, queue_draw, handle->map); +#endif + + handle->midi_MidiEvent = handle->map->map(handle->map->handle, + LV2_MIDI__MidiEvent); + + const LV2_URID ui_update_rate= handle->map->map(handle->map->handle, + LV2_UI__updateRate); + + handle->update_rate = 60.f; // fall-back + handle->sample_rate = rate; + + for(LV2_Options_Option *opt = opts; + opt && (opt->key != 0) && (opt->value != NULL); + opt++) + { + if( (opt->key == ui_update_rate) + && (opt->type == handle->notify.forge.Float) ) + { + handle->update_rate = *(float*)opt->value; + } + } + + handle->spf = handle->sample_rate / handle->update_rate; + + if(!props_init(&handle->props, descriptor->URI, + defs, MAX_NPROPS, &handle->state, &handle->stash, + handle->map, handle)) + { + fprintf(stderr, "failed to initialize property structure\n"); + free(handle); + return NULL; + } + + handle->cnt = 1; + + _render(handle, 0); + _fill_palette(handle->palette); + + return handle; +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void *data) +{ + plughandle_t *handle = (plughandle_t *)instance; + + switch(port) + { + case 0: + handle->control = (const void *)data; + break; + case 1: + _craft_connect(&handle->notify, data); + break; + + default: + break; + } +} + +static inline void +_note_off(plughandle_t *handle, int64_t frames, uint8_t key) +{ + active_note_t *active = &handle->actives[key]; + unsigned *offset = &handle->offsets[key]; + + // shift offset left + *offset = (*offset - 1) & MASK_NOTES; + + // prepend + passive_note_t *passive = &handle->passives[key][*offset]; + passive->start = active->start; + passive->end = handle->cnt + frames; + passive->style = active->style; + + active->start = 0; // invalidate this note +} + +static inline void +_note_on(plughandle_t *handle, int64_t frames, uint8_t key, uint8_t vel) +{ + active_note_t *active = &handle->actives[key]; + + if(active->start != 0) // found a missing note off + { + _note_off(handle, frames, key); + } + + active->start = handle->cnt + frames; + active->style = handle->palette[vel]; +} + +static inline void +_handle_midi(plughandle_t *handle, int64_t frames, + uint32_t size __attribute__((unused)), const uint8_t *msg) +{ + switch(lv2_midi_message_type(msg)) + { + case LV2_MIDI_MSG_NOTE_ON: + { + const uint8_t cha = msg[0] & 0x0f; + + if(cha == handle->state.channel) + { + _note_on(handle, frames, msg[1], msg[2]); + } + + } break; + case LV2_MIDI_MSG_NOTE_OFF: + { + const uint8_t cha = msg[0] & 0x0f; + + if(cha == handle->state.channel) + { + _note_off(handle, frames, msg[1]); + } + + } break; + default: + { + // nothing + } break; + } +} + +static void +_run(plughandle_t *handle, uint32_t from, uint32_t to) +{ + const uint32_t num = (to - from); + + for(uint32_t i = 0; i < num; i++, handle->cnt++, handle->thresh++) + { + if(handle->thresh >= handle->spf) + { + handle->thresh = 0; + + _render(handle, from + i); + props_set(&handle->props, &handle->notify.forge, from + i, + handle->urid.Canvas_graph, &handle->notify.ref); + } + } +} + +static void +run(LV2_Handle instance, uint32_t nsamples) +{ + plughandle_t *handle = instance; + + _craft_in(&handle->notify); + + props_idle(&handle->props, &handle->notify.forge, 0, &handle->notify.ref); + + int64_t from = 0; + + LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev) + { + const LV2_Atom *atom = &ev->body; + + if(atom->type == handle->midi_MidiEvent) + { + const uint8_t *msg = LV2_ATOM_BODY_CONST(atom); + _handle_midi(handle, ev->time.frames, atom->size, msg); + } + else + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom; + + props_advance(&handle->props, &handle->notify.forge, ev->time.frames, obj, + &handle->notify.ref); + } + + const int64_t to = ev->time.frames; + + _run(handle, from, to); + from = to; + } + + { + const int64_t to = nsamples; + + _run(handle, from, to); + } + + _craft_out(&handle->notify); +} + +#ifdef BUILD_IDISP +static LV2_Inline_Display_Image_Surface * +_idisp_render(LV2_Handle instance, uint32_t w, uint32_t h) +{ + plughandle_t *handle = instance; + + float aspect_ratio = 1.f; + + { + props_impl_t *impl = _props_impl_get(&handle->props, + handle->urid.Canvas_aspectRatio); + + if(impl) + { + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + memcpy(&aspect_ratio, impl->stash.body, impl->stash.size); + + _props_impl_unlock(impl, PROP_STATE_NONE); + } + } + + LV2_Inline_Display_Image_Surface *surf = + lv2_canvas_idisp_surf_configure(&handle->idisp, w, h, aspect_ratio); + + if(surf) + { + props_impl_t *impl = _props_impl_get(&handle->props, + handle->urid.Canvas_graph); + + if(impl) + { + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + lv2_canvas_idisp_render_body(&handle->idisp, impl->type, impl->stash.size, + impl->stash.body); + + _props_impl_unlock(impl, PROP_STATE_NONE); + } + } + + return surf; +} + +static const LV2_Inline_Display_Interface idisp_iface = { + .render = _idisp_render +}; +#endif + +static void +cleanup(LV2_Handle instance) +{ + plughandle_t *handle = instance; + + munlock(handle, sizeof(plughandle_t)); +#ifdef BUILD_IDISP + lv2_canvas_idisp_deinit(&handle->idisp); +#endif + free(handle); +} + +static LV2_State_Status +_state_save(LV2_Handle instance, LV2_State_Store_Function store, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = instance; + + return props_save(&handle->props, store, state, flags, features); +} + +static LV2_State_Status +_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = instance; + + return props_restore(&handle->props, retrieve, state, flags, features); +} + +static const LV2_State_Interface state_iface = { + .save = _state_save, + .restore = _state_restore +}; + +static const void* +extension_data(const char* uri) +{ + if(!strcmp(uri, LV2_STATE__interface)) + return &state_iface; +#ifdef BUILD_IDISP + else if(!strcmp(uri, LV2_INLINEDISPLAY__interface)) + return &idisp_iface; +#endif + + return NULL; +} + +const LV2_Descriptor monitors_midi_pianoroll = { + .URI = MONITORS__midi_pianoroll, + .instantiate = instantiate, + .connect_port = connect_port, + .activate = NULL, + .run = run, + .deactivate = NULL, + .cleanup = cleanup, + .extension_data = extension_data +}; diff --git a/monitors_time_metronom.c b/monitors_time_metronom.c new file mode 100644 index 0000000..1c2aec2 --- /dev/null +++ b/monitors_time_metronom.c @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <math.h> + +#include <monitors.h> + +#include <timely.lv2/timely.h> +#include <props.lv2/props.h> +#include <canvas.lv2/canvas.h> +#include <canvas.lv2/forge.h> + +#ifdef BUILD_IDISP +# include <canvas.lv2/idisp.h> +#endif + +#define MAX_GRAPH 0x20000 //FIXME actually measure this +#define MAX_NPROPS 5 + +typedef struct _plugstate_t plugstate_t; +typedef struct _plughandle_t plughandle_t; + +struct _plugstate_t { + float rotation; + int32_t hflip; + int32_t vflip; + float aspect_ratio; + uint8_t graph [MAX_GRAPH]; +}; + +struct _plughandle_t { + LV2_URID_Map *map; + + LV2_Log_Log *log; + LV2_Log_Logger logger; + + const LV2_Atom_Sequence *control; + craft_t notify; + + timely_t timely; + +#ifdef BUILD_IDISP + LV2_Canvas_Idisp idisp; +#endif + LV2_Canvas_URID urid; + float update_rate; + uint32_t graph_size; + + unsigned spf; + unsigned thresh; + int64_t cnt; + + plugstate_t state; + plugstate_t stash; + + PROPS_T(props, MAX_NPROPS); +}; + +static inline void +_out_of_memory(plughandle_t *handle) +{ + if(handle->log) + { + lv2_log_trace(&handle->logger, "out-of-memory\n"); + } + + _craft_clear(&handle->notify); +} + +static void +_render(plughandle_t *handle, int64_t frames __attribute__((unused))) +{ + LV2_Atom_Forge _forge = handle->notify.forge; // clone forge object + LV2_Atom_Forge *forge = &_forge; + LV2_Canvas_URID *urid = &handle->urid; + LV2_Atom_Forge_Frame frame; + + const float bar_beat = TIMELY_BAR_BEAT(&handle->timely); + const float beats_per_bar = TIMELY_BEATS_PER_BAR(&handle->timely); + const int32_t bar_beat_i = ceilf(bar_beat); + + lv2_atom_forge_set_buffer(forge, handle->state.graph, MAX_GRAPH); + + if( !lv2_atom_forge_tuple(forge, &frame) + || !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_rectangle(forge, urid, 0.f, 0.f, 1.f, 1.f) + || !lv2_canvas_forge_style(forge, urid, 0x0000003f) + || !lv2_canvas_forge_fill(forge, urid) + || !lv2_canvas_forge_lineWidth(forge, urid, 0.1f) + || !lv2_canvas_forge_save(forge, urid) ) + { + _out_of_memory(handle); + return; + } + + const float alpha = bar_beat_i / beats_per_bar * 2*M_PI - M_PI_2; + const float beta = bar_beat / beats_per_bar * 2*M_PI - M_PI_2; + + if( !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_arc(forge, urid, 0.5f, 0.5f, 0.2f, -M_PI_2, alpha) + || !lv2_canvas_forge_style(forge, urid, 0x7000ffff) + || !lv2_canvas_forge_stroke(forge, urid) ) + { + _out_of_memory(handle); + return; + } + + if( !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_arc(forge, urid, 0.5f, 0.5f, 0.4f, -M_PI_2, beta) + || !lv2_canvas_forge_style(forge, urid, 0xff7f00ff) + || !lv2_canvas_forge_stroke(forge, urid) ) + { + _out_of_memory(handle); + return; + } + + char label [16]; + snprintf(label, sizeof(label), "%"PRIi32, bar_beat_i); + + if( !lv2_canvas_forge_beginPath(forge, urid) + || !lv2_canvas_forge_moveTo(forge, urid, 0.5f, 0.5f) + || !lv2_canvas_forge_fontSize(forge, urid, 0.25f) + || !lv2_canvas_forge_style(forge, urid, 0xffffffff) + || !lv2_canvas_forge_fillText(forge, urid, label) ) + { + _out_of_memory(handle); + return; + } + + if( !lv2_canvas_forge_restore(forge, urid) ) + { + _out_of_memory(handle); + return; + } + + lv2_atom_forge_pop(forge, &frame); + + props_impl_t *impl = _props_impl_get(&handle->props, handle->urid.Canvas_graph); + if(impl) + { + const LV2_Atom *value= (const LV2_Atom *)handle->state.graph; + + _props_impl_set(&handle->props, impl, value->type, value->size, + LV2_ATOM_BODY_CONST(value)); + } + +#ifdef BUILD_IDISP + lv2_canvas_idisp_queue_draw(&handle->idisp); +#endif +} + +static const props_def_t defs [MAX_NPROPS] = { + { + .property = MONITORS__rotation, + .offset = offsetof(plugstate_t, rotation), + .type = LV2_ATOM__Float + }, + { + .property = MONITORS__horizontalFlip, + .offset = offsetof(plugstate_t, hflip), + .type = LV2_ATOM__Bool + }, + { + .property = MONITORS__verticalFlip, + .offset = offsetof(plugstate_t, vflip), + .type = LV2_ATOM__Bool + }, + { + .access = LV2_PATCH__readable, + .property = CANVAS__graph, + .offset = offsetof(plugstate_t, graph), + .type = LV2_ATOM__Tuple, + .max_size = MAX_GRAPH + }, + { + .property = CANVAS__aspectRatio, + .offset = offsetof(plugstate_t, aspect_ratio), + .type = LV2_ATOM__Float + } +}; + +static void +_cb(timely_t *timely __attribute__((unused)), + int64_t frames __attribute__((unused)), + LV2_URID type __attribute__((unused)), + void *data __attribute__((unused))) +{ + // do nothing +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, double rate, + const char *bundle_path __attribute__((unused)), + const LV2_Feature *const *features) +{ + plughandle_t *handle = calloc(1, sizeof(plughandle_t)); + if(!handle) + return NULL; + mlock(handle, sizeof(plughandle_t)); + + LV2_Options_Option *opts = NULL; +#ifdef BUILD_IDISP + LV2_Inline_Display *queue_draw = NULL; +#endif + for(unsigned i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_OPTIONS__options)) + opts = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_LOG__log)) + handle->log = features[i]->data; +#ifdef BUILD_IDISP + else if(!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) + queue_draw = features[i]->data; +#endif + } + + if(!handle->map) + { + fprintf(stderr, + "%s: Host does not support urid:map\n", descriptor->URI); + free(handle); + return NULL; + } + + if(!opts) + { + fprintf(stderr, + "%s: Host does not support opts:options\n", descriptor->URI); + free(handle); + return NULL; + } + + if(handle->log) + { + lv2_log_logger_init(&handle->logger, handle->map, handle->log); + } + + _craft_init(&handle->notify, handle->map); + lv2_canvas_urid_init(&handle->urid, handle->map); +#ifdef BUILD_IDISP + lv2_canvas_idisp_init(&handle->idisp, queue_draw, handle->map); +#endif + timely_init(&handle->timely, handle->map, rate, 0, _cb, handle); + + const LV2_URID ui_update_rate= handle->map->map(handle->map->handle, + LV2_UI__updateRate); + + handle->update_rate = 60.f; // fall-back + + for(LV2_Options_Option *opt = opts; + opt && (opt->key != 0) && (opt->value != NULL); + opt++) + { + if( (opt->key == ui_update_rate) && (opt->type == handle->notify.forge.Float) ) + handle->update_rate = *(float*)opt->value; + } + + handle->spf = rate / handle->update_rate; + + if(!props_init(&handle->props, descriptor->URI, + defs, MAX_NPROPS, &handle->state, &handle->stash, + handle->map, handle)) + { + fprintf(stderr, "failed to initialize property structure\n"); + free(handle); + return NULL; + } + + handle->cnt = 1; + + _render(handle, 0); + + return handle; +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void *data) +{ + plughandle_t *handle = (plughandle_t *)instance; + + switch(port) + { + case 0: + handle->control = (const void *)data; + break; + case 1: + _craft_connect(&handle->notify, data); + break; + + default: + break; + } +} + +static void +_run(plughandle_t *handle, uint32_t from, uint32_t to) +{ + const uint32_t num = (to - from); + + for(uint32_t i = 0; i < num; i++, handle->cnt++, handle->thresh++) + { + if(handle->thresh >= handle->spf) + { + handle->thresh = 0; + + _render(handle, from + i); + props_set(&handle->props, &handle->notify.forge, from + i, + handle->urid.Canvas_graph, &handle->notify.ref); + } + } +} + +static void +run(LV2_Handle instance, uint32_t nsamples) +{ + plughandle_t *handle = instance; + + _craft_in(&handle->notify); + + props_idle(&handle->props, &handle->notify.forge, 0, &handle->notify.ref); + + int64_t from = 0; + + LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev) + { + const LV2_Atom_Object *obj= (const LV2_Atom_Object *)&ev->body; + const int64_t to = ev->time.frames; + + if(!timely_advance(&handle->timely, obj, from, to)) + { + props_advance(&handle->props, &handle->notify.forge, to, obj, &handle->notify.ref); + } + + _run(handle, from, to); + from = to; + } + + { + const int64_t to = nsamples; + + timely_advance(&handle->timely, NULL, from, to); + _run(handle, from, to); + } + + _craft_out(&handle->notify); +} + +static LV2_State_Status +_state_save(LV2_Handle instance, LV2_State_Store_Function store, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = instance; + + return props_save(&handle->props, store, state, flags, features); +} + +static LV2_State_Status +_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = instance; + + return props_restore(&handle->props, retrieve, state, flags, features); +} + +static const LV2_State_Interface state_iface = { + .save = _state_save, + .restore = _state_restore +}; + +#ifdef BUILD_IDISP +static LV2_Inline_Display_Image_Surface * +_idisp_render(LV2_Handle instance, uint32_t w, uint32_t h) +{ + plughandle_t *handle = instance; + + float aspect_ratio = 1.f; + + { + props_impl_t *impl = _props_impl_get(&handle->props, + handle->urid.Canvas_aspectRatio); + + if(impl) + { + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + memcpy(&aspect_ratio, impl->stash.body, impl->stash.size); + + _props_impl_unlock(impl, PROP_STATE_NONE); + } + } + + LV2_Inline_Display_Image_Surface *surf = + lv2_canvas_idisp_surf_configure(&handle->idisp, w, h, aspect_ratio); + + if(surf) + { + props_impl_t *impl = _props_impl_get(&handle->props, + handle->urid.Canvas_graph); + + if(impl) + { + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + lv2_canvas_idisp_render_body(&handle->idisp, impl->type, impl->stash.size, + impl->stash.body); + + _props_impl_unlock(impl, PROP_STATE_NONE); + } + } + + return surf; +} + +static const LV2_Inline_Display_Interface idisp_iface = { + .render = _idisp_render +}; +#endif + +static void +cleanup(LV2_Handle instance) +{ + plughandle_t *handle = instance; + + munlock(handle, sizeof(plughandle_t)); +#ifdef BUILD_IDISP + lv2_canvas_idisp_deinit(&handle->idisp); +#endif + free(handle); +} + +static const void* +extension_data(const char* uri) +{ + if(!strcmp(uri, LV2_STATE__interface)) + return &state_iface; +#ifdef BUILD_IDISP + else if(!strcmp(uri, LV2_INLINEDISPLAY__interface)) + return &idisp_iface; +#endif + + return NULL; +} + +const LV2_Descriptor monitors_time_metronom = { + .URI = MONITORS__time_metronom, + .instantiate = instantiate, + .connect_port = connect_port, + .activate = NULL, + .run = run, + .deactivate = NULL, + .cleanup = cleanup, + .extension_data = extension_data +}; diff --git a/props.lv2/.gitlab-ci.yml b/props.lv2/.gitlab-ci.yml new file mode 100644 index 0000000..c027e92 --- /dev/null +++ b/props.lv2/.gitlab-ci.yml @@ -0,0 +1,72 @@ +stages: + - build + - test + - deploy + +.variables_template: &variables_definition + variables: + BASE_NAME: "props.lv2" + PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig" + +.common_template: &common_definition + <<: *variables_definition + stage: build + artifacts: + name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}" + paths: + - "${BASE_NAME}-$(cat VERSION)/" + +.build_template: &build_definition + <<: *common_definition + script: + - mkdir build + - pushd build + - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR} -DPLUGIN_DEST="${BASE_NAME}-$(cat ../VERSION)/${CI_BUILD_NAME}/${BASE_NAME}" -DCMAKE_CI_BUILD_NAME=${CI_BUILD_NAME} .. + - cmake .. # needed for darwin + - make + - make install + +.universal_linux_template: &universal_linux_definition + image: ventosus/universal-linux-gnu + <<: *build_definition + +.arm_linux_template: &arm_linux_definition + image: ventosus/arm-linux-gnueabihf + <<: *build_definition + +.universal_w64_template: &universal_w64_definition + image: ventosus/universal-w64-mingw32 + <<: *build_definition + +.universal_apple_template: &universal_apple_definition + image: ventosus/universal-apple-darwin + <<: *build_definition + +# building in docker +x86_64-linux-gnu: + <<: *universal_linux_definition + +i686-linux-gnu: + <<: *universal_linux_definition + +arm-linux-gnueabihf: + <<: *arm_linux_definition + +x86_64-w64-mingw32: + <<: *universal_w64_definition + +i686-w64-mingw32: + <<: *universal_w64_definition + +universal-apple-darwin: + <<: *universal_apple_definition + +pack: + <<: *variables_definition + stage: deploy + script: + - echo 'packing up...' + artifacts: + name: "${BASE_NAME}-$(cat VERSION)" + paths: + - "${BASE_NAME}-$(cat VERSION)/" diff --git a/props.lv2/COPYING b/props.lv2/COPYING new file mode 100644 index 0000000..ddb9a46 --- /dev/null +++ b/props.lv2/COPYING @@ -0,0 +1,201 @@ + The Artistic License 2.0 + + Copyright (c) 2000-2006, The Perl Foundation. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +Preamble + +This license establishes the terms under which a given free software +Package may be copied, modified, distributed, and/or redistributed. +The intent is that the Copyright Holder maintains some artistic +control over the development of that Package while still keeping the +Package available as open source and free software. + +You are always permitted to make arrangements wholly outside of this +license directly with the Copyright Holder of a given Package. If the +terms of this license do not permit the full use that you propose to +make of the Package, you should contact the Copyright Holder and seek +a different licensing arrangement. + +Definitions + + "Copyright Holder" means the individual(s) or organization(s) + named in the copyright notice for the entire Package. + + "Contributor" means any party that has contributed code or other + material to the Package, in accordance with the Copyright Holder's + procedures. + + "You" and "your" means any person who would like to copy, + distribute, or modify the Package. + + "Package" means the collection of files distributed by the + Copyright Holder, and derivatives of that collection and/or of + those files. A given Package may consist of either the Standard + Version, or a Modified Version. + + "Distribute" means providing a copy of the Package or making it + accessible to anyone else, or in the case of a company or + organization, to others outside of your company or organization. + + "Distributor Fee" means any fee that you charge for Distributing + this Package or providing support for this Package to another + party. It does not mean licensing fees. + + "Standard Version" refers to the Package if it has not been + modified, or has been modified only in ways explicitly requested + by the Copyright Holder. + + "Modified Version" means the Package, if it has been changed, and + such changes were not explicitly requested by the Copyright + Holder. + + "Original License" means this Artistic License as Distributed with + the Standard Version of the Package, in its current version or as + it may be modified by The Perl Foundation in the future. + + "Source" form means the source code, documentation source, and + configuration files for the Package. + + "Compiled" form means the compiled bytecode, object code, binary, + or any other form resulting from mechanical transformation or + translation of the Source form. + + +Permission for Use and Modification Without Distribution + +(1) You are permitted to use the Standard Version and create and use +Modified Versions for any purpose without restriction, provided that +you do not Distribute the Modified Version. + + +Permissions for Redistribution of the Standard Version + +(2) You may Distribute verbatim copies of the Source form of the +Standard Version of this Package in any medium without restriction, +either gratis or for a Distributor Fee, provided that you duplicate +all of the original copyright notices and associated disclaimers. At +your discretion, such verbatim copies may or may not include a +Compiled form of the Package. + +(3) You may apply any bug fixes, portability changes, and other +modifications made available from the Copyright Holder. The resulting +Package will still be considered the Standard Version, and as such +will be subject to the Original License. + + +Distribution of Modified Versions of the Package as Source + +(4) You may Distribute your Modified Version as Source (either gratis +or for a Distributor Fee, and with or without a Compiled form of the +Modified Version) provided that you clearly document how it differs +from the Standard Version, including, but not limited to, documenting +any non-standard features, executables, or modules, and provided that +you do at least ONE of the following: + + (a) make the Modified Version available to the Copyright Holder + of the Standard Version, under the Original License, so that the + Copyright Holder may include your modifications in the Standard + Version. + + (b) ensure that installation of your Modified Version does not + prevent the user installing or running the Standard Version. In + addition, the Modified Version must bear a name that is different + from the name of the Standard Version. + + (c) allow anyone who receives a copy of the Modified Version to + make the Source form of the Modified Version available to others + under + + (i) the Original License or + + (ii) a license that permits the licensee to freely copy, + modify and redistribute the Modified Version using the same + licensing terms that apply to the copy that the licensee + received, and requires that the Source form of the Modified + Version, and of any works derived from it, be made freely + available in that license fees are prohibited but Distributor + Fees are allowed. + + +Distribution of Compiled Forms of the Standard Version +or Modified Versions without the Source + +(5) You may Distribute Compiled forms of the Standard Version without +the Source, provided that you include complete instructions on how to +get the Source of the Standard Version. Such instructions must be +valid at the time of your distribution. If these instructions, at any +time while you are carrying out such distribution, become invalid, you +must provide new instructions on demand or cease further distribution. +If you provide valid instructions or cease distribution within thirty +days after you become aware that the instructions are invalid, then +you do not forfeit any of your rights under this license. + +(6) You may Distribute a Modified Version in Compiled form without +the Source, provided that you comply with Section 4 with respect to +the Source of the Modified Version. + + +Aggregating or Linking the Package + +(7) You may aggregate the Package (either the Standard Version or +Modified Version) with other packages and Distribute the resulting +aggregation provided that you do not charge a licensing fee for the +Package. Distributor Fees are permitted, and licensing fees for other +components in the aggregation are permitted. The terms of this license +apply to the use and Distribution of the Standard or Modified Versions +as included in the aggregation. + +(8) You are permitted to link Modified and Standard Versions with +other works, to embed the Package in a larger work of your own, or to +build stand-alone binary or bytecode versions of applications that +include the Package, and Distribute the result without restriction, +provided the result does not expose a direct interface to the Package. + + +Items That are Not Considered Part of a Modified Version + +(9) Works (including, but not limited to, modules and scripts) that +merely extend or make use of the Package, do not, by themselves, cause +the Package to be a Modified Version. In addition, such works are not +considered parts of the Package itself, and are not subject to the +terms of this license. + + +General Provisions + +(10) Any use, modification, and distribution of the Standard or +Modified Versions is governed by this Artistic License. By using, +modifying or distributing the Package, you accept this license. Do not +use, modify, or distribute the Package, if you do not accept this +license. + +(11) If your Modified Version has been derived from a Modified +Version made by someone other than you, you are nevertheless required +to ensure that your Modified Version complies with the requirements of +this license. + +(12) This license does not grant you the right to use any trademark, +service mark, tradename, or logo of the Copyright Holder. + +(13) This license includes the non-exclusive, worldwide, +free-of-charge patent license to make, have made, use, offer to sell, +sell, import and otherwise transfer the Package with respect to any +patent claims licensable by the Copyright Holder that are necessarily +infringed by the Package. If you institute patent litigation +(including a cross-claim or counterclaim) against any party alleging +that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the +date that such litigation is filed. + +(14) Disclaimer of Warranty: +THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS +IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL +LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/props.lv2/README.md b/props.lv2/README.md new file mode 100644 index 0000000..08466d2 --- /dev/null +++ b/props.lv2/README.md @@ -0,0 +1,20 @@ +# Props.lv2 + +## Utility header for property based LV2 plugins + +### License + +Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + +This is free software: you can redistribute it and/or modify +it under the terms of the Artistic License 2.0 as published by +The Perl Foundation. + +This source 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 +Artistic License 2.0 for more details. + +You should have received a copy of the Artistic License 2.0 +along the source as a COPYING file. If not, obtain it from +<http://www.perlfoundation.org/artistic_license_2_0>. diff --git a/props.lv2/VERSION b/props.lv2/VERSION new file mode 100644 index 0000000..66c0104 --- /dev/null +++ b/props.lv2/VERSION @@ -0,0 +1 @@ +0.1.131 diff --git a/props.lv2/meson.build b/props.lv2/meson.build new file mode 100644 index 0000000..d354d89 --- /dev/null +++ b/props.lv2/meson.build @@ -0,0 +1,82 @@ +project('props.lv2', 'c', default_options : [ + 'buildtype=release', + 'warning_level=3', + 'werror=false', + 'b_lto=false', + 'c_std=c11']) + +add_project_arguments('-D_GNU_SOURCE', language : 'c') + +conf_data = configuration_data() +cc = meson.get_compiler('c') + +cp = find_program('cp') +lv2_validate = find_program('lv2_validate', native : true, required : false) +sord_validate = find_program('sord_validate', native : true, required : false) +lv2lint = find_program('lv2lint', required : false) +clone = [cp, '@INPUT@', '@OUTPUT@'] + +m_dep = cc.find_library('m') +lv2_dep = dependency('lv2', version : '>=1.14.0') + +inc_dir = [] + +inst_dir = join_paths(get_option('libdir'), 'lv2', meson.project_name()) + +dsp_srcs = [join_paths('test', 'props.c')] + +c_args = ['-fvisibility=hidden', + '-ffast-math'] + +mod = shared_module('props', dsp_srcs, + c_args : c_args, + include_directories : inc_dir, + name_prefix : '', + dependencies : [m_dep, lv2_dep], + install : true, + install_dir : inst_dir) + +version = run_command('cat', 'VERSION').stdout().strip().split('.') +conf_data.set('MAJOR_VERSION', version[0]) +conf_data.set('MINOR_VERSION', version[1]) +conf_data.set('MICRO_VERSION', version[2]) + +suffix = mod.full_path().strip().split('.')[-1] +conf_data.set('MODULE_SUFFIX', '.' + suffix) + +manifest_ttl = configure_file( + input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl', + configuration : conf_data, + install : true, + install_dir : inst_dir) +dsp_ttl = custom_target('props_ttl', + input : join_paths('test', 'props.ttl'), + output : 'props.ttl', + command : clone, + install : true, + install_dir : inst_dir) +custom_target('chunk_bin', + input : join_paths('test', 'chunk.bin'), + output : 'chunk.bin', + command : clone, + install : true, + install_dir : inst_dir) + +props_test = executable('props_test', + join_paths('test', 'props_test.c'), + c_args : c_args, + install : false) + +test('Test', props_test, + timeout : 240) + +if lv2_validate.found() and sord_validate.found() + test('LV2 validate', lv2_validate, + args : [manifest_ttl, dsp_ttl]) +endif + +if lv2lint.found() + test('LV2 lint', lv2lint, + args : ['-Ewarn', + 'http://open-music-kontrollers.ch/lv2/props#test']) +endif diff --git a/props.lv2/props.h b/props.lv2/props.h new file mode 100644 index 0000000..06fec13 --- /dev/null +++ b/props.lv2/props.h @@ -0,0 +1,1012 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _LV2_PROPS_H_ +#define _LV2_PROPS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> +#include <stdatomic.h> +#include <stdio.h> + +#include <lv2/lv2plug.in/ns/lv2core/lv2.h> +#include <lv2/lv2plug.in/ns/ext/urid/urid.h> +#include <lv2/lv2plug.in/ns/ext/atom/atom.h> +#include <lv2/lv2plug.in/ns/ext/atom/forge.h> +#include <lv2/lv2plug.in/ns/ext/patch/patch.h> +#include <lv2/lv2plug.in/ns/ext/state/state.h> + +/***************************************************************************** + * API START + *****************************************************************************/ + +// structures +typedef struct _props_def_t props_def_t; +typedef struct _props_impl_t props_impl_t; +typedef struct _props_t props_t; + +// function callbacks +typedef void (*props_event_cb_t)( + void *data, + int64_t frames, + props_impl_t *impl); + +struct _props_def_t { + const char *property; + const char *type; + const char *access; + size_t offset; + bool hidden; + + uint32_t max_size; + props_event_cb_t event_cb; +}; + +struct _props_impl_t { + LV2_URID property; + LV2_URID type; + LV2_URID access; + + struct { + uint32_t size; + void *body; + } value; + struct { + uint32_t size; + void *body; + } stash; + + const props_def_t *def; + + atomic_int state; + bool stashing; +}; + +struct _props_t { + struct { + LV2_URID subject; + + LV2_URID patch_get; + LV2_URID patch_set; + LV2_URID patch_put; + LV2_URID patch_patch; + LV2_URID patch_wildcard; + LV2_URID patch_add; + LV2_URID patch_remove; + LV2_URID patch_subject; + LV2_URID patch_body; + LV2_URID patch_property; + LV2_URID patch_value; + LV2_URID patch_writable; + LV2_URID patch_readable; + LV2_URID patch_sequence; + LV2_URID patch_error; + LV2_URID patch_ack; + + LV2_URID atom_int; + LV2_URID atom_long; + LV2_URID atom_float; + LV2_URID atom_double; + LV2_URID atom_bool; + LV2_URID atom_urid; + LV2_URID atom_path; + LV2_URID atom_literal; + LV2_URID atom_vector; + LV2_URID atom_object; + LV2_URID atom_sequence; + } urid; + + void *data; + + bool stashing; + atomic_bool restoring; + + uint32_t max_size; + + unsigned nimpls; + props_impl_t impls [1]; +}; + +#define PROPS_T(PROPS, MAX_NIMPLS) \ + props_t (PROPS); \ + props_impl_t _impls [MAX_NIMPLS] + +// rt-safe +static inline int +props_init(props_t *props, const char *subject, + const props_def_t *defs, int nimpls, + void *value_base, void *stash_base, + LV2_URID_Map *map, void *data); + +// rt-safe +static inline void +props_idle(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + LV2_Atom_Forge_Ref *ref); + +// rt-safe +static inline int +props_advance(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + const LV2_Atom_Object *obj, LV2_Atom_Forge_Ref *ref); + +// rt-safe +static inline void +props_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + LV2_URID property, LV2_Atom_Forge_Ref *ref); + +// rt-safe +static inline void +props_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + LV2_URID property, LV2_Atom_Forge_Ref *ref); + +// rt-safe +static inline void +props_stash(props_t *props, LV2_URID property); + +// rt-safe +static inline LV2_URID +props_map(props_t *props, const char *property); + +// rt-safe +static inline const char * +props_unmap(props_t *props, LV2_URID property); + +// non-rt +static inline LV2_State_Status +props_save(props_t *props, LV2_State_Store_Function store, + LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features); + +// non-rt +static inline LV2_State_Status +props_restore(props_t *props, LV2_State_Retrieve_Function retrieve, + LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features); + +/***************************************************************************** + * API END + *****************************************************************************/ + +// enumerations +typedef enum _props_state_t { + PROP_STATE_NONE = 0, + PROP_STATE_LOCK = 1, + PROP_STATE_RESTORE = 2 +} props_state_t; + +static inline void +_props_impl_spin_lock(props_impl_t *impl, int from, int to) +{ + int expected = from; + const int desired = to; + + while(!atomic_compare_exchange_strong_explicit(&impl->state, &expected, desired, + memory_order_acquire, memory_order_acquire)) + { + // spin + } +} + +static inline bool +_props_impl_try_lock(props_impl_t *impl, int from, int to) +{ + int expected = from; + const int desired = to; + + return atomic_compare_exchange_strong_explicit(&impl->state, &expected, desired, + memory_order_acquire, memory_order_acquire); +} + +static inline void +_props_impl_unlock(props_impl_t *impl, int to) +{ + atomic_store_explicit(&impl->state, to, memory_order_release); +} + +static inline bool +_props_restoring_get(props_t *props) +{ + return atomic_exchange_explicit(&props->restoring, false, memory_order_acquire); +} + +static inline void +_props_restoring_set(props_t *props) +{ + atomic_store_explicit(&props->restoring, true, memory_order_release); +} + +static inline void +_props_qsort(props_impl_t *A, int n) +{ + if(n < 2) + return; + + const props_impl_t *p = A; + + int i = -1; + int j = n; + + while(true) + { + do { + i += 1; + } while(A[i].property < p->property); + + do { + j -= 1; + } while(A[j].property > p->property); + + if(i >= j) + break; + + const props_impl_t tmp = A[i]; + A[i] = A[j]; + A[j] = tmp; + } + + _props_qsort(A, j + 1); + _props_qsort(A + j + 1, n - j - 1); +} + +static inline props_impl_t * +_props_impl_get(props_t *props, LV2_URID property) +{ + props_impl_t *base = props->impls; + + for(int N = props->nimpls, half; N > 1; N -= half) + { + half = N/2; + props_impl_t *dst = &base[half]; + base = (dst->property > property) ? base : dst; + } + + return (base->property == property) ? base : NULL; +} + +static inline LV2_Atom_Forge_Ref +_props_patch_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + props_impl_t *impl, int32_t sequence_num) +{ + LV2_Atom_Forge_Frame obj_frame; + + LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames); + + if(ref) + ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_set); + { + if(props->urid.subject) // is optional + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_subject); + if(ref) + ref = lv2_atom_forge_urid(forge, props->urid.subject); + } + + if(sequence_num) // is optional + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_sequence); + if(ref) + ref = lv2_atom_forge_int(forge, sequence_num); + } + + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_property); + if(ref) + ref = lv2_atom_forge_urid(forge, impl->property); + + if(ref) + lv2_atom_forge_key(forge, props->urid.patch_value); + if(ref) + ref = lv2_atom_forge_atom(forge, impl->value.size, impl->type); + if(ref) + ref = lv2_atom_forge_write(forge, impl->value.body, impl->value.size); + } + if(ref) + lv2_atom_forge_pop(forge, &obj_frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_props_patch_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + props_impl_t *impl, int32_t sequence_num) +{ + LV2_Atom_Forge_Frame obj_frame; + + LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames); + + if(ref) + ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_get); + { + if(props->urid.subject) // is optional + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_subject); + if(ref) + ref = lv2_atom_forge_urid(forge, props->urid.subject); + } + + if(sequence_num) // is optional + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_sequence); + if(ref) + ref = lv2_atom_forge_int(forge, sequence_num); + } + + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_property); + if(ref) + ref = lv2_atom_forge_urid(forge, impl->property); + } + if(ref) + lv2_atom_forge_pop(forge, &obj_frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_props_patch_error(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + int32_t sequence_num) +{ + LV2_Atom_Forge_Frame obj_frame; + + LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames); + + if(ref) + ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_error); + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_sequence); + if(ref) + ref = lv2_atom_forge_int(forge, sequence_num); + } + if(ref) + lv2_atom_forge_pop(forge, &obj_frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_props_patch_ack(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + int32_t sequence_num) +{ + LV2_Atom_Forge_Frame obj_frame; + + LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames); + + if(ref) + ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_ack); + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_sequence); + if(ref) + ref = lv2_atom_forge_int(forge, sequence_num); + } + if(ref) + lv2_atom_forge_pop(forge, &obj_frame); + + return ref; +} + +static inline void +_props_impl_stash(props_t *props, props_impl_t *impl) +{ + if(_props_impl_try_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK)) + { + impl->stashing = false; + impl->stash.size = impl->value.size; + memcpy(impl->stash.body, impl->value.body, impl->value.size); + + _props_impl_unlock(impl, PROP_STATE_NONE); + } + else + { + impl->stashing = true; // try again later + props->stashing = true; + } +} + +static inline void +_props_impl_restore(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + props_impl_t *impl, LV2_Atom_Forge_Ref *ref) +{ + if(_props_impl_try_lock(impl, PROP_STATE_RESTORE, PROP_STATE_LOCK)) + { + impl->stashing = false; // makes no sense to stash a recently restored value + impl->value.size = impl->stash.size; + memcpy(impl->value.body, impl->stash.body, impl->stash.size); + + _props_impl_unlock(impl, PROP_STATE_NONE); + + if(*ref && !impl->def->hidden) + *ref = _props_patch_set(props, forge, frames, impl, 0); + + const props_def_t *def = impl->def; + if(def->event_cb) + def->event_cb(props->data, 0, impl); + } +} + +static inline void +_props_impl_set(props_t *props, props_impl_t *impl, LV2_URID type, + uint32_t size, const void *body) +{ + if( (impl->type == type) + && ( (impl->def->max_size == 0) || (size <= impl->def->max_size)) ) + { + impl->value.size = size; + memcpy(impl->value.body, body, size); + + _props_impl_stash(props, impl); + } +} + +static inline int +_props_impl_init(props_t *props, props_impl_t *impl, const props_def_t *def, + void *value_base, void *stash_base, LV2_URID_Map *map) +{ + if(!def->property || !def->type) + return 0; + + const LV2_URID type = map->map(map->handle, def->type); + const LV2_URID property = map->map(map->handle, def->property); + const LV2_URID access = def->access + ? map->map(map->handle, def->access) + : map->map(map->handle, LV2_PATCH__writable); + + if(!type || !property || !access) + return 0; + + impl->property = property; + impl->access = access; + impl->def = def; + impl->value.body = (uint8_t *)value_base + def->offset; + impl->stash.body = (uint8_t *)stash_base + def->offset; + + uint32_t size; + if( (type == props->urid.atom_int) + || (type == props->urid.atom_float) + || (type == props->urid.atom_bool) + || (type == props->urid.atom_urid) ) + { + size = 4; + } + else if((type == props->urid.atom_long) + || (type == props->urid.atom_double) ) + { + size = 8; + } + else if(type == props->urid.atom_literal) + { + size = sizeof(LV2_Atom_Literal_Body); + } + else if(type == props->urid.atom_vector) + { + size = sizeof(LV2_Atom_Vector_Body); + } + else if(type == props->urid.atom_object) + { + size = sizeof(LV2_Atom_Object_Body); + } + else if(type == props->urid.atom_sequence) + { + size = sizeof(LV2_Atom_Sequence_Body); + } + else + { + size = 0; // assume everything else as having size 0 + } + + impl->type = type; + impl->value.size = size; + impl->stash.size = size; + + atomic_init(&impl->state, PROP_STATE_NONE); + + // update maximal value size + const uint32_t max_size = def->max_size + ? def->max_size + : size; + + if(max_size > props->max_size) + { + props->max_size = max_size; + } + + return 1; +} + +static inline int +props_init(props_t *props, const char *subject, + const props_def_t *defs, int nimpls, + void *value_base, void *stash_base, + LV2_URID_Map *map, void *data) +{ + if(!props || !defs || !value_base || !stash_base || !map) + return 0; + + props->nimpls = nimpls; + props->data = data; + + props->urid.subject = subject ? map->map(map->handle, subject) : 0; + + props->urid.patch_get = map->map(map->handle, LV2_PATCH__Get); + props->urid.patch_set = map->map(map->handle, LV2_PATCH__Set); + props->urid.patch_put = map->map(map->handle, LV2_PATCH__Put); + props->urid.patch_patch = map->map(map->handle, LV2_PATCH__Patch); + props->urid.patch_wildcard = map->map(map->handle, LV2_PATCH__wildcard); + props->urid.patch_add = map->map(map->handle, LV2_PATCH__add); + props->urid.patch_remove = map->map(map->handle, LV2_PATCH__remove); + props->urid.patch_subject = map->map(map->handle, LV2_PATCH__subject); + props->urid.patch_body = map->map(map->handle, LV2_PATCH__body); + props->urid.patch_property = map->map(map->handle, LV2_PATCH__property); + props->urid.patch_value = map->map(map->handle, LV2_PATCH__value); + props->urid.patch_writable = map->map(map->handle, LV2_PATCH__writable); + props->urid.patch_readable = map->map(map->handle, LV2_PATCH__readable); + props->urid.patch_sequence = map->map(map->handle, LV2_PATCH__sequenceNumber); + props->urid.patch_ack = map->map(map->handle, LV2_PATCH__Ack); + props->urid.patch_error = map->map(map->handle, LV2_PATCH__Error); + + props->urid.atom_int = map->map(map->handle, LV2_ATOM__Int); + props->urid.atom_long = map->map(map->handle, LV2_ATOM__Long); + props->urid.atom_float = map->map(map->handle, LV2_ATOM__Float); + props->urid.atom_double = map->map(map->handle, LV2_ATOM__Double); + props->urid.atom_bool = map->map(map->handle, LV2_ATOM__Bool); + props->urid.atom_urid = map->map(map->handle, LV2_ATOM__URID); + props->urid.atom_path = map->map(map->handle, LV2_ATOM__Path); + props->urid.atom_literal = map->map(map->handle, LV2_ATOM__Literal); + props->urid.atom_vector = map->map(map->handle, LV2_ATOM__Vector); + props->urid.atom_object = map->map(map->handle, LV2_ATOM__Object); + props->urid.atom_sequence = map->map(map->handle, LV2_ATOM__Sequence); + + atomic_init(&props->restoring, false); + + int status = 1; + for(unsigned i = 0; i < props->nimpls; i++) + { + props_impl_t *impl = &props->impls[i]; + + status = status + && _props_impl_init(props, impl, &defs[i], value_base, stash_base, map); + } + + _props_qsort(props->impls, props->nimpls); + + return status; +} + +static inline void +props_idle(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + LV2_Atom_Forge_Ref *ref) +{ + if(_props_restoring_get(props)) + { + for(unsigned i = 0; i < props->nimpls; i++) + { + props_impl_t *impl = &props->impls[i]; + + _props_impl_restore(props, forge, frames, impl, ref); + } + } + + if(props->stashing) + { + props->stashing = false; + + for(unsigned i = 0; i < props->nimpls; i++) + { + props_impl_t *impl = &props->impls[i]; + + if(impl->stashing) + _props_impl_stash(props, impl); + } + } +} + +static inline int +props_advance(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + const LV2_Atom_Object *obj, LV2_Atom_Forge_Ref *ref) +{ + if(!lv2_atom_forge_is_object_type(forge, obj->atom.type)) + { + return 0; + } + + if(obj->body.otype == props->urid.patch_get) + { + const LV2_Atom_URID *subject = NULL; + const LV2_Atom_URID *property = NULL; + const LV2_Atom_Int *sequence = NULL; + + lv2_atom_object_get(obj, + props->urid.patch_subject, &subject, + props->urid.patch_property, &property, + props->urid.patch_sequence, &sequence, + 0); + + // check for a matching optional subject + if( (subject && props->urid.subject) + && ( (subject->atom.type != props->urid.atom_urid) + || (subject->body != props->urid.subject) ) ) + { + return 0; + } + + int32_t sequence_num = 0; + if(sequence && (sequence->atom.type == props->urid.atom_int)) + { + sequence_num = sequence->body; + } + + if(!property) + { + for(unsigned i = 0; i < props->nimpls; i++) + { + props_impl_t *impl = &props->impls[i]; + + if(*ref && !impl->def->hidden) + *ref = _props_patch_set(props, forge, frames, impl, sequence_num); + } + + return 1; + } + else if(property->atom.type == props->urid.atom_urid) + { + props_impl_t *impl = _props_impl_get(props, property->body); + + if(impl) + { + if(*ref && !impl->def->hidden) + *ref = _props_patch_set(props, forge, frames, impl, sequence_num); + + return 1; + } + else if(sequence_num) + { + if(*ref) + *ref = _props_patch_error(props, forge, frames, sequence_num); + } + } + else if(sequence_num) + { + if(*ref) + *ref = _props_patch_error(props, forge, frames, sequence_num); + } + } + else if(obj->body.otype == props->urid.patch_set) + { + const LV2_Atom_URID *subject = NULL; + const LV2_Atom_URID *property = NULL; + const LV2_Atom_Int *sequence = NULL; + const LV2_Atom *value = NULL; + + lv2_atom_object_get(obj, + props->urid.patch_subject, &subject, + props->urid.patch_property, &property, + props->urid.patch_sequence, &sequence, + props->urid.patch_value, &value, + 0); + + // check for a matching optional subject + if( (subject && props->urid.subject) + && ( (subject->atom.type != props->urid.atom_urid) + || (subject->body != props->urid.subject) ) ) + { + return 0; + } + + int32_t sequence_num = 0; + if(sequence && (sequence->atom.type == props->urid.atom_int)) + { + sequence_num = sequence->body; + } + + if(!property || (property->atom.type != props->urid.atom_urid) || !value) + { + if(sequence_num) + { + if(ref) + *ref = _props_patch_error(props, forge, frames, sequence_num); + } + + return 0; + } + + props_impl_t *impl = _props_impl_get(props, property->body); + if(impl && (impl->access == props->urid.patch_writable) ) + { + _props_impl_set(props, impl, value->type, value->size, + LV2_ATOM_BODY_CONST(value)); + + const props_def_t *def = impl->def; + if(def->event_cb) + def->event_cb(props->data, frames, impl); + + if(sequence_num) + { + if(*ref) + *ref = _props_patch_ack(props, forge, frames, sequence_num); + } + + return 1; + } + else if(sequence_num) + { + if(*ref) + *ref = _props_patch_error(props, forge, frames, sequence_num); + } + } + else if(obj->body.otype == props->urid.patch_put) + { + const LV2_Atom_URID *subject = NULL; + const LV2_Atom_Int *sequence = NULL; + const LV2_Atom_Object *body = NULL; + + lv2_atom_object_get(obj, + props->urid.patch_subject, &subject, + props->urid.patch_sequence, &sequence, + props->urid.patch_body, &body, + 0); + + // check for a matching optional subject + if( (subject && props->urid.subject) + && ( (subject->atom.type != props->urid.atom_urid) + || (subject->body != props->urid.subject) ) ) + { + return 0; + } + + int32_t sequence_num = 0; + if(sequence && (sequence->atom.type == props->urid.atom_int)) + { + sequence_num = sequence->body; + } + + if(!body || !lv2_atom_forge_is_object_type(forge, body->atom.type)) + { + if(sequence_num) + { + if(*ref) + *ref = _props_patch_error(props, forge, frames, sequence_num); + } + + return 0; + } + + LV2_ATOM_OBJECT_FOREACH(body, prop) + { + const LV2_URID property = prop->key; + const LV2_Atom *value = &prop->value; + + props_impl_t *impl = _props_impl_get(props, property); + if(impl && (impl->access == props->urid.patch_writable) ) + { + _props_impl_set(props, impl, value->type, value->size, + LV2_ATOM_BODY_CONST(value)); + + const props_def_t *def = impl->def; + if(def->event_cb) + def->event_cb(props->data, frames, impl); + } + } + + if(sequence_num) + { + if(*ref) + *ref = _props_patch_ack(props, forge, frames, sequence_num); + } + + return 1; + } + + return 0; // did not handle a patch event +} + +static inline void +props_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + LV2_URID property, LV2_Atom_Forge_Ref *ref) +{ + props_impl_t *impl = _props_impl_get(props, property); + + if(impl) + { + _props_impl_stash(props, impl); + + if(*ref && !impl->def->hidden) //TODO use patch:sequenceNumber + *ref = _props_patch_set(props, forge, frames, impl, 0); + } +} + +static inline void +props_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, + LV2_URID property, LV2_Atom_Forge_Ref *ref) +{ + props_impl_t *impl = _props_impl_get(props, property); + + if(impl) + { + if(*ref && !impl->def->hidden) //TODO use patch:sequenceNumber + *ref = _props_patch_get(props, forge, frames, impl, 0); + } +} + +static inline void +props_stash(props_t *props, LV2_URID property) +{ + props_impl_t *impl = _props_impl_get(props, property); + + if(impl) + _props_impl_stash(props, impl); +} + +static inline LV2_URID +props_map(props_t *props, const char *uri) +{ + for(unsigned i = 0; i < props->nimpls; i++) + { + props_impl_t *impl = &props->impls[i]; + + if(!strcmp(impl->def->property, uri)) + return impl->property; + } + + return 0; +} + +static inline const char * +props_unmap(props_t *props, LV2_URID property) +{ + props_impl_t *impl = _props_impl_get(props, property); + + if(impl) + return impl->def->property; + + return NULL; +} + +static inline LV2_State_Status +props_save(props_t *props, LV2_State_Store_Function store, + LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features) +{ + const LV2_State_Map_Path *map_path = NULL; + + // set POD flag if not already set by host + flags |= LV2_STATE_IS_POD; + + for(unsigned i = 0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_STATE__mapPath)) + { + map_path = features[i]->data; + break; + } + } + + void *body = malloc(props->max_size); // create memory to store widest value + if(body) + { + for(unsigned i = 0; i < props->nimpls; i++) + { + props_impl_t *impl = &props->impls[i]; + + if(impl->access == props->urid.patch_readable) + continue; // skip read-only, as it makes no sense to restore them + + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + // create temporary copy of value, store() may well be blocking + const uint32_t size = impl->stash.size; + memcpy(body, impl->stash.body, size); + + _props_impl_unlock(impl, PROP_STATE_NONE); + + if( map_path && (impl->type == props->urid.atom_path) ) + { + const char *path = strstr(body, "file://") + ? (char *)body + 7 // skip "file://" + : (char *)body; + char *abstract = map_path->abstract_path(map_path->handle, path); + if(abstract) + { + const uint32_t sz = strlen(abstract) + 1; + store(state, impl->property, abstract, sz, impl->type, flags); + + free(abstract); + } + } + else // !Path + { + store(state, impl->property, body, size, impl->type, flags); + } + } + + free(body); + } + + return LV2_STATE_SUCCESS; +} + +static inline LV2_State_Status +props_restore(props_t *props, LV2_State_Retrieve_Function retrieve, + LV2_State_Handle state, uint32_t flags __attribute__((unused)), + const LV2_Feature *const *features) +{ + const LV2_State_Map_Path *map_path = NULL; + + for(unsigned i = 0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_STATE__mapPath)) + map_path = features[i]->data; + } + + for(unsigned i = 0; i < props->nimpls; i++) + { + props_impl_t *impl = &props->impls[i]; + + if(impl->access == props->urid.patch_readable) + continue; // skip read-only, as it makes no sense to restore them + + size_t size; + uint32_t type; + uint32_t _flags; + const void *body = retrieve(state, impl->property, &size, &type, &_flags); + + if( body + && (type == impl->type) + && ( (impl->def->max_size == 0) || (size <= impl->def->max_size) ) ) + { + if(map_path && (type == props->urid.atom_path) ) + { + char *absolute = map_path->absolute_path(map_path->handle, body); + if(absolute) + { + const uint32_t sz = strlen(absolute) + 1; + + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + impl->stash.size = sz; + memcpy(impl->stash.body, absolute, sz); + + _props_impl_unlock(impl, PROP_STATE_RESTORE); + + free(absolute); + } + } + else // !Path + { + _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK); + + impl->stash.size = size; + memcpy(impl->stash.body, body, size); + + _props_impl_unlock(impl, PROP_STATE_RESTORE); + } + } + } + + _props_restoring_set(props); + + return LV2_STATE_SUCCESS; +} + +#ifdef __cplusplus +} +#endif + +#endif // _LV2_PROPS_H_ diff --git a/props.lv2/test/chunk.bin b/props.lv2/test/chunk.bin Binary files differnew file mode 100644 index 0000000..b66efb8 --- /dev/null +++ b/props.lv2/test/chunk.bin diff --git a/props.lv2/test/manifest.ttl.in b/props.lv2/test/manifest.ttl.in new file mode 100644 index 0000000..0ecc313 --- /dev/null +++ b/props.lv2/test/manifest.ttl.in @@ -0,0 +1,28 @@ +# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the Artistic License 2.0 as published by +# The Perl Foundation. +# +# This source 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 +# Artistic License 2.0 for more details. +# +# You should have received a copy of the Artistic License 2.0 +# along the source as a COPYING file. If not, obtain it from +# http://www.perlfoundation.org/artistic_license_2_0. + +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +@prefix props: <http://open-music-kontrollers.ch/lv2/props#> . + +# Orbit Looper +props:test + a lv2:Plugin ; + lv2:minorVersion @MINOR_VERSION@ ; + lv2:microVersion @MICRO_VERSION@ ; + lv2:binary <props@MODULE_SUFFIX@> ; + rdfs:seeAlso <props.ttl> . diff --git a/props.lv2/test/props.c b/props.lv2/test/props.c new file mode 100644 index 0000000..590c519 --- /dev/null +++ b/props.lv2/test/props.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> + +#include <props.h> + +#include <lv2/lv2plug.in/ns/ext/log/log.h> +#include <lv2/lv2plug.in/ns/ext/log/logger.h> + +#define PROPS_PREFIX "http://open-music-kontrollers.ch/lv2/props#" +#define PROPS_TEST_URI PROPS_PREFIX"test" + +#define MAX_NPROPS 7 +#define MAX_STRLEN 256 + +typedef struct _plugstate_t plugstate_t; +typedef struct _plughandle_t plughandle_t; + +struct _plugstate_t { + int32_t val1; + int64_t val2; + float val3; + double val4; + char val5 [MAX_STRLEN]; + char val6 [MAX_STRLEN]; + uint8_t val7 [MAX_STRLEN]; +}; + +struct _plughandle_t { + LV2_URID_Map *map; + LV2_Log_Log *log; + LV2_Log_Logger logger; + LV2_Atom_Forge forge; + LV2_Atom_Forge_Ref ref; + + PROPS_T(props, MAX_NPROPS); + plugstate_t state; + plugstate_t stash; + + struct { + LV2_URID val2; + LV2_URID val4; + } urid; + + const LV2_Atom_Sequence *event_in; + LV2_Atom_Sequence *event_out; +}; + +static void +_intercept(void *data, int64_t frames __attribute__((unused)), props_impl_t *impl) +{ + plughandle_t *handle = data; + + lv2_log_trace(&handle->logger, "SET : %s\n", impl->def->property); +} + +static void +_intercept_stat1(void *data, int64_t frames, props_impl_t *impl) +{ + plughandle_t *handle = data; + + _intercept(data, frames, impl); + + handle->state.val2 = handle->state.val1 * 2; + + props_set(&handle->props, &handle->forge, frames, handle->urid.val2, &handle->ref); +} + +static void +_intercept_stat3(void *data, int64_t frames, props_impl_t *impl) +{ + plughandle_t *handle = data; + + _intercept(data, frames, impl); + + handle->state.val4 = handle->state.val3 * 2; + + props_set(&handle->props, &handle->forge, frames, handle->urid.val4, &handle->ref); +} + +static void +_intercept_stat6(void *data, int64_t frames, props_impl_t *impl) +{ + plughandle_t *handle = data; + + _intercept(data, frames, impl); + + const char *path = strstr(handle->state.val6, "file://") + ? handle->state.val6 + 7 // skip "file://" + : handle->state.val6; + FILE *f = fopen(path, "wb"); // create empty file + if(f) + fclose(f); +} + +static const props_def_t defs [MAX_NPROPS] = { + { + .property = PROPS_PREFIX"statInt", + .offset = offsetof(plugstate_t, val1), + .type = LV2_ATOM__Int, + .event_cb = _intercept_stat1, + }, + { + .property = PROPS_PREFIX"statLong", + .access = LV2_PATCH__readable, + .offset = offsetof(plugstate_t, val2), + .type = LV2_ATOM__Long, + .event_cb = _intercept, + }, + { + .property = PROPS_PREFIX"statFloat", + .offset = offsetof(plugstate_t, val3), + .type = LV2_ATOM__Float, + .event_cb = _intercept_stat3, + }, + { + .property = PROPS_PREFIX"statDouble", + .access = LV2_PATCH__readable, + .offset = offsetof(plugstate_t, val4), + .type = LV2_ATOM__Double, + .event_cb = _intercept, + }, + { + .property = PROPS_PREFIX"statString", + .offset = offsetof(plugstate_t, val5), + .type = LV2_ATOM__String, + .event_cb = _intercept, + .max_size = MAX_STRLEN // strlen + }, + { + .property = PROPS_PREFIX"statPath", + .offset = offsetof(plugstate_t, val6), + .type = LV2_ATOM__Path, + .event_cb = _intercept_stat6, + .max_size = MAX_STRLEN // strlen + }, + { + .property = PROPS_PREFIX"statChunk", + .offset = offsetof(plugstate_t, val7), + .type = LV2_ATOM__Chunk, + .event_cb = _intercept, + .max_size = MAX_STRLEN // strlen + } +}; + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate __attribute__((unused)), + const char *bundle_path __attribute__((unused)), + const LV2_Feature *const *features) +{ + plughandle_t *handle = calloc(1, sizeof(plughandle_t)); + if(!handle) + return NULL; + + for(unsigned i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_LOG__log)) + handle->log = features[i]->data; + } + + if(!handle->map) + { + fprintf(stderr, + "%s: Host does not support urid:map\n", descriptor->URI); + free(handle); + return NULL; + } + if(!handle->log) + { + fprintf(stderr, + "%s: Host does not support log:log\n", descriptor->URI); + free(handle); + return NULL; + } + + lv2_log_logger_init(&handle->logger, handle->map, handle->log); + lv2_atom_forge_init(&handle->forge, handle->map); + + if(!props_init(&handle->props, descriptor->URI, + defs, MAX_NPROPS, &handle->state, &handle->stash, + handle->map, handle)) + { + lv2_log_error(&handle->logger, "failed to initialize property structure\n"); + free(handle); + return NULL; + } + + handle->urid.val2 = props_map(&handle->props, PROPS_PREFIX"statLong"); + handle->urid.val4 = props_map(&handle->props, PROPS_PREFIX"statDouble"); + + return handle; +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void *data) +{ + plughandle_t *handle = (plughandle_t *)instance; + + switch(port) + { + case 0: + handle->event_in = (const LV2_Atom_Sequence *)data; + break; + case 1: + handle->event_out = (LV2_Atom_Sequence *)data; + break; + default: + break; + } +} + +static void +run(LV2_Handle instance, uint32_t nsamples __attribute__((unused))) +{ + plughandle_t *handle = instance; + + uint32_t capacity = handle->event_out->atom.size; + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->event_out, capacity); + handle->ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0); + + props_idle(&handle->props, &handle->forge, 0, &handle->ref); + + LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev) + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; + + if(handle->ref) + props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref); + } + + if(handle->ref) + lv2_atom_forge_pop(&handle->forge, &frame); + else + lv2_atom_sequence_clear(handle->event_out); +} + +static void +cleanup(LV2_Handle instance) +{ + plughandle_t *handle = instance; + + free(handle); +} + +static LV2_State_Status +_state_save(LV2_Handle instance, LV2_State_Store_Function store, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = (plughandle_t *)instance; + + return props_save(&handle->props, store, state, flags, features); +} + +static LV2_State_Status +_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = (plughandle_t *)instance; + + return props_restore(&handle->props, retrieve, state, flags, features); +} + +LV2_State_Interface state_iface = { + .save = _state_save, + .restore = _state_restore +}; + +static const void * +extension_data(const char *uri) +{ + if(!strcmp(uri, LV2_STATE__interface)) + return &state_iface; + return NULL; +} + +const LV2_Descriptor props_test = { + .URI = PROPS_TEST_URI, + .instantiate = instantiate, + .connect_port = connect_port, + .activate = NULL, + .run = run, + .deactivate = NULL, + .cleanup = cleanup, + .extension_data = extension_data +}; + +#ifdef _WIN32 +__declspec(dllexport) +#else +__attribute__((visibility("default"))) +#endif +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch(index) + { + case 0: + return &props_test; + default: + return NULL; + } +} diff --git a/props.lv2/test/props.ttl b/props.lv2/test/props.ttl new file mode 100644 index 0000000..0ce45d6 --- /dev/null +++ b/props.lv2/test/props.ttl @@ -0,0 +1,152 @@ +# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the Artistic License 2.0 as published by +# The Perl Foundation. +# +# This source 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 +# Artistic License 2.0 for more details. +# +# You should have received a copy of the Artistic License 2.0 +# along the source as a COPYING file. If not, obtain it from +# http://www.perlfoundation.org/artistic_license_2_0. + +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix atom: <http://lv2plug.in/ns/ext/atom#> . +@prefix urid: <http://lv2plug.in/ns/ext/urid#> . +@prefix state: <http://lv2plug.in/ns/ext/state#> . +@prefix patch: <http://lv2plug.in/ns/ext/patch#> . +@prefix log: <http://lv2plug.in/ns/ext/log#> . +@prefix units: <http://lv2plug.in/ns/extensions/units#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . + +@prefix lic: <http://opensource.org/licenses/> . +@prefix omk: <http://open-music-kontrollers.ch/ventosus#> . +@prefix proj: <http://open-music-kontrollers.ch/lv2/> . +@prefix props: <http://open-music-kontrollers.ch/lv2/props#> . + +# Maintainer +omk:me + a foaf:Person ; + foaf:name "Hanspeter Portner" ; + foaf:mbox <mailto:dev@open-music-kontrollers.ch> ; + foaf:homepage <http://open-music-kontrollers.ch> . + +# Project +proj:props + a doap:Project ; + doap:maintainer omk:me ; + doap:name "Props Bundle" . + +props:statInt + a lv2:Parameter ; + rdfs:range atom:Int ; + rdfs:label "statInt" ; + rdfs:comment "This is a 32-bit integer" ; + units:unit units:hz ; + lv2:minimum 0 ; + lv2:maximum 10 . + +props:statLong + a lv2:Parameter ; + rdfs:range atom:Long ; + rdfs:label "statLong" ; + rdfs:comment "This is a 64-bit integer" ; + units:unit units:khz ; + lv2:minimum 0 ; + lv2:maximum 20 . + +props:statFloat + a lv2:Parameter ; + rdfs:range atom:Float ; + rdfs:label "statFloat" ; + rdfs:comment "This is a 32-bit float" ; + units:unit units:mhz ; + lv2:minimum -0.5 ; + lv2:maximum 0.5 . + +props:statDouble + a lv2:Parameter ; + rdfs:range atom:Double ; + rdfs:label "statDouble" ; + rdfs:comment "This is a 64-bit double" ; + units:unit units:db ; + lv2:minimum -1.0 ; + lv2:maximum 1.0 . + +props:statString + a lv2:Parameter ; + rdfs:range atom:String ; + rdfs:label "statString" ; + rdfs:comment "This is a string" . + +props:statPath + a lv2:Parameter ; + rdfs:range atom:Path ; + rdfs:label "statPath" ; + rdfs:comment "This is a path" . + +props:statChunk + a lv2:Parameter ; + rdfs:range atom:Chunk; + rdfs:label "statChunk" ; + rdfs:comment "This is a chunk" . + +# Looper Test +props:test + a lv2:Plugin , + lv2:ConverterPlugin ; + doap:name "Props Test" ; + doap:license lic:Artistic-2.0 ; + lv2:project proj:props ; + lv2:requiredFeature urid:map, log:log, state:loadDefaultState ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore ; + lv2:extensionData state:interface ; + + lv2:port [ + # sink event port + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:index 0 ; + lv2:symbol "event_in" ; + lv2:name "Event Input" ; + lv2:designation lv2:control ; + ] , [ + # source event port + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message ; + lv2:index 1 ; + lv2:symbol "event_out" ; + lv2:name "Event Output" ; + lv2:designation lv2:control ; + ] ; + + patch:writable + props:statInt , + props:statFloat , + props:statString , + props:statPath , + props:statChunk ; + + patch:readable + props:statLong , + props:statDouble ; + + state:state [ + props:statInt 4 ; + props:statFloat "0.4"^^xsd:float ; + props:statString "Hello world" ; + props:statPath <props.ttl> ; + props:statChunk "AQIDBAUGBw=="^^xsd:base64Binary ; + ] . diff --git a/props.lv2/test/props_test.c b/props.lv2/test/props_test.c new file mode 100644 index 0000000..f028e32 --- /dev/null +++ b/props.lv2/test/props_test.c @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <assert.h> + +#include <props.h> + +#define MAX_URIDS 512 +#define STR_SIZE 32 +#define CHUNK_SIZE 16 +#define VEC_SIZE 13 + +#define PROPS_PREFIX "http://open-music-kontrollers.ch/lv2/props#" +#define PROPS_TEST_URI PROPS_PREFIX"test" + +typedef struct _plugstate_t plugstate_t; +typedef struct _urid_t urid_t; +typedef struct _handle_t handle_t; +typedef void (*test_t)(handle_t *handle); +typedef void *(*ser_atom_realloc_t)(void *data, void *buf, size_t size); +typedef void (*ser_atom_free_t)(void *data, void *buf); + +typedef struct _ser_atom_t ser_atom_t; + +struct _plugstate_t { + int32_t b32; + int32_t i32; + int64_t i64; + float f32; + double f64; + uint32_t urid; + char str [STR_SIZE]; + char uri [STR_SIZE]; + char path [STR_SIZE]; + uint8_t chunk [CHUNK_SIZE]; + LV2_Atom_Literal_Body lit; + char lit_body [STR_SIZE]; + LV2_Atom_Vector_Body vec; + int32_t vec_body [VEC_SIZE]; + LV2_Atom_Object_Body obj; //FIXME + LV2_Atom_Sequence_Body seq; //FIXME +}; + +struct _urid_t { + LV2_URID urid; + char *uri; +}; + +enum { + PROP_b32 = 0, + PROP_i32, + PROP_i64, + PROP_f32, + PROP_f64, + PROP_urid, + PROP_str, + PROP_uri, + PROP_path, + PROP_chunk, + PROP_lit, + PROP_vec, + PROP_obj, + PROP_seq, + + MAX_NPROPS +}; + +struct _handle_t { + PROPS_T(props, MAX_NPROPS); + plugstate_t state; + plugstate_t stash; + + LV2_URID_Map map; + + urid_t urids [MAX_URIDS]; + LV2_URID urid; +}; + +struct _ser_atom_t { + ser_atom_realloc_t realloc; + ser_atom_free_t free; + void *data; + + size_t size; + size_t offset; + union { + uint8_t *buf; + LV2_Atom *atom; + }; +}; + +static LV2_Atom_Forge_Ref +_ser_atom_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf, + uint32_t size) +{ + ser_atom_t *ser = handle; + const size_t needed = ser->offset + size; + + while(needed > ser->size) + { + const size_t augmented = ser->size + ? ser->size << 1 + : 1024; + uint8_t *grown = ser->realloc(ser->data, ser->buf, augmented); + if(!grown) // out-of-memory + { + return 0; + } + + ser->buf = grown; + ser->size = augmented; + } + + const LV2_Atom_Forge_Ref ref = ser->offset + 1; + memcpy(&ser->buf[ser->offset], buf, size); + ser->offset += size; + + return ref; +} + +static LV2_Atom * +_ser_atom_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref) +{ + ser_atom_t *ser = handle; + + if(!ref) // invalid reference + { + return NULL; + } + + const size_t offset = ref - 1; + return (LV2_Atom *)&ser->buf[offset]; +} + +static void * +_ser_atom_realloc(void *data, void *buf, size_t size) +{ + (void)data; + + return realloc(buf, size); +} + +static void +_ser_atom_free(void *data, void *buf) +{ + (void)data; + + free(buf); +} + +static int +ser_atom_deinit(ser_atom_t *ser) +{ + if(!ser) + { + return -1; + } + + if(ser->buf) + { + ser->free(ser->data, ser->buf); + } + + ser->size = 0; + ser->offset = 0; + ser->buf = NULL; + + return 0; +} + +static int +ser_atom_funcs(ser_atom_t *ser, ser_atom_realloc_t realloc, + ser_atom_free_t free, void *data) +{ + if(!ser || !realloc || !free || ser_atom_deinit(ser)) + { + return -1; + } + + ser->realloc = realloc; + ser->free = free; + ser->data = data; + + return 0; +} + +static int +ser_atom_init(ser_atom_t *ser) +{ + if(!ser) + { + return -1; + } + + ser->size = 0; + ser->offset = 0; + ser->buf = NULL; + + return ser_atom_funcs(ser, _ser_atom_realloc, _ser_atom_free, NULL); +} + +#if 0 +static int +ser_atom_reset(ser_atom_t *ser, LV2_Atom_Forge *forge) +{ + if(!ser || !forge) + { + return -1; + } + + lv2_atom_forge_set_sink(forge, _ser_atom_sink, _ser_atom_deref, ser); + + ser->offset = 0; + + return 0; +} +#endif + +static LV2_Atom * +ser_atom_get(ser_atom_t *ser) +{ + if(!ser) + { + return NULL; + } + + return ser->atom; +} + +static LV2_URID +_map(LV2_URID_Map_Handle instance, const char *uri) +{ + handle_t *handle = instance; + + urid_t *itm; + for(itm=handle->urids; itm->urid; itm++) + { + if(!strcmp(itm->uri, uri)) + return itm->urid; + } + + assert(handle->urid + 1 < MAX_URIDS); + + // create new + itm->urid = ++handle->urid; + itm->uri = strdup(uri); + + return itm->urid; +} + +static const props_def_t defs [MAX_NPROPS] = { + [PROP_b32] = { + .property = PROPS_PREFIX"b32", + .offset = offsetof(plugstate_t, b32), + .type = LV2_ATOM__Bool + }, + [PROP_i32] = { + .property = PROPS_PREFIX"i32", + .offset = offsetof(plugstate_t, i32), + .type = LV2_ATOM__Int + }, + [PROP_i64] = { + .property = PROPS_PREFIX"i64", + .offset = offsetof(plugstate_t, i64), + .type = LV2_ATOM__Long + }, + [PROP_f32] = { + .property = PROPS_PREFIX"f32", + .offset = offsetof(plugstate_t, f32), + .type = LV2_ATOM__Float + }, + [PROP_f64] = { + .property = PROPS_PREFIX"f64", + .offset = offsetof(plugstate_t, f64), + .type = LV2_ATOM__Double + }, + [PROP_urid] = { + .property = PROPS_PREFIX"urid", + .offset = offsetof(plugstate_t, urid), + .type = LV2_ATOM__URID + }, + [PROP_str] = { + .property = PROPS_PREFIX"str", + .offset = offsetof(plugstate_t, str), + .type = LV2_ATOM__String, + .max_size = STR_SIZE + }, + [PROP_uri] = { + .property = PROPS_PREFIX"uri", + .offset = offsetof(plugstate_t, uri), + .type = LV2_ATOM__URI, + .max_size = STR_SIZE + }, + [PROP_path] = { + .property = PROPS_PREFIX"path", + .offset = offsetof(plugstate_t, path), + .type = LV2_ATOM__Path, + .max_size = STR_SIZE + }, + [PROP_chunk] = { + .property = PROPS_PREFIX"chunk", + .offset = offsetof(plugstate_t, chunk), + .type = LV2_ATOM__Chunk, + .max_size = CHUNK_SIZE + }, + [PROP_lit] = { + .property = PROPS_PREFIX"lit", + .offset = offsetof(plugstate_t, lit), + .type = LV2_ATOM__Literal, + .max_size = sizeof(LV2_Atom_Literal_Body) + STR_SIZE + }, + [PROP_vec] = { + .property = PROPS_PREFIX"vec", + .offset = offsetof(plugstate_t, vec), + .type = LV2_ATOM__Literal, + .max_size = sizeof(LV2_Atom_Vector_Body) + VEC_SIZE*sizeof(int32_t) + }, + [PROP_obj] = { + .property = PROPS_PREFIX"obj", + .offset = offsetof(plugstate_t, obj), + .type = LV2_ATOM__Object, + .max_size = sizeof(LV2_Atom_Object_Body) + 0 //FIXME + }, + [PROP_seq] = { + .property = PROPS_PREFIX"seq", + .offset = offsetof(plugstate_t, seq), + .type = LV2_ATOM__Sequence, + .max_size = sizeof(LV2_Atom_Sequence_Body) + 0 //FIXME + } +}; + +static void +_test_1(handle_t *handle) +{ + assert(handle); + + props_t *props = &handle->props; + plugstate_t *state = &handle->state; + plugstate_t *stash = &handle->stash; + LV2_URID_Map *map = &handle->map; + + for(unsigned i = 0; i < MAX_NPROPS; i++) + { + const props_def_t *def = &defs[i]; + + const LV2_URID property = props_map(props, def->property); + assert(property != 0); + assert(property == map->map(map->handle, def->property)); + + assert(strcmp(props_unmap(props, property), def->property) == 0); + + props_impl_t *impl = _props_impl_get(props, property); + assert(impl); + + const LV2_URID type = map->map(map->handle, def->type); + const LV2_URID access = map->map(map->handle, def->access + ? def->access : LV2_PATCH__writable); + + assert(impl->property == property); + assert(impl->type == type); + assert(impl->access == access); + + assert(impl->def == def); + + assert(atomic_load(&impl->state) == PROP_STATE_NONE); + assert(impl->stashing == false); + + switch(i) + { + case PROP_b32: + { + assert(impl->value.size == sizeof(state->b32)); + assert(impl->value.body == &state->b32); + + assert(impl->stash.size == sizeof(stash->b32)); + assert(impl->stash.body == &stash->b32); + } break; + case PROP_i32: + { + assert(impl->value.size == sizeof(state->i32)); + assert(impl->value.body == &state->i32); + + assert(impl->stash.size == sizeof(stash->i32)); + assert(impl->stash.body == &stash->i32); + } break; + case PROP_i64: + { + assert(impl->value.size == sizeof(state->i64)); + assert(impl->value.body == &state->i64); + + assert(impl->stash.size == sizeof(stash->i64)); + assert(impl->stash.body == &stash->i64); + } break; + case PROP_f32: + { + assert(impl->value.size == sizeof(state->f32)); + assert(impl->value.body == &state->f32); + + assert(impl->stash.size == sizeof(stash->f32)); + assert(impl->stash.body == &stash->f32); + } break; + case PROP_f64: + { + assert(impl->value.size == sizeof(state->f64)); + assert(impl->value.body == &state->f64); + + assert(impl->stash.size == sizeof(stash->f64)); + assert(impl->stash.body == &stash->f64); + } break; + case PROP_urid: + { + assert(impl->value.size == sizeof(state->urid)); + assert(impl->value.body == &state->urid); + + assert(impl->stash.size == sizeof(stash->urid)); + assert(impl->stash.body == &stash->urid); + } break; + case PROP_str: + { + assert(impl->value.size == 0); + assert(impl->value.body == &state->str); + + assert(impl->stash.size == 0); + assert(impl->stash.body == &stash->str); + } break; + case PROP_uri: + { + assert(impl->value.size == 0); + assert(impl->value.body == &state->uri); + + assert(impl->stash.size == 0); + assert(impl->stash.body == &stash->uri); + } break; + case PROP_path: + { + assert(impl->value.size == 0); + assert(impl->value.body == &state->path); + + assert(impl->stash.size == 0); + assert(impl->stash.body == &stash->path); + } break; + case PROP_chunk: + { + assert(impl->value.size == 0); + assert(impl->value.body == &state->chunk); + + assert(impl->stash.size == 0); + assert(impl->stash.body == &stash->chunk); + } break; + case PROP_lit: + { + assert(impl->value.size == sizeof(state->lit)); + assert(impl->value.body == &state->lit); + + assert(impl->stash.size == sizeof(stash->lit)); + assert(impl->stash.body == &stash->lit); + } break; + case PROP_vec: + { + assert(impl->value.size == sizeof(state->vec)); + assert(impl->value.body == &state->vec); + + assert(impl->stash.size == sizeof(stash->vec)); + assert(impl->stash.body == &stash->vec); + } break; + case PROP_obj: + { + assert(impl->value.size == sizeof(state->obj)); + assert(impl->value.body == &state->obj); + + assert(impl->stash.size == sizeof(stash->obj)); + assert(impl->stash.body == &stash->obj); + } break; + case PROP_seq: + { + assert(impl->value.size == sizeof(state->seq)); + assert(impl->value.body == &state->seq); + + assert(impl->stash.size == sizeof(stash->seq)); + assert(impl->stash.body == &stash->seq); + } break; + default: + { + assert(false); + } break; + } + } +} + +static void +_test_2(handle_t *handle) +{ + assert(handle); + + props_t *props = &handle->props; + plugstate_t *state = &handle->state; + plugstate_t *stash = &handle->stash; + LV2_URID_Map *map = &handle->map; + + LV2_Atom_Forge forge; + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge_Ref ref; + ser_atom_t ser; + + lv2_atom_forge_init(&forge, map); + assert(ser_atom_init(&ser) == 0); + + lv2_atom_forge_set_sink(&forge, _ser_atom_sink, _ser_atom_deref, &ser); + + ref = lv2_atom_forge_sequence_head(&forge, &frame, 0); + assert(ref); + + props_idle(props, &forge, 0, &ref); + assert(ref); + + const LV2_URID property = props_map(props, defs[0].property); + assert(property); + + state->b32 = true; + + props_set(props, &forge, 1, property, &ref); + assert(ref); + + state->b32 = false; + + lv2_atom_forge_pop(&forge, &frame); + + const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)ser_atom_get(&ser); + assert(seq); + + unsigned nevs = 0; + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) + { + const LV2_Atom *atom = &ev->body; + + assert(ev->time.frames == 1); + assert(atom->type == forge.Object); + + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom; + assert(obj->body.id == 0); + assert(obj->body.otype == props->urid.patch_set); + + unsigned nprops = 0; + LV2_ATOM_OBJECT_FOREACH(obj, prop) + { + assert(prop->context == 0); + + if(prop->key == props->urid.patch_subject) + { + const LV2_Atom_URID *val = (const LV2_Atom_URID *)&prop->value; + + assert(val->atom.type == forge.URID); + assert(val->atom.size == sizeof(uint32_t)); + assert(val->body == props->urid.subject); + + nprops |= 0x1; + } + else if(prop->key == props->urid.patch_property) + { + const LV2_Atom_URID *val = (const LV2_Atom_URID *)&prop->value; + + assert(val->atom.type == forge.URID); + assert(val->atom.size == sizeof(uint32_t)); + assert(val->body == property); + + nprops |= 0x2; + } + else if(prop->key == props->urid.patch_value) + { + const LV2_Atom_Bool *val = (const LV2_Atom_Bool *)&prop->value; + + assert(val->atom.type == forge.Bool); + assert(val->atom.size == sizeof(int32_t)); + assert(val->body == true); + + nprops |= 0x4; + } + else + { + assert(false); + } + } + assert(nprops == 0x7); + + assert(props_advance(props, &forge, ev->time.frames, obj, &ref) == 1); + + assert(state->b32 == true); + assert(stash->b32 == true); + + nevs |= 0x1; + } + assert(nevs == 0x1); + + assert(ser_atom_deinit(&ser) == 0); +} + +static const test_t tests [] = { + _test_1, + _test_2, + NULL +}; + +int +main(int argc __attribute__((unused)), char **argv __attribute__((unused))) +{ + static handle_t handle; + + for(const test_t *test = tests; *test; test++) + { + memset(&handle, 0, sizeof(handle)); + + handle.map.handle = &handle; + handle.map.map = _map; + + assert(props_init(&handle.props, PROPS_PREFIX"subj", defs, MAX_NPROPS, + &handle.state, &handle.stash, &handle.map, NULL) == 1); + + (*test)(&handle); + } + + for(urid_t *itm=handle.urids; itm->urid; itm++) + { + free(itm->uri); + } + + return 0; +} diff --git a/timely.lv2/.gitlab-ci.yml b/timely.lv2/.gitlab-ci.yml new file mode 100644 index 0000000..571044c --- /dev/null +++ b/timely.lv2/.gitlab-ci.yml @@ -0,0 +1,75 @@ +stages: + - build + - test + - deploy + +.variables_template: &variables_definition + variables: + BASE_NAME: "timely.lv2" + PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig" + +.common_template: &common_definition + <<: *variables_definition + stage: build + artifacts: + name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}" + paths: + - "${BASE_NAME}-$(cat VERSION)/" + +.build_template: &build_definition + <<: *common_definition + script: + - mkdir build + - pushd build + - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR} -DPLUGIN_DEST="${BASE_NAME}-$(cat ../VERSION)/${CI_BUILD_NAME}/${BASE_NAME}" -DCMAKE_CI_BUILD_NAME=${CI_BUILD_NAME} .. + - cmake .. # needed for darwin + - make + - make install + +.universal_linux_template: &universal_linux_definition + image: ventosus/universal-linux-gnu + <<: *build_definition + +.arm_linux_template: &arm_linux_definition + image: ventosus/arm-linux-gnueabihf + <<: *build_definition + +.universal_w64_template: &universal_w64_definition + image: ventosus/universal-w64-mingw32 + <<: *build_definition + +.universal_apple_template: &universal_apple_definition + image: ventosus/universal-apple-darwin + <<: *build_definition + +# building in docker +x86_64-linux-gnu: + <<: *universal_linux_definition + +i686-linux-gnu: + <<: *universal_linux_definition + +arm-linux-gnueabihf: + <<: *arm_linux_definition + +aarch64-linux-gnu: + <<: *arm_linux_definition + +x86_64-w64-mingw32: + <<: *universal_w64_definition + +i686-w64-mingw32: + <<: *universal_w64_definition + +universal-apple-darwin: + <<: *universal_apple_definition + +pack: + <<: *variables_definition + stage: deploy + script: + - echo 'packing up...' + artifacts: + name: "${BASE_NAME}-$(cat VERSION)" + paths: + - "${BASE_NAME}-$(cat VERSION)/" diff --git a/timely.lv2/COPYING b/timely.lv2/COPYING new file mode 100644 index 0000000..ddb9a46 --- /dev/null +++ b/timely.lv2/COPYING @@ -0,0 +1,201 @@ + The Artistic License 2.0 + + Copyright (c) 2000-2006, The Perl Foundation. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +Preamble + +This license establishes the terms under which a given free software +Package may be copied, modified, distributed, and/or redistributed. +The intent is that the Copyright Holder maintains some artistic +control over the development of that Package while still keeping the +Package available as open source and free software. + +You are always permitted to make arrangements wholly outside of this +license directly with the Copyright Holder of a given Package. If the +terms of this license do not permit the full use that you propose to +make of the Package, you should contact the Copyright Holder and seek +a different licensing arrangement. + +Definitions + + "Copyright Holder" means the individual(s) or organization(s) + named in the copyright notice for the entire Package. + + "Contributor" means any party that has contributed code or other + material to the Package, in accordance with the Copyright Holder's + procedures. + + "You" and "your" means any person who would like to copy, + distribute, or modify the Package. + + "Package" means the collection of files distributed by the + Copyright Holder, and derivatives of that collection and/or of + those files. A given Package may consist of either the Standard + Version, or a Modified Version. + + "Distribute" means providing a copy of the Package or making it + accessible to anyone else, or in the case of a company or + organization, to others outside of your company or organization. + + "Distributor Fee" means any fee that you charge for Distributing + this Package or providing support for this Package to another + party. It does not mean licensing fees. + + "Standard Version" refers to the Package if it has not been + modified, or has been modified only in ways explicitly requested + by the Copyright Holder. + + "Modified Version" means the Package, if it has been changed, and + such changes were not explicitly requested by the Copyright + Holder. + + "Original License" means this Artistic License as Distributed with + the Standard Version of the Package, in its current version or as + it may be modified by The Perl Foundation in the future. + + "Source" form means the source code, documentation source, and + configuration files for the Package. + + "Compiled" form means the compiled bytecode, object code, binary, + or any other form resulting from mechanical transformation or + translation of the Source form. + + +Permission for Use and Modification Without Distribution + +(1) You are permitted to use the Standard Version and create and use +Modified Versions for any purpose without restriction, provided that +you do not Distribute the Modified Version. + + +Permissions for Redistribution of the Standard Version + +(2) You may Distribute verbatim copies of the Source form of the +Standard Version of this Package in any medium without restriction, +either gratis or for a Distributor Fee, provided that you duplicate +all of the original copyright notices and associated disclaimers. At +your discretion, such verbatim copies may or may not include a +Compiled form of the Package. + +(3) You may apply any bug fixes, portability changes, and other +modifications made available from the Copyright Holder. The resulting +Package will still be considered the Standard Version, and as such +will be subject to the Original License. + + +Distribution of Modified Versions of the Package as Source + +(4) You may Distribute your Modified Version as Source (either gratis +or for a Distributor Fee, and with or without a Compiled form of the +Modified Version) provided that you clearly document how it differs +from the Standard Version, including, but not limited to, documenting +any non-standard features, executables, or modules, and provided that +you do at least ONE of the following: + + (a) make the Modified Version available to the Copyright Holder + of the Standard Version, under the Original License, so that the + Copyright Holder may include your modifications in the Standard + Version. + + (b) ensure that installation of your Modified Version does not + prevent the user installing or running the Standard Version. In + addition, the Modified Version must bear a name that is different + from the name of the Standard Version. + + (c) allow anyone who receives a copy of the Modified Version to + make the Source form of the Modified Version available to others + under + + (i) the Original License or + + (ii) a license that permits the licensee to freely copy, + modify and redistribute the Modified Version using the same + licensing terms that apply to the copy that the licensee + received, and requires that the Source form of the Modified + Version, and of any works derived from it, be made freely + available in that license fees are prohibited but Distributor + Fees are allowed. + + +Distribution of Compiled Forms of the Standard Version +or Modified Versions without the Source + +(5) You may Distribute Compiled forms of the Standard Version without +the Source, provided that you include complete instructions on how to +get the Source of the Standard Version. Such instructions must be +valid at the time of your distribution. If these instructions, at any +time while you are carrying out such distribution, become invalid, you +must provide new instructions on demand or cease further distribution. +If you provide valid instructions or cease distribution within thirty +days after you become aware that the instructions are invalid, then +you do not forfeit any of your rights under this license. + +(6) You may Distribute a Modified Version in Compiled form without +the Source, provided that you comply with Section 4 with respect to +the Source of the Modified Version. + + +Aggregating or Linking the Package + +(7) You may aggregate the Package (either the Standard Version or +Modified Version) with other packages and Distribute the resulting +aggregation provided that you do not charge a licensing fee for the +Package. Distributor Fees are permitted, and licensing fees for other +components in the aggregation are permitted. The terms of this license +apply to the use and Distribution of the Standard or Modified Versions +as included in the aggregation. + +(8) You are permitted to link Modified and Standard Versions with +other works, to embed the Package in a larger work of your own, or to +build stand-alone binary or bytecode versions of applications that +include the Package, and Distribute the result without restriction, +provided the result does not expose a direct interface to the Package. + + +Items That are Not Considered Part of a Modified Version + +(9) Works (including, but not limited to, modules and scripts) that +merely extend or make use of the Package, do not, by themselves, cause +the Package to be a Modified Version. In addition, such works are not +considered parts of the Package itself, and are not subject to the +terms of this license. + + +General Provisions + +(10) Any use, modification, and distribution of the Standard or +Modified Versions is governed by this Artistic License. By using, +modifying or distributing the Package, you accept this license. Do not +use, modify, or distribute the Package, if you do not accept this +license. + +(11) If your Modified Version has been derived from a Modified +Version made by someone other than you, you are nevertheless required +to ensure that your Modified Version complies with the requirements of +this license. + +(12) This license does not grant you the right to use any trademark, +service mark, tradename, or logo of the Copyright Holder. + +(13) This license includes the non-exclusive, worldwide, +free-of-charge patent license to make, have made, use, offer to sell, +sell, import and otherwise transfer the Package with respect to any +patent claims licensable by the Copyright Holder that are necessarily +infringed by the Package. If you institute patent litigation +(including a cross-claim or counterclaim) against any party alleging +that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the +date that such litigation is filed. + +(14) Disclaimer of Warranty: +THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS +IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL +LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/timely.lv2/README.md b/timely.lv2/README.md new file mode 100644 index 0000000..c422cf9 --- /dev/null +++ b/timely.lv2/README.md @@ -0,0 +1,20 @@ +# Timely.lv2 + +## Utility header for time-based LV2 plugins + +### License + +Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + +This is free software: you can redistribute it and/or modify +it under the terms of the Artistic License 2.0 as published by +The Perl Foundation. + +This source 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 +Artistic License 2.0 for more details. + +You should have received a copy of the Artistic License 2.0 +along the source as a COPYING file. If not, obtain it from +<http://www.perlfoundation.org/artistic_license_2_0>. diff --git a/timely.lv2/VERSION b/timely.lv2/VERSION new file mode 100644 index 0000000..352e98e --- /dev/null +++ b/timely.lv2/VERSION @@ -0,0 +1 @@ +0.1.49 diff --git a/timely.lv2/meson.build b/timely.lv2/meson.build new file mode 100644 index 0000000..df8d684 --- /dev/null +++ b/timely.lv2/meson.build @@ -0,0 +1,67 @@ +project('timely.lv2', 'c', default_options : [ + 'buildtype=release', + 'warning_level=3', + 'werror=false', + 'b_lto=false', + 'c_std=c11']) + +add_project_arguments('-D_GNU_SOURCE', language : 'c') + +conf_data = configuration_data() +cc = meson.get_compiler('c') + +cp = find_program('cp') +lv2_validate = find_program('lv2_validate', native : true, required : false) +sord_validate = find_program('sord_validate', native : true, required : false) +lv2lint = find_program('lv2lint', required : false) +clone = [cp, '@INPUT@', '@OUTPUT@'] + +m_dep = cc.find_library('m') +lv2_dep = dependency('lv2', version : '>=1.14.0') + +inc_dir = [] + +inst_dir = join_paths(get_option('libdir'), 'lv2', meson.project_name()) + +dsp_srcs = [join_paths('test', 'timely.c')] + +c_args = ['-fvisibility=hidden', + '-ffast-math'] + +mod = shared_module('timely', dsp_srcs, + c_args : c_args, + include_directories : inc_dir, + name_prefix : '', + dependencies : [m_dep, lv2_dep], + install : true, + install_dir : inst_dir) + +version = run_command('cat', 'VERSION').stdout().strip().split('.') +conf_data.set('MAJOR_VERSION', version[0]) +conf_data.set('MINOR_VERSION', version[1]) +conf_data.set('MICRO_VERSION', version[2]) + +suffix = mod.full_path().strip().split('.')[-1] +conf_data.set('MODULE_SUFFIX', '.' + suffix) + +manifest_ttl = configure_file(input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl', + configuration : conf_data, + install : true, + install_dir : inst_dir) +dsp_ttl = custom_target('timely_ttl', + input : join_paths('test', 'timely.ttl'), + output : 'timely.ttl', + command : clone, + install : true, + install_dir : inst_dir) + +if lv2_validate.found() and sord_validate.found() + test('LV2 validate', lv2_validate, + args : [manifest_ttl, dsp_ttl]) +endif + +if lv2lint.found() + test('LV2 lint', lv2lint, + args : ['-Ewarn', + 'http://open-music-kontrollers.ch/lv2/timely#test']) +endif diff --git a/test/manifest.ttl.in b/timely.lv2/test/manifest.ttl.in index e55fd39..e55fd39 100644 --- a/test/manifest.ttl.in +++ b/timely.lv2/test/manifest.ttl.in diff --git a/test/timely.c b/timely.lv2/test/timely.c index ba2d63a..ba2d63a 100644 --- a/test/timely.c +++ b/timely.lv2/test/timely.c diff --git a/test/timely.ttl b/timely.lv2/test/timely.ttl index 6064c9c..6064c9c 100644 --- a/test/timely.ttl +++ b/timely.lv2/test/timely.ttl diff --git a/timely.h b/timely.lv2/timely.h index ed9497b..ed9497b 100644 --- a/timely.h +++ b/timely.lv2/timely.h |