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.
 
 
 
 

405 lines
20 KiB

//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.
*/