Browse Source

Initial commit

master
Nils 10 months ago
parent
commit
9c75a62233
  1. 64
      README.md
  2. 26
      TODO
  3. 188
      meson.build
  4. 142
      src/artifainterface.cpp
  5. 51
      src/artifainterface.hpp
  6. 35
      src/createxdgdesktop.py
  7. 419
      src/gui.c
  8. 37
      src/gui.h
  9. 251
      src/include/LICENSE_jReadJWrite.html
  10. 2
      src/include/_what_is_this
  11. 789
      src/include/jRead.c
  12. 134
      src/include/jRead.h
  13. 566
      src/include/jWrite.c
  14. 218
      src/include/jWrite.h
  15. 689
      src/include/nsm.h
  16. 30012
      src/include/nuklear.h
  17. 499
      src/include/nuklear_glfw_gl3.h
  18. 230
      src/instrument.c
  19. 59
      src/instrument.h
  20. 484
      src/jackclient.c
  21. 31
      src/jackclient.h
  22. 153
      src/main.c
  23. 156
      src/nsm_callbacks.c
  24. 31
      src/nsm_callbacks.h
  25. 247
      src/programstate.c
  26. 96
      src/programstate.h
  27. 148
      src/style.c
  28. 43
      src/tonsatz.h
  29. 109
      src/vjelo.svg
  30. 6
      subprojects/liblssartifa/AUTHORS
  31. 675
      subprojects/liblssartifa/COPYING
  32. 22
      subprojects/liblssartifa/README
  33. 75
      subprojects/liblssartifa/artifastring/artifastring_constants.h
  34. 104
      subprojects/liblssartifa/artifastring/artifastring_defines.h
  35. 704
      subprojects/liblssartifa/artifastring/artifastring_instrument.cpp
  36. 241
      subprojects/liblssartifa/artifastring/artifastring_instrument.h
  37. 1261
      subprojects/liblssartifa/artifastring/artifastring_string.cpp
  38. 304
      subprojects/liblssartifa/artifastring/artifastring_string.h
  39. 4837
      subprojects/liblssartifa/artifastring/constants/body_cello_1.h
  40. 3228
      subprojects/liblssartifa/artifastring/constants/body_viola_1.h
  41. 8055
      subprojects/liblssartifa/artifastring/constants/body_violin_1.h
  42. 521
      subprojects/liblssartifa/artifastring/constants/haptic_response_1.h
  43. 521
      subprojects/liblssartifa/artifastring/constants/haptic_response_2.h
  44. 521
      subprojects/liblssartifa/artifastring/constants/haptic_response_3.h
  45. 521
      subprojects/liblssartifa/artifastring/constants/haptic_response_4.h
  46. 81
      subprojects/liblssartifa/artifastring/constants/lowpass_1.h
  47. 152
      subprojects/liblssartifa/artifastring/constants/lowpass_2.h
  48. 223
      subprojects/liblssartifa/artifastring/constants/lowpass_3.h
  49. 294
      subprojects/liblssartifa/artifastring/constants/lowpass_4.h
  50. 584
      subprojects/liblssartifa/artifastring/constants/strings_cello.h
  51. 393
      subprojects/liblssartifa/artifastring/constants/strings_viola.h
  52. 966
      subprojects/liblssartifa/artifastring/constants/strings_violin.h
  53. 286
      subprojects/liblssartifa/artifastring/fft_convolution.cpp
  54. 69
      subprojects/liblssartifa/artifastring/fft_convolution.h
  55. 40
      subprojects/liblssartifa/artifastring/midi_pos.cpp
  56. 29
      subprojects/liblssartifa/artifastring/midi_pos.h
  57. 88
      subprojects/liblssartifa/meson.build

64
README.md

@ -1,3 +1,65 @@
# Vjelo
Virtual folk instrument that bows continously.
Virtual folk instrument that bows continously.
## Build
### Dependencies
The build system will do a check for the following dependencies:
* fftw3
* eigen3
* JACK Audio Connection Kit
* GLFW3
* OpenGL with Glu and Glew
* liblo
* For X11: x11 lib, xinput, xcursor (Other OS have not been tested yet)
* NSM - New Session Manager during runtime, through Agordejo (Optioal but recommended)
Only for building:
* C and C++ compiler (e.g. gcc)
* meson build system
* python3
* help2man
* date
### Compilation and Installation
```
meson setup build --prefix=/usr
cd build
ninja
sudo ninja install
```
## License and Credits
This project is, in general, licensed under GPLv3-only.
Technically and legally each file is licensed individually. Please see each files header except
where 3rd parties did not provide license headers in their files. For the sake of leaving the files
unmodified we included LICENSE etc. files in the directory structure where needed. And this list:
All code, except the files mentioned below, by Laborejo Software Suite ( https://laborejo.org )
* Logo based on https://freesvg.org/sewing-machine-3287311 - A CC0 image uploaded to Pixabay.com by user piecebb8 (downloaded 2022-11-26)
* Our own Logo is licensed CC-By-Sa 4.0 https://creativecommons.org/licenses/by-sa/4.0/
* jRead v1.6 and jWrite v1.2 by Tony Wilk (2015)
* https://www.codeproject.com/Articles/885389/jRead-An-in-place-JSON-Element-Reader
* The Code Project Open License (CPOL) 1.02
* nsm.h by https://github.com/jackaudio/new-session-manager, ISC License. See file itself for complete list of authors.
* Nuklear GUI header library Micha Mettke et. al. https://github.com/Immediate-Mode-UI/Nuklear ("Public Domain" www.unlicense.org)
* Embeds ProggyClean.ttf font by Tristan Grimmer (MIT license).
* Embeds stb_texedit, stb_truetype and stb_rectpack by Sean Barret (public domain)
* Uses stddoc.c from r-lyeh@github.com for documentation generation
* Artifastring by Graham Percival
* https://github.com/gperciva/artifastring (2013) - GPLv3-Only
* the files we use come from a fork by Nick Bailey (2019) https://github.com/nickbailey/artifastring
* Inspiration and some functions: smrgygurdy
* https://github.com/nickbailey/smrgygurdy
* GPLv3-Only because it builds on artifastring
* Authors: EE+Computing Students Team E Maintained by: Nick Bailey Date: 21 March 2011. We read the code on commit 74226d1 / Aug 27, 2020
* No actual files of this library have been used here but we rewrote some files functions from C++ to C, adopting them to our more narrow usecase.
* Some of our files can therefore be considered derivative work of smrgygurdy. But we are all GPLv3 here anyway.

26
TODO

@ -0,0 +1,26 @@
2022-11-25 "Later or Never":
* CC7 switch with release envelope so it isn't that sudden. But a reverb plugin will soften that
effect.
* Reintroduce the idea of wheel and key sfx. The code for that is currently commented out because
we never were able to record good sfx samples.
* Different Mono Modes: "Highest note wins" but remember what other keys are currently pressed and
fall back when highest not get's released. This can be done with lv2 midi plugins.
* Autostart modes (radio buttons): With the first note on, with jack transport play, on program
start (which current behaviour)
* General tuning, different to a' = 440Hz. It looks like the current tuning is hardcoded in the
artifastring physical model, staticly generated numbers. In this case we won't have a general
tuning. Using the same method as our pitchwheel won't work because that only raises the frequency
because it is based on a virtual finger-on-fretboard position, which can't go below the beginning
of the string.
* FINGER_STRENGTH for key presses is currently a fixed value. Could be a mapped to velocity or with
a random value to have a more humanized feeld. But the effect is not *that* useful.
Command to autoconnect jack during development:
jack-matchmaker Vjelo:Melody$ system:playback_1$ Vjelo:Melody$ system:playback_2$ .*EasyKeys.* "Vjelo:Melody in" .*LPD8.* "Vjelo:Melody in" Vjelo:Drone$ system:playback_1$ Vjelo:Drone$ system:playback_2$

188
meson.build

@ -0,0 +1,188 @@
##############################################################################
# Copyright (C) 2022- Nils Hilbricht
#License for this build file. This is not the license for the software itself, which is mostly GPLv3.
#https://www.isc.org/licenses/
#
#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.
#
#THE SOFTWARE IS PROVIDED โ€œAS ISโ€ AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
#INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
##############################################################################
project(
'Vjelo',
'c', 'cpp',
version : '1.0.0',
license : 'GPLv3',
default_options: [
'warning_level=2',
'optimization=3',
]
)
##############
#Subprojects
##############
liblssartifa_proj = subproject('liblssartifa')
liblssartifa_dep = liblssartifa_proj.get_variable('liblssartifa_dep')
##############
#Dependencies
##############
#If we ever do OSX, here is the GCC Call for GLFW3 etc.
#LIBS := $(GLFW3) -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo -lm -lGLEW -L/usr/local/lib
cc = meson.get_compiler('c')
dep_gl = dependency('gl')
dep_glfw = dependency('glfw3')
dep_glu = dependency('glu')
dep_glew = dependency('glew')
dep_x11 = dependency('x11')
dep_xi = dependency('xi')
dep_xcursor = dependency('xcursor')
dep_liblo = dependency('liblo') #and not 'lo'
dep_jack = dependency('jack') #and not 'libjack'
#Build dependencies
#Libraries to gather information, build manpage and documentation
#We just run them so a packager get's the info we need these programs
exe_help2man = find_program('help2man')
exe_date = find_program('date')
exe_py3 = find_program('python3')
##############
# Definitions and Information
# In this project the meson file is the central hub for information that is used in many places
# of the program and documentation, such as the name and version number.
##############
#name, version and license are in the project definition above0
tagline = 'Virtual folk instrument that bows continously.'
year = run_command(exe_date, '+%Y', check:true).stdout().strip()
author = 'Laborejo Software Suite'
url_domain = 'laborejo'
url_tld = 'org'
weburl = 'https://'+url_domain+'.'+url_tld
name_exe = meson.project_name().to_lower()
desktopFilename = '@0@.@1@.@2@.desktop'.format(url_tld, url_domain, name_exe)
##############
#Build Targets
##############
artifa_header_include = liblssartifa_proj.get_variable('liblssartifa_includes')
vjelo = executable(name_exe,
sources: [
'src/artifainterface.cpp', #meson automatically compiles and links this as cpp. We have extern C in this file as an interface to the c++ artifastring lib.
#GUI and NSM
'src/main.c',
'src/gui.c',
'src/programstate.c',
'src/nsm_callbacks.c',
'src/instrument.c',
'src/jackclient.c',
#Include unmodified copies of other source codes
'src/include/jRead.c', 'src/include/jWrite.c',
],
include_directories : artifa_header_include,
dependencies: [
liblssartifa_dep,
dep_gl, dep_glfw, dep_glu, dep_glew,
dep_liblo, dep_jack,
],
install: true, #one or more files of this target are installed during the install step
c_args : [
'-Wno-unused-variable',
'-Wno-unused-parameter',
'-Wno-maybe-uninitialized',
'-march=native',
'-ffast-math',
'-DLSS_NAME="' + meson.project_name() + '"',
'-DLSS_EXECUTABLE="' + name_exe + '"',
'-DLSS_VERSION="' + meson.project_version() + '"',
'-DLSS_YEAR="' + year + '"',
'-DLSS_TAGLINE="' + tagline + '"',
'-DLSS_AUTHOR="' + author + '"',
'-DLSS_WEBURL="' + weburl + '"',
'-DLSS_LICENSE="' + meson.project_license()[0] + '"',
],
cpp_args : [
'-Wno-unused-variable',
'-Wno-unused-parameter',
'-Wno-unused-variable',
'-Wno-unused-parameter',
'-ffast-math',
#'-mfpmath=sse', #This will choose sse1 and the program will not work!
'-DNDEBUG',
'-march=native',
'-msse2', #On my own machine this is implicit in -march=native
],
)
custom_target('manpage',
output: name_exe+'.1',
capture: true, #we could also use the --output=FILE argument of help2man
command: [exe_help2man, './vjelo',
'--no-info',
'--name='+tagline, #Name really is the description after the name. The name itself is included in any case.
'--version-string=' + meson.project_name() + ' Version ' + meson.project_version(),
'--source=' + author,
],
build_by_default: true,
build_always_stale: true,
depends: [vjelo], #build the program first, before building the manpage
install:true,
install_dir: get_option('datadir') / 'man' / 'man1'
)
#Meson is NOT ABLE to append text at the end of a file, let alone multiple lines.
#After trying for more than 4 hours I gave up. Nothing works: shell redirections >>, pipes | , sed, awkโ€ฆ
#The only way is to forward strings to an external script, which is a shame because that is what we wanted to avoid.
#We will use Python3 because Meson itself is Python, so at least that exists.
args = [
'Name=' + meson.project_name(),
'Icon=' + name_exe,
'Exec=' + name_exe,
'X-NSM-Exec=' + name_exe,
'Comment=' + tagline,
]
custom_target('xdg_desktop',
output: desktopFilename,
capture: true, #python just prints
command: [exe_py3,
meson.current_source_dir() / 'src' / 'createxdgdesktop.py',
'\n'.join(args),
],
build_by_default: true,
build_always_stale: true,
depends: [vjelo], #build the program first, before building the manpage
install:true,
install_dir : get_option('datadir') / 'applications'
)
##############
#Installation
##############
install_data('src/vjelo.svg', install_dir : get_option('datadir') / 'icons/hicolor/scalable/apps')

142
src/artifainterface.cpp

@ -0,0 +1,142 @@
/**
Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This application is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Standard lib and System libs
#include <stdlib.h>
#include <cmath>
//Unmodified 3rd party includes in our codebase.
#include "artifastring/artifastring_instrument.h" //Header files of the static artifastring library.
#include <artifastring_defines.h>
//Modified 3rd party includes in our codebase
//Our own files
#include "artifainterface.hpp"
//The functions are in part inspired by SMRGyGurdy by Nick Bailey.
//It is a C++ interface to ArtifaString and the inspiration for Vjelo.
//SMRGyGurdy and Artifastring are GPLv3, and so are we.
//https://github.com/nickbailey/smrgygurdy
//Our Artifastring is in fact not the original but a fork from Nick Bailey, meant for SMRGyGurdy:
//https://github.com/nickbailey/artifastring
//https://nachtimwald.com/2017/08/18/wrapping-c-objects-in-c/
const int string2Midi[4] = {36, 43, 50, 57};
struct oneArtifaString {
void *obj;
};
oneArtifaString_t * newCelloString(int pSampleRate) {
oneArtifaString_t * artStr;
ArtifastringInstrument * cppArtString;
artStr = (typeof(artStr)) malloc(sizeof(*artStr));
cppArtString = new ArtifastringInstrument(Cello, 0, pSampleRate); //InstrumentType (enum), instrument variant number, sampleRate
artStr->obj = cppArtString;
return artStr;
}
oneArtifaString_t * newViolinString(int pSampleRate) {
oneArtifaString_t * artStr;
ArtifastringInstrument * cppArtString;
artStr = (typeof(artStr)) malloc(sizeof(*artStr));
cppArtString = new ArtifastringInstrument(Violin, 0, pSampleRate); //InstrumentType (enum), instrument variant number, sampleRate
artStr->obj = cppArtString;
return artStr;
}
//oneArtifaString_t newViolinString(int sampleRate);
void artifaStringDestroy(oneArtifaString_t * artStr) {
if (artStr == NULL) return;
delete static_cast<ArtifastringInstrument *>(artStr->obj);
free(artStr);
}
void artifaStringReset(oneArtifaString_t * artStr) {
ArtifastringInstrument * obj = static_cast<ArtifastringInstrument *>(artStr->obj);
obj->reset();
}
void artifaStringWaitSamples(oneArtifaString_t * artStr, short pBuffer[], int pBufferSize) {
ArtifastringInstrument * obj = static_cast<ArtifastringInstrument *>(artStr->obj);
obj->wait_samples(pBuffer, pBufferSize);
}
void artifaStringBow(oneArtifaString_t * artStr, int pStringNumber, float pBowRatioFromBridge, float pBowForce, float pBowSpeed) {
if (pStringNumber < 0) return;
ArtifastringInstrument * obj = static_cast<ArtifastringInstrument *>(artStr->obj);
obj->bow (pStringNumber, pBowRatioFromBridge, pBowForce, pBowSpeed);
}
void artifaStringSetFinger(oneArtifaString_t * artStr, int pStringNumber, float pFingerPositionFromNut, float pFingerStrength) {
if (pStringNumber < 0) return;
ArtifastringInstrument * obj = static_cast<ArtifastringInstrument *>(artStr->obj);
//First parameter String number,
//2nd pFingerPositionFromNut Measured as a fraction of string length. This comes from artifaStringGetFingerPositionFromNut() below
//3r sets how firmly the finger is pressed against the string. 1.0 indicates normal
//finger strength, while 0.0001 indicates a very light finger suitable for playing harmonic notes.
obj->finger(pStringNumber, pFingerPositionFromNut, pFingerStrength);
}
int artifaStringGetStringNumber(int pMidiNote) {
//We actually need this function, and not a direct midiNote->Sound, to calculate offsets for
//bend and detune.
//We have four strings available.
//Try to use the first hand position, e.g. as low as possible
// Below instrument's range?
if (pMidiNote < string2Midi[0]) {
return -1; //Calling funtion does not need to handle. We handle this ourselves in bow and setFinger above.
}
int stringNumber = 4; //Try to find the highest string possible, which results in the lowest finger position
while (-- stringNumber)
if (pMidiNote > string2Midi[stringNumber])
break;
return stringNumber;
}
float artifaStringGetFingerPositionFromNut(int pMidiNote, int pStringNumber) {
// Find note number associated with an open string. 4 open strings.
int openmidi = string2Midi[pStringNumber];
// The distance down the string is 1.0 - 2^(n/12) (for 12ET)
// where n is the number of midi notes the requested note is
// above the open string.
//
// In 12ET, 12 semitones above the open string is an octave,
// i.e. 2^(-12/12) = half way down.
return 1.0 - pow(2.0, (float)(openmidi-pMidiNote)/12);
}

51
src/artifainterface.hpp

@ -0,0 +1,51 @@
/**
Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This application is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* This file is compiled by a C++ compiler.
* It wraps the unmodified C++ Artifastring library with classes into a C interface with opaque structs
* and functions.
*/
#ifndef ARTIFAINTERFACE_HPP
#define ARTIFAINTERFACE_HPP
#ifdef __cplusplus
extern "C" {
#endif
struct oneArtifaString;
typedef struct oneArtifaString oneArtifaString_t;
oneArtifaString_t * newCelloString(int pSampleRate);
oneArtifaString_t * newViolinString(int pSampleRate);
void artifaStringDestroy(oneArtifaString_t * oneArtifaString);
void artifaStringReset(oneArtifaString_t * oneArtifaString);
void artifaStringWaitSamples(oneArtifaString_t * artStr, short pBuffer[], int pBufferSize);
void artifaStringBow(oneArtifaString_t * artStr, int pStringNumber, float pBowRatioFromBridge, float pBowForce, float pBowSpeed);
void artifaStringSetFinger(oneArtifaString_t * artStr, int pStringNumber, float pFingerPositionFromNut, float pFingerStrength);
float artifaStringGetFingerPositionFromNut(int pMidiNote, int pStringNumber);
int artifaStringGetStringNumber(int pMidiNote);
#ifdef __cplusplus
}
#endif
#endif // ARTIFAINTERFACE_HPP

35
src/createxdgdesktop.py

@ -0,0 +1,35 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This application is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import sys
print ("""[Desktop Entry]
Type=Application
Terminal=false
StartupNotify=false
Version=1.0
Categories=AudioVideo;Audio;
X-NSM-Capable=true""" #no newline here!
)
for arg in sys.argv[1:]: #except the program name
print (arg)

419
src/gui.c

@ -0,0 +1,419 @@
/**
Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This application is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Standard lib and System libs
#include <stdbool.h>
#include <stddef.h>
#include <GL/glew.h>
#include <stdio.h>
//Unmodified 3rd party includes in our codebase
#define MAX_VERTEX_BUFFER 512 * 1024
#define MAX_ELEMENT_BUFFER 128 * 1024
#define NK_BUTTON_TRIGGER_ON_RELEASE
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_IMPLEMENTATION
#define NK_GLFW_GL3_IMPLEMENTATION
#define NK_KEYSTATE_BASED_INPUT
#include "include/nuklear.h"
#include "include/nuklear_glfw_gl3.h"
//Modified 3rd party includes in our codebase
#include "style.c"
//Our own files
#include "gui.h"
#include "programstate.h"
#include "tonsatz.h"
#include "nsm_callbacks.h"
// https://github.com/Immediate-Mode-UI/Nuklear/wiki/Complete-font-guide
extern ProgramState programState;
extern ProgramState defaultState;
int win_width;
int win_height;
struct nk_glfw glfw = {0};
static GLFWwindow *glfwWindow;
struct nk_context *ctx;
#define F_SLIDER_RESOLUTION 0.01f
const int frame_w = 300;
const int frame_h = 340;
const int v_spacing = 20;
const int title_h = 50;
bool showControls = true
;
int last_scroll = 0; //-1 or 1. reset by the loop. Set by scroll_callback
static void error_callback(int e, const char *d) {
printf("Error %d: %s\n", e, d);
}
static void scroll_callback(GLFWwindow *win, double xoff, double yoff) {
//Only called when actual scrolling happened
last_scroll = (int)yoff;
}
void horizontalFloatFader(struct nk_context * ctx, const char * leftLabel, const char * ccNumber, float pMin, float pMax, float pSliderResolution, float * stateValue, float defaultValue, bool showAsPercent, const char * unitLabel) {
nk_layout_row_begin(ctx, NK_DYNAMIC, v_spacing, 4);
//Label
nk_layout_row_push(ctx, 0.17f);
nk_label(ctx, leftLabel, NK_TEXT_ALIGN_LEFT | NK_TEXT_ALIGN_MIDDLE);
//CC Number
nk_layout_row_push(ctx, 0.13f);
nk_label(ctx, ccNumber, NK_TEXT_ALIGN_LEFT | NK_TEXT_ALIGN_MIDDLE);
//Slider
nk_layout_row_push(ctx, 0.49f);
if (nk_widget_is_mouse_clicked(ctx, NK_BUTTON_LEFT)) {
struct nk_rect bounds;
nk_layout_peek(&bounds, ctx);
int widget_x = bounds.x;
int widget_w = bounds.w;
int mouse_x = ctx->input.mouse.pos.x;
float percent = (float)(mouse_x - widget_x) / bounds.w;
*stateValue = percent *pMax; //Set to value directly
}
else if (nk_widget_is_mouse_clicked(ctx, NK_BUTTON_RIGHT)) {
*stateValue = defaultValue; //reset default
}
else if (last_scroll && nk_widget_is_hovered(ctx)) {
//There was mouse wheel scrolling while hovering this slider. Scroll 1%
*stateValue += 2* (pMax/100) * last_scroll; // last_scroll is 1 or -1
}
nk_slider_float(ctx, 0.0f, stateValue, pMax, pSliderResolution);
nk_layout_row_push(ctx, 0.21f);
if (showAsPercent) { //Current Percent
nk_labelf(ctx, NK_TEXT_ALIGN_RIGHT | NK_TEXT_ALIGN_MIDDLE, "%d%%", (int)(*stateValue / pMax * 100));
}
else { //Show direct value
nk_labelf(ctx, NK_TEXT_ALIGN_RIGHT | NK_TEXT_ALIGN_MIDDLE, unitLabel, *stateValue);
}
nk_layout_row_end(ctx);
}
void gui_process_eventloop() {
/* Input */
glfwPollEvents();
nk_glfw3_new_frame(&glfw);
/* GUI */
if (nk_begin(ctx, "title", nk_rect(0, 0, 2*(frame_w)+2, title_h), NK_WINDOW_BORDER)) {
nk_layout_row_dynamic(ctx, title_h/4, 1);
if (nk_widget_is_mouse_clicked(ctx, NK_BUTTON_LEFT)) {
showControls = !showControls;
}
if (showControls) {
nk_label(ctx, "[Manual]" , NK_TEXT_ALIGN_RIGHT);
}
else {
nk_label(ctx, "[Controls]" , NK_TEXT_ALIGN_RIGHT);
}
nk_layout_row_dynamic(ctx, title_h/4, 1);
nk_label(ctx, LSS_NAME " - " LSS_TAGLINE, NK_TEXT_ALIGN_CENTERED | NK_TEXT_ALIGN_MIDDLE);
}
nk_end(ctx);
if (showControls) {
if (programState.portActivity[0] > 0) { //Midi In activity on melody port
nk_style_push_style_item(ctx, &ctx->style.window.header.normal, nk_style_item_color(nk_rgba(100,0,0,50)));
}
if (nk_begin(ctx, "Melody Strings Mixer", nk_rect(0, 0+title_h, frame_w, frame_h), NK_WINDOW_TITLE|NK_WINDOW_BORDER))
{
//nk_size progressPercent;
//progressPercent = (nk_size)(programState.audioLevel_melody_V * 100 / MAX_AUDIO_LEVEL);
//nk_progress(ctx, &progressPercent, 100, NK_MODIFIABLE);
//programState.audioLevel_melody_V = (float)progressPercent * MAX_AUDIO_LEVEL / 100.0f;
//nk_value_float(ctx, "Melody V", programState.audioLevel_melody_V);
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "Parallel Strings", NK_TEXT_ALIGN_LEFT);
horizontalFloatFader(ctx,"I","CC 10", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_melody_I, defaultState.audioLevel_melody_I, true, NULL);
horizontalFloatFader(ctx,"VI","CC 11", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_melody_IV, defaultState.audioLevel_melody_IV, true, NULL);
horizontalFloatFader(ctx,"V","CC 12", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_melody_V, defaultState.audioLevel_melody_V, true, NULL);
horizontalFloatFader(ctx,"VIIIa","CC 13", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_melody_VIII, defaultState.audioLevel_melody_VIII, true, NULL);
horizontalFloatFader(ctx,"VIIIb","CC 14", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_melody_VIII_Violin, defaultState.audioLevel_melody_VIII_Violin, true, NULL);
nk_layout_row_dynamic(ctx, v_spacing/2, 1); //Empty Row
/*
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "Sound Effects", NK_TEXT_ALIGN_LEFT);
horizontalFloatFader(ctx,"Keys","CC 20", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_sfx_keys, defaultState.audioLevel_sfx_keys , true, NULL);
*/
nk_layout_row_dynamic(ctx, v_spacing/2, 1); //Empty Row
nk_layout_row_dynamic(ctx, v_spacing, 1);
programState.autoplayMelody = nk_check_label(ctx, "Autoplay Mode", (nk_bool)programState.autoplayMelody);
if (programState.autoplayMelody) {
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "plays fallback-note without input", NK_TEXT_ALIGN_LEFT);
nk_layout_row_dynamic(ctx, v_spacing, 3);
const char * fallbackPitchName = tonsatz_midiToNotename(programState.autoplayMelody_fallbackPitch, ENGLISH);
if (nk_widget_is_mouse_clicked(ctx, NK_BUTTON_RIGHT)) programState.autoplayMelody_fallbackPitch = defaultState.autoplayMelody_fallbackPitch; //reset default
nk_property_int(ctx, fallbackPitchName, 36, &programState.autoplayMelody_fallbackPitch, 92, 1, 1.0f);
}
}
nk_end(ctx);
if (programState.portActivity[0] > 0) { //Midi In activity on melody port
nk_style_pop_style_item(ctx);
}
if (programState.portActivity[1] > 0) { //Midi In activity on drone port
nk_style_push_style_item(ctx, &ctx->style.window.header.normal, nk_style_item_color(nk_rgba(100,0,0,50)));
}
if (nk_begin(ctx, "Drone Strings Mixer", nk_rect(frame_w+2, title_h, frame_w, frame_h), NK_WINDOW_TITLE|NK_WINDOW_BORDER))
{
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "Parallel Strings", NK_TEXT_ALIGN_LEFT);
horizontalFloatFader(ctx,"I","CC 15", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_drone_I, defaultState.audioLevel_drone_I, true, NULL);
horizontalFloatFader(ctx,"VI","CC 16", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_drone_IV, defaultState.audioLevel_drone_IV, true, NULL);
horizontalFloatFader(ctx,"V", "CC 17", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_drone_V, defaultState.audioLevel_drone_V, true, NULL);
horizontalFloatFader(ctx,"VIII","CC 18", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_drone_VIII, defaultState.audioLevel_drone_VIII, true, NULL);
nk_layout_row_dynamic(ctx, v_spacing, 1); //Spacer for the missing drone string
nk_layout_row_dynamic(ctx, v_spacing/2, 1); //Empty Row
/*
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "Sound Effects", NK_TEXT_ALIGN_LEFT);
horizontalFloatFader(ctx,"Wheel", "CC 19", 0.0f, MAX_AUDIO_LEVEL, F_SLIDER_RESOLUTION, &programState.audioLevel_sfx_wheel, defaultState.audioLevel_sfx_wheel, true, NULL);
*/
nk_layout_row_dynamic(ctx, v_spacing/2, 1); //Empty Row
nk_layout_row_dynamic(ctx, v_spacing, 1);
programState.autoplayDrone = nk_check_label(ctx, "Autoplay Mode", (nk_bool)programState.autoplayDrone);
if (programState.autoplayDrone) {
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "plays a constant drone note", NK_TEXT_ALIGN_LEFT);
nk_layout_row_dynamic(ctx, v_spacing, 3);
const char * fallbackDronePitchName = tonsatz_midiToNotename(programState.droneRootMidiPitch, ENGLISH);
if (nk_widget_is_mouse_clicked(ctx, NK_BUTTON_RIGHT)) programState.droneRootMidiPitch = defaultState.droneRootMidiPitch; //reset default
nk_property_int(ctx, fallbackDronePitchName, 36, &programState.droneRootMidiPitch, 92, 1, 1.0f);
}
}
nk_end(ctx);
if (programState.portActivity[1] > 0) { //Midi In activity on drone port
nk_style_pop_style_item(ctx);
}
if (nk_begin(ctx, "Controls", nk_rect(0, title_h+frame_h+2, frame_w, frame_h), NK_WINDOW_TITLE|NK_WINDOW_BORDER))
{
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "Wheel Speeds", NK_TEXT_ALIGN_LEFT);
horizontalFloatFader(ctx,"Melody","CC 1", 0.0f, 1.0f, F_SLIDER_RESOLUTION, &programState.wheelSpeedMelody, defaultState.wheelSpeedMelody, false, "%.2f m/s");
horizontalFloatFader(ctx,"Drone","CC 2", 0.0f, 1.0f, F_SLIDER_RESOLUTION, &programState.wheelSpeedDrone, defaultState.wheelSpeedDrone, false, "%.2f m/s");
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "0 m/s will halt the instrument.", NK_TEXT_ALIGN_LEFT);
nk_layout_row_dynamic(ctx, v_spacing/2, 1); //Empty Row
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "Force of strings pressed against wheel", NK_TEXT_ALIGN_LEFT);
horizontalFloatFader(ctx,"Melody","CC 3", 0.0f, 10.0f, F_SLIDER_RESOLUTION, &programState.forceMelody, defaultState.forceMelody, false, "%.1f kg");
horizontalFloatFader(ctx,"Drone","CC 4", 0.0f, 15.0f, F_SLIDER_RESOLUTION, &programState.forceDrone, defaultState.forceDrone, false, "%.1f kg");
nk_layout_row_dynamic(ctx, v_spacing/2, 1); //Empty Row
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "Aftertouch & Pitchbend Sensitivity", NK_TEXT_ALIGN_LEFT);
horizontalFloatFader(ctx,"","CC 5", 0.0f, 0.025f, 0.00001f, &programState.afterTouchPitchbendSensitivity, defaultState.afterTouchPitchbendSensitivity, true, ""); //Very sensitive
}
nk_end(ctx);
if (nk_begin(ctx, "Status", nk_rect(frame_w+2, title_h+frame_h+2, frame_w, frame_h), NK_WINDOW_TITLE|NK_WINDOW_BORDER))
{
if (!programState.cc7Switch) {
nk_layout_row_dynamic(ctx, v_spacing, 1);
nk_label(ctx, "CC7 Volume switch is off. No sound.", NK_TEXT_ALIGN_LEFT);
}
}
nk_end(ctx);
} // End If Show Controls.
// If no controls are visible show a text with the manual
else {
if (nk_begin(ctx, "Information and Manual", nk_rect(0, 0+title_h, frame_w*2+2, frame_h*2+2), NK_WINDOW_TITLE|NK_WINDOW_BORDER))
{
//TODO: Wrapping and newlines are broken or not implemente in Nuklear. We cannot use labels to display a single long text.
//Text Editor is too much work, also the wrong design. We just assume a fixed text and fine-tune the values by hand.
nk_layout_row_dynamic(ctx, 0, 1);
nk_label_wrap(ctx, "Play this instrument in auto mode for a typical style.");
nk_layout_row_dynamic(ctx, 100, 1);
nk_label_wrap(ctx, "Embrace the creaking and noisy sounds when playing very high notes or having a very low wheel speed. Set your melody fallback note to an octave or fourth below your melody root note. Always play legato and never let go of your midi keyboard keys. Construct note repetitions by weaving other notes (e.g. one step higher or lower) in between, or let briefly go of the key to trigger the fallback note. From time to time don't directly jump wide distances between keys but fill them with very fast 'fake glissandos', by playing all keys in between rapidly.");
nk_layout_row_dynamic(ctx, 50, 1);
nk_label_wrap(ctx, "The whole instrument is influenced by the wheel speed. Higher wheel produce more stable high notes, but sound more dull and pressed. Autoplays start and stops with the wheel.");
nk_layout_row_dynamic(ctx, 60, 1);
nk_label_wrap(ctx, "Both the MIDI pitchbend and channel aftertouch simulate pressing the virtual keys harder, which raises the pitch slightly on an real instrument. Use the sensitivity-slider in the GUI to match the bending amount to your play style and hardware.");
nk_layout_row_dynamic(ctx, 50, 1);
nk_label_wrap(ctx, "Control Changes are received only the Melody input port for the whole instrument, even for the drone controls. The separate drone midi input port is solely to play notes when not in autoplay-mode.");
nk_layout_row_dynamic(ctx, 0, 1); //Size 0 is 2 or 3 lines automatically.
nk_label_wrap(ctx, "Right click on a control element to reset it to it's default value.");
nk_layout_row_dynamic(ctx, 0, 1);
nk_label_wrap(ctx, "Use these plugins and techniques to get a better sound and more realistic playstyle.");
nk_label_wrap(ctx, " - Equalizer and/or Exiter to boost the high melody frequencies. The instrument algorithm lacks brilliance.");
nk_label_wrap(ctx, " - Use Calf Stereo Enhancer (Haas) to create a stereo spectum. Feed Melody into left input and Drone into right input.");
nk_label_wrap(ctx, " - ssr.lv2 - A sympathetic string resonator. Special kind of reverb. Very typical for stringed folk or drone instruments.");
nk_label_wrap(ctx, " - Finally a real reverb, such as the Zita one. Reduce wet signal because ssr.lv2 already adds some.");
nk_layout_row_dynamic(ctx, v_spacing/2, 1); //Empty Row
nk_layout_row_dynamic(ctx, 0, 1); //Size 0 is 2 or 3 lines automatically.
nk_label_wrap(ctx, "Copyright " LSS_AUTHOR " " LSS_YEAR " " LSS_WEBURL " " LSS_LICENSE);
nk_label_wrap(ctx, "Based on SMRGyGurdy by Nick Baley (https://github.com/nickbailey/smrgygurdy) and Artifastring by Graham Percival (https://github.com/gperciva/artifastring)");
}
nk_end(ctx);
} // End If/Else Show Manual
last_scroll = 0; //reset for next iteration of the event loop
/* Draw */
glViewport(0, 0, win_width, win_height);
glClear(GL_COLOR_BUFFER_BIT);
nk_glfw3_render(&glfw, NK_ANTI_ALIASING_ON, MAX_VERTEX_BUFFER, MAX_ELEMENT_BUFFER);
glfwSwapBuffers(glfwWindow);
}
void gui_init() {
/* Platform */
win_width = 2*frame_w + 2;
win_height = title_h + 2*frame_h + 2;
/* GLFW */
glfwSetErrorCallback(error_callback);
if (!glfwInit()) {
fprintf(stdout, "[GFLW] failed to init!\n");
exit(1);
}
//Window hints are for the NEXT window created.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
if (programState.nsm && programState.applicationWindowVisible == false) { //From the save file. if no save file it will be "true"
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
}
else {
glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE);
}
nsm_send_visibility_status();
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
glfwWindow = glfwCreateWindow(win_width, win_height, LSS_NAME " - " LSS_TAGLINE, NULL, NULL);
glfwMakeContextCurrent(glfwWindow);
/* Glew */
glewExperimental = 1;
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to setup GLEW\n");
exit(1);
}
/* create context */
ctx = nk_glfw3_init(&glfw, glfwWindow, NK_GLFW3_INSTALL_CALLBACKS);
glfwSetScrollCallback(glfwWindow, scroll_callback);
set_style(ctx, THEME_DARK);
{struct nk_font_atlas *atlas;
nk_glfw3_font_stash_begin(&glfw, &atlas);
nk_glfw3_font_stash_end(&glfw);}
}
bool gui_is_visible() {
return (bool) glfwGetWindowAttrib(glfwWindow, GLFW_VISIBLE);
}
bool gui_about_to_close() {
return (bool) glfwWindowShouldClose(glfwWindow);
}
void gui_reset_close() {
glfwSetWindowShouldClose(glfwWindow, 0);
}
void gui_shutdown() {
nk_glfw3_shutdown(&glfw);
glfwTerminate();
}
void gui_hide() {
glfwHideWindow(glfwWindow);
}
void gui_show() {
glfwShowWindow(glfwWindow);
}

37
src/gui.h

@ -0,0 +1,37 @@
/**
Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This application is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GUI_H
#define GUI_H
//GLFW and Nuklear based GUI.
#include <stdbool.h>
void gui_init(); //Call in main before event loop
void gui_process_eventloop(); //Call this from your main event loop
void gui_shutdown(); //Call this right before you quite
bool gui_about_to_close(); // Use this to break your main event loop...
void gui_reset_close(); //... or not ...
void gui_hide();
void gui_show();
bool gui_is_visible();
#endif // not defined GUI_H

251
src/include/LICENSE_jReadJWrite.html

@ -0,0 +1,251 @@
?<html>
<head>
<title>The Code Project Open License (CPOL)</title>
<Style>
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt }
H1,H2,H3,H4,H5 { color: #ff9900; font-weight: bold; }
H1 { font-size: 14pt;color:black }
H2 { font-size: 13pt; }
H3 { font-size: 12pt; }
H4 { font-size: 10pt; color: black; }
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; }
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; }
.SpacedList li { padding: 5px 0px 5px 0px;}
</style>
</head>
<body bgcolor="#FFFFFF" color=#000000>
<h1>The Code Project Open License (CPOL) 1.02</h1>
<br />
<center>
<div style="text-align: left; border: 2px solid #000000; width: 660; background-color: #FFFFD9; padding: 20px;">
<h2>Preamble</h2>
<p>
This License governs Your use of the Work. This License is intended to allow developers
to use the Source Code and Executable Files provided as part of the Work in any
application in any form.
</p>
<p>
The main points subject to the terms of the License are:</p>
<ul>
<li>Source Code and Executable Files can be used in commercial applications;</li>
<li>Source Code and Executable Files can be redistributed; and</li>
<li>Source Code can be modified to create derivative works.</li>
<li>No claim of suitability, guarantee, or any warranty whatsoever is provided. The software is
provided "as-is".</li>
<li>The Article accompanying the Work may not be distributed or republished without the
Author's consent</li>
</ul>
<p>
This License is entered between You, the individual or other entity reading or otherwise
making use of the Work licensed pursuant to this License and the individual or other
entity which offers the Work under the terms of this License ("Author").</p>
<h2>License</h2>
<p>
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CODE PROJECT OPEN
LICENSE ("LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE
LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT
LAW IS PROHIBITED.</p>
<p>
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HEREIN, YOU ACCEPT AND AGREE TO BE
BOUND BY THE TERMS OF THIS LICENSE. THE AUTHOR GRANTS YOU THE RIGHTS CONTAINED HEREIN
IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. IF YOU DO NOT
AGREE TO ACCEPT AND BE BOUND BY THE TERMS OF THIS LICENSE, YOU CANNOT MAKE ANY
USE OF THE WORK.</p>
<ol class="SpacedList">
<li><strong>Definitions.</strong>
<ol class="SpacedList" style="list-style-type: lower-alpha;">
<li><strong>"Articles"</strong> means, collectively, all articles written by Author
which describes how the Source Code and Executable Files for the Work may be used
by a user.</li>
<li><b>"Author"</b> means the individual or entity that offers the Work under the terms
of this License.<strong></strong></li>
<li><strong>"Derivative Work"</strong> means a work based upon the Work or upon the
Work and other pre-existing works.</li>
<li><b>"Executable Files"</b> refer to the executables, binary files, configuration
and any required data files included in the Work.</li>
<li>"<b>Publisher</b>" means the provider of the website, magazine, CD-ROM, DVD or other
medium from or by which the Work is obtained by You.</li>
<li><b>"Source Code"</b> refers to the collection of source code and configuration files
used to create the Executable Files.</li>
<li><b>"Standard Version"</b> refers to such a Work if it has not been modified, or
has been modified in accordance with the consent of the Author, such consent being
in the full discretion of the Author. </li>
<li><b>"Work"</b> refers to the collection of files distributed by the Publisher, including
the Source Code, Executable Files, binaries, data files, documentation, whitepapers
and the Articles. </li>
<li><b>"You"</b> is you, an individual or entity wishing to use the Work and exercise
your rights under this License.
</li>
</ol>
</li>
<li><strong>Fair Use/Fair Use Rights.</strong> Nothing in this License is intended to
reduce, limit, or restrict any rights arising from fair use, fair dealing, first
sale or other limitations on the exclusive rights of the copyright owner under copyright
law or other applicable laws.
</li>
<li><strong>License Grant.</strong> Subject to the terms and conditions of this License,
the Author hereby grants You a worldwide, royalty-free, non-exclusive, perpetual
(for the duration of the applicable copyright) license to exercise the rights in
the Work as stated below:
<ol class="SpacedList" style="list-style-type: lower-alpha;">
<li>You may use the standard version of the Source Code or Executable Files in Your
own applications. </li>
<li>You may apply bug fixes, portability fixes and other modifications obtained from
the Public Domain or from the Author. A Work modified in such a way shall still
be considered the standard version and will be subject to this License.</li>
<li>You may otherwise modify Your copy of this Work (excluding the Articles) in any
way to create a Derivative Work, provided that You insert a prominent notice in
each changed file stating how, when and where You changed that file.</li>
<li>You may distribute the standard version of the Executable Files and Source Code
or Derivative Work in aggregate with other (possibly commercial) programs as part
of a larger (possibly commercial) software distribution. </li>
<li>The Articles discussing the Work published in any form by the author may not be
distributed or republished without the Author&#39;s consent. The author retains
copyright to any such Articles. You may use the Executable Files and Source Code
pursuant to this License but you may not repost or republish or otherwise distribute
or make available the Articles, without the prior written consent of the Author.</li>
</ol>
Any subroutines or modules supplied by You and linked into the Source Code or Executable
Files of this Work shall not be considered part of this Work and will not be subject
to the terms of this License.
</li>
<li><strong>Patent License.</strong> Subject to the terms and conditions of this License,
each Author hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have made, use, import,
and otherwise transfer the Work.</li>
<li><strong>Restrictions.</strong> The license granted in Section 3 above is expressly
made subject to and limited by the following restrictions:
<ol class="SpacedList" style="list-style-type: lower-alpha;">
<li>You agree not to remove any of the original copyright, patent, trademark, and
attribution notices and associated disclaimers that may appear in the Source Code
or Executable Files. </li>
<li>You agree not to advertise or in any way imply that this Work is a product of Your
own. </li>
<li>The name of the Author may not be used to endorse or promote products derived from
the Work without the prior written consent of the Author.</li>
<li>You agree not to sell, lease, or rent any part of the Work. This does not restrict
you from including the Work or any part of the Work inside a larger software
distribution that itself is being sold. The Work by itself, though, cannot be sold,
leased or rented.</li>
<li>You may distribute the Executable Files and Source Code only under the terms of
this License, and You must include a copy of, or the Uniform Resource Identifier
for, this License with every copy of the Executable Files or Source Code You distribute
and ensure that anyone receiving such Executable Files and Source Code agrees that
the terms of this License apply to such Executable Files and/or Source Code. You
may not offer or impose any terms on the Work that alter or restrict the terms of
this License or the recipients&#39; exercise of the rights granted hereunder. You
may not sublicense the Work. You must keep intact all notices that refer to this
License and to the disclaimer of warranties. You may not distribute the Executable
Files or Source Code with any technological measures that control access or use
of the Work in a manner inconsistent with the terms of this License. </li>
<li>You agree not to use the Work for illegal, immoral or improper purposes, or on pages
containing illegal, immoral or improper material. The Work is subject to applicable
export laws. You agree to comply with all such laws and regulations that may apply
to the Work after Your receipt of the Work.
</li>
</ol>
</li>
<li><strong>Representations, Warranties and Disclaimer.</strong> THIS WORK IS PROVIDED
"AS IS", "WHERE IS" AND "AS AVAILABLE", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES
OR CONDITIONS OR GUARANTEES. YOU, THE USER, ASSUME ALL RISK IN ITS USE, INCLUDING
COPYRIGHT INFRINGEMENT, PATENT INFRINGEMENT, SUITABILITY, ETC. AUTHOR EXPRESSLY
DISCLAIMS ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS, INCLUDING
WITHOUT LIMITATION, WARRANTIES OR CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY
OR FITNESS FOR A PARTICULAR PURPOSE, OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT,
OR THAT THE WORK (OR ANY PORTION THEREOF) IS CORRECT, USEFUL, BUG-FREE OR FREE OF
VIRUSES. YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE
WORKS.
</li>
<li><b>Indemnity. </b>You agree to defend, indemnify and hold harmless the Author and
the Publisher from and against any claims, suits, losses, damages, liabilities,
costs, and expenses (including reasonable legal or attorneysโ€™ fees) resulting from
or relating to any use of the Work by You.
</li>
<li><strong>Limitation on Liability.</strong> EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL THE AUTHOR OR THE PUBLISHER BE LIABLE TO YOU ON ANY LEGAL
THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK OR OTHERWISE, EVEN IF THE AUTHOR
OR THE PUBLISHER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
</li>
<li><strong>Termination.</strong>
<ol style="list-style-type: lower-alpha;">
<li>This License and the rights granted hereunder will terminate automatically upon
any breach by You of any term of this License. Individuals or entities who have
received Derivative Works from You under this License, however, will not have their
licenses terminated provided such individuals or entities remain in full compliance
with those licenses. Sections 1, 2, 6, 7, 8, 9, 10 and 11 will survive any termination
of this License. </li>
<li>If You bring a copyright, trademark, patent or any other infringement claim against
any contributor over infringements You claim are made by the Work, your License
from such contributor to the Work ends automatically.</li>
<li>Subject to the above terms and conditions, this License is perpetual (for the duration
of the applicable copyright in the Work). Notwithstanding the above, the Author
reserves the right to release the Work under different license terms or to stop
distributing the Work at any time; provided, however that any such election will
not serve to withdraw this License (or any other license that has been, or is required
to be, granted under the terms of this License), and this License will continue
in full force and effect unless terminated as stated above.
</li>
</ol>
</li>
<li><strong>Publisher</strong>. The parties hereby confirm that the Publisher shall
not, under any circumstances, be responsible for and shall not have any liability
in respect of the subject matter of this License. The Publisher makes no warranty
whatsoever in connection with the Work and shall not be liable to You or any party
on any legal theory for any damages whatsoever, including without limitation any
general, special, incidental or consequential damages arising in connection to this
license. The Publisher reserves the right to cease making the Work available to
You at any time without notice</li>
<li><strong>Miscellaneous</strong>
<ol class="SpacedList" style="list-style-type: lower-alpha;">
<li>This License shall be governed by the laws of the location of the head office of
the Author or if the Author is an individual, the laws of location of the principal
place of residence of the Author.</li>
<li>If any provision of this License is invalid or unenforceable under applicable law,
it shall not affect the validity or enforceability of the remainder of the terms
of this License, and without further action by the parties to this License, such
provision shall be reformed to the minimum extent necessary to make such provision
valid and enforceable. </li>
<li>No term or provision of this License shall be deemed waived and no breach consented
to unless such waiver or consent shall be in writing and signed by the party to
be charged with such waiver or consent. </li>
<li>This License constitutes the entire agreement between the parties with respect to
the Work licensed herein. There are no understandings, agreements or representations
with respect to the Work not specified herein. The Author shall not be bound by
any additional provisions that may appear in any communication from You. This License
may not be modified without the mutual written agreement of the Author and You.
</li>
</ol>
</li>
</ol>
</div>
</center>
</body>
</html>

2
src/include/_what_is_this

@ -0,0 +1,2 @@
In /include/ we store files that have been copied over from external projects,
such as header-only libraries. All files are unmodified.

789
src/include/jRead.c

@ -0,0 +1,789 @@
//Author: Tony Wilk
//Source: https://www.codeproject.com/Articles/885389/jRead-An-in-place-JSON-Element-Reader
//LICENSE: The Code Project Open License (CPOL) 1.02
//see provided file ICENSE_jReadJWrite.html or https://www.codeproject.com/info/cpol10.aspx
// jRead.cpp
// Version 1v6
//
// jRead - an in-place JSON element reader
// =======================================
//
// Instead of parsing JSON into some structure, this maintains the input JSON as unaltered text
// and allows queries to be made on it directly.
//
// e.g. with the simple JSON:
// {
// "astring":"This is a string",
// "anumber":42,
// "myarray":[ "one", 2, {"description":"element 3"}, null ],
// "yesno":true,
// "HowMany":"1234",
// "foo":null
// }
//
// calling:
// jRead( json, "{'myarray'[0", &jElem );
//
// would return:
// jElem.dataType= JREAD_STRING;
// jElem.elements= 1
// jElem.bytelen= 3
// jElem.pValue -> "one"
//
// or you could call the helper functions:
// jRead_string( json, "{'astring'", destString, MAXLEN );
// jRead_int( json, "{'anumber'", &myint );
// jRead_string( json, "{'myarray'[3", destString, MAXLEN );
// etc.
//
// Note that the helper functions do type coersion and always return a value
// (on error an empty string is returned or value of zero etc.)
//
// The query string simply defines the route to the required data item
// as an arbitary list of object or array specifiers:
// object element= "{'keyname'"
// array element= "[INDEX"
//
// The jRead() function fills a jReadElement structure to describe the located element
// this can be used to locate any element, not just terminal values
// e.g.
// jRead( json, "{'myarray'", &jElem );
//
// in this case jElem would contain:
// jElem.dataType= JSON_ARRAY
// jElem.elements= 4
// jElem.bytelen= 46
// jElem.pValue -> [ "one", 2, {"descripton":"element 3"}, null ] ...
//
// allowing jRead to be called again on the array:
// e.g.
// jRead( jElem.pValue, "[3", &jElem ); // get 4th element - the null value
//
// .oO! see main.c runExamples() for a whole bunch of examples !Oo.
// -------------------------------------------------------
//
// Note that jRead never modifies the source JSON and does not allocate any memory.
// i.e. elements are returned as pointer and length into the source text.
//
// Functions
// =========
// Main JSON reader:
// int jRead( char * JsonSource, char *query, jReadElement &pResult );
//
// Extended function using query parameters for indexing:
// int jRead( char * JsonSource, char *query, jReadElement &pResult, int *queryParams );
//
// Function to step thru JSON arrays instead of indexing:
// char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult );
//
// Optional Helper functions:
// long jRead_long( char *pJson, char *pQuery );
// int jRead_int( char *pJson, char *pQuery );
// double jRead_double( char *pJson, char *pQuery );
// int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen );
//
// Optional String output Functions
// char * jReadTypeToString( int dataType ); // string describes dataType
// char * jReadErrorToString( int error ); // string descibes error code
//
// *NEW* in 1v2
// - "{NUMBER" returns the "key" value at that index within an object
// - jReadParam() adds queryParams which can be used as indexes into arrays (or into
// objects to return key values) by specifying '*' in the query string
// e.g. jReadParam( pJson, "[*", &result, &index )
// *NEW in 1v4
// - fixed a couple of error return values
// - added #define JREAD_DOUBLE_QUOTE_IN_QUERY
// *NEW* in 1v5 (11mar2015)
// - fixed null ptr if '[*' used when null param passed
// *NEW* in 1v6 (24sep2016)
// - fixed handling of empty arrays and objects
//
// TonyWilk, 24sep2016
// mail at tonywilk . co .uk
//
// License: "Free as in You Owe Me a Beer"
// - actually, since some people really worry about licenses, you are free to apply
// whatever licence you want.
//
// Note: jRead_atol() and jRead_atof() are modified from original routines
// fast_atol() and fast_atof() 09-May-2009 Tom Van Baak (tvb) www.LeapSecond.com
//
// You may want to replace the use of jRead_atol() and jRead_atof() in helper functions
// of your own. Especially note that my atof does not handle exponents.
//
//
#include <stdio.h>
#include "jRead.h"
// By default we use single quote in query strings so it's a lot easier
// to type in code i.e. "{'key'" instead of "{\"key\""
//
#ifdef JREAD_DOUBLE_QUOTE_IN_QUERY
#define QUERY_QUOTE '\"'
#else
#define QUERY_QUOTE '\''
#endif
//------------------------------------------------------
// Internal Functions
char * jReadSkipWhitespace( char *sp );
char * jReadFindTok( char *sp, int *tokType );
char * jReadGetString( char *pJson, struct jReadElement *pElem, char quote );
int jReadTextLen( char *pJson );
int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 );
char * jReadCountObject( char *pJson, struct jReadElement *pResult, int keyIndex );
char * jReadCountArray( char *pJson, struct jReadElement *pResult );
char * jRead_atoi( char *p, unsigned int *result );
char * jRead_atol( char *p, long *result );
char * jRead_atof( char *p, double *result);
//=======================================================
char *jReadSkipWhitespace( char *sp )
{
while( (*sp != '\0') && (*sp <= ' ') )
sp++;
return sp;
};
// Find start of a token
// - returns pointer to start of next token or element
// returns type via tokType
//
char *jReadFindTok( char *sp, int *tokType )
{
char c;
sp= jReadSkipWhitespace(sp);
c= *sp;
if( c == '\0' ) *tokType= JREAD_EOL;
else if((c == '"') || (c == QUERY_QUOTE))*tokType= JREAD_STRING;
else if((c >= '0') && (c <= '9')) *tokType= JREAD_NUMBER;
else if( c == '-') *tokType= JREAD_NUMBER;
else if( c == '{') *tokType= JREAD_OBJECT;
else if( c == '[') *tokType= JREAD_ARRAY;
else if( c == '}') *tokType= JREAD_EOBJECT;
else if( c == ']') *tokType= JREAD_EARRAY;
else if((c == 't') || (c == 'f')) *tokType= JREAD_BOOL;
else if( c == 'n') *tokType= JREAD_NULL;
else if( c == ':') *tokType= JREAD_COLON;
else if( c == ',') *tokType= JREAD_COMMA;
else if( c == '*') *tokType= JREAD_QPARAM;
else *tokType= JREAD_ERROR;
return sp;
};
// jReadGetString
// - assumes next element is "string" which may include "\" sequences
// - returns pointer to -------------^
// - pElem contains result ( JREAD_STRING, length, pointer to string)
// - pass quote = '"' for Json, quote = '\'' for query scanning
//
// returns: pointer into pJson after the string (char after the " terminator)
// pElem contains pointer and length of string (or dataType=JREAD_ERROR)
//
char * jReadGetString( char *pJson, struct jReadElement *pElem, char quote )
{
short skipch;
pElem->dataType= JREAD_ERROR;
pElem->elements= 1;
pElem->bytelen= 0;
pJson= jReadSkipWhitespace( pJson );
if( *pJson == quote )
{
pJson++;
pElem->pValue= pJson; // -> start of actual string
pElem->bytelen=0;
skipch= 0;
while( *pJson != '\0' )
{
if( skipch )
skipch= 0;
else if( *pJson == '\\' ) // "\" sequence
skipch= 1;
else if( *pJson == quote )
{
pElem->dataType= JREAD_STRING;
pJson++;
break;
}
pElem->bytelen++;
pJson++;
};
};
return pJson;
};
// jReadTextLen
// - used to identify length of element text
// - returns no. of chars from pJson upto a terminator
// - terminators: ' ' , } ]
//
int jReadTextLen( char *pJson )
{
int len= 0;
while( (*pJson > ' ' ) && // any ctrl char incl '\0'
(*pJson != ',' ) &&
(*pJson != '}' ) &&
(*pJson != ']' ) )
{
len++;
pJson++;
}
return len;
}
// compare two json elements
// returns: 0 if they are identical strings, else 1
//
int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 )
{
int i;
if( (j1->dataType != JREAD_STRING) ||
(j2->dataType != JREAD_STRING) ||
(j1->bytelen != j2->bytelen ) )
return 1;
for( i=0; i< j1->bytelen; i++ )
if( ((char *)(j1->pValue))[i] != ((char *)(j2->pValue))[i] )
return 1;
return 0;
}
// read unsigned int from string
char * jRead_atoi( char *p, unsigned int *result )
{
unsigned int x = 0;
while (*p >= '0' && *p <= '9') {
x = (x*10) + (*p - '0');
++p;
}
*result= x;
return p;
}
// read long int from string
//
char * jRead_atol( char *p, long *result )
{
long x = 0;
int neg = 0;
if (*p == '-') {
neg = 1;
++p;
}
while (*p >= '0' && *p <= '9') {
x = (x*10) + (*p - '0');
++p;
}
if (neg) {
x = -x;
}
*result= x;
return p;
}
#define valid_digit(c) ((c) >= '0' && (c) <= '9')
// read double from string
// *CAUTION* does not handle exponents
//
//
char * jRead_atof( char *p, double *result)
{
double sign, value;
// Get sign, if any.
sign = 1.0;
if (*p == '-') {
sign = -1.0;
p += 1;
} else if (*p == '+') {
p += 1;
}
// Get digits before decimal point or exponent, if any.
for (value = 0.0; valid_digit(*p); p += 1) {
value = value * 10.0 + (*p - '0');
}
// Get digits after decimal point, if any.
if (*p == '.') {
double pow10 = 10.0;
p += 1;
while (valid_digit(*p)) {
value += (*p - '0') / pow10;
pow10 *= 10.0;
p += 1;
}
}
*result= sign * value;
return p;
}
// read element into destination buffer and add '\0' terminator
// - always copies element irrespective of dataType (unless it's an error)
// - destBuffer is always '\0'-terminated (even on zero lenght returns)
// - returns pointer to destBuffer
//
char *jRead_strcpy( char *destBuffer, int destLength, struct jReadElement *pElement )
{
int i;
int len= pElement->bytelen;
char *pdest= destBuffer;
char *psrc= (char *)pElement->pValue;
if( pElement->error == 0 )
{
if( len >= destLength )
len= destLength;
for( i=0; i<destLength; i++ )
*pdest++= *psrc++;
}
*pdest= '\0';
return destBuffer;
}
// jReadCountObject
// - used when query ends at an object, we want to return the object length
// - on entry pJson -> "{... "
// - used to skip unwanted values which are objects
// - keyIndex normally passed as -1 unless we're looking for the nth "key" value
// in which case keyIndex is the index of the key we want
//
char * jReadCountObject( char *pJson, struct jReadElement *pResult, int keyIndex )
{
struct jReadElement jElement;
int jTok;
char *sp;
pResult->dataType= JREAD_OBJECT;
pResult->error= 0;
pResult->elements= 0;
pResult->pValue= pJson;
sp= jReadFindTok( pJson+1, &jTok ); // check for empty object
if( jTok == JREAD_EOBJECT )
{
pJson= sp+1;
}else
{
while( 1 )