Raylib 3
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
12 KiB

//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.
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 };
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 };
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.
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;
//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.modeDescription = ""; // \n for line break. Label to describe a mode. Set by files like draw_port_grids.c etc.
//programState.connectedPortNames #empty
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.
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