Browse Source

Code dump

master
Nils 2 years ago
commit
4136a6aed7
  1. 4
      .gitignore
  2. 251
      LICENSE_jReadJWrite.html
  3. 10
      Makefile
  4. 94
      camera.c
  5. 18
      camera.h
  6. 52
      constants.h
  7. 144
      draw.c
  8. 36
      draw.h
  9. 99
      draw_activity.c
  10. 6
      draw_activity.h
  11. 115
      draw_meterbridge.c
  12. 6
      draw_meterbridge.h
  13. 121
      draw_port_grids.c
  14. 6
      draw_port_grids.h
  15. 107
      draw_xpitches_yports.c
  16. 6
      draw_xpitches_yports.h
  17. 103
      draw_xports_ypitches.c
  18. 6
      draw_xports_ypitches.h
  19. 181
      drawhelper.c
  20. 14
      drawhelper.h
  21. 100
      effect_starfield.c
  22. 6
      effect_starfield.h
  23. BIN
      font.ttf
  24. 40
      glsl330/bloom.fs
  25. 393
      gui.c
  26. 7
      gui.h
  27. 659
      gui_file_dialog.h
  28. 789
      jRead.c
  29. 134
      jRead.h
  30. 566
      jWrite.c
  31. 218
      jWrite.h
  32. 266
      jackclient.c
  33. 19
      jackclient.h
  34. 405
      main.c
  35. 689
      nsm.h
  36. 248
      programstate.c
  37. 79
      programstate.h
  38. 3734
      raygui.h

4
.gitignore

@ -0,0 +1,4 @@
*.out
*.gch
*.json
tgvs

251
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>

10
Makefile

@ -0,0 +1,10 @@
.PHONY: clean
all:
gcc -o tgvs main.c draw.c gui.c jackclient.c drawhelper.c draw_xpitches_yports.c draw_xports_ypitches.c draw_port_grids.c draw_meterbridge.c draw_activity.c programstate.c jRead.c jWrite.c camera.c effect_starfield.c -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 -ljack -llo
clean:
rm tgvs
rm *.gch

94
camera.c

@ -0,0 +1,94 @@
//Standard lib
//Third party
#include "raylib.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "camera.h"
extern ProgramState programState;
// https://www.raylib.com/examples/web/core/loader.html?name=core_2d_camera
//Camera controls, such as zoom and reset, are in gui.c
static int screenWidth;
static int screenHeight;
void cameraMoveUpdate() {
programState.camera.offset = (Vector2){ screenWidth/2+programState.cameraCenterOnX, screenHeight/2+programState.cameraCenterOnY };
}
void cameraLeft() {
programState.cameraCenterOnX -= 10;
cameraMoveUpdate();
}
void cameraRight() {
programState.cameraCenterOnX += 10;
cameraMoveUpdate();
}
void cameraUp() {
programState.cameraCenterOnY -= 10;
cameraMoveUpdate();
}
void cameraDown() {
programState.cameraCenterOnY += 10;
cameraMoveUpdate();
}
void cameraMaybeZoom() {
//float rememberZoom = programState.camera.zoom;
if (!programState.lockInput) {
programState.camera.zoom += ((float)GetMouseWheelMove()*0.05f);
if (programState.camera.zoom < 0)
programState.camera.zoom = 0.0f;
//if (rememberZoom != programState.camera.zoom) {}
}
}
void cameraReset() {
//Only called through user action. Camera startup values are either loaded from file or set in programstate.c
programState.camera.zoom = 1.0f;
programState.camera.rotation = 0.0f;
programState.cameraCenterOnX = 0;
programState.cameraCenterOnY = 0;
cameraMoveUpdate();
}
void cameraNormalizeRotation() {
if (programState.camera.rotation > 359) programState.camera.rotation -= 360;
else if (programState.camera.rotation < 0) programState.camera.rotation += 360;
}
void cameraRotateLeft() {
programState.camera.rotation--;
cameraNormalizeRotation();
}
void cameraRotateRight() {
programState.camera.rotation++;
cameraNormalizeRotation();
}
void cameraRotate180() {
programState.camera.rotation += 180.0f;
cameraNormalizeRotation();
}
void cameraFlip() {
programState.camera.zoom *= -1.0f; //this is the same as rotation by 180, and not mirror
//programState.camera.rotation += 180.0f;
}
void mainLoop_cameraReposition() {
if (programState.guiRedrawNeeded) {
screenWidth = GetScreenWidth(); //Changes on resize
screenHeight = GetScreenHeight(); //Changes on resize
programState.camera.target = (Vector2){ screenWidth/2, screenHeight/2 };
programState.camera.offset = (Vector2){ screenWidth/2+programState.cameraCenterOnX, screenHeight/2+programState.cameraCenterOnY };
}
}

18
camera.h

@ -0,0 +1,18 @@
#ifndef CAMERA_H
#define CAMERA_H
void cameraReset();
void cameraMaybeZoom();
void mainLoop_cameraReposition();
void cameraRotateLeft();
void cameraRotateRight();
void cameraRotate180();
//void cameraFlip();
void cameraLeft();
void cameraRight();
void cameraUp();
void cameraDown();
#endif // not defined CAMERA_H

52
constants.h

@ -0,0 +1,52 @@
#ifndef CONSTANTS_H
#define CONSTANTS_H
#include "raylib.h"
#ifndef MAX
#define MAX(a,b) ( (a) < (b) ? (b) : (a) )
#endif
#define VIS_PORTS 16 // Program is able to handle <100 ports. But we never intend anything different than 16 to be honest.
#define VIS_PORTS_SQRT 4 //please choose by hand. If not possibe choose the next higher. e.g. 13 ports -> 4²
//Pixel values
#define NOTE_SMALL_SIDE 14
#define NOTE_LONG_SIDE 20
#define NOTE_BORDER 4 //halved for left/right padding. the NOTE sides already include the border.
#define NOTE_GAP 3
//For GUI
#define HEIGHT 25
#define SPACING 35
typedef struct { // C-N00b: using typedef lets us type Note nt; or (Note){...} instead of struct Note.
int active;
int port;
int x;
int y;
int pitch;
int velocity; //converted to color, size etc. by raylib
float countdown; //0.0f is not existent, 1 is permanently visible, everything between is the fade out process with auto-decrement. To start the timer set to 0.999 or so
} Note;
enum {
//ports are 0-15
background=VIS_PORTS+0,
backgroundLight=VIS_PORTS+1,
backgroundLighter=VIS_PORTS+2,
};
enum {
//Layers in order.
layerIndex_notes,
layerIndex_effects,
layerIndex_sprites,
layerIndex_count, //last one
};
#endif // not defined CONSTANTS_H

144
draw.c

@ -0,0 +1,144 @@
//Standard lib
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//Third party
#include "raylib.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "drawhelper.h"
#include "draw.h"
//Drawing Routines
#include "draw_xpitches_yports.h"
#include "draw_xports_ypitches.h"
#include "draw_port_grids.h"
#include "draw_meterbridge.h"
#include "draw_activity.h"
//Effect Routines
#include "effect_starfield.h"
#define GLSL_VERSION 330
extern ProgramState programState;
Effect effects[NR_OF_EFFECTS] = {
{ false, "Starfield", false, 1.0f, 1, 2, effect_starfield},
{ false, "Snow Fall", false, 1.0f, 1, 3, effect_starfield},
{ false, "Falling Leafs", false, 1.0f, 1, 4, effect_starfield},
{ false, "Rain", false, 1.0f, 1, 5, effect_starfield},
{ false, "Embers", false, 1.0f, 1, 5, effect_starfield},
};
//This needs to be manually synced to gui.c combox
//This order is permanent. No re-ordering after the first release!
//The save file and the GUI depend on it.
void (*drawFunctions[5])() = {
draw_xpitches_yports,
draw_xports_ypitches,
draw_port_grids,
draw_meterbridge,
draw_activity,
};
static Texture2D backgroundTexture;
static RenderTexture2D renderTarget;
static Shader shader;
void loadBackgroundImage(char * path, bool newImage) {
if (programState.nsm) {
char * temporaryAbsolutePath = malloc (sizeof(char) * 4096); //4096 is the max path length in linux
snprintf(temporaryAbsolutePath, (sizeof(char) * 4096), "%s/%s", programState.nsmDirectory, "background.png");
if (newImage) { //User clicked on the GUI button and chose a file
unlink(temporaryAbsolutePath);
symlink(path, temporaryAbsolutePath); //source-file, link
}
programState.pathBackgroundImage = "background.png";
backgroundTexture = LoadTexture(temporaryAbsolutePath);
free(temporaryAbsolutePath);
}
else {
programState.pathBackgroundImage = path;
backgroundTexture = LoadTexture(programState.pathBackgroundImage);
}
}
void init_draw() {
renderTarget = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
shader = LoadShader(0, FormatText("glsl%i/bloom.fs", GLSL_VERSION)); //vs file, fs file
}
void mainLoop_draw(Note *allGUINotes) {
if (IsWindowResized()) {
//printf("Window resized to: %i x %i\n", GetScreenWidth(), GetScreenHeight());
programState.guiRedrawNeeded = true;
}
//Drawing Layers from bottom to top:
//Background Image (or plain color)
//Background Sprites (internal Z-Order = Y)
//Background Effects (e.g. Starfield)
//Main Visualization (Notes)
//Foreground Sprites (internal Z-Order = Y)
//Foreground Effects (e.g. Rain)
if (programState.showBackgroundImageLayer) {
//void DrawTexture(Texture2D texture, int posX, int posY, Color tint);
//Draw centered but not scaled.
DrawTexture(backgroundTexture, GetScreenWidth()/2-backgroundTexture.width/2, GetScreenHeight()/2-backgroundTexture.height/2, WHITE);
}
//BeginTextureMode(renderTarget); // Enable drawing to texture
//ClearBackground(BLACK); //Texture background
//Background Effects Pass
if (programState.showEffectLayer) {
for (int i=0; i<NR_OF_EFFECTS; i++) {
if (effects[i].enabled && !effects[i].foreground) {
effects[i].function(programState.guiRedrawNeeded, effects[i].opacity, effects[i].speed, effects[i].size);
}
}
}
//EndTextureMode(); // End drawing to texture (now we have a texture available for next passes)
//BeginShaderMode(shader);
// DrawTextureRec(renderTarget.texture, (Rectangle){ 0, 0, renderTarget.texture.width, -renderTarget.texture.height }, (Vector2){ 0, 0 }, WHITE);
//EndShaderMode();
if (programState.showDrawMode) {
BeginMode2D(programState.camera);
drawFunctions[programState.drawMode](allGUINotes, programState.guiRedrawNeeded); //One Mode at a time
EndMode2D();
}
//Foreground Effects Pass
if (programState.showEffectLayer) {
for (int i=0; i<NR_OF_EFFECTS; i++) {
if (effects[i].enabled && effects[i].foreground) {
effects[i].function(programState.guiRedrawNeeded, effects[i].opacity, effects[i].speed, effects[i].size);
}
}
}
//DrawCircle(GetScreenWidth()/2, GetScreenHeight()/2, 10, RAYWHITE); //show center point
programState.guiRedrawNeeded = false;
}

36
draw.h

@ -0,0 +1,36 @@
#ifndef DRAW_H
#define DRAW_H
#define NR_OF_EFFECTS 5
typedef void (*effectFunction)(bool guiRedrawNeeded, float opacity, int speedModifier, int size);
typedef struct {
bool enabled;
char * name;
bool foreground;
float opacity;
int speed;
int size; // effect depends on the drawing mode. This could be everything
effectFunction function;
} Effect;
typedef struct {
bool enabled;
char * path; //NSM = subpath, Standalone: Absolute Path
bool foreground;
float opacity;
Color tint;
int x;
int y;
float rotation;
float zoom;
//If you want animation or different versions you have to put them all in one image and then devide into tiles:
int tileSize; //from the whole picture, how big is the square that should be displayed.
int tileIndex; //When the image is divided through size which of the resulting tiles should be displayed. 1D, starting from 0, max is 127
} Sprite;
void mainLoop_draw(Note *allGUINotes);
void loadBackgroundImage(char * path, bool newImage);
void init_draw();
#endif // not defined DRAW_H

99
draw_activity.c

@ -0,0 +1,99 @@
//Standard lib
#include <stdio.h>
#include <math.h>
//Third party
#include "raylib.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "drawhelper.h"
#include "draw_activity.h"
static int screenWidth;
static int screenHeight;
static int radius = 80;
const float distanceFactor = 2.5;
static int xOffset;
static int yOffset;
extern ProgramState programState;
static float fadeout[VIS_PORTS];
static void reposition(Note *allGUINotes) {
//This is both init and update. We only call it internally, the name of the function doesn't matter.
//Once guaranteed called on program start and then each window resize
screenWidth = GetScreenWidth();
screenHeight = GetScreenHeight();
xOffset = (int)(screenWidth/2) - (VIS_PORTS_SQRT/2)*radius*distanceFactor;
yOffset = (int)(screenHeight/2) - (VIS_PORTS_SQRT/2)*radius*distanceFactor;
programState.modeDescription = "Each port is one polygon.\nNumber of sites equals active notes.\nThere is no time-dimension.";
}
void drawShape(Vector2 center, int port, float rotation) {
//DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color);
//float fadeValue = pow(sinf(rotation / 180 * PI), 2); //sine wants radians, not degree
//float fadeValue = (float)(GetRandomValue(1,10)-5.0f) / 100.0f + 1.0f; //sparkle
//Color color = Fade(programState.colors[port], fadeValue);
Color border = Fade(BLACK, fadeout[port]);
Color color = Fade(programState.colors[port], fadeout[port]);
if (programState.portActivity[port] < 3) {
//DrawCircleV(Vector2 center, float radius, Color color);
DrawCircleV(center, radius*0.7+NOTE_BORDER, border);
DrawCircleV(center, radius*0.7, color);
}
else {
DrawPoly(center, programState.portActivity[port], radius+NOTE_BORDER*2, rotation, border);
DrawPoly(center, programState.portActivity[port], radius, rotation, color);
}
}
void draw_activity(Note *allGUINotes, int redrawNeeded) {
float ft = GetFrameTime();
int bpmFactor = programState.bpm/40 * programState.bpm/20; //exponential curve. The higher bpm the faster we get. Below 90 it should feel really slow
float rotation = fmod(GetTime() * bpmFactor * 5, 360.0f);
if (redrawNeeded) {
reposition(allGUINotes);
}
for (int port=0; port<VIS_PORTS; port++) {
int portRow = port / VIS_PORTS_SQRT;
int portColumn = port % VIS_PORTS_SQRT;
int blockY = portRow * radius * distanceFactor;
int blockX = portColumn * radius * distanceFactor;
Vector2 center = (Vector2){ xOffset + blockX + radius, yOffset + blockY + radius };
if (programState.showPortBackground && portShouldDraw(port)) {
DrawCircleV(center, radius-NOTE_BORDER, programState.colors[backgroundLight]);
}
if (programState.portActivity[port] > 0) {
//printf("port %i notes %i\n", port, programState.portActivity[port]);
fadeout[port] = 1.0f;
drawShape(center, port, rotation);
}
else if (fadeout[port] > 0.0f) {
if (fadeout[port] == 1.0f) { fadeout[port] = 0.999; } //begin countdown
fadeout[port] = calculateCountdown(fadeout[port], ft);
drawShape(center, port, rotation);
}
if (programState.showConnectedPortnames) {
drawJackPortName(programState.connectedPortNames[port], xOffset+blockX, yOffset+blockY, 18, RAYWHITE, false); //text, x, y, int fontsize, color, vertical
}
}
}

6
draw_activity.h

@ -0,0 +1,6 @@
#ifndef DRAW_ACTIVITY_H
#define DRAW_ACTIVITY_H
void draw_activity(Note *allGUINotes, int redrawNeeded);
#endif // not defined DRAW_ACTIVITY_H

115
draw_meterbridge.c

@ -0,0 +1,115 @@
//Standard lib
#include <stdio.h>
//Third party
#include "raylib.h"
#include "raygui.h" // Required for GUI controls
//Our own files
#include "constants.h"
#include "programstate.h"
#include "drawhelper.h"
#include "draw_meterbridge.h"
extern ProgramState programState;
static Rectangle backgroundRect;
static bool guiGroupEditMode= false;
static int screenHeight;
static int bottom;
static void reposition(Note *allGUINotes) {
//This is both init and update. We only call it internally, the name of the function doesn't matter.
//Once guaranteed called on program start and then each window resize
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
int xOffset = (int)(screenWidth/2) - (int)(60*(NOTE_LONG_SIDE+NOTE_GAP)); //we want all tracks around the center.
int yOffset = (int)(screenHeight/2) - (int)(NOTE_SMALL_SIDE+NOTE_GAP)*2; //we want all tracks below the center.
programState.modeDescription = "All port are in one horizontal row.\
\nPitches are left to right, low to high.\
\n\
\nVelocity 'shoots' notes upwards, then they fall back\
\ninfluenced by the fade-out setting.\
\n\
\nYou can set the a 'grouping' parameter for this mode\
\nwhich combines several pitches into a single indicator.\
\nGrouping uses the pitch marker pitch as baseline.\
\nThink of it like the tonic or root note.\
\n\
\n\
\nNotes are only lit up when they are played.\
\nThere is no time-dimension.";
int rootNote = programState.pitchMarkerValue % 12; //factor out the octave. used to start groups on the pitchmarker as root note of the scale
int xpos = xOffset;
int pitchToTheCenter = 64 - (128/programState.meterbridge_grouping/2);
for (int port=0; port<VIS_PORTS; port++) {
for (int midiPitch=0; midiPitch<128; midiPitch++) { //We prepare the full range of pitches, but the main loop will only activate the current pitch range.
int adjustedNotePitch = (int)((midiPitch-rootNote)/programState.meterbridge_grouping) + pitchToTheCenter;
xpos = xOffset + adjustedNotePitch*NOTE_LONG_SIDE + adjustedNotePitch*NOTE_GAP/2;
allGUINotes[port*128 + midiPitch].x = xpos;
allGUINotes[port*128 + midiPitch].y = yOffset + NOTE_SMALL_SIDE + NOTE_GAP*2;
}
}
backgroundRect.x = allGUINotes[programState.pitchMin].x; //includes offset
backgroundRect.y = (float)(yOffset + NOTE_SMALL_SIDE + NOTE_GAP*2 - NOTE_GAP);
backgroundRect.width = allGUINotes[programState.pitchMax].x - allGUINotes[programState.pitchMin].x;
backgroundRect.height = (float)NOTE_SMALL_SIDE;
}
static void drawBackground() {
if (programState.showPortBackground) {
DrawRectangleRec(backgroundRect, programState.colors[backgroundLight]);
}
}
void draw_meterbridge(Note *allGUINotes, int redrawNeeded) {
Note * nt;
float ft = GetFrameTime();
if (redrawNeeded) {
reposition(allGUINotes);
}
//Z-Order is created by call-order. First is bottom.
drawBackground();
if (programState.showPitchMarker) {
nt = &allGUINotes[programState.pitchMarkerValue];
Rectangle rec = (Rectangle){nt->x, nt->y+NOTE_SMALL_SIDE, NOTE_LONG_SIDE-NOTE_BORDER, NOTE_SMALL_SIDE-NOTE_BORDER}; //TODO: Ha! :) This falls down as well, together with the note.
DrawRectangleRounded(rec, 0.5, 8, programState.colors[backgroundLighter]);
}
//Debug: Show 0 and 127
//DrawLine(allGUINotes[0].x, 0, allGUINotes[0].x, GetScreenHeight(), RAYWHITE); //(int startPosX, int startPosY, int endPosX, int endPosY, Color color);
//DrawLine(allGUINotes[127].x, 0, allGUINotes[127].x, GetScreenHeight(), RAYWHITE); //(int startPosX, int startPosY, int endPosX, int endPosY, Color color);
for (int port=0; port<VIS_PORTS; port++) {
for (int midiPitch=programState.pitchMin; midiPitch<=programState.pitchMax; midiPitch++) {
nt = &allGUINotes[port*128 + midiPitch];
if (nt->active) {
nt->y = backgroundRect.y - 2*(nt->countdown * nt->velocity); //Shift the notes upwards. Don't use -= or +=, that is exponential modifications
reduceCountdown(nt, ft/2); //handles 0.0 and 1.0 as special cases
drawNoteRect(nt, 1, 0); //note, rotated, square. Will not draw if not active/countdown == 0
}
}
}
//Our own GUI section
if (programState.guiVisible) {
EndMode2D(); //We are already in camera mode because this is a draw_ function. GUI is not under camera control.
int row = 0;
screenHeight = GetScreenHeight(); //Changes on resize
bottom = screenHeight - 2*SPACING;
int remember = programState.meterbridge_grouping;
GuiLabel((Rectangle){ 34, bottom-row*SPACING, 105, HEIGHT }, "Meterbridge Note Grouping"); // void GuiLabel(Rectangle bounds, const char *text);
if (GuiSpinner((Rectangle){ 250, bottom-row*SPACING, 80, HEIGHT }, "", &programState.meterbridge_grouping, 0, 127, guiGroupEditMode)) guiGroupEditMode = !guiGroupEditMode; // bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode)
if (programState.meterbridge_grouping > 128) {programState.meterbridge_grouping=128;}
if (programState.meterbridge_grouping < 1) {programState.meterbridge_grouping=1;}
if (remember != programState.meterbridge_grouping) { reposition(allGUINotes); } //we cannot set guiRedrawNeeded to true here because parent draw() will reset it after we return
row++;
BeginMode2D(programState.camera); //Switch back to previously active camera mode
}
}

6
draw_meterbridge.h

@ -0,0 +1,6 @@
#ifndef DRAW_METERBRIDGE_H
#define DRAW_METERBRIDGE_H
void draw_meterbridge(Note *allGUINotes, int redrawNeeded);
#endif // not defined DRAW_METERBRIDGE_H

121
draw_port_grids.c

@ -0,0 +1,121 @@
//Standard lib
#include <stdio.h>
//Third party
#include "raylib.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "drawhelper.h"
#include "draw_port_grids.h"
/* Each port is a grid square, ordered in rows, like text
* Pitches are put on a 12x12 square for octaves.
* The screen does not move or scroll. Notes fade out and make room for the next note-on.
*/
extern ProgramState programState;
static Rectangle backgroundRects[VIS_PORTS]; // 4 floats: x, y, w, h
static void reposition(Note *allGUINotes) {
//This is both init and update. We only call it internally, the name of the function doesn't matter.
//Once guaranteed called on program start and then each window resize
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
int blockSize = 13 * (NOTE_SMALL_SIDE + NOTE_GAP); //space the blocks one row/column apart = 13
int xOffset = (int)(screenWidth/2) - (VIS_PORTS_SQRT/2)*blockSize; //shift half of the grid blocks from the center to the left
int yOffset = (int)(screenHeight/2) - (VIS_PORTS_SQRT/2)*blockSize; //shift half of the grid blocks from the center to the left
//top left corner of each block
int blockX;
int blockY;
int portRow;
int portColumn;
int pitchRow;
int pitchColumn;
programState.modeDescription = "Each port is one grid.\
\nEach pitch is one square, from top left\nto bottom right.\
\nPitch rows wrap, just like normal text.\n\n\
\n\
The higher the velocity of a note the more\nvibrant its color.\n\
\n\
Notes are only lit up when they are played.\n\
There is no time-dimension.";
for (int port=0; port<VIS_PORTS; port++) {
//We loop in a way that is guaranteed to have >= grid blocks available for all notes
//But we will never try to access more notes than exist in allGUINotes.
portRow = port / VIS_PORTS_SQRT;
portColumn = port % VIS_PORTS_SQRT;
blockY = portRow * blockSize;
blockX = portColumn * blockSize;
backgroundRects[port].x = (float)(xOffset + blockX);
backgroundRects[port].y = (float)(yOffset + blockY);
backgroundRects[port].width = (float)(12 * (NOTE_SMALL_SIDE + NOTE_GAP)); //blocksize without filling the extra gap
backgroundRects[port].height = (float)(12 * (NOTE_SMALL_SIDE + NOTE_GAP));
//Build a 12x12 Grid for octaves.
for (int midiPitch=0; midiPitch<128; midiPitch++) {
pitchRow = midiPitch / 12 +1; //shift one down because it is prettier
pitchColumn = midiPitch % 12;
allGUINotes[port*128 + midiPitch].x = xOffset + blockX + pitchColumn * (NOTE_SMALL_SIDE + NOTE_GAP);
allGUINotes[port*128 + midiPitch].y = yOffset + blockY + pitchRow * (NOTE_SMALL_SIDE + NOTE_GAP);
}
}
}
static void drawBackground() {
//Each port is one track
//The background rects already have their positions and dimensions set.
if (programState.showPortBackground) {
for (int port=0; port<VIS_PORTS; port++) {
if (portShouldDraw(port)) {
DrawRectangleRec(backgroundRects[port], programState.colors[backgroundLight]);
}
}
}
}
void draw_port_grids(Note *allGUINotes, int redrawNeeded) {
Note * nt;
float ft = GetFrameTime();
if (redrawNeeded) {
reposition(allGUINotes);
}
//Z-Order is created by call-order. First is bottom.
drawBackground();
if (programState.showConnectedPortnames) {
for (int port=0; port<VIS_PORTS; port++) {
nt = &allGUINotes[port*128]; // top left note of each port
drawJackPortName(programState.connectedPortNames[port], nt->x, nt->y-1.5*(NOTE_SMALL_SIDE + NOTE_GAP), 18, RAYWHITE, false); //text, x, y, int fontsize, color, vertical
}
}
if (programState.showPitchMarker) {
//Each block has a pitch marker at one note that we draw with a different color.
//We use the already set note positions but create a background note behind the real one.
for (int port=0; port<VIS_PORTS; port++) {
if (portShouldDraw(port)) {
nt = &allGUINotes[port*128 + programState.pitchMarkerValue];
Rectangle rec = (Rectangle){nt->x, nt->y, NOTE_SMALL_SIDE-NOTE_BORDER, NOTE_SMALL_SIDE-NOTE_BORDER};
DrawRectangleRounded(rec, 0.5, 8, programState.colors[backgroundLighter]);
}
}
}
for (int port=0; port<VIS_PORTS; port++) {
for (int midiPitch=0; midiPitch<128; midiPitch++) { //128 is a fixed midi value, no need to dynamically get the size
nt = &allGUINotes[port*128 + midiPitch];
if (nt->active) {
reduceCountdown(nt, ft); //handles 0.0 and 1.0 as special cases
drawNoteRect(nt, 0, 1); //note, rotated, square. Will not draw if not active/countdown == 0
}
}
}
}

6
draw_port_grids.h

@ -0,0 +1,6 @@
#ifndef DRAW_PORT_GRIDS_H
#define DRAW_PORT_GRIDS_H
void draw_port_grids(Note *allGUINotes, int redrawNeeded);
#endif // not defined DRAW_PORT_GRIDS_H

107
draw_xpitches_yports.c

@ -0,0 +1,107 @@
//Standard lib
#include <stdio.h>
//Third party
#include "raylib.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "drawhelper.h"
#include "draw_xpitches_yports.h"
/* Each port is a track, which are ordered top to bottom
* Each pitch is a rectangle in that track. midi note 0 on the left to 127 on the right.
* The screen does not move or scroll. Notes fade out and make room for the next note-on.
*/
extern ProgramState programState;
static Rectangle backgroundRects[VIS_PORTS]; // 4 floats: x, y, w, h
static void reposition(Note *allGUINotes) {
//This is both init and update. We only call it internally, the name of the function doesn't matter.
//Once guaranteed called on program start and then each window resize
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
int xOffset = (int)(screenWidth/2) - (int)(60*(NOTE_SMALL_SIDE+NOTE_GAP)); //we want all tracks around the center.
int yOffset = (int)(screenHeight/2) - (int)(VIS_PORTS*(NOTE_LONG_SIDE+NOTE_GAP))/2; //we want all tracks around the center.
programState.modeDescription = "Each port is a horizontal track.\n\
Each pitch is one square, going from left lowest\nto right highest.\n\
\n\
The higher the velocity of a note the more\nvibrant its color.\n\
\n\
Notes are only lit up when they are played.\n\
There is no time-dimension.";
for (int port=0; port<VIS_PORTS; port++) {
backgroundRects[port].x = xOffset + programState.pitchMin * (NOTE_SMALL_SIDE + NOTE_GAP/2);
backgroundRects[port].y = (float)(yOffset + port*NOTE_LONG_SIDE + port*NOTE_GAP*2 - NOTE_GAP);
backgroundRects[port].width = (programState.pitchMax-programState.pitchMin+1)*(NOTE_SMALL_SIDE + NOTE_GAP/2);
backgroundRects[port].height = (float)NOTE_LONG_SIDE;
for (int midiPitch=0; midiPitch<128; midiPitch++) { //We prepare the full range of pitches, but the main loop will only activate the current pitch range.
allGUINotes[port*128 + midiPitch].x = xOffset + midiPitch*(NOTE_SMALL_SIDE + NOTE_GAP/2);
allGUINotes[port*128 + midiPitch].y = yOffset + port*NOTE_LONG_SIDE + port*NOTE_GAP*2;
}
}
}
static void drawBackground() {
//Each port is one track
//The background rects already have their positions and dimensions set.
if (programState.showPortBackground) {
for (int port=0; port<VIS_PORTS; port++) {
if (portShouldDraw(port))
{
DrawRectangleRec(backgroundRects[port], programState.colors[backgroundLight]);
}
}
}
}
void draw_xpitches_yports(Note *allGUINotes, int redrawNeeded) {
Note * nt;
float ft = GetFrameTime();
if (redrawNeeded) {
reposition(allGUINotes);
}
//Z-Order is created by call-order. First is bottom.
drawBackground();
if (programState.showConnectedPortnames) {
for (int port=0; port<VIS_PORTS; port++) {
nt = &allGUINotes[port*128+programState.pitchMin]; // choose note where to start writing
drawJackPortName(programState.connectedPortNames[port], nt->x, nt->y, 18, RAYWHITE, false); //text, x, y, int fontsize, color, vertical
}
}
if (programState.showPitchMarker) {
for (int port=0; port<VIS_PORTS; port++) {
if (portShouldDraw(port)) {
nt = &allGUINotes[port*128 + programState.pitchMarkerValue];
Rectangle rec = (Rectangle){nt->x, nt->y, NOTE_SMALL_SIDE-NOTE_BORDER, NOTE_LONG_SIDE-NOTE_BORDER};
DrawRectangleRounded(rec, 0.5, 8, programState.colors[backgroundLighter]);
}
}
}
//Debug: Show 0 and 127
//DrawLine(allGUINotes[0].x, 0, allGUINotes[0].x, GetScreenHeight(), RAYWHITE); //(int startPosX, int startPosY, int endPosX, int endPosY, Color color);
//DrawLine(allGUINotes[127].x, 0, allGUINotes[127].x, GetScreenHeight(), RAYWHITE); //(int startPosX, int startPosY, int endPosX, int endPosY, Color color);
for (int port=0; port<VIS_PORTS; port++) {
for (int midiPitch=programState.pitchMin; midiPitch<=programState.pitchMax; midiPitch++) {
nt = &allGUINotes[port*128 + midiPitch];
if (nt->active) {
reduceCountdown(nt, ft); //handles 0.0 and 1.0 as special cases
drawNoteRect(nt, 0, 0); //note, rotated, square. Will not draw if countdown == 0
}
}
}
}

6
draw_xpitches_yports.h

@ -0,0 +1,6 @@
#ifndef DRAW_XPITCHES_YPORTS_H
#define DRAW_XPITCHES_YPORTS_H
void draw_xpitches_yports(Note *allGUINotes, int redrawNeeded);
#endif // not defined DRAW_XPITCHES_YPORTS_H

103
draw_xports_ypitches.c

@ -0,0 +1,103 @@
//Standard lib
//Third party
#include "raylib.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "drawhelper.h"
#include "draw_xports_ypitches.h"
/* Each port is a column, ordered from left to right
* Each pitch is a rectangle in that column. midi note 0 on the bottom to 127 on top
* The screen does not move or scroll. Notes fade out and make room for the next note-on.
*/
extern ProgramState programState;
static Rectangle backgroundRects[VIS_PORTS]; // 4 floats: x, y, w, h
static void reposition(Note *allGUINotes) {
//This is both init and update. We only call it internally, the name of the function doesn't matter.
//Once guaranteed called on program start and then each window resize
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
int xOffset = (int)(screenWidth/2) - (int)((VIS_PORTS*NOTE_LONG_SIDE)/2);
//int yOffset = -1 * ((int)(screenWidth/2) - ((programState.pitchMax - programState.pitchMin +1 ) * NOTE_SMALL_SIDE))/2;
int yOffset = -1 * ((int)(screenHeight/2) - (60 * NOTE_SMALL_SIDE)/2); //we want all tracks around the center.
programState.modeDescription = "Each port is a vertical column.\n\
Each pitch is one square, going from top highest\nto bottomw lowest.\n\
\n\
The higher the velocity of a note the more\nvibrant its color.\n\
\n\
Notes are only lit up when they are played.\n\
There is no time-dimension.";
for (int port=0; port<VIS_PORTS; port++) {
backgroundRects[port].x = xOffset + port*(NOTE_LONG_SIDE + NOTE_GAP) - NOTE_GAP/2;
backgroundRects[port].y = yOffset + (127-programState.pitchMax) * NOTE_SMALL_SIDE;
backgroundRects[port].width = (float)NOTE_LONG_SIDE;
backgroundRects[port].height = (programState.pitchMax - programState.pitchMin +1 ) * NOTE_SMALL_SIDE;
for (int midiPitch=0; midiPitch<128; midiPitch++) { //We prepare the full range of pitches, but the main loop will only activate the current pitch range.
allGUINotes[port*128 + midiPitch].x = xOffset + port*(NOTE_LONG_SIDE + NOTE_GAP);
allGUINotes[port*128 + midiPitch].y = yOffset + (127-midiPitch) * NOTE_SMALL_SIDE ;
}
}
}
static void drawBackground() {
//Each port is one track
//The background rects already have their positions and dimensions set.
if (programState.showPortBackground) {
for (int port=0; port<VIS_PORTS; port++) {
if (portShouldDraw(port)) {
DrawRectangleRec(backgroundRects[port], programState.colors[backgroundLight]);
}
}
}
}
void draw_xports_ypitches(Note *allGUINotes, int redrawNeeded) {
Note * nt;
float ft = GetFrameTime();
if (redrawNeeded) {
reposition(allGUINotes);
}
//Z-Order is created by call-order. First is bottom.
drawBackground();
if (programState.showConnectedPortnames) {
for (int port=0; port<VIS_PORTS; port++) {
nt = &allGUINotes[port*128+programState.pitchMin]; // bottom left note of each port
drawJackPortName(programState.connectedPortNames[port], nt->x, nt->y+20, 18, RAYWHITE, true); //text, x, y, int fontsize, color, vertical
}
}
if (programState.showPitchMarker) {
for (int port=0; port<VIS_PORTS; port++) {
if (portShouldDraw(port)) {
nt = &allGUINotes[port*128 + programState.pitchMarkerValue];
Rectangle rec = (Rectangle){nt->x, nt->y, NOTE_LONG_SIDE-NOTE_BORDER, NOTE_SMALL_SIDE-NOTE_BORDER};
DrawRectangleRounded(rec, 0.5, 8, programState.colors[backgroundLighter]);
}
}
}
for (int port=0; port<VIS_PORTS; port++) {
for (int midiPitch=programState.pitchMin; midiPitch<=programState.pitchMax; midiPitch++) {
nt = &allGUINotes[port*128 + midiPitch];
if (nt->active) {
reduceCountdown(nt, ft); //handles 0.0 and 1.0 as special cases
drawNoteRect(nt, 1, 0); //note, rotated, square. Will not draw if countdown == 0
}
}
}
}

6
draw_xports_ypitches.h

@ -0,0 +1,6 @@
#ifndef DRAW_XPORTS_YPITCHES_H
#define DRAW_XPORTS_YPITCHES_H
void draw_xports_ypitches(Note *allGUINotes, int redrawNeeded);
#endif // not defined DRAW_XPORTS_YPITCHES_H

181
drawhelper.c

@ -0,0 +1,181 @@
//Standard lib
#include <string.h> //strchr
#include <stdio.h> //print error messages
//Third party
#include "raylib.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "drawhelper.h"
extern ProgramState programState;
float intToHue(int number, int maxnumber) {
//previously used to auto-color ports this is now only used for the default port colors.
//portnum is 0-VIS_PORTS
//hue is 0-360
//return (input-inputLowest) / (inputHighest-inputLowest) * (outputHighest-outputLowest) + outputLowest
return (float)number / (float)maxnumber * 360.0f;
}
Color intToColor(int number, int maxnumber) {
Color result;
Vector3 hsv; // 3 floats: hue/color, saturation, value/brightness
hsv = (Vector3){intToHue(number, VIS_PORTS), 1.0f, (float)(127+40)/(float)167}; //Saturation is always 1, we tweak Value to never go into the black range.
result = ColorFromHSV(hsv);
return result;
}
Color midiToColor(Note * note) {
Color result;
Vector3 hsv; // 3 floats: hue/color, saturation, value/brightness
//hsv = (Vector3){intToHue(note->port, VIS_PORTS), 1.0f, (float)(note->velocity+40)/(float)167}; //Saturation is always 1, we tweak Value to never go into the black range.
float hue=ColorToHSV(programState.colors[note->port]).x;
hsv = (Vector3){hue, 1.0f, (float)(note->velocity+40)/(float)167}; //Saturation is always 1, we tweak Value to never go into the black range.
result = ColorFromHSV(hsv);
return result;
}
void drawNoteRect(Note * note, int rotated, int square) {
if (note->active) {
//opacity is between 0.0f and 1.0f
int w,h;
if (square==1) {
w = NOTE_SMALL_SIDE - NOTE_BORDER;
h = NOTE_SMALL_SIDE - NOTE_BORDER;
}
else { //Not Square
if (rotated==0) {
w = NOTE_SMALL_SIDE - NOTE_BORDER;
h = NOTE_LONG_SIDE - NOTE_BORDER;
}
else {
h = NOTE_SMALL_SIDE - NOTE_BORDER;
w = NOTE_LONG_SIDE - NOTE_BORDER;
}
}
const Rectangle rec = (Rectangle){note->x, note->y, w,h};
const Rectangle outer = (Rectangle){note->x-NOTE_BORDER/2, note->y-NOTE_BORDER/2, w+NOTE_BORDER, h+NOTE_BORDER};
Color bordercolor = Fade(BLACK, note->countdown);
Color color = Fade(programState.colors[note->port], note->countdown);
//Drawing order is reverse Z-Order
DrawRectangleRounded(outer, 0.5, 8, bordercolor); //float roundness, int segments/smoothness, Color
DrawRectangleRounded(rec, 0.5, 8, color);
}
}
bool portShouldDraw(int port) {
//Check various conditions if a port background, port name and pitch marker should be drawn
if ( programState.showPortBackgroundForUnconnected || ( programState.connectedPortNames[port] && programState.connectedPortNames[port][0] != '\0' )) {
return true;
}
else {
return false;
}
}
void drawJackPortName(const char * portname, int x, int y, int fontsize, Color color, bool vertical) {
if (portname && portname[0] != '\0') {
const char * finalPortName;
if (programState.includeClientInPortNameDisplay) {
finalPortName = portname; //TODO: Pretty Names don't contain the client name. Add it ourselves?
}
else {
if (strchr(portname, ':') == NULL ) //Pretty Names don't contain the client name.
{
finalPortName = portname;
}
else {
finalPortName = strchr(portname, ':')+1;
}
} //final port name is ready.
//Vertical Mode
//void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float scale, Color tint); // Draw one character (codepoint)
if (vertical) {
//TODO: Wow, that is ugly!
int counter = 0;
char ch = finalPortName[counter];
Font defaultFont = GetFontDefault();
while (ch != '\0') {
DrawTextCodepoint(defaultFont, ch, (Vector2){ x, y+counter*(fontsize/1.5) }, 1.8f, color);
counter++;
ch = finalPortName[counter];
}
}
else {
// Horizontal Mode
DrawText(finalPortName, x, y, fontsize, color);
}
}
}
float calculateCountdown(float countdown, float ft) {
float bpmFactor;
switch (programState.fadeOutMode) {
case 0: // framelegnth * user factor
if (countdown < 1.0f) { countdown -= (float)programState.fadeOutFactor * ft; } //note off has been triggered. Fade out.
if (countdown <= 0.0f) { countdown = 0; } // Is it now, or was already, below 0?
break;
case 1: // bpm
bpmFactor = (float)(programState.bpm/40);
if (countdown < 1.0f) { countdown -= bpmFactor * ft; } //note off has been triggered. Fade out.
if (countdown <= 0.0f) { countdown = 0.0f; } // Is it now, or was already, below 0?
break;
case 2: // no fadeout
if (countdown < 1.0f) { countdown = 0.0f; } // As soon as note off arrives
break;
default: //just in case
countdown = 0.0f;
break;
}
return countdown;
}
void reduceCountdown(Note * nt, float ft) {
//Notes countdown is set to 0 (note-off), 1 (note-on) or 0.999 (note-off) by our jack midi client.
float bpmFactor;
if (nt->active == 1) {
switch (programState.fadeOutMode) {
case 0: // framelegnth * user factor
if (nt->countdown < 1.0f) { nt->countdown -= (float)programState.fadeOutFactor * ft; } //note off has been triggered. Fade out.
if (nt->countdown <= 0.0f) { nt->active = false; } // Is it now, or was already, below 0?
break;
case 1: // bpm
bpmFactor = (float)(programState.bpm/40);
if (nt->countdown < 1.0f) { nt->countdown -= bpmFactor * ft; } //note off has been triggered. Fade out.
if (nt->countdown <= 0.0f) { nt->active = false; } // Is it now, or was already, below 0?
break;
case 2: // no fadeout
if (nt->countdown < 1.0f) { nt->active = false; } // As soon as note off arrives
break;
default: //just in case
nt->active = false;
break;
}
}
}

14
drawhelper.h

@ -0,0 +1,14 @@
#ifndef DRAWHELPER_H
#define DRAWHELPER_H
#include "constants.h"
Color midiToColor(Note * note);
Color intToColor(int number, int maxnumber);
void drawNoteRect(Note * note, int rotated, int square);
void reduceCountdown(Note * nt, float ft);
float calculateCountdown(float countdown, float ft);
void drawJackPortName(const char * portname, int x, int y, int fontsize, Color color, bool vertical);
bool portShouldDraw(int port);
#endif // not defined DRAWHELPER_H

100
effect_starfield.c

@ -0,0 +1,100 @@
//Standard lib
#include <stdio.h>
//Third party
#include "raylib.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "effect_starfield.h"
extern ProgramState programState;
static int screenWidth;
static int screenHeight;
static const int numberOfStars=540;
static const int starLayers = 3;
typedef struct {
int x;
int y;
int z;
Color color;
float rand;
} Star;
static Star stars[540]; //1080 * 0.5 Not quite one star per line
void reposition() {
//Recreate all stars and begin from blank.
//We want a certain star density, no matter the screen resolution
screenWidth = GetScreenWidth();
screenHeight = GetScreenHeight();
Color color;
int rand;
int layer;
int partitionSize=(starLayers+1)*2 / 8; //8 partions
for (int i=0; i<numberOfStars; i++) {
rand = GetRandomValue(1, (starLayers+1)*2);
if (rand<= 5*partitionSize) layer = 1;
else if (rand<= 7*partitionSize) layer = 2;
else if (rand<= 8*partitionSize) layer = 3;
switch (layer) {
//0 does not exist
case 1:
color = BROWN;
break;
case 2:
color = BEIGE;
break;
case 3:
color = PURPLE;
break;
default:
color = DARKBLUE;
}
float randomFactor = ((float)GetRandomValue(0, 20)-10.0f) / 100.0f;
stars[i] = (Star){ GetRandomValue(0, screenWidth), GetRandomValue(0, screenHeight), layer, color, randomFactor };
}
}
void effect_starfield(bool redrawNeeded, float opacity, int speedModifier, int size) {
float ft = GetFrameTime();
int bpmFactor = programState.bpm/40 * programState.bpm/20; //exponential curve. The higher bpm the faster we get. Below 90 it should feel really slow
Star * star;
if (redrawNeeded) {
reposition();
}
for (int i=0; i<numberOfStars; i++) {
star = &stars[i];
if (programState.transportRolling) {
//Move Forward
star->x -= (star->z * ft * bpmFactor * 3 * speedModifier); //... the mysterious number 3.
//wrap around
if (star->x <= 0) {
star->x += screenWidth;
star->y = GetRandomValue(0, screenHeight);
}
else if (star->x > screenWidth) {
star->x -= screenWidth;
star->y = GetRandomValue(0, screenHeight);
}
}
//Wobble wobble
int rand = GetRandomValue(1, 200);
if (rand == 1) {star->y += 1;}
else if (rand == 2) {star->y -= 1;}
DrawRectangle(star->x, star->y, size, size, Fade(star->color, opacity));
}
}

6
effect_starfield.h

@ -0,0 +1,6 @@
#ifndef effect_starfield_H
#define effect_starfield_H
void effect_starfield(bool redrawNeeded, float opacity, int speedModifier, int size);
#endif // not defined effect_starfield_H

BIN
font.ttf

Binary file not shown.

40
glsl330/bloom.fs

@ -0,0 +1,40 @@
#version 330
// Input vertex attributes (from vertex shader)
in vec2 fragTexCoord;
in vec4 fragColor;
// Input uniform values
uniform sampler2D texture0;
uniform vec4 colDiffuse;
// Output fragment color
out vec4 finalColor;
// NOTE: Add here your custom variables
const vec2 size = vec2(1920, 1080); // render size
const float samples = 5.0; // pixels per axis; higher = bigger glow, worse performance
const float quality = 2.5; // lower = smaller glow, better quality
void main()
{
vec4 sum = vec4(0);
vec2 sizeFactor = vec2(1)/size*quality;
// Texel color fetching from texture sampler
vec4 source = texture(texture0, fragTexCoord);
const int range = 2; // should be = (samples - 1)/2;
for (int x = -range; x <= range; x++)
{
for (int y = -range; y <= range; y++)
{
sum += texture(texture0, fragTexCoord + vec2(x, y)*sizeFactor);
}
}
// Calculate final fragment color
finalColor = ((sum/(samples*samples)) + source)*colDiffuse;
}

393
gui.c

@ -0,0 +1,393 @@
//Standard lib
#include <stdlib.h> // atoi for commandline argument char to int
#include <stdio.h>
#include <string.h>
#include <unistd.h>
//Third party
#include "raylib.h"
#define RAYGUI_IMPLEMENTATION
#include "raygui.h" // Required for GUI controls
#define GUI_FILE_DIALOG_IMPLEMENTATION
#include "gui_file_dialog.h"
//Our own files
#include "constants.h"
#include "programstate.h"
#include "gui.h"
#include "jackclient.h"
#include "camera.h"
#include "draw.h"
extern ProgramState programState;
extern Effect effects[NR_OF_EFFECTS];
static int screenWidth;
static int screenHeight;
static int center;
static int bottom;
static int remember; // Helper var: Did a value change so we need to instruct a gui reposition/draw?
static float alpha; //Helper var for colors.
static Vector2 panelScroll = { 0, 0 }; //This is the current state of scrolling. It must be declared outside the loop of course
static int currentColorIndex=0;
static int currentPaletteIndex=0; //see programstate.c setColorPalette()
static int columnX; //in pixel
static const int checkboxWidth = 24;
static bool colorChoiceEditMode = false;
static bool colorPaletteEditMode = false;
static bool pitchMarkerEditMode = false;
static bool pitchMaxEditMode = false;
static bool pitchMinEditMode= false;
static int configTab = layerIndex_notes; //index from enum in constants.h
static char fileNameToLoad[512] = { 0 };
static GuiFileDialogState fileDialogState;
//"Port 00;Port 01;Port 02;Port 03;Port 04;Port 05;Port 06;Port 07;Port 08;Port 09;Port 10;Port 11;Port 12;Port 13;Port 14;Port 15;Background;Panels;Marker"
static char colorsPortsString[VIS_PORTS*9 + 25];
void init_gui() {
for (int i=0; i<VIS_PORTS; i++) {
//Only works if VIS_PORTS is below 100 ports.
snprintf(colorsPortsString+strlen(colorsPortsString), sizeof(colorsPortsString), "Port %2d;", i); //9 because the substring is 9 chars.
}
snprintf(colorsPortsString+strlen(colorsPortsString), sizeof(colorsPortsString), "Background;Panels;Marker"); //Attach to the end
fileDialogState = InitGuiFileDialog(420, 310, GetWorkingDirectory(), false);
}
bool guiIsKeyPressed(int keyConstant) {
//Wrap Raylib function to only accept input when we want it.
if (programState.lockInput) {
return false;
}
else {
return IsKeyPressed(keyConstant);
}
}
bool guiIsKeyDown(int keyConstant) {
//Wrap Raylib function to only accept input when we want it.
if (programState.lockInput) {
return false;
}
else {
return IsKeyDown(keyConstant);
}
}
void mainLoop_gui() {
//bool pressed = IsMouseButtonPressed(0) || IsMouseButtonPressed(1); //0 left, 1 right //Registers no matter where you clicked
//if (pressed) { printf("click\n"); };
GuiUnlock();
programState.lockInput = false;
if (fileDialogState.SelectFilePressed)
{
// Load image file (if supported extension)
if (IsFileExtension(fileDialogState.fileNameText, ".png"))
{
strcpy(fileNameToLoad, TextFormat("%s/%s", fileDialogState.dirPathText, fileDialogState.fileNameText));
loadBackgroundImage(fileNameToLoad, true); //also saves the path in programstate
}
fileDialogState.SelectFilePressed = false;
}
GuiFileDialog(&fileDialogState); //does nothing if not activated. This needs to be processed before the GUI locks.
if (fileDialogState.fileDialogActive) {
GuiLock();
programState.lockInput = true;
}
//Shortcuts and toggles
bool controlDown=false;
if ( guiIsKeyDown(KEY_LEFT_CONTROL) || guiIsKeyDown(KEY_RIGHT_CONTROL) ) {
controlDown = true;
}
if (controlDown && guiIsKeyPressed(KEY_S)) {
saveStateToFile();
}
if (guiIsKeyPressed(KEY_SPACE)){ toggleTransport(); }
if (guiIsKeyPressed(KEY_G) || guiIsKeyPressed(KEY_ESCAPE)) { programState.guiVisible = !programState.guiVisible; }
if (guiIsKeyPressed(KEY_B)){ programState.showPortBackground = !programState.showPortBackground; }
if (guiIsKeyPressed(KEY_P)){ programState.showPitchMarker = !programState.showPitchMarker; }
if (guiIsKeyPressed(KEY_N)){ programState.showConnectedPortnames = !programState.showConnectedPortnames; }
if (guiIsKeyPressed(KEY_C)){ programState.alwaysShowClock = !programState.alwaysShowClock; }
//Camera Controls
cameraMaybeZoom();
if (guiIsKeyPressed(KEY_R)){ cameraReset(); }
if (guiIsKeyDown(KEY_A)) { cameraLeft(); }
else if (guiIsKeyDown(KEY_D)) { cameraRight(); }
if (guiIsKeyDown(KEY_W)) { cameraUp(); }
else if (!controlDown && guiIsKeyDown(KEY_S)) { cameraDown(); }
if (guiIsKeyDown(KEY_Q)) { cameraRotateLeft(); }
else if (guiIsKeyDown(KEY_E)) { cameraRotateRight(); }
else if (guiIsKeyPressed(KEY_X)) { cameraRotate180(); }
//Undocumented because it is based on the current window size.
if (guiIsKeyPressed(KEY_F)){ ToggleFullscreen(); }
if (guiIsKeyPressed(KEY_M)){
programState.drawMode++;
if (programState.drawMode > 4) { programState.drawMode = 0;} //wrap around
programState.guiRedrawNeeded = true;
}
//Draw GUI Elements
//All position numbers are trial and error
//RayGUI draws and delievers the result in one ste. e.g. button clicked.
//The state of e.g. ComboBox index is given each time in the caller function.
//newState = GuiElement(RectPositionDimension, options, oldState);
if (programState.alwaysShowClock || programState.guiVisible) {
//DrawFPS(0,0); //Fugly!
screenWidth = GetScreenWidth(); //Changes on resize
screenHeight = GetScreenHeight(); //Changes on resize
center = screenWidth/2;
bottom = screenHeight - 2*SPACING;
//DrawText(programState.clockString, center, bottom-2*SPACING, 20, RAYWHITE); //text, x, y, int fontsize, color
DrawTextEx(GuiGetFont(), programState.clockString, (Vector2){center, bottom-2*SPACING}, 31, 1, RAYWHITE);
}
if (programState.guiVisible) {
//Clock is drawn outside of guiVisible with an individual check
//Transport Control Buttons
if (GuiButton((Rectangle){center-100-5, bottom, 100, HEIGHT}, "Rewind")) {
//No shortcut
seekToStart();
}
if (GuiButton((Rectangle){center, bottom, 205, HEIGHT}, "[Space] Play / Pause")) {
toggleTransport();
}
//Save
if (programState.filePath && programState.filePath[0] != '\0') {
if (GuiButton((Rectangle){ screenWidth-120, bottom-1*SPACING, 105, HEIGHT }, "[Ctrl+S] Save")) {
saveStateToFile();
}
}
//Quit
programState.instructedToQuit = GuiButton((Rectangle){ screenWidth-120, bottom, 105, HEIGHT }, "Quit");
int row=1;
columnX = 10; //in pixel
//Configs for all layers
//Layer Config "Tabs" Panel
DrawRectangle(columnX, row*SPACING-5, 160+230, HEIGHT+10, (Color){ 35, 40, 47, 255 });
GuiLabel((Rectangle){ columnX+checkboxWidth, row*SPACING, 105, HEIGHT }, "Show Config for"); // void GuiLabel(Rectangle bounds, const char *text);
configTab = GuiComboBox((Rectangle){ 160, row*SPACING, 200, HEIGHT }, "Music Layer;Effects;Sprites;General", configTab);
row++;
//Tab for each Layer. Begin at row=2
if (configTab == layerIndex_notes) {
remember = programState.showDrawMode;
programState.showDrawMode = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "Enable Note Layer", programState.showDrawMode);
if (remember != programState.showDrawMode) { programState.guiRedrawNeeded = true;}
row++;
remember = programState.drawMode;
GuiLabel((Rectangle){ columnX+checkboxWidth, row*SPACING, 105, HEIGHT }, "[M] Note Layer Mode"); // void GuiLabel(Rectangle bounds, const char *text);
programState.drawMode = GuiComboBox((Rectangle){ 200, row*SPACING, 130, HEIGHT }, "Horizontal;Vertical;Grid;Meterbridge;Port Shapes;", programState.drawMode); //These need to be manually synced to draw.c drawFunctions[]
if (remember != programState.drawMode) { programState.guiRedrawNeeded = true;}
row++;
//Show Port Background Checkbox
programState.showPortBackground = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "[B] Port Background", programState.showPortBackground);
row++;
//Show Unconnected Port Background Checkbox
programState.showPortBackgroundForUnconnected = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "Draw unconnected Port Background", programState.showPortBackgroundForUnconnected);
row++;
//Pitch Marker
programState.showPitchMarker = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "[P] Pitch Marker", programState.showPitchMarker);
remember = programState.pitchMarkerValue;
if (GuiSpinner((Rectangle){ 200, row*SPACING, 80, HEIGHT }, "", &programState.pitchMarkerValue, 0, 127, pitchMarkerEditMode)) pitchMarkerEditMode = !pitchMarkerEditMode; // bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode)
//Bug in raygui. If edit mode for spinner is true the max check is not done.
if (programState.pitchMarkerValue > 127) {programState.pitchMarkerValue=127;}
if (programState.pitchMarkerValue < 0) {programState.pitchMarkerValue=0;}
if (remember != programState.pitchMarkerValue) { programState.guiRedrawNeeded = true;}
row++;
// FadeOut Mode
GuiLabel((Rectangle){ columnX+checkboxWidth, row*SPACING, 105, HEIGHT }, "Fade Out Mode"); // void GuiLabel(Rectangle bounds, const char *text);
programState.fadeOutMode = GuiComboBox((Rectangle){ 200, row*SPACING, 150, HEIGHT }, "Fixed;Tempo (bpm);Off", programState.fadeOutMode); //This needs to be manually synced to programState options
if (programState.fadeOutMode == 0) {
GuiSpinner((Rectangle){ 75+220+120, row*SPACING, 80, HEIGHT }, "Factor", &programState.fadeOutFactor, 1, 9, false); // bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode)
//Bug in raygui. If edit mode for spinner is true the max check is not done.
if (programState.fadeOutFactor > 9) {programState.fadeOutFactor=9;}
if (programState.fadeOutFactor < 1) {programState.fadeOutFactor=1;}
}
row++;
// Show Port Names
programState.showConnectedPortnames = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "[N] Show Connected Port Names", programState.showConnectedPortnames);
row++;
programState.includeClientInPortNameDisplay = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "Include Client in Port Names", programState.includeClientInPortNameDisplay);
row++;
//Pitch Range Min
GuiLabel((Rectangle){ columnX+checkboxWidth, row*SPACING, 105, HEIGHT }, "Pitch Range Minimum"); // void GuiLabel(Rectangle bounds, const char *text);
remember = programState.pitchMin;
if (GuiSpinner((Rectangle){ 200, row*SPACING, 80, HEIGHT }, "", &programState.pitchMin, 0, 127, pitchMinEditMode)) pitchMinEditMode = !pitchMinEditMode; // bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode)
//Bug in raygui. If edit mode for spinner is true the max check is not done.
if (programState.pitchMin > 127) {programState.pitchMin=127;}
if (programState.pitchMin < 0) {programState.pitchMin=0;}
if (remember != programState.pitchMin) { programState.guiRedrawNeeded = true;}
row++;
//Pitch Range Max
GuiLabel((Rectangle){ columnX+checkboxWidth, row*SPACING, 105, HEIGHT }, "Pitch Range Maximum"); // void GuiLabel(Rectangle bounds, const char *text);
remember = programState.pitchMax;
if (GuiSpinner((Rectangle){ 200, row*SPACING, 80, HEIGHT }, "", &programState.pitchMax, 0, 127, pitchMaxEditMode)) pitchMaxEditMode = !pitchMaxEditMode; // bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode)
//Bug in raygui. If edit mode for spinner is true the max check is not done.
if (programState.pitchMax > 127) {programState.pitchMax=127;}
if (programState.pitchMax < 0) {programState.pitchMax=0;}
if (remember != programState.pitchMax) { programState.guiRedrawNeeded = true;}
//Second Column
////////////////
columnX=400;
//NOTE: GuiDropdownBox must draw after any other control that can be covered on unfolding
//Therefore no automatic rows
GuiLabel((Rectangle){ columnX, 2*SPACING, 105, HEIGHT }, "Color for");
GuiLabel((Rectangle){ columnX, 5*SPACING, 105, HEIGHT }, "Set Palette");
remember = currentPaletteIndex;
if (GuiDropdownBox((Rectangle){ columnX, 5*SPACING, 130, HEIGHT }, "Default;Random;Grayscale", &currentPaletteIndex, colorPaletteEditMode)) { colorPaletteEditMode = !colorPaletteEditMode; }
if (remember != currentPaletteIndex) { setColorPalette(currentPaletteIndex);}
//Draw last in this column
if (GuiDropdownBox((Rectangle){ columnX, 3*SPACING, 130, HEIGHT }, colorsPortsString, &currentColorIndex, colorChoiceEditMode)) colorChoiceEditMode = !colorChoiceEditMode;
//Third Column
////////////////
row = 2;
columnX=550;
row++; alpha = GuiColorBarAlpha((Rectangle){ columnX, row*SPACING, 200, HEIGHT }, (float)programState.colors[currentColorIndex].a/255.0f);
programState.colors[currentColorIndex] = Fade(programState.colors[currentColorIndex], alpha);
row++; programState.colors[currentColorIndex] = GuiColorPicker((Rectangle){ columnX, row*SPACING, 196, 192 }, programState.colors[currentColorIndex]);
char colorString[24];
sprintf(colorString, "R%i G%i B%i A%i", programState.colors[currentColorIndex].r, programState.colors[currentColorIndex].g, programState.colors[currentColorIndex].b, programState.colors[currentColorIndex].a);
GuiLabel((Rectangle){ columnX, row*SPACING+190, 105, HEIGHT }, colorString);
//Fourth Column
////////////////
row = 2;
columnX=850;
//Show keys for camera control
GuiLabel((Rectangle){ columnX, row*SPACING, 105, HEIGHT }, "Camera Controls");
row++; GuiLabel((Rectangle){ columnX, row*SPACING, 105, HEIGHT }, "[W][A][S][D] Pan");
row++; GuiLabel((Rectangle){ columnX, row*SPACING, 105, HEIGHT }, "[Q][E][X] Rotate");
row++; GuiLabel((Rectangle){ columnX, row*SPACING, 105, HEIGHT }, "[R] Reset");
row++;
//Help Label 40 per with 80 characters in the top right corner.
GuiLabel((Rectangle){ screenWidth-450, 1*SPACING, 105, HEIGHT }, "Mode Help"); // void GuiLabel(Rectangle bounds, const char *text);
Rectangle panelRec = { screenWidth-450, 2*SPACING, 410, 300 }; //What the user sees
Rectangle panelContentRec = {0, 0, 400, 500 }; //content, bigger than panelRec
Rectangle helpText = GuiScrollPanel(panelRec, panelContentRec, &panelScroll); //Rectangle GuiScrollPanel(Rectangle bounds, Rectangle content, Vector2 *scroll);
BeginScissorMode(helpText.x, helpText.y, helpText.width, helpText.height); // Begin scissor mode (define screen area for following drawing)
//GuiLabelColor((Rectangle){ panelRec.x + panelScroll.x, panelRec.y + panelScroll.y, panelContentRec.width, panelContentRec.height }, "Hallo Leute hier ist\nEin schöner Text für euch. Cool!\nUnd noch ne Zeile.", BLACK); // void GuiLabelColor(Rectangle bounds, const char *text, Color color);
GuiLabelColor((Rectangle){ panelRec.x + panelScroll.x, -4.5*SPACING + panelScroll.y, panelContentRec.width, panelContentRec.height }, programState.modeDescription, RAYWHITE); // void GuiLabelColor(Rectangle bounds, const char *text, Color color);
EndScissorMode();
}
//Effect Layer
else if (configTab == layerIndex_effects) {
//Show BG Layer Switch
remember = programState.showEffectLayer;
programState.showEffectLayer = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "Enable Effects", programState.showEffectLayer);
if (remember != programState.showEffectLayer) { programState.guiRedrawNeeded = true;}
row++;
for (int i=0; i<NR_OF_EFFECTS; i++) {
//Enabled
remember =effects[i].enabled;
effects[i].enabled = GuiCheckBox((Rectangle){ columnX+10, row*SPACING, 20, HEIGHT }, effects[i].name, effects[i].enabled);
if (remember != effects[i].enabled) { programState.guiRedrawNeeded = true;}
//Foreground
effects[i].foreground = GuiCheckBox((Rectangle){ columnX+150, row*SPACING, 20, HEIGHT }, "in foreground", effects[i].foreground);
//Opacity
effects[i].opacity = GuiColorBarAlpha((Rectangle){ columnX+300, row*SPACING, 100, HEIGHT }, effects[i].opacity);
//Speed
effects[i].speed = GuiSlider((Rectangle){ columnX+460, row*SPACING, 100, HEIGHT }, "Speed", TextFormat("x%i", effects[i].speed), effects[i].speed, -4, 4);
//Size
effects[i].size = GuiSlider((Rectangle){ columnX+660, row*SPACING, 100, HEIGHT }, "Size", TextFormat("%i", effects[i].size), effects[i].size, 1, 8);
row++;
}
}
//General Config
else if (configTab == layerIndex_count) {
// Show Clock even when GUI is not visible
programState.alwaysShowClock = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "[C] Clock always visible", programState.alwaysShowClock);
row++;
remember = programState.showBackgroundImageLayer;
programState.showBackgroundImageLayer = GuiCheckBox((Rectangle){ columnX, row*SPACING, 20, HEIGHT }, "Enable Background Image", programState.showBackgroundImageLayer);
if (remember != programState.showBackgroundImageLayer) { programState.guiRedrawNeeded = true;}
row++;
if (GuiButton((Rectangle){ columnX, row*SPACING, 205, HEIGHT}, "Open Image")) {
fileDialogState.fileDialogActive = true;
}
}
}
}

7
gui.h

@ -0,0 +1,7 @@
#ifndef GUI_H
#define GUI_H
void mainLoop_gui();
void init_gui();
#endif // not defined GUI_H

659
gui_file_dialog.h

@ -0,0 +1,659 @@
/*******************************************************************************************
*
* FileDialog v1.1 - Modal file dialog to open/save files
*
* MODULE USAGE:
* #define GUI_FILE_DIALOG_IMPLEMENTATION
* #include "gui_file_dialog.h"
*
* INIT: GuiFileDialogState state = InitGuiFileDialog();
* DRAW: GuiFileDialog(&state);
*
* NOTE: This module depends on some raylib file system functions:
* - GetDirectoryFiles()
* - ClearDirectoryFiles()
* - GetWorkingDirectory()
* - DirectoryExists()
* - FileExists()
*
* LICENSE: zlib/libpng
*
* Copyright (c) 2019-2020 Ramon Santamaria (@raysan5)
*
* This software is provided "as-is", without any express or implied warranty. In no event
* will the authors be held liable for any damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose, including commercial
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not claim that you
* wrote the original software. If you use this software in a product, an acknowledgment
* in the product documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
* as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
**********************************************************************************************/
#include "raylib.h"
// WARNING: raygui implementation is expected to be defined before including this header
#undef RAYGUI_IMPLEMENTATION
#include "raygui.h"
#ifndef GUI_FILE_DIALOG_H
#define GUI_FILE_DIALOG_H
typedef struct {
Vector2 position;
Vector2 size;
bool fileDialogActive;
bool dirPathEditMode;
char dirPathText[256];
int filesListScrollIndex;
bool filesListEditMode;
int filesListActive;
bool fileNameEditMode;
char fileNameText[256];
bool SelectFilePressed;
bool CancelFilePressed;
int fileTypeActive;
int itemFocused;
// Custom state variables (depend on development software)
// NOTE: This variables should be added manually if required
char **dirFiles;
int dirFilesCount;
char filterExt[256];
char dirPathTextCopy[256];
char fileNameTextCopy[256];
int prevFilesListActive;
} GuiFileDialogState;
#ifdef __cplusplus
extern "C" { // Prevents name mangling of functions
#endif
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
//...
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// ...
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
//...
//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------
GuiFileDialogState InitGuiFileDialog(int width, int height, const char *initPath, bool active);
void GuiFileDialog(GuiFileDialogState *state);
#ifdef __cplusplus
}
#endif
#endif // GUI_FILE_DIALOG_H
/***********************************************************************************
*
* GUI_FILE_DIALOG IMPLEMENTATION
*
************************************************************************************/
#if defined(GUI_FILE_DIALOG_IMPLEMENTATION)
#include "raygui.h"
#include <string.h> // Required for: strcpy()
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
#define MAX_DIRECTORY_FILES 1024
#define MAX_DIR_PATH_LENGTH 1024
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
#if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
// Detailed file info type
typedef struct FileInfo {
const char *name;
int size;
int modTime;
int type;
int icon;
} FileInfo;
#else
// Filename only
typedef char *FileInfo;
#endif
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
FileInfo *dirFilesIcon = NULL;
//----------------------------------------------------------------------------------
// Internal Module Functions Definition
//----------------------------------------------------------------------------------
// Read all filenames from directory (supported file types)
static char **ReadDirectoryFiles(const char *dir, int *filesCount, char *filterExt);
#if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
// List View control for files info with extended parameters
static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int active);
#endif
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
GuiFileDialogState InitGuiFileDialog(int width, int height, const char *initPath, bool active)
{
GuiFileDialogState state = { 0 };
// Default dialog size is 440x310
state.size.x = width == -1 ? 440 : width;
state.size.y = height == -1 ? 310 : height;
state.position = (Vector2){ GetScreenWidth()/2 - state.size.x/2, GetScreenHeight()/2 - state.size.y/2 };
state.fileDialogActive = active;
state.dirPathEditMode = false;
state.filesListActive = -1;
state.prevFilesListActive = state.filesListActive;
state.filesListScrollIndex = 0;
state.fileNameEditMode = false;
state.SelectFilePressed = false;
state.CancelFilePressed = false;
state.fileTypeActive = 0;
strcpy(state.fileNameText, "\0");
// Custom variables initialization
if (initPath && DirectoryExists(initPath))
{
strcpy(state.dirPathText, initPath);
}
else if (initPath && FileExists(initPath))
{
strcpy(state.dirPathText, GetDirectoryPath(initPath));
strcpy(state.fileNameText, GetFileName(initPath));
}
else strcpy(state.dirPathText, GetWorkingDirectory());
strcpy(state.dirPathTextCopy, state.dirPathText);
strcpy(state.fileNameTextCopy, state.fileNameText);
strcpy(state.filterExt, "all");
state.dirFilesCount = 0;
state.dirFiles = NULL; // NOTE: Loaded lazily on window active
return state;
}
// Read files in new path
static void FD_RELOAD_DIRPATH(GuiFileDialogState *state)
{
for (int i = 0; i < state->dirFilesCount; i++) RL_FREE(state->dirFiles[i]);
RL_FREE(state->dirFiles);
state->dirFiles = ReadDirectoryFiles(state->dirPathText, &state->dirFilesCount, state->filterExt);
state->itemFocused = 0;
}
// Update and draw file dialog
void GuiFileDialog(GuiFileDialogState *state)
{
if (state->fileDialogActive)
{
const int winWidth = state->size.x;
const int winHeight = state->size.y;
// Load dirFilesIcon and state->dirFiles lazily on windows open
// NOTE: they are automatically unloaded at fileDialog closing
//------------------------------------------------------------------------------------
if (dirFilesIcon == NULL)
{
dirFilesIcon = (FileInfo *)RL_MALLOC(MAX_DIRECTORY_FILES*sizeof(FileInfo)); // Max files to read
for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesIcon[i] = (char *)calloc(MAX_DIR_PATH_LENGTH, 1); // Max file name length
}
if (state->dirFiles == NULL)
{
state->dirFiles = ReadDirectoryFiles(state->dirPathText, &state->dirFilesCount, state->filterExt);
for(int f = 0; f < state->dirFilesCount; f++)
{
if (strcmp(state->fileNameText, state->dirFiles[f]) == 0)
{
if (state->filesListActive != f) state->filesListScrollIndex = state->filesListActive = f; // make it active and visible only on first call
break;
}
}
}
//------------------------------------------------------------------------------------
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)), 0.85f));
state->fileDialogActive = !GuiWindowBox((Rectangle){ state->position.x + 0, state->position.y + 0, winWidth, winHeight }, "#198#LuaJIT | Select File Dialog");
if (GuiButton((Rectangle){ state->position.x + winWidth - 50, state->position.y + 35, 40, 25 }, "< .."))// || IsKeyReleased(KEY_DPAD_Y))
{
// Move dir path one level up
strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText));
// RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles())
FD_RELOAD_DIRPATH(state);
state->filesListActive = -1;
strcpy(state->fileNameText, "\0");
strcpy(state->fileNameTextCopy, state->fileNameText);
}
if (GuiTextBox((Rectangle){ state->position.x + 10, state->position.y + 35, winWidth - 65, 25 }, state->dirPathText, 256, state->dirPathEditMode))
{
if (state->dirPathEditMode)
{
// Verify if a valid path has been introduced
if (DirectoryExists(state->dirPathText))
{
// RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles())
FD_RELOAD_DIRPATH(state);
strcpy(state->dirPathTextCopy, state->dirPathText);
}
else strcpy(state->dirPathText, state->dirPathTextCopy);
}
state->dirPathEditMode = !state->dirPathEditMode;
}
int prevTextAlignment = GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT);
int prevElementsHeight = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT);
GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 24);
// TODO: ListViewElements should be aligned left
# if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
FileInfo fileInfo;
state->filesListActive = GuiListViewFiles((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 }, fileInfo, state->dirFilesCount, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive);
# else
state->filesListActive = GuiListViewEx((Rectangle){ state->position.x + 10, state->position.y + 70, winWidth - 20, winHeight - 135 }, (const char**)dirFilesIcon, state->dirFilesCount, &state->itemFocused, &state->filesListScrollIndex, state->filesListActive);
# endif
GuiSetStyle(LISTVIEW, TEXT_ALIGNMENT, prevTextAlignment);
GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, prevElementsHeight);
if ((state->filesListActive >= 0) && (state->filesListActive != state->prevFilesListActive))
//&& (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A)))
{
strcpy(state->fileNameText, state->dirFiles[state->filesListActive]);
if (DirectoryExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText)))
{
if (TextIsEqual(state->fileNameText, "..")) strcpy(state->dirPathText, GetPrevDirectoryPath(state->dirPathText));
else strcpy(state->dirPathText, TextFormat("%s/%s", strcmp(state->dirPathText, "/")==0 ? "" : state->dirPathText, state->fileNameText));
strcpy(state->dirPathTextCopy, state->dirPathText);
// RL_FREE previous dirFiles (reloaded by ReadDirectoryFiles())
FD_RELOAD_DIRPATH(state);
strcpy(state->dirPathTextCopy, state->dirPathText);
state->filesListActive = -1;
strcpy(state->fileNameText, "\0");
strcpy(state->fileNameTextCopy, state->fileNameText);
}
state->prevFilesListActive = state->filesListActive;
}
GuiLabel((Rectangle){ state->position.x + 10, state->position.y + winHeight - 60, 68, 25 }, "File name:");
if (GuiTextBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 60, winWidth - 200, 25 }, state->fileNameText, 128, state->fileNameEditMode))
{
if (*state->fileNameText)
{
// Verify if a valid filename has been introduced
if (FileExists(TextFormat("%s/%s", state->dirPathText, state->fileNameText)))
{
// Select filename from list view
for (int i = 0; i < state->dirFilesCount; i++)
{
if (TextIsEqual(state->fileNameText, state->dirFiles[i]))
{
state->filesListActive = i;
strcpy(state->fileNameTextCopy, state->fileNameText);
break;
}
}
}
else
{
strcpy(state->fileNameText, state->fileNameTextCopy);
}
}
state->fileNameEditMode = !state->fileNameEditMode;
}
state->fileTypeActive = GuiComboBox((Rectangle){ state->position.x + 75, state->position.y + winHeight - 30, winWidth - 200, 25 }, "All files", state->fileTypeActive);
GuiLabel((Rectangle){ state->position.x + 10, state->position.y + winHeight - 30, 68, 25 }, "File filter:");
state->SelectFilePressed = GuiButton((Rectangle){ state->position.x + winWidth - 120, state->position.y + winHeight - 60, 110,
#ifdef PLATFORM_DESKTOP
25
#else
25+30
#endif
}, "Select");// || IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_DPAD_A);
if (state->SelectFilePressed) state->fileDialogActive = false;
#ifdef PLATFORM_DESKTOP
if (GuiButton((Rectangle){ state->position.x + winWidth - 120, state->position.y + winHeight - 30, 110, 25 }, "Quit")) state->fileDialogActive = false;
#endif
// File dialog has been closed!
if (!state->fileDialogActive)
{
// RL_FREE dirFiles memory
for (int i = 0; i < state->dirFilesCount; i++)
{
RL_FREE(state->dirFiles[i]);
RL_FREE(dirFilesIcon[i]);
}
RL_FREE(state->dirFiles);
RL_FREE(dirFilesIcon);
dirFilesIcon = NULL;
state->dirFiles = NULL;
}
}
}
// Read all filenames from directory (supported file types)
static inline int _file_comp(const char *d1, const char *d2, const char *dir)
{
const bool b1 = DirectoryExists(TextFormat("%s/%s", dir, d1));
const bool b2 = DirectoryExists(TextFormat("%s/%s", dir, d2));
if (b1 && !b2) return -1;
if (!b1 && b2) return 1;
if (!FileExists(TextFormat("%s/%s", dir, d1))) return 1;
if (!FileExists(TextFormat("%s/%s", dir, d2))) return -1;
return strcmp(d1, d2);
}
static char **ReadDirectoryFiles(const char *dir, int *filesCount, char *filterExt)
{
int validFilesCount = 0;
char **validFiles = (char **)RL_MALLOC(MAX_DIRECTORY_FILES*sizeof(char *)); // Max files to read
for (int i = 0; i < MAX_DIRECTORY_FILES; i++) validFiles[i] = (char *)RL_MALLOC(MAX_DIR_PATH_LENGTH); // Max file name length
int filterExtCount = 0;
const char **extensions = GuiTextSplit(filterExt, &filterExtCount, NULL);
bool filterExtensions = true;
int dirFilesCount = 0;
char **files = GetDirectoryFiles(dir, &dirFilesCount);
// Sort files and directories: dir by name + files by name
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Sorting/Quicksort#C
if (dirFilesCount > 1)
{
const int MAX = 64;
unsigned int left = 0, stack[64], pos = 0, seed = rand(), len = dirFilesCount;
for ( ; ; )
{
for (; left+1 < len; len++) /* sort left to len-1 */
{
if (pos == MAX) len = stack[pos = 0]; /* stack overflow, reset */
char *pivot = files[left+seed%(len-left)]; /* pick random pivot */
seed = seed*69069+1; /* next pseudorandom number */
stack[pos++] = len; /* sort right part later */
for (unsigned int right = left-1; ; ) /* inner loop: partitioning */
{
while (_file_comp(files[++right], pivot, dir) < 0);/* look for greater element */
while (_file_comp(pivot, files[--len], dir) < 0); /* look for smaller element */
if (right >= len) break; /* partition point found? */
char *temp = files[right];
files[right] = files[len]; /* the only swap */
files[len] = temp;
} /* partitioned, continue left part */
}
if (pos == 0) break; /* stack empty? */
left = len; /* left to right is sorted */
len = stack[--pos]; /* get next range to sort */
}
}
if (TextIsEqual(extensions[0], "all")) filterExtensions = false;
for (int i = 0; (i < dirFilesCount) && (validFilesCount < MAX_DIRECTORY_FILES); i++)
{
if (TextIsEqual(files[i], ".")) continue;
if (!filterExtensions)
{
strncpy(validFiles[validFilesCount], files[i], MAX_DIR_PATH_LENGTH);
// Only filter files by extensions, directories should be available
if (DirectoryExists(TextFormat("%s/%s", dir, files[i]))) strcpy(dirFilesIcon[validFilesCount], TextFormat("%s", files[i]));
else
{
// TODO: Assign custom filetype icons depending on file extension (image, audio, text, video, models...)
if (IsFileExtension(files[i], ".png")) strcpy(dirFilesIcon[validFilesCount], TextFormat("%s", files[i]));
else strcpy(dirFilesIcon[validFilesCount], TextFormat("%s", files[i]));
}
validFilesCount++;
}
else
{
for (int j = 0; j < filterExtCount; j++)
{
// Check file type extensions supported
// NOTE: We just store valid files list
if (IsFileExtension(files[i], extensions[j]))
{
// TODO: Assign custom filetype icons depending on file extension (image, audio, text, video, models...)
if (IsFileExtension(files[i], ".png")) strcpy(dirFilesIcon[validFilesCount], TextFormat("%s", files[i]));
else strcpy(dirFilesIcon[validFilesCount], TextFormat("%s", files[i]));
validFilesCount++;
}
}
}
}
ClearDirectoryFiles();
*filesCount = validFilesCount;
return validFiles;
}
#if defined(USE_CUSTOM_LISTVIEW_FILEINFO)
// List View control for files info with extended parameters
static int GuiListViewFiles(Rectangle bounds, FileInfo *files, int count, int *focus, int *scrollIndex, int active)
{
GuiControlState state = guiState;
int itemFocused = (focus == NULL)? -1 : *focus;
int itemSelected = active;
// Check if we need a scroll bar
bool useScrollBar = false;
if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING))*count > bounds.height) useScrollBar = true;
// Define base item rectangle [0]
Rectangle itemBounds = { 0 };
itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING);
itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH);
itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) - GuiGetStyle(DEFAULT, BORDER_WIDTH);
itemBounds.height = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH);
// Get items on the list
int visibleItems = bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
if (visibleItems > count) visibleItems = count;
int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex;
if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0;
int endIndex = startIndex + visibleItems;
// Update control
//--------------------------------------------------------------------
if ((state != GUI_STATE_DISABLED) && !guiLocked)
{
Vector2 mousePoint = GetMousePosition();
// Check mouse inside list view
if (CheckCollisionPointRec(mousePoint, bounds))
{
state = GUI_STATE_FOCUSED;
// Check focused and selected item
for (int i = 0; i < visibleItems; i++)
{
if (CheckCollisionPointRec(mousePoint, itemBounds))
{
itemFocused = startIndex + i;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) itemSelected = startIndex + i;
break;
}
// Update item rectangle y position for next item
itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
}
if (useScrollBar)
{
int wheelMove = GetMouseWheelMove();
startIndex -= wheelMove;
if (startIndex < 0) startIndex = 0;
else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems;
endIndex = startIndex + visibleItems;
if (endIndex > count) endIndex = count;
}
}
else itemFocused = -1;
// Reset item rectangle y to [0]
itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH);
}
//--------------------------------------------------------------------
// Draw control
//--------------------------------------------------------------------
DrawRectangleRec(bounds, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background
DrawRectangleLinesEx(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), guiAlpha));
// TODO: Draw list view header with file sections: icon+name | size | type | modTime
// Draw visible items
for (int i = 0; i < visibleItems; i++)
{
if (state == GUI_STATE_DISABLED)
{
if ((startIndex + i) == itemSelected)
{
DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED)), guiAlpha));
DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), guiAlpha));
}
// TODO: Draw full file info line: icon+name | size | type | modTime
GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED)), guiAlpha));
}
else
{
if ((startIndex + i) == itemSelected)
{
// Draw item selected
DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha));
DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), guiAlpha));
GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED)), guiAlpha));
}
else if ((startIndex + i) == itemFocused)
{
// Draw item focused
DrawRectangleRec(itemBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED)), guiAlpha));
DrawRectangleLinesEx(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), guiAlpha));
GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED)), guiAlpha));
}
else
{
// Draw item normal
GuiDrawText(files[startIndex + i].name, GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL)), guiAlpha));
}
}
// Update item rectangle y position for next item
itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING));
}
if (useScrollBar)
{
Rectangle scrollBarBounds = {
bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH),
bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH),
bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH)
};
// Calculate percentage of visible items and apply same percentage to scrollbar
float percentVisible = (float)(endIndex - startIndex)/count;
float sliderSize = bounds.height*percentVisible;
int prevSliderSize = GuiGetStyle(SCROLLBAR, SLIDER_WIDTH); // Save default slider size
int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed
GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, sliderSize); // Change slider size
GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed
startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems);
GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default
GuiSetStyle(SCROLLBAR, SLIDER_WIDTH, prevSliderSize); // Reset slider size to default
}
//--------------------------------------------------------------------
if (focus != NULL) *focus = itemFocused;
if (scrollIndex != NULL) *scrollIndex = startIndex;
return itemSelected;
}
#endif // USE_CUSTOM_LISTVIEW_FILEINFO
#endif // GUI_FILE_DIALOG_IMPLEMENTATION

789
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 )
{
pJson= jReadGetString( ++pJson, &jElement, '\"' );
if( jElement.dataType != JREAD_STRING )
{
pResult->error= 3; // Expected "key"
break;
}
if( pResult->elements == keyIndex ) // if passed keyIndex
{
*pResult= jElement; // we return "key" at this index
pResult->dataType= JREAD_KEY;
return pJson;
}
pJson= jReadFindTok( pJson, &jTok );
if( jTok != JREAD_COLON )
{
pResult->error= 4; // Expected ":"
break;
}
pJson= jRead( ++pJson, "", &jElement );
if( pResult->error )
break;
pJson= jReadFindTok( pJson, &jTok );
pResult->elements++;
if( jTok == JREAD_EOBJECT )
{
pJson++;
break;
}
if( jTok != JREAD_COMMA )
{
pResult->error= 6; // Expected "," in object
break;
}
}
}
if( keyIndex >= 0 )
{
// we wanted a "key" value - that we didn't find
pResult->dataType= JREAD_ERROR;
pResult->error= 11; // Object key not found (bad index)
}else{
pResult->bytelen= pJson - (char *)pResult->pValue;
}
return pJson;
}
// jReadCountArray
// - used when query ends at an array, we want to return the array length
// - on entry pJson -> "[... "
// - used to skip unwanted values which are arrays
//
char * jReadCountArray( char *pJson, struct jReadElement *pResult )
{
struct jReadElement jElement;
int jTok;
char *sp;
pResult->dataType= JREAD_ARRAY;
pResult->error= 0;
pResult->elements= 0;
pResult->pValue= pJson;
sp= jReadFindTok( pJson+1, &jTok ); // check for empty array
if( jTok == JREAD_EARRAY )
{
pJson= sp+1;
}else
{
while( 1 )
{
pJson= jRead( ++pJson, "", &jElement ); // array value
if( pResult->error )
break;
pJson= jReadFindTok( pJson, &jTok ); // , or ]
pResult->elements++;
if( jTok == JREAD_EARRAY )
{
pJson++;
break;
}
if( jTok != JREAD_COMMA )
{
pResult->error= 9; // Expected "," in array
break;
}
}
}
pResult->bytelen= pJson - (char *)pResult->pValue;
return pJson;
}
// jReadArrayStep()
// - reads one value from an array
// - assumes pJsonArray points at the start of an array or array element
//
char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult )
{
int jTok;
pJsonArray= jReadFindTok( pJsonArray, &jTok );
switch( jTok )
{
case JREAD_ARRAY: // start of array
case JREAD_COMMA: // element separator
return jRead( ++pJsonArray, "", pResult );
case JREAD_EARRAY: // end of array
pResult->error= 13; // End of array found
break;
default: // some other error
pResult->error= 9; // expected comma in array
break;
}
pResult->dataType= JREAD_ERROR;
return pJsonArray;
}
// jRead
// - reads a complete JSON <value>
// - matches pQuery against pJson, results in pResult
// returns: pointer into pJson
//
// Note: is recursive
//
char * jRead( char *pJson, char *pQuery, struct jReadElement *pResult )
{
return jReadParam( pJson, pQuery, pResult, NULL );
}
char * jReadParam( char *pJson, char *pQuery, struct jReadElement *pResult, int *queryParams )
{
int qTok, jTok, bytelen;
unsigned int index, count;
struct jReadElement qElement, jElement;
pJson= jReadFindTok( pJson, &jTok );
pQuery= jReadFindTok( pQuery, &qTok );
pResult->dataType= jTok;
pResult->bytelen= pResult->elements= pResult->error= 0;
pResult->pValue= pJson;
if( (qTok != JREAD_EOL) && (qTok != jTok) )
{
pResult->error= 1; // JSON does not match Query
return pJson;
}
switch( jTok )
{
case JREAD_ERROR: // general error, eof etc.
pResult->error= 2; // Error reading JSON value
break;
case JREAD_OBJECT: // "{"
if( qTok == JREAD_EOL )
return jReadCountObject( pJson, pResult, -1 ); // return length of object
pQuery= jReadFindTok( ++pQuery, &qTok ); // "('key'...", "{NUMBER", "{*" or EOL
if( qTok != JREAD_STRING )
{
index= 0;
switch( qTok )
{
case JREAD_NUMBER:
pQuery= jRead_atoi( (char *)pQuery, &index ); // index value
break;
case JREAD_QPARAM:
pQuery++;
index= (queryParams != NULL) ? *queryParams++ : 0; // substitute parameter
break;
default:
pResult->error= 12; // Bad Object key
return pJson;
}
return jReadCountObject( pJson, pResult, index );
}
pQuery= jReadGetString( pQuery, &qElement, QUERY_QUOTE ); // qElement = query 'key'
//
// read <key> : <value> , ... }
// loop 'til key matched
//
while( 1 )
{
pJson= jReadGetString( ++pJson, &jElement, '\"' );
if( jElement.dataType != JREAD_STRING )
{
pResult->error= 3; // Expected "key"
break;
}
pJson= jReadFindTok( pJson, &jTok );
if( jTok != JREAD_COLON )
{
pResult->error= 4; // Expected ":"
break;
}
// compare object keys
if( jReadStrcmp( &qElement, &jElement ) == 0 )
{
// found object key
return jReadParam( ++pJson, pQuery, pResult, queryParams );
}
// no key match... skip this value
pJson= jRead( ++pJson, "", pResult );
pJson= jReadFindTok( pJson, &jTok );
if( jTok == JREAD_EOBJECT )
{
pResult->error= 5; // Object key not found
break;
}
if( jTok != JREAD_COMMA )
{
pResult->error= 6; // Expected "," in object
break;
}
}
break;
case JREAD_ARRAY: // "[NUMBER" or "[*"
//
// read index, skip values 'til index
//
if( qTok == JREAD_EOL )
return jReadCountArray( pJson, pResult ); // return length of object
index= 0;
pQuery= jReadFindTok( ++pQuery, &qTok ); // "[NUMBER" or "[*"
if( qTok == JREAD_NUMBER )
{
pQuery= jRead_atoi( pQuery, &index ); // get array index
}else if( qTok == JREAD_QPARAM )
{
pQuery++;
index= (queryParams != NULL) ? *queryParams++ : 0; // substitute parameter
}
count=0;
while( 1 )
{
if( count == index )
return jReadParam( ++pJson, pQuery, pResult, queryParams ); // return value at index
// not this index... skip this value
pJson= jRead( ++pJson, "", &jElement );
if( pResult->error )
break;
count++;
pJson= jReadFindTok( pJson, &jTok ); // , or ]
if( jTok == JREAD_EARRAY )
{
pResult->error= 10; // Array element not found (bad index)
break;
}
if( jTok != JREAD_COMMA )
{
pResult->error= 9; // Expected "," in array
break;
}
}
break;
case JREAD_STRING: // "string"
pJson= jReadGetString( pJson, pResult, '\"' );
break;
case JREAD_NUMBER: // number (may be -ve) int or float
case JREAD_BOOL: // true or false
case JREAD_NULL: // null
bytelen= jReadTextLen( pJson );
pResult->dataType= jTok;
pResult->bytelen= bytelen;
pResult->pValue= pJson;
pResult->elements= 1;
pJson += bytelen;
break;
default:
pResult->error= 8; // unexpected character (in pResult->dataType)
}
// We get here on a 'terminal value'
// - make sure the query string is empty also
pQuery= jReadFindTok( pQuery, &qTok );
if( !pResult->error && (qTok != JREAD_EOL) )
pResult->error= 7; // terminal value found before end of query
if( pResult->error )
{
pResult->dataType= JREAD_ERROR;
pResult->elements= pResult->bytelen= 0;
pResult->pValue= pJson; // return pointer into JSON at error point
}
return pJson;
}
//--------------------------------------------------------------------
// Optional helper functions
// - simple routines to extract values from JSON
// - does coercion of types where possible
// - always returns a value (e.g. 0 or "" on error)
//
// Note: by default, pass NULL for queryParams
// unless you are using '*' in the query for indexing
//
// jRead_long
// - reads signed long value from JSON
// - returns number from NUMBER or STRING elements (if possible)
// returns 1 or 0 from BOOL elements
// otherwise returns 0
//
long jRead_long( char *pJson, char *pQuery, int *queryParams )
{
struct jReadElement elem;
long result;
jReadParam( pJson, pQuery, &elem, queryParams );
if( (elem.dataType == JREAD_ERROR) || (elem.dataType == JREAD_NULL))
return 0;
if( elem.dataType == JREAD_BOOL )
return *((char *)elem.pValue)=='t' ? 1 : 0;
jRead_atol( (char *)elem.pValue, &result );
return result;
}
int jRead_int( char *pJson, char *pQuery, int *queryParams )
{
return (int)jRead_long( pJson, pQuery, queryParams );
}
// jRead_double
// - returns double from JSON
// - returns number from NUMBER or STRING elements
// otherwise returns 0.0
//
double jRead_double( char *pJson, char *pQuery, int *queryParams )
{
struct jReadElement elem;
double result;
jReadParam( pJson, pQuery, &elem, queryParams );
if( elem.dataType == JREAD_ERROR )
return 0.0;
jRead_atof( (char *)elem.pValue, &result );
return result;
}
// jRead_string
// Copy string to pDest and '\0'-terminate it (upto destlen total bytes)
// returns: character length of string (excluding '\0' terminator)
//
// Note: any element can be returned as a string
//
int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen, int *queryParams )
{
struct jReadElement elem;
int i;
*pDest= '\0';
jReadParam( pJson, pQuery, &elem, queryParams );
if( elem.dataType == JREAD_ERROR )
return 0;
for( i=0; (i<elem.bytelen) && (i<destlen-1); i++ )
*pDest++ = ((char *)elem.pValue)[i];
*pDest= '\0';
return elem.bytelen;
}
//-------------------------------------------------
// Optional String output Functions
//
char *jReadTypeStrings[]={
"Error", // 0
"Object", // 1
"Array", // 2
"String", // 3
"Number", // 4
"Bool", // 5
"null", // 6
"Object key", // 7
"colon", // 8
"eol", // 9
"comma", // 10
"}", // 11
"]", // 12
"* parameter" // 13
};
char *jReadTypeToString( int dataType )
{
return jReadTypeStrings[ dataType ];
};
char * jReadErrorStrings[]={
"Ok", // 0
"JSON does not match Query", // 1
"Error reading JSON value", // 2
"Expected \"key\"", // 3
"Expected ':'", // 4
"Object key not found", // 5
"Expected ',' in object", // 6
"Terminal value found before end of query", // 7
"Unexpected character", // 8
"Expected ',' in array", // 9
"Array element not found (bad index)", // 10
"Object key not found (bad index)", // 11
"Bad object key", // 12
"End of array found", // 13
"End of object found" // 14
};
char * jReadErrorToString( int error )
{
if( (error >=0 ) && (error <= 14))
return jReadErrorStrings[ error ];
return "Unknown error";
};
// end of jRead.c

134
jRead.h

@ -0,0 +1,134 @@
//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.h
//
// see jRead.c for more information
//
// uncomment this if you really want to use double quotes in query strings instead of '
//#define JREAD_DOUBLE_QUOTE_IN_QUERY
//
// return dataTypes:
#define JREAD_ERROR 0 // general error, eof etc.
#define JREAD_OBJECT 1 // "{"
#define JREAD_ARRAY 2 // "["
#define JREAD_STRING 3 // "string"
#define JREAD_NUMBER 4 // number (may be -ve) int or float
#define JREAD_BOOL 5 // true or false
#define JREAD_NULL 6 // null
#define JREAD_KEY 7 // object "key"
// internal values:
#define JREAD_COLON 8 // ":"
#define JREAD_EOL 9 // end of input string (ptr at '\0')
#define JREAD_COMMA 10 // ","
#define JREAD_EOBJECT 11 // "}"
#define JREAD_EARRAY 12 // "]"
#define JREAD_QPARAM 13 // "*" query string parameter
//------------------------------------------------------
// jReadElement
// - structure to return JSON elements
// - error=0 for valid returns
//
// *NOTES*
// the returned pValue pointer points into the passed JSON
// string returns are not '\0' terminated.
// bytelen specifies the length of the returned data pointed to by pValue
//
struct jReadElement{
int dataType; // one of JREAD_...
int elements; // number of elements (e.g. elements in array or object)
int bytelen; // byte length of element (e.g. length of string, array text "[ ... ]" etc.)
void * pValue; // pointer to value string in JSON text
int error; // error value if dataType == JREAD_ERROR
};
//------------------------------------------------------
// The JSON reader function
//
// - reads a '\0'-terminated JSON text string from pJson
// - traverses the JSON according to the pQuery string
// - returns the result value in pResult
//
// returns: pointer into pJson after the queried value
//
// e.g.
// With JSON like: "{ ..., "key":"value", ... }"
//
// jRead( pJson, "{'key'", &result );
// returns with:
// result.dataType= JREAD_STRING, result.pValue->'value', result.bytelen=5
//
char * jRead( char *pJson, char *pQuery, struct jReadElement *pResult );
// version of jRead which allows one or more queryParam integers to be substituted
// for array or object indexes marked by a '*' in the query
//
// e.g. jReadParam( pJson, "[*", &resultElement, &arrayIndex );
//
// *!* CAUTION *!*
// You can supply an array of integers which are indexed for each '*' in pQuery
// however, horrid things will happen if you don't supply enough parameters
//
char * jReadParam( char *pJson, char *pQuery, struct jReadElement *pResult, int *queryParams );
// Array Stepping function
// - assumes pJsonArray is JSON source of an array "[ ... ]"
// - returns next element of the array in pResult
// - returns pointer to end of element, to be passed to next call of jReadArrayStep()
// - if end of array is encountered, pResult->error = 13 "End of array found"
//
// e.g.
// With JSON like: "{ ... "arrayInObject":[ elem1,elem2,... ], ... }"
//
// pJson= jRead( pJson, "{'arrayInObject'", &theArray );
// if( theArray.dataType == JREAD_ARRAY )
// {
// char *pArray= (char *)theArray.pValue;
// jReadElement arrayElement;
// int index;
// for( index=0; index < theArray.elements; index++ )
// {
// pArray= jReadArrayStep( pArray, &arrayElement );
// ...
//
// Note: this significantly speeds up traversing arrays.
//
char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult );
#define EXPORT_OPTIONAL_FUNCTIONS
#ifdef EXPORT_OPTIONAL_FUNCTIONS
//------------------------------------------------------
// Optional Helper Functions
//
long jRead_long( char *pJson, char *pQuery, int *queryParams );
int jRead_int( char *pJson, char *pQuery, int *queryParams );
double jRead_double( char *pJson, char *pQuery, int *queryParams );
int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen, int *queryParams );
//------------------------------------------------------
// Optional String output Functions
//
char *jReadTypeToString( int dataType ); // string describes dataType
char * jReadErrorToString( int error ); // string descibes error code
//------------------------------------------------------
// Other jRead utilities which may be useful...
//
char * jRead_atoi( char *p, unsigned int *result ); // string to unsigned int
char * jRead_atol( char *p, long *result ); // string to signed long
char * jRead_atof( char *p, double *result); // string to double (does not do exponents)
int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 ); // compare STRING elements
// copy element to '\0'-terminated buffer
char * jRead_strcpy( char *destBuffer, int destLength, struct jReadElement *pElement );
#endif
// end of jRead.h

566
jWrite.c

@ -0,0 +1,566 @@
//Author: Tony Wilk
//Source: https://www.codeproject.com/Articles/887604/jWrite-A-Really-Simple-JSON-Writer-in-C
//LICENSE: The Code Project Open License (CPOL) 1.02
//see provided file ICENSE_jReadJWrite.html or https://www.codeproject.com/info/cpol10.aspx
//
// jWrite.c version 1v2
//
// A *really* simple JSON writer in C
//
// see: jWrite.h for info
//
// TonyWilk, Mar 2015
//
#define _CRT_SECURE_NO_WARNINGS // stop complaining about deprecated functions
#include <stddef.h>
#include <stdio.h>
#include <string.h> // memset()
#include "jWrite.h"
//#include <stdint.h> // definintion of uint32_t, int32_t
typedef unsigned int uint32_t;
typedef int int32_t;
// the jWrite functions take the above jWriteControl structure pointer
// to maintain state while writing a JSON string.
//
// You can opt to use a single global instance of a jWriteControl structure
// which simplifies the function parameters or to supply your own structure
//
#ifdef JW_GLOBAL_CONTROL_STRUCT
struct jWriteControl g_jWriteControl; // global control struct
#define JWC_DECL // function parameter decl is empty
#define JWC_DECL0
#define JWC(x) g_jWriteControl.x // functions access global
#define JWC_PARAM // pointer to struct is empty
#define JWC_PARAM0
#else
#define JWC_DECL struct jWriteControl *jwc, // function parameter is ptr to control struct
#define JWC_DECL0 struct jWriteControl *jwc // function parameter, no params
#define JWC(x) jwc->x // functions use pointer
#define JWC_PARAM jwc, // pointer to stuct
#define JWC_PARAM0 jwc // pointer to stuct, no params
#endif
//------------------------------------------
// Internal functions
//
void jwPutch( JWC_DECL char c );
void jwPutstr( JWC_DECL char *str );
void jwPutraw( JWC_DECL char *str );
void modp_itoa10(int32_t value, char* str);
void modp_dtoa2(double value, char* str, int prec);
void jwPretty( JWC_DECL0 );
enum jwNodeType jwPop( JWC_DECL0 );
void jwPush( JWC_DECL enum jwNodeType nodeType );
//------------------------------------------
// jwOpen
// - open writing of JSON starting with rootType = JW_OBJECT or JW_ARRAY
// - initialise with user string buffer of length buflen
// - isPretty=JW_PRETTY adds \n and spaces to prettify output (else JW_COMPACT)
//
void jwOpen( JWC_DECL char *buffer, unsigned int buflen,
enum jwNodeType rootType, int isPretty )
{
memset( buffer, 0, buflen ); // zap the whole destination buffer
JWC(buffer)= buffer;
JWC(buflen)= buflen;
JWC(bufp)= buffer;
JWC(nodeStack)[0].nodeType= rootType;
JWC(nodeStack)[0].elementNo= 0;
JWC(stackpos)=0;
JWC(error)= JWRITE_OK;
JWC(callNo)= 1;
JWC(isPretty)= isPretty;
jwPutch( JWC_PARAM (rootType==JW_OBJECT) ? '{' : '[' );
}
//------------------------------------------
// jwClose
// - closes the root JSON object started by jwOpen()
// - returns error code
//
int jwClose( JWC_DECL0 )
{
if( JWC(error) == JWRITE_OK )
{
if( JWC(stackpos) == 0 )
{
enum jwNodeType node= JWC(nodeStack)[0].nodeType;
if( JWC(isPretty) )
jwPutch( JWC_PARAM '\n' );
jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']');
}else{
JWC(error)= JWRITE_NEST_ERROR; // nesting error, not all objects closed when jwClose() called
}
}
return JWC(error);
}
//------------------------------------------
// End the current array/object
//
int jwEnd( JWC_DECL0 )
{
if( JWC(error) == JWRITE_OK )
{
enum jwNodeType node;
int lastElemNo= JWC(nodeStack)[JWC(stackpos)].elementNo;
node= jwPop( JWC_PARAM0 );
if( lastElemNo > 0 )
jwPretty( JWC_PARAM0 );
jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']');
}
return JWC(error);
}
//------------------------------------------
// jwErrorPos
// - Returns position of error: the nth call to a jWrite function
//
int jwErrorPos( JWC_DECL0 )
{
return JWC(callNo);
}
//------------------------------------------
// Object insert functions
//
int _jwObj( JWC_DECL char *key );
// put raw string to object (i.e. contents of rawtext without quotes)
//
void jwObj_raw( JWC_DECL char *key, char *rawtext )
{
if(_jwObj( JWC_PARAM key ) == JWRITE_OK)
jwPutraw( JWC_PARAM rawtext);
}
// put "quoted" string to object
//
void jwObj_string( JWC_DECL char *key, char *value )
{
if(_jwObj( JWC_PARAM key ) == JWRITE_OK)
jwPutstr( JWC_PARAM value );
}
void jwObj_int( JWC_DECL char *key, int value )
{
modp_itoa10( value, JWC(tmpbuf) );
jwObj_raw( JWC_PARAM key, JWC(tmpbuf) );
}
void jwObj_double( JWC_DECL char *key, double value )
{
modp_dtoa2( value, JWC(tmpbuf), 6 );
jwObj_raw( JWC_PARAM key, JWC(tmpbuf) );
}
void jwObj_bool( JWC_DECL char *key, int oneOrZero )
{
jwObj_raw( JWC_PARAM key, (oneOrZero) ? "true" : "false" );
}
void jwObj_null( JWC_DECL char *key )
{
jwObj_raw( JWC_PARAM key, "null" );
}
// put Object in Object
//
void jwObj_object( JWC_DECL char *key )
{
if(_jwObj( JWC_PARAM key ) == JWRITE_OK)
{
jwPutch( JWC_PARAM '{' );
jwPush( JWC_PARAM JW_OBJECT );
}
}
// put Array in Object
//
void jwObj_array( JWC_DECL char *key )
{
if(_jwObj( JWC_PARAM key ) == JWRITE_OK)
{
jwPutch( JWC_PARAM '[' );
jwPush( JWC_PARAM JW_ARRAY );
}
}
//------------------------------------------
// Array insert functions
//
int _jwArr( JWC_DECL0 );
// put raw string to array (i.e. contents of rawtext without quotes)
//
void jwArr_raw( JWC_DECL char *rawtext )
{
if(_jwArr( JWC_PARAM0 ) == JWRITE_OK)
jwPutraw( JWC_PARAM rawtext);
}
// put "quoted" string to array
//
void jwArr_string( JWC_DECL char *value )
{
if(_jwArr( JWC_PARAM0 ) == JWRITE_OK)
jwPutstr( JWC_PARAM value );
}
void jwArr_int( JWC_DECL int value )
{
modp_itoa10( value, JWC(tmpbuf) );
jwArr_raw( JWC_PARAM JWC(tmpbuf) );
}
void jwArr_double( JWC_DECL double value )
{
modp_dtoa2( value, JWC(tmpbuf), 6 );
jwArr_raw( JWC_PARAM JWC(tmpbuf) );
}
void jwArr_bool( JWC_DECL int oneOrZero )
{
jwArr_raw( JWC_PARAM (oneOrZero) ? "true" : "false" );
}
void jwArr_null( JWC_DECL0 )
{
jwArr_raw( JWC_PARAM "null" );
}
void jwArr_object( JWC_DECL0 )
{
if(_jwArr( JWC_PARAM0 ) == JWRITE_OK)
{
jwPutch( JWC_PARAM '{' );
jwPush( JWC_PARAM JW_OBJECT );
}
}
void jwArr_array( JWC_DECL0 )
{
if(_jwArr( JWC_PARAM0 ) == JWRITE_OK)
{
jwPutch( JWC_PARAM '[' );
jwPush( JWC_PARAM JW_ARRAY );
}
}
//------------------------------------------
// jwErrorToString
// - returns string describing error code
//
char *jwErrorToString( int err )
{
switch( err )
{
case JWRITE_OK: return "OK";
case JWRITE_BUF_FULL: return "output buffer full";
case JWRITE_NOT_ARRAY: return "tried to write Array value into Object";
case JWRITE_NOT_OBJECT: return "tried to write Object key/value into Array";
case JWRITE_STACK_FULL: return "array/object nesting > JWRITE_STACK_DEPTH";
case JWRITE_STACK_EMPTY:return "stack underflow error (too many 'end's)";
case JWRITE_NEST_ERROR: return "nesting error, not all objects closed when jwClose() called";
}
return "Unknown error";
}
//============================================================================
// Internal functions
//
void jwPretty( JWC_DECL0 )
{
int i;
if( JWC(isPretty) )
{
jwPutch( JWC_PARAM '\n' );
for( i=0; i<JWC(stackpos)+1; i++ )
jwPutraw( JWC_PARAM " " );
}
}
// Push / Pop node stack
//
void jwPush( JWC_DECL enum jwNodeType nodeType )
{
if( (JWC(stackpos)+1) >= JWRITE_STACK_DEPTH )
JWC(error)= JWRITE_STACK_FULL; // array/object nesting > JWRITE_STACK_DEPTH
else
{
JWC(nodeStack[++JWC(stackpos)]).nodeType= nodeType;
JWC(nodeStack[JWC(stackpos)]).elementNo= 0;
}
}
enum jwNodeType jwPop( JWC_DECL0 )
{
enum jwNodeType retval= JWC(nodeStack[JWC(stackpos)]).nodeType;
if( JWC(stackpos) == 0 )
JWC(error)= JWRITE_STACK_EMPTY; // stack underflow error (too many 'end's)
else
JWC(stackpos)--;
return retval;
}
void jwPutch( JWC_DECL char c )
{
if( (unsigned int)(JWC(bufp) - JWC(buffer)) >= JWC(buflen) )
{
JWC(error)= JWRITE_BUF_FULL;
}else{
*JWC(bufp)++ = c;
}
}
// put string enclosed in quotes
//
void jwPutstr( JWC_DECL char *str )
{
jwPutch( JWC_PARAM '\"' );
while( *str != '\0' )
jwPutch( JWC_PARAM *str++ );
jwPutch( JWC_PARAM '\"' );
}
// put raw string
//
void jwPutraw( JWC_DECL char *str )
{
while( *str != '\0' )
jwPutch( JWC_PARAM *str++ );
}
// *common Object function*
// - checks error
// - checks current node is OBJECT
// - adds comma if reqd
// - adds "key" :
//
int _jwObj( JWC_DECL char *key )
{
if(JWC(error) == JWRITE_OK)
{
JWC(callNo)++;
if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_OBJECT )
JWC(error)= JWRITE_NOT_OBJECT; // tried to write Object key/value into Array
else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 )
jwPutch( JWC_PARAM ',' );
jwPretty( JWC_PARAM0 );
jwPutstr( JWC_PARAM key );
jwPutch( JWC_PARAM ':' );
if( JWC(isPretty) )
jwPutch( JWC_PARAM ' ' );
}
return JWC(error);
}
// *common Array function*
// - checks error
// - checks current node is ARRAY
// - adds comma if reqd
//
int _jwArr( JWC_DECL0 )
{
if(JWC(error) == JWRITE_OK)
{
JWC(callNo)++;
if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_ARRAY )
JWC(error)= JWRITE_NOT_ARRAY; // tried to write array value into Object
else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 )
jwPutch( JWC_PARAM ',' );
jwPretty( JWC_PARAM0 );
}
return JWC(error);
}
//=================================================================
//
// modp value-to-string functions
// - modified for C89
//
// We use these functions as they are a lot faster than sprintf()
//
// Origin of these routines:
/*
* <pre>
* Copyright &copy; 2007, Nick Galbreath -- nickg [at] modp [dot] com
* All rights reserved.
* http://code.google.com/p/stringencoders/
* Released under the bsd license.
* </pre>
*/
static void strreverse(char* begin, char* end)
{
char aux;
while (end > begin)
aux = *end, *end-- = *begin, *begin++ = aux;
}
/** \brief convert an signed integer to char buffer
*
* \param[in] value
* \param[out] buf the output buffer. Should be 16 chars or more.
*/
void modp_itoa10(int32_t value, char* str)
{
char* wstr=str;
// Take care of sign
unsigned int uvalue = (value < 0) ? -value : value;
// Conversion. Number is reversed.
do *wstr++ = (char)(48 + (uvalue % 10)); while(uvalue /= 10);
if (value < 0) *wstr++ = '-';
*wstr='\0';
// Reverse string
strreverse(str,wstr-1);
}
/**
* Powers of 10
* 10^0 to 10^9
*/
static const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000};
/** \brief convert a floating point number to char buffer with a
* variable-precision format, and no trailing zeros
*
* This is similar to "%.[0-9]f" in the printf style, except it will
* NOT include trailing zeros after the decimal point. This type
* of format oddly does not exists with printf.
*
* If the input value is greater than 1<<31, then the output format
* will be switched exponential format.
*
* \param[in] value
* \param[out] buf The allocated output buffer. Should be 32 chars or more.
* \param[in] precision Number of digits to the right of the decimal point.
* Can only be 0-9.
*/
void modp_dtoa2(double value, char* str, int prec)
{
/* if input is larger than thres_max, revert to exponential */
const double thres_max = (double)(0x7FFFFFFF);
int count;
double diff = 0.0;
char* wstr = str;
int neg= 0;
int whole;
double tmp;
uint32_t frac;
/* Hacky test for NaN
* under -fast-math this won't work, but then you also won't
* have correct nan values anyways. The alternative is
* to link with libmath (bad) or hack IEEE double bits (bad)
*/
if (! (value == value)) {
str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\0';
return;
}
if (prec < 0) {
prec = 0;
} else if (prec > 9) {
/* precision of >= 10 can lead to overflow errors */
prec = 9;
}
/* we'll work in positive values and deal with the
negative sign issue later */
if (value < 0) {
neg = 1;
value = -value;
}
whole = (int) value;
tmp = (value - whole) * pow10[prec];
frac = (uint32_t)(tmp);
diff = tmp - frac;
if (diff > 0.5) {
++frac;
/* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */
if (frac >= pow10[prec]) {
frac = 0;
++whole;
}
} else if (diff == 0.5 && ((frac == 0) || (frac & 1))) {
/* if halfway, round up if odd, OR
if last digit is 0. That last part is strange */
++frac;
}
/* for very large numbers switch back to native sprintf for exponentials.
anyone want to write code to replace this? */
/*
normal printf behavior is to print EVERY whole number digit
which can be 100s of characters overflowing your buffers == bad
*/
if (value > thres_max) {
sprintf(str, "%e", neg ? -value : value);
return;
}
if (prec == 0) {
diff = value - whole;
if (diff > 0.5) {
/* greater than 0.5, round up, e.g. 1.6 -> 2 */
++whole;
} else if (diff == 0.5 && (whole & 1)) {
/* exactly 0.5 and ODD, then round up */
/* 1.5 -> 2, but 2.5 -> 2 */
++whole;
}
//vvvvvvvvvvvvvvvvvvv Diff from modp_dto2
} else if (frac) {
count = prec;
// now do fractional part, as an unsigned number
// we know it is not 0 but we can have leading zeros, these
// should be removed
while (!(frac % 10)) {
--count;
frac /= 10;
}
//^^^^^^^^^^^^^^^^^^^ Diff from modp_dto2
// now do fractional part, as an unsigned number
do {
--count;
*wstr++ = (char)(48 + (frac % 10));
} while (frac /= 10);
// add extra 0s
while (count-- > 0) *wstr++ = '0';
// add decimal
*wstr++ = '.';
}
// do whole part
// Take care of sign
// Conversion. Number is reversed.
do *wstr++ = (char)(48 + (whole % 10)); while (whole /= 10);
if (neg) {
*wstr++ = '-';
}
*wstr='\0';
strreverse(str, wstr-1);
}
//=================================================================
/* end of jWrite.c */

218
jWrite.h

@ -0,0 +1,218 @@
//Author: Tony Wilk
//Source: https://www.codeproject.com/Articles/887604/jWrite-A-Really-Simple-JSON-Writer-in-C
//LICENSE: The Code Project Open License (CPOL) 1.02
//see provided file ICENSE_jReadJWrite.html or https://www.codeproject.com/info/cpol10.aspx
//
// jWrite.h
//
// A *really* simple JSON writer in C (C89)
// - a collection of functions to generate JSON semi-automatically
//
// The idea is to simplify writing native C values into a JSON string and
// to provide some error trapping to ensure that the result is valid JSON.
//
// Example:
// jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY ); // open root node as object
// jwObj_string( "key", "value" );
// jwObj_int( "int", 1 );
// jwObj_array( "anArray");
// jwArr_int( 0 );
// jwArr_int( 1 );
// jwArr_int( 2 );
// jwEnd();
// err= jwClose(); // close root object
//
// results in:
//
// {
// "key": "value",
// "int": 1,
// "anArray": [
// 0,
// 1,
// 2
// ]
// }
//
// Note that jWrite handles string quoting and getting commas in the right place.
// If the sequence of calls is incorrect
// e.g.
// jwOpen( buffer, buflen, JW_OBJECT, 1 );
// jwObj_string( "key", "value" );
// jwArr_int( 0 );
// ...
//
// then the error code returned from jwClose() would indicate that you attempted to
// put an array element into an object (instead of a key:value pair)
// To locate the error, the supplied buffer has the JSON created upto the error point
// and a call to jwErrorPos() would return the function call at which the error occurred
// - in this case 3, the 3rd function call "jwArr_int(0)" is not correct at this point.
//
// The root JSON type can be JW_OBJECT or JW_ARRAY.
//
// For more information on each function, see the prototypes below.
//
//
// GLOBAL vs. Application-Supplied Control Structure
// -------------------------------------------------
// jWrite requires a jWriteControl structure to save the internal state.
// For many applications it is much simpler for this to be a global variable as
// used by the above examples.
//
// To use multiple instances of jWrite, an application has to supply unique instances
// of jWriteControl structures.
//
// This feature is enabled by commenting out the definition of JW_GLOBAL_CONTROL_STRUCT
//
// All the jWrite functions then take an additional parameter: a ptr to the structure
// e.g.
// struct jWriteControl jwc;
//
// jwOpen( &jwc, buffer, buflen, JW_OBJECT, 1 );
// jwObj_string( &jwc, "key", "value" );
// jwObj_int( &jwc, "int", 1 );
// jwObj_array( &jwc, "anArray");
// jwArr_int( &jwc, 0 );
// jwArr_int( &jwc, 1 );
// jwArr_int( &jwc, 2 );
// jwEnd( &jwc );
// err= jwClose( &jwc );
//
// - which is more flexible, but a pain to type in !
//
// TonyWilk, Mar 2015
//
//
#define JW_GLOBAL_CONTROL_STRUCT // <--- comment this out to use applic-supplied jWriteControl
#define JWRITE_STACK_DEPTH 32 // max nesting depth of objects/arrays
#define JW_COMPACT 0 // output string control for jwOpen()
#define JW_PRETTY 1 // pretty adds \n and indentation
enum jwNodeType{
JW_OBJECT= 1,
JW_ARRAY
};
struct jwNodeStack{
enum jwNodeType nodeType;
int elementNo;
};
struct jWriteControl{
char *buffer; // pointer to application's buffer
unsigned int buflen; // length of buffer
char *bufp; // current write position in buffer
char tmpbuf[32]; // local buffer for int/double convertions
int error; // error code
int callNo; // API call on which error occurred
struct jwNodeStack nodeStack[JWRITE_STACK_DEPTH]; // stack of array/object nodes
int stackpos;
int isPretty; // 1= pretty output (inserts \n and spaces)
};
// Error Codes
// -----------
#define JWRITE_OK 0
#define JWRITE_BUF_FULL 1 // output buffer full
#define JWRITE_NOT_ARRAY 2 // tried to write Array value into Object
#define JWRITE_NOT_OBJECT 3 // tried to write Object key/value into Array
#define JWRITE_STACK_FULL 4 // array/object nesting > JWRITE_STACK_DEPTH
#define JWRITE_STACK_EMPTY 5 // stack underflow error (too many 'end's)
#define JWRITE_NEST_ERROR 6 // nesting error, not all objects closed when jwClose() called
// API functions
// -------------
// Returns '\0'-termianted string describing the error (as returned by jwClose())
//
char *jwErrorToString( int err );
#ifdef JW_GLOBAL_CONTROL_STRUCT /* USING GLOBAL g_jWriteControl */
// jwOpen
// - initialises jWrite with the application supplied 'buffer' of length 'buflen'
// in operation, the buffer will always contain a valid '\0'-terminated string
// - jWrite will not overrun the buffer (it returns an "output buffer full" error)
// - rootType is the base JSON type: JW_OBJECT or JW_ARRAY
// - isPretty controls 'prettifying' the output: JW_PRETTY or JW_COMPACT
void jwOpen( char *buffer, unsigned int buflen, enum jwNodeType rootType, int isPretty );
// jwClose
// - closes the element opened by jwOpen()
// - returns error code (0 = JWRITE_OK)
// - after an error, all following jWrite calls are skipped internally
// so the error code is for the first error detected
int jwClose( );
// jwErrorPos
// - if jwClose returned an error, this function returns the number of the jWrite function call
// which caused that error.
int jwErrorPos( );
// Object insertion functions
// - used to insert "key":"value" pairs into an object
//
void jwObj_string( char *key, char *value );
void jwObj_int( char *key, int value );
void jwObj_double( char *key, double value );
void jwObj_bool( char *key, int oneOrZero );
void jwObj_null( char *key );
void jwObj_object( char *key );
void jwObj_array( char *key );
// Array insertion functions
// - used to insert "value" elements into an array
//
void jwArr_string( char *value );
void jwArr_int( int value );
void jwArr_double( double value );
void jwArr_bool( int oneOrZero );
void jwArr_null( );
void jwArr_object( );
void jwArr_array( );
// jwEnd
// - defines the end of an Object or Array definition
int jwEnd( );
// these 'raw' routines write the JSON value as the contents of rawtext
// i.e. enclosing quotes are not added
// - use if your app. supplies its own value->string functions
//
void jwObj_raw( char *key, char *rawtext );
void jwArr_raw( char *rawtext );
#else /* JW_GLOBAL_CONTROL_STRUCT not defined */
// Same API functions with app-supplied control struct option
//
void jwOpen( struct jWriteControl *jwc, char *buffer, unsigned int buflen, enum jwNodeType rootType, int isPretty );
int jwClose( struct jWriteControl *jwc );
int jwErrorPos( struct jWriteControl *jwc );
void jwObj_string( struct jWriteControl *jwc, char *key, char *value );
void jwObj_int( struct jWriteControl *jwc, char *key, int value );
void jwObj_double( struct jWriteControl *jwc, char *key, double value );
void jwObj_bool( struct jWriteControl *jwc, char *key, int oneOrZero );
void jwObj_null( struct jWriteControl *jwc, char *key );
void jwObj_object( struct jWriteControl *jwc, char *key );
void jwObj_array( struct jWriteControl *jwc, char *key );
void jwArr_string( struct jWriteControl *jwc, char *value );
void jwArr_int( struct jWriteControl *jwc, int value );
void jwArr_double( struct jWriteControl *jwc, double value );
void jwArr_bool( struct jWriteControl *jwc, int oneOrZero );
void jwArr_null( struct jWriteControl *jwc );
void jwArr_object( struct jWriteControl *jwc );
void jwArr_array( struct jWriteControl *jwc );
int jwEnd( struct jWriteControl *jwc );
void jwObj_raw( struct jWriteControl *jwc, char *key, char *rawtext );
void jwArr_raw( struct jWriteControl *jwc, char *rawtext );
#endif /* JW_GLOBAL_CONTROL_STRUCT */
/* end of jWrite.h */

266
jackclient.c

@ -0,0 +1,266 @@
//Standard lib
#include <string.h> //memcpy
#include <stdio.h> //print error messages
#include <unistd.h>
//Third party
#include <jack/jack.h>
#include <jack/midiport.h>
#include <jack/ringbuffer.h>
#include <jack/metadata.h>
//Our own files
#include "jackclient.h"
#include "constants.h"
#include "programstate.h"
extern ProgramState programState;
static jack_client_t* client;
//static jack_ringbuffer_t *ringbuffer = NULL;
jack_ringbuffer_t *allRingbuffers[VIS_PORTS];
jack_port_t *allPorts[VIS_PORTS];
static pthread_mutex_t msg_thread_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t data_ready = PTHREAD_COND_INITIALIZER;
#define RBSIZE 512
typedef struct {
uint8_t buffer[128];
uint32_t size;
} midimsg;
int process (jack_nframes_t frames, void* arg) {
void* buffer;
jack_nframes_t N;
jack_nframes_t i;
jack_port_t* port;
jack_ringbuffer_t* rb=NULL;
for (int portnum=0; portnum<VIS_PORTS; portnum++) {
port = allPorts[portnum];
rb = allRingbuffers[portnum];
buffer = jack_port_get_buffer (port, frames);
//assert (buffer);
N = jack_midi_get_event_count (buffer);
for (int i = 0; i < N; ++i) {
jack_midi_event_t event;
int r;
r = jack_midi_event_get (&event, buffer, i);
if (r == 0 && jack_ringbuffer_write_space(rb) >= sizeof(midimsg)) {
midimsg message;
message.size = event.size;
memcpy (message.buffer, event.buffer, MAX(sizeof(message.buffer), event.size));
jack_ringbuffer_write (rb, (void *) &message, sizeof(midimsg));
}
}
}
if (pthread_mutex_trylock (&msg_thread_lock) == 0) {
pthread_cond_signal (&data_ready);
pthread_mutex_unlock (&msg_thread_lock);
}
return 0;
}
static void handleMessage (Note *allGUINotes, int portnum, midimsg* event) {
if (event->size == 0) {
return;
}
uint8_t type = event->buffer[0] & 0xf0;
uint8_t channel = event->buffer[0] & 0xf;
Note * note;
switch (type) {
case 0x90:
//assert (event->size == 3);
//printf (" note on (channel %2d): pitch %3d, velocity %3d\n", channel, event->buffer[1], event->buffer[2]);
programState.portActivity[portnum] += 1;
note = &allGUINotes[portnum*128 + event->buffer[1]];
note->active = 1;
note->countdown = 1.0f;
note->velocity = event->buffer[2];
note->pitch = event->buffer[1];
note->port = portnum;
break;
case 0x80:
//assert (event->size == 3);
//printf (" note off (channel %2d): pitch %3d, velocity %3d\n", channel, event->buffer[1], event->buffer[2]);
programState.portActivity[portnum] -= 1;
if (programState.portActivity[portnum] < 1) { programState.portActivity[portnum] = 0;}
note = &allGUINotes[portnum*128 + event->buffer[1]];
note->countdown = 0.999f; //GUI will reduce that to 0 on its own as fade-out effect
//Do NOT set to inactive. Note-Off triggers a graphical fadeout, only when that is over it is inactive.
break;
//case 0xb0:
//assert (event->size == 3);
//printf (" control change (channel %2d): controller %3d, value %3d", channel, event->buffer[1], event->buffer[2]);
// break;
default:
break;
}
}
void mainLoop_processMidiMessages(Note *allGUINotes) {
for (int portnum=0; portnum<VIS_PORTS; portnum++) {
int mqlen = jack_ringbuffer_read_space (allRingbuffers[portnum]) / sizeof(midimsg);
for (int i=0; i < mqlen; ++i) {
size_t j;
midimsg message;
jack_ringbuffer_read(allRingbuffers[portnum], (char*) &message, sizeof(midimsg));
handleMessage (allGUINotes, portnum, &message);
}
}
pthread_cond_wait (&data_ready, &msg_thread_lock);
}
void mainLoop_collectTimingAndTransportInProgramState() {
jack_position_t pos;
jack_transport_state_t state = jack_transport_query(client, &pos);
int totalSeconds = pos.frame / pos.frame_rate;
int hours = totalSeconds / 3600; //int devision drops decimal
int minutes = totalSeconds / 60 % 60;
int seconds = totalSeconds % 60;
snprintf(programState.clockString, (sizeof(char) * 255), "%i:%02i:%02i ( %is )", hours, minutes, seconds, totalSeconds);
programState.transportRolling = state != JackTransportStopped;
if (pos.beats_per_minute > 1.0d) { //comparing to 0 does not work
programState.bpm = pos.beats_per_minute;
}
else
{
programState.bpm = 120.0d;
}
}
void ourConnectCallback(jack_port_id_t a, jack_port_id_t b, int connect, void *arg) {
//On each connection change go through our ports and check the situation.
//Only the first port is enough for our purposes
//TODO: optimisation opportunity: keep a list of our port ids and check that first.
for (int portnum=0; portnum<VIS_PORTS; portnum++) {
const char** connectedPorts;
connectedPorts = jack_port_get_connections(allPorts[portnum]);
if (connectedPorts && connectedPorts[0])
{
//We could use connectedPorts[0] directly as a name, this is the real portname.
//However, we try to get a pretty name from Jack Metadata first
jack_port_t * externalPort;
externalPort = jack_port_by_name(client, connectedPorts[0]);
jack_uuid_t externalId = jack_port_uuid(externalPort);
char * externalPrettyName;
char * externalType;
int returnCode = jack_get_property(externalId, JACK_METADATA_PRETTY_NAME, &externalPrettyName, &externalType);
if (!returnCode && externalPrettyName) {
//const char * prettyPortConst = externalPrettyName;
//strcpy(prettyPortNameCopy, externalPrettyName); //strcpy is const -> non-const
programState.connectedPortNames[portnum] = externalPrettyName;
//if (externalPrettyName) jack_free(externalPrettyName); //TODO: If I free that the name either vanishes, segfaults, is wrong or garbage or it is always the same. No matter what string copy, conversion etc. I tried
if (externalType) jack_free(externalType);
}
else { // No Metadata for this port found
programState.connectedPortNames[portnum] = connectedPorts[0];
}
}
else
{
programState.connectedPortNames[portnum] = ""; //Reset
}
jack_free(connectedPorts);
}
}
void createJackClient() {
int errorCode;
client = jack_client_open (programState.name, JackNullOption, NULL);
if (client == NULL) {
fprintf (stderr, "Could not create JACK client.\n");
exit (EXIT_FAILURE);
}
jack_set_process_callback (client, process, 0);
for (int portnum=0; portnum<VIS_PORTS;portnum++) {
allRingbuffers[portnum] = jack_ringbuffer_create (RBSIZE * sizeof(midimsg)); //Communicate between RT and Non-RT
char name[64];
sprintf(name, "%i", portnum);
allPorts[portnum] = jack_port_register (client, name, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
if (allPorts[portnum] == NULL) {
fprintf (stderr, "Could not register port.\n");
exit (EXIT_FAILURE);
}
}
//Callbacks must be registered before activating the client
errorCode = jack_set_port_connect_callback(client, ourConnectCallback, 0);
if (errorCode != 0) {
fprintf (stderr, "Could not register port registration callback.\n");
exit (EXIT_FAILURE);
}
//Now activate
errorCode = jack_activate (client);
if (errorCode != 0) {
fprintf (stderr, "Could not activate client.\n");
exit (EXIT_FAILURE);
}
}
void closeJackClient() {
pthread_mutex_unlock (&msg_thread_lock);
jack_deactivate (client);
jack_client_close (client);
for (int portnum=0; portnum<VIS_PORTS;portnum++) {
jack_ringbuffer_free (allRingbuffers[portnum]);
}
printf("Jack Client closed succesfully\n");
}
void seekToStart() {
jack_transport_locate(client, 0);
}
void toggleTransport() {
//This is only the active toggle from our side. No reaction to external transport
//Called from non-rt thread
jack_position_t pos;
jack_transport_state_t state = jack_transport_query(client, &pos);
if (state == JackTransportStopped) {
jack_transport_start(client);
}
else if (state == JackTransportStarting) {
//Waiting for sync ready
}
else {
jack_transport_stop(client);
}
}

19
jackclient.h

@ -0,0 +1,19 @@
#ifndef JACKCLIENT_H
#define JACKCLIENT_H
#include "constants.h" //for Note struct
#include <jack/jack.h>
int process (jack_nframes_t frames, void* arg);
void createJackClient();
void closeJackClient();
void mainLoop_processMidiMessages(Note *allGUINotes);
void toggleTransport();
void seekToStart();
//char * positionAsClock();
void mainLoop_collectTimingAndTransportInProgramState();
#endif // not defined JACKCLIENT_H

405
main.c

@ -0,0 +1,405 @@
//Standard lib
#include <stdio.h>
#include <signal.h>
//Third party
#include "raylib.h" //systemlib
#include "raygui.h" //included in our source
#include "nsm.h" //included in our source
//Our own files
#include "constants.h"
#include "programstate.h"
#include "draw.h"
#include "jackclient.h"
#include "gui.h"
#include "camera.h"
#define OUR_NAME "The Grand Visualizer Show"
//Internal setup
static bool readySwitch = false; //switched on once nsm is ready or we start ourselves
extern ProgramState programState;
//NSM setup
static const char *nsm_url;
static nsm_client_t *nsm = 0;
static int wait_nsm = 1;
void callback_nsm_show( void *userdata ) {
UnhideWindow();
programState.applicationWindowVisible = true; //saved. Restored on program start.
nsm_send_is_shown(nsm);
}
void callback_nsm_hide( void *userdata ) {
HideWindow();
programState.applicationWindowVisible = false; //saved. Restored on program start.
nsm_send_is_hidden(nsm);
}
int callback_nsm_save(char **out_msg,void *userdata) {
saveStateToFile();
return ERR_OK;
}
bool poll_nsm() {
if (nsm)
{
nsm_check_nowait(nsm);
return true;
}
return false;
}
int callback_nsm_open(const char *save_file_path, const char *display_name, const char *client_id, char **out_msg, void *userdata) {
wait_nsm = 0;
InitWindow(1920, 1080, client_id);
initProgramState(true, save_file_path, client_id); //true = nsm mode
printf("NSM Save File Path: %s, Client Id: %s\n", save_file_path, client_id);
if (strstr(nsm_get_session_manager_features(nsm), ":optional-gui:" )) {
nsm_set_show_callback(nsm, callback_nsm_show, 0);
nsm_set_hide_callback(nsm, callback_nsm_hide, 0);
}
readySwitch = true;
return ERR_OK;
}
void doExit() {
if (!nsm_url) {
saveStateToFile();
}
closeJackClient();
//UnloadRenderTexture(renderTarget); // Unload render texture
//UnloadShader(shader);
free(programState.clockString);
free(programState.name);
if (programState.nsm)
free(programState.filePath);
CloseWindow(); // Close window and OpenGL context
}
void signalExit(int signal) {
doExit();
}
int main(int argc, char **argv) {
signal(SIGINT, signalExit);
signal(SIGTERM, signalExit);
////////////////////////////////
// Init Window (and RayLib) first because file load already creates textures or Commandline and Internal Program State
////////////////////////////////
//SetConfigFlags(FLAG_WINDOW_RESIZABLE); //When a tiling window manager releases from tiled to floating the window dimensions will be honored.
SetConfigFlags(FLAG_MSAA_4X_HINT); // Enable Multi Sampling Anti Aliasing 4x (if available)
//Init Window itself is right before setting the name, see below NSM mode callback and standalone branch
////////////////////////////////
// Setup NSM or Commandline and Internal Program State
////////////////////////////////
//Debug test filePath = "/home/nils/temp/hallo.json";
nsm_url = getenv("NSM_URL");
//NSM Mode
if (nsm_url) {
nsm = nsm_new();
nsm_set_open_callback(nsm, callback_nsm_open, 0); //sets filePath
nsm_set_save_callback(nsm, callback_nsm_save, 0);
int nsmInitErr = nsm_init(nsm, nsm_url);
if (nsmInitErr == 0) {
nsm_send_announce(nsm, OUR_NAME, ":optional-gui:", argv[0]);
}
else {
nsm_free( nsm );
nsm = 0;
printf("Tried to start in NSM, but failed.\n");
return 0; //End program here. This was started under NSM so we won't fall back to standalone without telling
}
}
//Standalone Mode
else {
const char * filePath;
readySwitch = true;
switch(argc) { //TODO: simple parser for now. Just one file name or nothing.
case 1:
filePath = "";
break;
case 2:
filePath = argv[1]; //TODO: Make sure this is a .json file extension?
break;
default:
printf("Unsupported arguments. %s filename.json\n", argv[0]);
return 0;
}
InitWindow(1920, 1080, OUR_NAME);
initProgramState(false, filePath, OUR_NAME); //false = no nsm
}
while (!readySwitch) {
poll_nsm(); //wait for nsm.
}
////////////////////////////////
// Setup Audio/Video
////////////////////////////////
createJackClient();
Note allGUINotes[VIS_PORTS*128];
for (int port=0; port<VIS_PORTS; port++) {
for (int midiPitch=0; midiPitch<128; midiPitch++) { //128 is a fixed midi value, no need to dynamically decide the size
allGUINotes[port*128 + midiPitch] = (Note){false, 0, 0, 0, 0, 0, 0.0f}; //bool active, int port; int x; int y; int pitch; int velocity; float countdown;
}
}
if (nsm_url) {
if (programState.applicationWindowVisible == false) { //From the save file. if no save file it will be "true"
//TODO: The window will be shown briefly and then hide again. But we need to call InitWindow before HideWindow
HideWindow();
nsm_send_is_hidden(nsm);
}
else {
UnhideWindow();
nsm_send_is_shown(nsm);
}
}
/* This may be redundant on the desktop platform
EnableCursor();
ShowCursor()
*/
GuiSetFont(LoadFont("font.ttf"));
//GuiSetStyle(int control, int property, int value);
int guiDark = ColorToInt((Color){ 45, 50, 57, 255 });
int guiHighlight = ColorToInt((Color){ 55, 61, 69, 127 });
int guiBright = ColorToInt(RAYWHITE);
GuiSetStyle(DEFAULT, BACKGROUND_COLOR, guiDark);
GuiSetStyle(DEFAULT, LINE_COLOR, guiBright);
GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, guiDark);
GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, guiHighlight);
GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, guiHighlight);
GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, guiDark);
GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, guiBright);
GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, guiBright);
GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, guiBright);
GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, guiBright);
GuiSetStyle(DEFAULT, TEXT_SIZE, 16); //Default = 10
GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT = 1
GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, guiBright);
GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, guiBright);
GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, guiBright);
GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, guiBright);
init_draw();
init_gui(); //gui.c
SetTargetFPS(60); // Frames per second limiter, so we don't run with 100% cpu
SetExitKey(0); // "Deactivate" ESC to exit
////////////////////////////////
// Main Loop
////////////////////////////////
while(!WindowShouldClose() && !programState.instructedToQuit ) { // Close Key(deactivated!) or WM Close or internal Quit signal via button or NSM
poll_nsm(); //just returns false if not under nsm
mainLoop_collectTimingAndTransportInProgramState();
mainLoop_cameraReposition();
mainLoop_processMidiMessages(allGUINotes); //jackclient.c
BeginDrawing(); //Z-Order is order of calling
//ClearBackground(programState.colors[background]); //Real background
ClearBackground(programState.colors[background]); //Real background
mainLoop_draw(allGUINotes); //resets programState.guiRedrawNeeded to false each call
mainLoop_gui(); //Highest z-order for GUI. Also controls and shortcuts
EndDrawing();
}
////////////////////////////////
// Main Loop End
////////////////////////////////
doExit();
}
//TODO und Plan
/*
* Starfield Farben und Größe als options
* Die Farbpalette ist momentan generell für dunkle Farben ausgelegt. Starfield z.B. geht davon aus. Wenn nicht dann müsste Starfield custom GUI controls anbieten für seine eigenen 3 Farben.
BPM faktor berechnung muss in eigene Funktion. Den brauch ich überall.
Das mit dem Farbton muss ich nochmal nachschlagen. Tint ist das. Ich will nen Slider, der das ganze einfärbt, aber nicht die originalfarben komplett ersetzt. Da muss ich wahrscheinlich irgendwas multiplizieren.
Vielleicht muss ich aus RayGUI den HUE Slider aus dem Colorpicker rausnehmen und um 90° drehen. Wenn der auf "weiß" ist dann ist normal
* Distribution: Raylib statisch eingelinkt? Bzw. einfach die Files reinkopiert. Umbenennen für Debian?
* Pixeleffekte: Es gibt zwei Layer: Vor den Noten und Hinter den Noten. Da können die effekte ausgewählt werden. Und zwar mit ja/nein. Das ist einfach im backend muss aber in der GUI noch irgendwie gemacht werden. Schnee, Regen, Feuer, Plasma, Blätter fallen, Sternenflug nach vorne.
Die brauchen auch Opacity pro Layer.
Pixeleffekte brauch auch noch einen Speed-Multipler, float.
Es gibt für jeden Layer eine Hauptdatei, außer die midivis, die werden direkt angezeigt.
* Bugs: nach fenster resize ist camera und rotation so ein bischen schwupps.. da muss man dann manuell nachbessern.
* Man muss um eine neue Random Palette zu kriegen erst von der wegwechseln und dann wieder neu dahin.
*
* Was jetzt? Zeitachsen, e.g. Piano Roll dynamisch erstellen? Oder Bilder?
*
* Zeitachse zurückspulen ist ein Problem. Es sei denn es gibt eine Löschmöglichkeit, die man nicht sieht wenn alles exakt gleich wäre. Oder Kahlschlag: Wenn Transport-Pause ist, dann ist leer. Das ist natürlich hart für analytisches angucken. Aber so ist das ja auch nicht gedacht.
* Es könnte beides geben: Hold und Clear. Mit Hold gibt es die Möglichkeit das Stück zweimal durchlaufen zu lassen. Dann wären beim zweiten Durchgang schon alle Noten da und man würde nur evtl. Grafikeffekte live sehen, etwa partikeleffekte oder die sprites.
*
* Noten ignorieren den Alphawert momentan weil sie velocity als alpha haben. Das könnte eine config werden in Zukunft. Notes Alpha: Velocity, Fixed.
Pitch Marker per Oktave Option. Und per CC setzen. Dann kann man Modulationen zeigen. Das ist der einstiegspunkt für CC controls.
Quadrant links unten ist für custom GUIs. baut von unten nach oben.
Spiegeleffekte sind immer schön. Meterbridge und unten drunter Spiegel mit Wasser-Shader?
http://www.afqa123.com/2018/10/08/2d-sprite-reflections/
*Selbst mit Sprites und Animationsmöglichkeiten per CC ist es immer noch schön simple Modi wie "SNES Tanz" zu haben, die nur auf normale Musik reagiert. das mit den Sprites erfordert ja eigene Kompositionsarbeit
*
* Wie verträgt sich die manuelle Kamera mit auto-scrolling "follow midi" modi, die ich noch nicht habe. Die brauchen übrigens einen Timstamp in der Note
* Target FPS einstellen per commandline? Globale Config? Menü? Ist das umstellbar? Kann ich das abrufen ob das überhaupt unterstützt wird?
*
* Kamera Mirror? So das selbst die schrift gespiegelt ist? Mit zoom-1 ging es nicht, oder? Ist das vielleicht die Größe auf -x zu setzen?
*
* Wie bekomme ich die ttf ins fertige Programm? Zur Not natürlich install. Aber reinkompilieren fände ich besser. Das wird aber noch mehr. Es gibt auch noch verschiedene Bilder die dazu kommen.
*
* Die ganzen printf log nachrichten mit meinem Client Namen versehen. und zwar programState.name
*
* Raylib kann nicht close->hide machen, so wie Qt. Ich hab die funktionalität erstmal rausgenommen. An WindowShouldClose() hängt noch ne ganze menge mehr dran, die ganze KeyPress logic. Das kann ich nicht umschreiben oder hijacken. Ergo ist mein Programm schließbar. Na dann soll es zumindest den letzten Stand speichern. Vielleicht kann ich glfw direkt benutzen?
#include <GLFW/glfw3.h>
*
* Richtiger commandline parser mit usage und --help und --version
*
* Weil rayaudio mit drin ist brauch ich momentan celt und opus als libs. Das rauskriegen. Das liegt aber daran wie raylib kompiliert wurde. Also wenn ich statisch linke...
* Es gibt bestimmt so open game art leute oder demo-scene leute, die nen Music Vizualizer mit diskreten informationen (midi) machen wollen
* Modus oder parameter per CC verändern. Ganzer Modus ist vielleicht nicht so appealing, aber die Leuchtintensität wäre das ganz cool. z.B. auf CC7 Volume. Oder Wobbly Notes auf Modwheel. Das passt auch zur Lightshow
* Man muss nicht zwingend reale Musik hier reinschicken. Man kann auch ne Light-Show machen.
* Jeder Modus braucht aber einen Docstring, in dem stehen muss wie es gedacht ist. Ob das jetzt 16 channel music trigger ist oder per channel light/dance show etc.
* Jeder Modus kann ja einfach so, ohne Skriptsprache oder config datei, eigene GUI Felder machen. Oberer Bildschirmrand ist für die, untere Hälfte ist generisch. Muss man ja nicht übertreiben.
* Zeit ist das lineare Element, nicht rhythmus.
* Wie kriegt man Lookahead hin? Für live input geht das also nicht.
* Man muss das stück zweimal laufen lassen und beim zweiten mal wird dann
* gerendert. Oder es wird eine statische save-datei erzeugt mit dem zeitindex
* und beim render-durchlauf wird nur mit jack transport gestartet und gestoppt.
*
* CC Controls sind schon cooler als GUI controls. Dann kann man da sachen stufenlos einstellen mit hardware controllern. Dann kann man auch so One-Shot effekte Feuern, wie ne richtige Lightshow. "Add Random Firework Shot"
* MIDI Learn? So Effekt-Trigger erfordern dann nen Layermodus. Die sind einfach oben drauf.
*
* Latency look ahead 30 sekunden, eine Minute. Für Videos, wenn nix live gespielt wird. Dann kann man sehen was kommt und der playback cursor ist in der mitte. Wir sind das tool, dass die latency reported.
* Gibt es tools, die meinen Code und das Programm auf RT tauglichkeit testen können?
* https://github.com/fundamental/stoat
* Text-Einblendungen ("So groß wie möglich" und "Feste Größe mit Wordwrap") über JACK Meta Data, midi karaoke?, spezialport? oder sonst wie? Dann können Programme ihre Titel zeigen.
* Laufschrift unten drunter im DEMO-Scene style
*
* Es kann auch einen modus geben mit Spezial-Animationen von Sprites oder 3D Models, der muss dann aber extra per MIDI Spur programmiert werden mit speziellen werten nur für unser Programm.
* SNES Sprites mit Tanzschritten oder so.
* Oder der Winamp 3D Bär
*
* In-Scale Mode. Man sucht sich eine Tonart aus und das wird darauf gemappt.
* Die Notenbreite kann ruhig mehr ausnutzen. Muss natürlich auf die Fenstergröße skaliert sein. Das passt auch dazu wenn bestimmte Tracks nicht verbunden sind.
* Nicht verbundene Tracks könnte auch von BG-ALTERNATIVE auf BG geschaltet werden. Not Connected: Don't treat differently (default), BG ("deactivated"), Omit
*
* Modus Slideshow: Zeige Bilder in einer Reihenfolge. Da wir ja in NSM sind kann man das einfach reinkopieren. der bleibt beim letzten bild stehen oder wrap around. Jede Note ist ein Impuls. Modus 2: 127 Bilder, jeder Pitch ist 000.png bis 127.png
*
* CC Switch für Modus Switch (direktanwahl) wäre schon cool.
* Kann ich parallel malen? Technisch schon, aber nicht mit den noten. die sind momentan an einer position. Ich bräuchte sonst Rects für jede Variante. Und die müssten auch alle immer mit aktiv und countdown parallel verändert werden. Das klingt aber machbar.
* Das sind meine Layer. Dann brauch ich nur noch nen Z Order die vom user einstellbar ist.
* JACK schreibt trotzdem immer in den gleichen Array. Der ist dann getrennt von den x,y position. Evtl. önnte ich es auch so machen, dass statt ALLE NOTEN IMMER. Immer nur die in der Datenstruktur sind, die gerade aktiv sind. Das ist vielleicht mit dem zugriff schwieriger?
*
* Widget mit Repositionierbaren Listenposition, die an und aus sind.
*
* Grafiken, .png und .apng müssen transparenz unterstützen. Dann kann man daraus sowas wie titel und "DANCE!" machen, die mit dem Layer/Overlay Modus oben drüben liegen.
*
* Vollbildauflösung muss einstellbar sein. Wird im Save File gespeichert.
*
* Bilder und Grafiken von Instrumenten mit Griffzeichnungen
* Akkord-Analyse für Gitarren.
*
* RayLib bug. Wenn wir hidden sind und closeWindow machen kommt: WARNING: GLFW: Error: 65537 Description: The GLFW library is not initialized
* Leider bringt es auch nix vorher das window auf show zu machen.
*
* Readme:
* Name: The Grand Visualizer Show (TGVS)
* Englischer Name, weil das kein Teil der LSS ist.
* Es ist für die eigenen Songs gedacht, am Ende der Production-Phase.
* Man hat Midi Ausgänge und einzelspuren oder kann zur Not einzelne Spuren mit Aubio in Midi-Signale umwandeln.
*
* Ein Instrument for Jack Midi Port. Wenn das ein midi-channel file ist dann muss man vorher splitten.
* Control-Ports braucht es nicht. Dafür gibt es die nicht belegten CCs? Oder ich suche mir davon welche aus, dann muss ich nicht alle 128 abfragen für alle Ports?
* Am Ende des Tages gibt es kein universelles Belegungsschema für die Draw Modes. Die müssen halt gelernt werden.
* If further developed at all, this application is bound to run into feature-heaven/hell.
* There are so many possible ideas and variations how to visualize, and we want them all!
* With progressing development you can expect a nightmare amount of GUI controls, different rules
* and behaviour for different program modes and one can only hope that at least the basic drawing
* modes are straightforward without the need for a video tutorial to just see something on the screen.
* Be warned: Persons who use the words "KISS" or "UNIX PHILOSOPHY" will be sighed at! :)
winkel für rotation: 360* / 128 = 2.8125° pro midi Step . 64 = 180° . 127 = 357,1875°
* Ist der Sprite Overlay was anderes als die Slideshow? Ja, slideshow ist noten-ports, weil das sehr viel einfacher ist für den benutzer
Sprites und Kontrollport
*
* Die Kamera wird über einen speziellen Midi-Kontrollport gesteuert. Alle Parameter absolut (0% links, 64 = Mitte, 127 = 100% rechts des Bildschirm) und Relativ (CC unter 64 = gehe nach links, CC=64 nix, CC über 64=gehe nach rechts)
*
*
*Es gibt Sprites. Das sind bilder vom Hintergrundbild bis kleine Elemente. Man kann die mit der Maus steuern, drag and drop zum platzieren. Die anderen controls muss man sich dann überlegen.
* Default sind die immer bei 0,0. Die können skaliert werden, mit aspect ratio, auf volle breite. Dann macht natürlich zoom nix mehr. Aber das ist der einfachste Fall. einfach 1920x1080 bilder reinladen und gut ist.
*
* Es gibt 128 Sprites. Einen Pro Pitch. Die sind im Control Port.
* Der momentan zu kontrollierende Sprite wird per Midi-Note ausgewählt. Alle Sprites die momentan Note On sind hören! :) Ne halt, das ist schwierig mit nem Hardware controller? ne, eigentlich geht das..
* Die Kamera ist fest auf CC0-4 und der ist es egal ob note on oder nicht.
* Sprites sind in der Kamera mit drin?
* Sprites haben tilesize und tileindex. Per default ist das auf 100% und 0
*
*
* CCs sind auf Sprites aufgeteilt. Es gibt 12 Stück mit je 10 CCs.
* Wird einfach durchnummeriert. Wenn das irgendwann nicht reicht mach ich einfach nen 2. Sprite Port auf.
* 8 CCs am Ende sind frei.
* Sprite Z Order ist implizit. Höchster Sprite ist ganz oben. Wird aber in foreground und background getrennt.
* Sprite CC Parameter:
*
* x abs in %
* y abs in %
* x delta relative, 64=0 , nach links ist minus pixel, nacht rechts ist plus pixel
* y delta
* zoom. 64 = Default
* Opacity
* Rotation. 0 = 0° default, ab dann wird nach rechts gedreht und 127 ist dann knapp vor 360°
* tileindex . default 0
* Color/Hue. Irgendwie einen Regler zum einfärben
* RESERVED FOR FUTURE USE
Bilder-Slideshow ist die alphabetische Liste in einem DIR. Ein CC o.ä. switched zum nächsten Bild. Es gibt eine Überblendungseinstellung, wie lange (und mit welchem Effekt?) gecrossfaded wird. Das kann natürlich nur NACH dem Signal kommmen, weil wir keine künstliche Latenz
haben. Da die Slideshow aber sowieso bewusst midi-programmiert werden muss vom user kann das auch einfach vor der Zeit eingestellt werden, immer auf die vier, und unser effekt braucht dann halt einen Schlag bei 120 bpm.
*/

689
nsm.h

@ -0,0 +1,689 @@
/*************************************************************************/
/* Copyright (C) 2012 Jonathan Moore Liles */
/* Copyright (C) 2020- Nils Hilbricht */
/* */
/* 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 THE AUTHOR DISCLAIMS ALL */
/* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED */
/* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE */
/* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL */
/* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR */
/* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER */
/* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR */
/* PERFORMANCE OF THIS SOFTWARE. */
/*************************************************************************/
/*************************************************************************/
/* A simple, callback based C API for NSM clients. */
/* */
/* Simplified Example: */
/* */
/* #include "nsm.h" */
/* */
/* static nsm_client_t *nsm = 0; */
/* static int wait_nsm = 1; */
/* */
/* int */
/* cb_nsm_open ( const char *save_file_path, //See API Docs 2.2.2 */
/* const char *display_name, //Not useful */
/* const char *client_id, //Use as JACK Client Name */
/* char **out_msg, */
/* void *userdata ) */
/* { */
/* do_open_stuff(); //Your own function */
/* wait_nsm = 0; */
/* return ERR_OK; */
/* } */
/* */
/* int */
/* cb_nsm_save ( char **out_msg, */
/* void *userdata ) */
/* { */
/* do_save_stuff(); //Your own function */
/* return ERR_OK; */
/* } */
/* */
/* void */
/* cb_nsm_show ( void *userdata ) */
/* { */
/* do_show_ui(); //Your own function */
/* nsm_send_is_shown ( nsm ); */
/* } */
/* */
/* void */
/* cb_nsm_hide ( void *userdata ) */
/* { */
/* do_hide_ui(); //Your own function */
/* nsm_send_is_hidden ( nsm ); */
/* } */
/* */
/* gboolean */
/* poll_nsm() */
/* { */
/* if ( nsm ) */
/* { */
/* nsm_check_nowait( nsm ); */
/* return true; */
/* } */
/* return false; */
/* } */
/* */
/* int main( int argc, char **argv ) */
/* { */
/* const char *nsm_url = getenv( "NSM_URL" ); */
/* */
/* if ( nsm_url ) */
/* { */
/* nsm = nsm_new(); */
/* */
/* nsm_set_open_callback( nsm, cb_nsm_open, 0 ); */
/* nsm_set_save_callback( nsm, cb_nsm_save, 0 ); */
/* */
/* if ( 0 == nsm_init( nsm, nsm_url ) ) */
/* { */
/* nsm_send_announce( nsm, "FOO", ":optional-gui:", argv[0] );*/
/* */
/* ********************************************************************** */
/* This will block for at most 100 sec and */
/* waiting for the NSM server open callback. */
/* DISCLAIMER: YOU MAY NOT NEED TO DO THAT. */
/* ********************************************************************** */
/* */
/* int timeout = 0; */
/* while ( wait_nsm ) */
/* { */
/* nsm_check_wait( nsm, 500 ); */
/* timeout += 1; */
/* if ( timeout > 200 ) */
/* exit ( 1 ); */
/* } */
/* */
/* ********************************************************************** */
/* This will check if the server support optional-gui */
/* and connect the callbacks when support is found. */
/* If you don't use the above blocking block */
/* this could be done in cb_nsm_open() as well. */
/* DISCLAIMER: YOU MAY NOT NEED TO DO THAT. */
/* ********************************************************************** */
/* */
/* if ( strstr( nsm_get_session_manager_features ( nsm ), */
/* ":optional-gui:" ) ) */
/* { */
/* nsm_set_show_callback( nsm, cb_nsm_show, 0 ); */
/* nsm_set_hide_callback( nsm, cb_nsm_hide, 0 ); */
/* } */
/* */
/* ********************************************************************** */
/* */
/* do_timeout_add( 200, poll_nsm, Null ); //Your own function */
/* } */
/* else */
/* { */
/* nsm_free( nsm ); */
/* nsm = 0; */
/* } */
/* } */
/* } */
/**************************************************************************/
#ifndef _NSM_H
#define _NSM_H
#define NSM_API_VERSION_MAJOR 1
#define NSM_API_VERSION_MINOR 0
#include <lo/lo.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
typedef void * nsm_client_t;
typedef int (nsm_open_callback)( const char *name, const char *display_name, const char *client_id, char **out_msg, void *userdata );
typedef int (nsm_save_callback)( char **out_msg, void *userdata );
typedef void (nsm_show_gui_callback)( void *userdata );
typedef void (nsm_hide_gui_callback)( void *userdata );
typedef void (nsm_active_callback)( int b, void *userdata );
typedef void (nsm_session_is_loaded_callback)( void *userdata );
typedef int (nsm_broadcast_callback)( const char *, lo_message m, void *userdata );
#define _NSM() ((struct _nsm_client_t*)nsm)
#define NSM_EXPORT __attribute__((unused)) static
/* private parts */
struct _nsm_client_t
{
const char *nsm_url;
lo_server _server;
lo_server_thread _st;
lo_address nsm_addr;
int nsm_is_active;
char *nsm_client_id;
char *_session_manager_name;
char *_session_manager_features;
nsm_open_callback *open;
void *open_userdata;
nsm_save_callback *save;
void *save_userdata;
nsm_show_gui_callback *show;
void *show_userdata;
nsm_hide_gui_callback *hide;
void *hide_userdata;
nsm_active_callback *active;
void *active_userdata;
nsm_session_is_loaded_callback *session_is_loaded;
void *session_is_loaded_userdata;
nsm_broadcast_callback *broadcast;
void *broadcast_userdata;
};
enum
{
ERR_OK = 0,
ERR_GENERAL = -1,
ERR_INCOMPATIBLE_API = -2,
ERR_BLACKLISTED = -3,
ERR_LAUNCH_FAILED = -4,
ERR_NO_SUCH_FILE = -5,
ERR_NO_SESSION_OPEN = -6,
ERR_UNSAVED_CHANGES = -7,
ERR_NOT_NOW = -8
};
NSM_EXPORT
int
nsm_is_active ( nsm_client_t *nsm )
{
return _NSM()->nsm_is_active;
}
NSM_EXPORT
const char *
nsm_get_session_manager_name ( nsm_client_t *nsm )
{
return _NSM()->_session_manager_name;
}
NSM_EXPORT
const char *
nsm_get_session_manager_features ( nsm_client_t *nsm )
{
return _NSM()->_session_manager_features;
}
NSM_EXPORT
nsm_client_t *
nsm_new ( void )
{
struct _nsm_client_t *nsm = (struct _nsm_client_t*)malloc( sizeof( struct _nsm_client_t ) );
nsm->nsm_url = 0;
nsm->nsm_is_active = 0;
nsm->nsm_client_id = 0;
nsm->_server = 0;
nsm->_st = 0;
nsm->nsm_addr = 0;
nsm->_session_manager_name = 0;
nsm->_session_manager_features = 0;
nsm->open = 0;
nsm->save = 0;
nsm->show = 0;
nsm->hide = 0;
nsm->active = 0;
nsm->session_is_loaded = 0;
nsm->broadcast = 0;
return (nsm_client_t *)nsm;
}
/*******************************************/
/* CLIENT TO SERVER INFORMATIONAL MESSAGES */
/*******************************************/
NSM_EXPORT
void
nsm_send_is_dirty ( nsm_client_t *nsm )
{
if ( _NSM()->nsm_is_active )
lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/is_dirty", "" );
}
NSM_EXPORT
void
nsm_send_is_clean ( nsm_client_t *nsm )
{
if ( _NSM()->nsm_is_active )
lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/is_clean", "" );
}
NSM_EXPORT
void
nsm_send_is_shown ( nsm_client_t *nsm )
{
if ( _NSM()->nsm_is_active )
lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", "" );
}
NSM_EXPORT
void
nsm_send_is_hidden ( nsm_client_t *nsm )
{
if ( _NSM()->nsm_is_active )
lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
}
NSM_EXPORT
void
nsm_send_progress ( nsm_client_t *nsm, float p )
{
if ( _NSM()->nsm_is_active )
lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/progress", "f", p );
}
NSM_EXPORT
void
nsm_send_message ( nsm_client_t *nsm, int priority, const char *msg )
{
if ( _NSM()->nsm_is_active )
lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/message", "is", priority, msg );
}
NSM_EXPORT void
nsm_send_announce ( nsm_client_t *nsm, const char *app_name, const char *capabilities, const char *process_name )
{
lo_address to = lo_address_new_from_url( _NSM()->nsm_url );
if ( ! to )
{
fprintf( stderr, "NSM: Bad address!" );
return;
}
int pid = (int)getpid();
lo_send_from( to, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii",
app_name,
capabilities,
process_name,
NSM_API_VERSION_MAJOR,
NSM_API_VERSION_MINOR,
pid );
lo_address_free( to );
}
NSM_EXPORT void
nsm_send_broadcast ( nsm_client_t *nsm, lo_message msg )
{
if ( _NSM()->nsm_is_active )
lo_send_message_from( _NSM()->nsm_addr, _NSM()->_server, "/nsm/server/broadcast", msg );
}
NSM_EXPORT
void
nsm_check_wait ( nsm_client_t *nsm, int timeout )
{
if ( lo_server_wait( _NSM()->_server, timeout ) )
while ( lo_server_recv_noblock( _NSM()->_server, 0 ) ) {}
}
NSM_EXPORT
void
nsm_check_nowait (nsm_client_t *nsm )
{
nsm_check_wait( nsm, 0 );
}
NSM_EXPORT
void
nsm_thread_start ( nsm_client_t *nsm )
{
lo_server_thread_start( _NSM()->_st );
}
NSM_EXPORT
void
nsm_thread_stop ( nsm_client_t *nsm )
{
lo_server_thread_stop( _NSM()->_st );
}
NSM_EXPORT void
nsm_free ( nsm_client_t *nsm )
{
if ( _NSM()->_st )
nsm_thread_stop( nsm );
if ( _NSM()->_st )
lo_server_thread_free( _NSM()->_st );
else
lo_server_free( _NSM()->_server );
lo_address_free(_NSM()->nsm_addr);
free(_NSM()->nsm_client_id);
free(_NSM()->_session_manager_name);
free(_NSM()->_session_manager_features);
free( _NSM() );
}
/*****************/
/* SET CALLBACKS */
/*****************/
NSM_EXPORT
void
nsm_set_open_callback( nsm_client_t *nsm, nsm_open_callback *open_callback, void *userdata )
{
_NSM()->open = open_callback;
_NSM()->open_userdata = userdata;
}
NSM_EXPORT
void
nsm_set_save_callback( nsm_client_t *nsm, nsm_save_callback *save_callback, void *userdata )
{
_NSM()->save = save_callback;
_NSM()->save_userdata = userdata;
}
NSM_EXPORT
void
nsm_set_show_callback( nsm_client_t *nsm, nsm_show_gui_callback *show_callback, void *userdata )
{
_NSM()->show = show_callback;
_NSM()->show_userdata = userdata;
}
NSM_EXPORT
void
nsm_set_hide_callback( nsm_client_t *nsm, nsm_hide_gui_callback *hide_callback, void *userdata )
{
_NSM()->hide = hide_callback;
_NSM()->hide_userdata = userdata;
}
NSM_EXPORT
void
nsm_set_active_callback( nsm_client_t *nsm, nsm_active_callback *active_callback, void *userdata )
{
_NSM()->active = active_callback;
_NSM()->active_userdata = userdata;
}
NSM_EXPORT
void
nsm_set_session_is_loaded_callback( nsm_client_t *nsm, nsm_session_is_loaded_callback *session_is_loaded_callback, void *userdata )
{
_NSM()->session_is_loaded = session_is_loaded_callback;
_NSM()->session_is_loaded_userdata = userdata;
}
NSM_EXPORT
void
nsm_set_broadcast_callback( nsm_client_t *nsm, nsm_broadcast_callback *broadcast_callback, void *userdata )
{
_NSM()->broadcast = broadcast_callback;
_NSM()->broadcast_userdata = userdata;
}
/****************/
/* OSC HANDLERS */
/****************/
#undef OSC_REPLY
#undef OSC_REPLY_ERR
#define OSC_REPLY( value ) lo_send_from( ((struct _nsm_client_t*)user_data)->nsm_addr, ((struct _nsm_client_t*)user_data)->_server, LO_TT_IMMEDIATE, "/reply", "ss", path, value )
#define OSC_REPLY_ERR( errcode, value ) lo_send_from( ((struct _nsm_client_t*)user_data)->nsm_addr, ((struct _nsm_client_t*)user_data)->_server, LO_TT_IMMEDIATE, "/error", "sis", path, errcode, value )
NSM_EXPORT int _nsm_osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
(void) types;
(void) argc;
(void) msg;
char *out_msg = NULL;
struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;
nsm->nsm_client_id = strdup( &argv[2]->s );
if ( ! nsm->open )
return 0;
int r = nsm->open( &argv[0]->s, &argv[1]->s, &argv[2]->s, &out_msg, nsm->open_userdata );
if ( r )
OSC_REPLY_ERR( r, ( out_msg ? out_msg : "") );
else
OSC_REPLY( "OK" );
if ( out_msg )
free( out_msg );
return 0;
}
NSM_EXPORT int _nsm_osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
(void) types;
(void) argv;
(void) argc;
(void) msg;
char *out_msg = NULL;
struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;
if ( ! nsm->save )
return 0;
int r = nsm->save(&out_msg, nsm->save_userdata );
if ( r )
OSC_REPLY_ERR( r, ( out_msg ? out_msg : "") );
else
OSC_REPLY( "OK" );
if ( out_msg )
free( out_msg );
return 0;
}
NSM_EXPORT int _nsm_osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
(void) path;
(void) types;
(void) argc;
if ( strcmp( &argv[0]->s, "/nsm/server/announce" ) )
return -1;
struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;
fprintf( stderr, "NSM: Successfully registered. NSM says: %s", &argv[1]->s );
nsm->nsm_is_active = 1;
nsm->_session_manager_name = strdup( &argv[2]->s );
nsm->_session_manager_features = strdup( &argv[3]->s );
nsm->nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));
if ( nsm->active )
nsm->active( nsm->nsm_is_active, nsm->active_userdata );
return 0;
}
NSM_EXPORT int _nsm_osc_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
(void) path;
(void) types;
(void) argc;
(void) msg;
if ( strcmp( &argv[0]->s, "/nsm/server/announce" ) )
return -1;
struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;
fprintf( stderr, "NSM: Failed to register with NSM server: %s", &argv[2]->s );
nsm->nsm_is_active = 0;
if ( nsm->active )
nsm->active( nsm->nsm_is_active, nsm->active_userdata );
return 0;
}
NSM_EXPORT int _nsm_osc_session_is_loaded ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
(void) path;
(void) types;
(void) argv;
(void) argc;
(void) msg;
struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;
if ( ! nsm->session_is_loaded )
return 0;
nsm->session_is_loaded( nsm->session_is_loaded_userdata );
return 0;
}
NSM_EXPORT int _nsm_osc_show ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;
if ( ! nsm->show )
return 0;
nsm->show( nsm->show_userdata );
return 0;
}
NSM_EXPORT int _nsm_osc_hide ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;
if ( ! nsm->hide )
return 0;
nsm->hide( nsm->hide_userdata );
return 0;
}
NSM_EXPORT int _nsm_osc_broadcast ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
(void) types;
(void) argv;
(void) argc;
struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;
if ( ! nsm->broadcast )
return 0;
return nsm->broadcast( path, msg, nsm->broadcast_userdata );
}
NSM_EXPORT
int
nsm_init ( nsm_client_t *nsm, const char *nsm_url )
{
_NSM()->nsm_url = nsm_url;
lo_address addr = lo_address_new_from_url( nsm_url );
int proto = lo_address_get_protocol( addr );
lo_address_free( addr );
_NSM()->_server = lo_server_new_with_proto( NULL, proto, NULL );
if ( ! _NSM()->_server )
return -1;
lo_server_add_method( _NSM()->_server, "/error", "sis", _nsm_osc_error, _NSM() );
lo_server_add_method( _NSM()->_server, "/reply", "ssss", _nsm_osc_announce_reply, _NSM() );
lo_server_add_method( _NSM()->_server, "/nsm/client/open", "sss", _nsm_osc_open, _NSM() );
lo_server_add_method( _NSM()->_server, "/nsm/client/save", "", _nsm_osc_save, _NSM() );
lo_server_add_method( _NSM()->_server, "/nsm/client/session_is_loaded", "", _nsm_osc_session_is_loaded, _NSM() );
lo_server_add_method( _NSM()->_server, "/nsm/client/show_optional_gui", "", _nsm_osc_show, _NSM() );
lo_server_add_method( _NSM()->_server, "/nsm/client/hide_optional_gui", "", _nsm_osc_hide, _NSM() );
lo_server_add_method( _NSM()->_server, NULL, NULL, _nsm_osc_broadcast, _NSM() );
return 0;
}
NSM_EXPORT
int
nsm_init_thread ( nsm_client_t *nsm, const char *nsm_url )
{
_NSM()->nsm_url = nsm_url;
lo_address addr = lo_address_new_from_url( nsm_url );
int proto = lo_address_get_protocol( addr );
lo_address_free( addr );
_NSM()->_st = lo_server_thread_new_with_proto( NULL, proto, NULL );
_NSM()->_server = lo_server_thread_get_server( _NSM()->_st );
if ( ! _NSM()->_server )
return -1;
lo_server_thread_add_method( _NSM()->_st, "/error", "sis", _nsm_osc_error, _NSM() );
lo_server_thread_add_method( _NSM()->_st, "/reply", "ssss", _nsm_osc_announce_reply, _NSM() );
lo_server_thread_add_method( _NSM()->_st, "/nsm/client/open", "sss", _nsm_osc_open, _NSM() );
lo_server_thread_add_method( _NSM()->_st, "/nsm/client/save", "", _nsm_osc_save, _NSM() );
lo_server_thread_add_method( _NSM()->_st, "/nsm/client/session_is_loaded", "", _nsm_osc_session_is_loaded, _NSM() );
lo_server_thread_add_method( _NSM()->_st, "/nsm/client/show_optional_gui", "", _nsm_osc_show, _NSM() );
lo_server_thread_add_method( _NSM()->_st, "/nsm/client/hide_optional_gui", "", _nsm_osc_hide, _NSM() );
lo_server_thread_add_method( _NSM()->_st, NULL, NULL, _nsm_osc_broadcast, _NSM() );
return 0;
}
#endif /* NSM_H */

248
programstate.c

@ -0,0 +1,248 @@
//Standard lib
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
//Third party
#include "raylib.h"
#include "jRead.h" //included in our source https://www.codeproject.com/Articles/885389/jRead-An-in-place-JSON-Element-Reader
#include "jWrite.h" //included in our source https://www.codeproject.com/Articles/887604/jWrite-A-Really-Simple-JSON-Writer-in-C
//Our own files
#include "constants.h"
#include "programstate.h"
#include "drawhelper.h"
#include "draw.h"
#define SAVE_BUFFER_LEN 1024*1024*4 //4 MB to be on the safe side. This is just RAM, not the file size.
ProgramState programState; //global singleton state. All other files use extern ProgramState programState;
void setColorPalette(int number) {
//First set Background to default, in case a palette only works with notes.
//Otherwise the palette can overwrite.
srand(time(0));
programState.colors[VIS_PORTS+0] = (Color){ 45, 50, 57, 255 }; //LSS Background color. The general background, lowest layer.
programState.colors[VIS_PORTS+1] = (Color){ 55, 61, 69, 127 }; //used for note backgrounds
programState.colors[VIS_PORTS+2] = (Color){ 65, 65, 65, 200 }; //e.g. pitch marker
switch (number) {
case 1: //random notes. Background default
for (int i=0; i<VIS_PORTS; i++) {
programState.colors[i] = (Color) { GetRandomValue(0,255), GetRandomValue(0,255), GetRandomValue(0,255), 255 };
}
break;
case 2: //greyscale
for (int i=0; i<VIS_PORTS; i++) {
programState.colors[i] = (Color) { (255/VIS_PORTS)*i, (255/VIS_PORTS)*i, (255/VIS_PORTS)*i, 255 };
}
break;
default: //incl. 0
for (int i=0; i<VIS_PORTS; i++) {
programState.colors[i] = intToColor(i, VIS_PORTS);
}
}
}
void initProgramState(bool nsm, const char * filePath, const char * programName) {
//Initial Global State
//Some values may be overwritten directly after init by loading from a file, such as drawMode
//Others, such as guiRedrawNeeded, are dynamic values.
//NSM (New Session Manager) is a special state that decides mostly about program paths.
//With NSM=true filePath will be a directory and we save under it. Any images loaded are symlinked into that directory
//With NSM=false filePath will be a direct (json) file, given by the user. Images will be loaded from their original place.
programState.nsm = nsm;
programState.camera = (Camera2D){ 0 }; //Not saved itself, but it values will be.
//Saved
programState.showPitchMarker = false;
programState.pitchMarkerValue = 60; //middle C
programState.showPortBackground = true; // Background for notes, not the layer
programState.showPortBackgroundForUnconnected = true; // Draw background also for unconnected ports
programState.fadeOutMode = 1; // 0 = framelength * programState.fadeOutFactor, 1 = bpm, 2 = instant off
programState.fadeOutFactor = 3; //higher=faster. must be >= 1. multiplied with time of one frame (0.016s @ 60fps). Factor 3 is the same as bpm fadeout with 120bpm
programState.showConnectedPortnames = true;
programState.includeClientInPortNameDisplay = false;
programState.alwaysShowClock = false;
programState.applicationWindowVisible = true; //for nsm
//.camera is not saved, but we will save the values individually
programState.camera.zoom = 1.0f;
programState.camera.rotation = 0.0f;
programState.cameraCenterOnX = 0;
programState.cameraCenterOnY = 0;
programState.pitchMin = 0+2*12; //Note Drawing Range for all modes
programState.pitchMax = 127-2*12; //Pitch is inclusive. Note Drawing Range for all modes
//Layer Switches. Also see enum in constants.h
programState.showBackgroundImageLayer = false; //or plain color
programState.showEffectLayer = true;
programState.showSpriteLayer = true;
programState.showDrawMode = true;
//Layer Modes and Settings
//char * backgroundImagePath;
programState.drawMode = 0;
setColorPalette(0);
//For specific modes only
programState.meterbridge_grouping = 1; // 127 / 1 as default -> show all notes individually. 12 would mean show activity in one octave as the same rectangle. pitchmarker is the root note.
//Images and paths
programState.pathBackgroundImage = ""; //set by load or draw.h. This is a path, not the texture.
//Not Saved
programState.clockString = malloc (sizeof(char) * 255); //set by jack
programState.guiRedrawNeeded = true; //screen size or drawing configuration changed
programState.instructedToQuit = false; //internal signal for the quit button
programState.guiVisible = false; //Press ESC
programState.bpm = 0.0d; //set by jack
//programState.transportRolling;
programState.modeDescription = ""; // \n for line break. Label to describe a mode. Set by files like draw_port_grids.c etc.
//programState.connectedPortNames #empty
programState.portActivity[VIS_PORTS];
programState.lockInput = false; //Prevent keypresses while in file open dialog etc.
//Not saved but provided or changed by NSM or args
programState.name = strdup(programName);
if (nsm) {
programState.nsmDirectory = strdup(filePath); //Our directory in NSM, empty standalone. Don't use for checks, use bool programState.nsm instead
programState.filePath = malloc (sizeof(char) * 4096); //4096 is the max path length in linux
snprintf(programState.filePath, (sizeof(char) * 4096), "%s/%s", filePath, "tgvssave.json");
mkdir(programState.nsmDirectory, 0755); //let silently fail if already exists. We need the dir to symlink image files.
}
else {
programState.filePath = strdup(filePath); //This is always the direct json save file.
}
loadStateFromFile();
}
void loadStateFromFile() {
//call only after initProgramState
if (programState.nsm) {
}
if (programState.filePath && programState.filePath[0] != '\0') {
char * buffer;
buffer = LoadFileText(programState.filePath); // Load text data from file (read), returns a '\0' terminated string
//Begin de-serializing save data
if (buffer) {
//printf("%s\n", buffer); //print whole json file.
//A third parameter NULL is for jRead query params and recommended as default
programState.showPitchMarker = (bool)jRead_int(buffer, "{'showPitchMarker'", NULL);
programState.pitchMarkerValue = jRead_int(buffer, "{'pitchMarkerValue'", NULL);
programState.showPortBackground = (bool)jRead_int(buffer, "{'showPortBackground'", NULL);
programState.showPortBackgroundForUnconnected = (bool)jRead_int(buffer, "{'showPortBackgroundForUnconnected'", NULL);
programState.fadeOutMode = jRead_int(buffer, "{'fadeOutMode'", NULL);
programState.fadeOutFactor = jRead_int(buffer, "{'fadeOutFactor'", NULL);
programState.showConnectedPortnames = (bool)jRead_int(buffer, "{'showConnectedPortnames'", NULL);
programState.includeClientInPortNameDisplay = (bool)jRead_int(buffer, "{'includeClientInPortNameDisplay'", NULL);
programState.alwaysShowClock = (bool)jRead_int(buffer, "{'alwaysShowClock'", NULL);
programState.applicationWindowVisible = (bool)jRead_int(buffer, "{'applicationWindowVisible'", NULL);
programState.cameraCenterOnX = jRead_int(buffer, "{'cameraCenterOnX'", NULL);
programState.cameraCenterOnY = jRead_int(buffer, "{'cameraCenterOnY'", NULL);
programState.camera.zoom = (float)jRead_double(buffer, "{'camera.zoom'", NULL);
programState.camera.rotation = (float)jRead_double(buffer, "{'camera.rotation'", NULL);
programState.pitchMin = jRead_int(buffer, "{'pitchMin'", NULL);
programState.pitchMax = jRead_int(buffer, "{'pitchMax'", NULL);
programState.meterbridge_grouping = jRead_int(buffer, "{'meterbridge_grouping'", NULL);
programState.drawMode = jRead_int(buffer, "{'drawMode'", NULL);
programState.showBackgroundImageLayer = (bool)jRead_int(buffer, "{'showBackgroundImageLayer'", NULL);
programState.showEffectLayer = (bool)jRead_int(buffer, "{'showEffectLayer'", NULL);
programState.showSpriteLayer = (bool)jRead_int(buffer, "{'showSpriteLayer'", NULL);
programState.showDrawMode = (bool)jRead_int(buffer, "{'showDrawMode'", NULL);
//int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen, int *queryParams );
char bgimgpath[4096];
jRead_string(buffer, "{'pathBackgroundImage'", bgimgpath, 4096 , NULL);
loadBackgroundImage(bgimgpath, false); //sets programState.pathBackgroundImage
char colorKey[64];
for (int i=0; i<19; i++) {
sprintf(colorKey, "{'colors_%i'", i);
//Thanks jRead! we saved the color value as string, because the long-write function does not exist. But luckily it parses everything back during read :).
programState.colors[i] = GetColor(jRead_long(buffer, colorKey, NULL));
}
}
else {
printf("Unable to open %s. Starting with default program state. Expected for first run.\n", programState.filePath);
}
}
else {
printf("No filepath present. Not loading anything, every setting is temporary.\n");
}
}
void saveStateToFile() {
//call only after initProgramState
if (programState.filePath && programState.filePath[0] != '\0') {
char buffer[SAVE_BUFFER_LEN];
int returnJWCode;
jwOpen( buffer, SAVE_BUFFER_LEN, JW_OBJECT, JW_PRETTY ); // open root node as object
//jwObj_string( "key", "value" );
jwObj_int( "showPitchMarker", (int)programState.showPitchMarker );
jwObj_int( "pitchMarkerValue", programState.pitchMarkerValue );
jwObj_int( "showPortBackground", (int)programState.showPortBackground );
jwObj_int( "showPortBackgroundForUnconnected", (int)programState.showPortBackgroundForUnconnected );
jwObj_int( "fadeOutMode", programState.fadeOutMode );
jwObj_int( "fadeOutFactor", programState.fadeOutFactor );
jwObj_int( "showConnectedPortnames", (int)programState.showConnectedPortnames );
jwObj_int( "includeClientInPortNameDisplay", (int)programState.includeClientInPortNameDisplay );
jwObj_int( "alwaysShowClock", (int)programState.alwaysShowClock );
jwObj_int( "applicationWindowVisible", (int)programState.applicationWindowVisible );
jwObj_int( "cameraCenterOnX", programState.cameraCenterOnX );
jwObj_int( "cameraCenterOnY", programState.cameraCenterOnY );
jwObj_double( "camera.zoom", (double)programState.camera.zoom );
jwObj_double( "camera.rotation", (double)programState.camera.rotation );
jwObj_int( "pitchMin", programState.pitchMin );
jwObj_int( "pitchMax", programState.pitchMax );
jwObj_int( "meterbridge_grouping", programState.meterbridge_grouping );
jwObj_int( "drawMode", programState.drawMode );
jwObj_int( "showBackgroundImageLayer", (int)programState.showBackgroundImageLayer );
jwObj_int( "showEffectLayer", (int)programState.showEffectLayer );
jwObj_int( "showSpriteLayer", (int)programState.showSpriteLayer );
jwObj_int( "showDrawMode", (int)programState.showDrawMode );
jwObj_string( "pathBackgroundImage", programState.pathBackgroundImage );
char colorKey[64];
char colorValue[256];
for (int i=0; i<19; i++) {
sprintf(colorKey, "colors_%i", i);
sprintf(colorValue, "%li", ColorToInt(programState.colors[i]));
jwObj_string( colorKey, colorValue );
}
returnJWCode = jwClose();
SaveFileText(programState.filePath, buffer); // Save text data to file (write), string must be '\0' terminated
}
}

79
programstate.h

@ -0,0 +1,79 @@
#ifndef PROGRAMSTATE_H
#define PROGRAMSTATE_H
#include "constants.h"
//Singleton. This is also saved and loaded. See main.c
typedef struct {
bool nsm; //under nsm or not
//Saved
/////////////////////
bool guiVisible;
bool showPitchMarker;
int pitchMarkerValue;
bool showPortBackground;
bool showPortBackgroundForUnconnected;
int fadeOutMode;
int fadeOutFactor;
bool showConnectedPortnames;
bool includeClientInPortNameDisplay;
bool alwaysShowClock;
bool applicationWindowVisible;
int cameraCenterOnX;
int cameraCenterOnY;
int pitchMin; //Note Drawing Range for all modes
int pitchMax; //Note Drawing Range for all modes
//Layer Switches
bool showBackgroundImageLayer; //or plain color
bool showEffectLayer;
bool showSpriteLayer;
bool showDrawMode;
bool showForegroundSpritesLayer;
bool showForegroundEffectsLayer;
//Layer Modes and Settings
//char * backgroundImagePath;
int drawMode; //Vertical, Grids...
Color colors[VIS_PORTS+3];
//For specific modes only
int meterbridge_grouping; // 127 / 1 as default -> show all notes individually. 12 would mean show activity in one octave as the same rectangle. pitchmarker is the root note.
//Images and paths
char * pathBackgroundImage; //in nsm this is always background.png , standalone it is an absolute file path
//Not Saved
/////////////////////
bool guiRedrawNeeded;
bool instructedToQuit;
bool transportRolling;
double bpm;
char* clockString;
const char* connectedPortNames[VIS_PORTS];
const char * modeDescription; //48 chars per line.\n for line break. Label to describe a mode. Set by files like draw_port_grids.c etc.
int portActivity[VIS_PORTS]; //0 means no activity, any other number is the number of current note-ons
bool lockInput; //Prevent keypresses while in file open dialog etc.
Camera2D camera;
//Not saved but provided by NSM or argc
/////////////////////
char * name;
char * nsmDirectory; //Our directory in NSM, empty standalone. Don't use for checks, use bool programState.nsm instead
char * filePath; //This is always the direct json save file. There is only one filePath per session. We do not provide "reload" or "open"
} ProgramState;
void initProgramState(bool nsm, const char * filePath, const char * programName);
void loadStateFromFile();
void saveStateToFile();
void setColorPalette(int number);
#endif // not defined PROGRAMSTATE_H

3734
raygui.h

File diff suppressed because it is too large
Loading…
Cancel
Save