57 changed files with 60709 additions and 1 deletions
@ -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. |
|||
|
@ -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$ |
@ -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') |
@ -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); |
|||
} |
@ -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
|
@ -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) |
@ -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); |
|||
} |
@ -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
|
@ -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'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' 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> |
@ -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. |
@ -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 ) |
|||