Hey, are there any new approaches to that?
I'm also thinking about saving and loading presets. My idea is to use one JSON file for storing all those. If I understand it correctly, the Write class isn't able doing this since it can only write whole files, overwrite them or append to it. So I'd have to have one file per preset, is that right?
So what about the JSON and JSON Value classes? Are they only for reading .json files? Does anybody have a good example showing this?
Are there any disadvantages (real time audio etc.?) about using something like this:
https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp
I just tried to create and write a .json file on Bela with it and it works fine. The only trouble is, that it takes pretty long compiling (probably because all is one .hpp file?)
Thanks in advance!!

    fennec the Write class isn't able doing this since it can only write whole files, overwrite them or append to i

    it's meant for streaming data to disk while the audio process is running (e.g.: write sensor data), and it's pretty bad at it. It is not meant for reading/writing presets.

    if you don't need the full flexibility of JSON, you can use these config file utilities http://docs.bela.io/namespaceConfigFileUtils.html . If you have your own format, you can just use the underlying I/O utilities to read/write files (http://docs.bela.io/namespaceIoUtils.html).

    fennec So what about the JSON and JSON Value classes? Are they only for reading .json files? Does anybody have a good example showing this?

    Not a "good" one. But see here https://forum.bela.io/d/2937-are-ioutils-still-available/3 (which eventually revers to nlohmann's json).

    As usual, all disk I/O should not - at least in principle - take place in the audio thread. OTOH, one may be willing to tolerate audio dropouts while storing or loading a preset, so if it's easier and this is not a problem, you can also do that from the audio thread.

    fennec The only trouble is, that it takes pretty long compiling (probably because all is one .hpp file?)

    There are ways to work around that, for instance do not include the .hpp file directly from your render.cpp file, rather create a preset.cpp file where you perform all the JSON stuff and has a simpler API which is exposed to the file in render.cpp (such API should be exposed in something like preset.h, which should not include the json hpp file). This way, preset.cpp will require a long time to build, but render.cpp won't, so once you sorted out your preset stuff, you can keep building your code in render.cpp (and/or other files) without the building slow down due to the the json hpp file. Or you could set up distcc to offload some of the compilation tasks to the host computer: https://gist.github.com/jarmitage/2a5dffbcfdd5532371f097e9bb80e4b0

      fennec Are there any disadvantages (real time audio etc.?) about using something like this:
      https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp

      I'm currently using the nlohmann JSON library with is nice to work with, except ofc the added compile time due to the large size and probably the heavy use of templates.

      giuliomoro There are ways to work around that, for instance do not include the .hpp file directly from your render.cpp file, rather create a preset.cpp file where you perform all the JSON stuff and has a simpler API which is exposed to the file in render.cpp (such API should be exposed in something like preset.h, which should not include the json hpp file).

      This was also my solution to the compile time issue. If you somehow still need to include the JSON library in a header, you can include json_fwd.hpp which is a lot smaller but still includes all necessary declarations.

      Thanks!
      this works super fine 😃
      although I wasn't able to use the json_fwd.hpp file. But I rearranged the code and as long as I don't change it again, compiling takes much less time.
      Really cool! This is an important step for me to actually use BELA live on stage, especially when connecting it to Midi Program Changes. So thanks again for your replies!

      6 days later

      giuliomoro so I tried to do something like this but I'm also running into heavy compile times. How do I create this header file without including the json hpp file if I'm using that type for the state private variable and the return type of some functions?

      #include "json.hpp"
      using json = nlohmann::json;
      
      #ifndef STATE_H_
      #define STATE_H_
      #endif
      
      class SaveState {
      	private:
      		nlohmann::json state;
      	public:
      		nlohmann::json getCurrentSong();
      		void loadSaveState();
      	        SaveState();
      };

      Should I just store the state as the original string? The getCurrentSong method could be a private function, but that also would require setting the return type to json in the header, right?

      • Ward replied to this.

        This is was I was thinking of:

        #pragma once
        #include <string>
        
        namespace nlohmann {
        class json; // forward declaration
        };
        
        class SaveState {
        	private:
        		nlohmann::json* state; // this is a pointer, so it doesn't need the full class declaration
        	public:
        		std::string getCurrentSong();
        		void loadSaveState();
        	        SaveState();
        };

        note that state is now a pointer, so it needs to be assigned a value in the constructor of SaveState:

        state = new nlohmann:json;

        this way getCurrentSong() returns a std::string and a file including this file doesn't need to include (directly or indirectly) json.hpp.

        If you need to return more stuff from getCurrenSong() than a simple string, you could define your custom struct, e.g.:

        struct Song {
        std::string name;
        std::string author;
        unsigned int duration;
        }

        and use this declaration for getCurrentSong():

        		Song getCurrentSong();

        then, in its implementation you would have to parse the json and fill in the values of the struct object that you'll return.

        Every file that includes state.h will also include json.hpp which is a 893KB file (!!).

        nickcamillo #include "json.hpp"

        Instead include json_fwd.hpp which is a file of only 6KB. This should cut down a lot of compilation time for all files including state.h.

        In state.cpp you will need to include the full library using #include "json.hpp".

        @giuliomoro when I try your suggestion I get a reference to json is ambiguous error when defining the state pointer:

        nlohmann::json* state

        @Ward when I try to use the fwd declarations I get this error on the definition of the state variable:

        implicit instantiation of undefined template 'nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char>, bool, long long, unsigned long long, double, std::allocator, adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> >, void>' column: 18, line: 12
        #include "json_fwd.hpp"
        
        #ifndef STATE_H_
        #define STATE_H_
        #endif
        
        class SaveState {
        	private:
        		nlohmann::json state;
        	public:
        		nlohmann::json getCurrentSong();
        		void loadSaveState();
        	        SaveState();
        };};

          nickcamillo when I try your suggestion I get a reference to json is ambiguous error when defining the state pointer:

          nlohmann::json* state

          Uff ... turns out nlohmann::json is not actually a json class in the nlohmann namespace, but json is an alias for basic_json<>, courtesy of:

           using json = basic_json<>;

          pfffffff.... so you'd need an even more hack-ish thing:

          SaveState.h:

          #include <string>
          
          class myjson;
          class SaveState {
          	private:
          		myjson* state; // this is a pointer, so it doesn't need the full class declaration
          	public:
          		std::string getCurrentSong();
          		void loadSaveState();
          	        SaveState();
          };

          Then in SaveState.cpp:

          #include "json.hpp"
          #include "SaveState.h"
          class myjson: public nlohmann::json {}; // definition for the myjson class

          Anyhow, given how your library provides a json_fwd.hpp, things can be simplified without using the myjson class:
          SaveState.h:

          #include <string>
          #include "json_fwd.hpp"
          
          class SaveState {
          	private:
          		nlohmann::json* state; // this is a pointer, so it doesn't need the full class declaration
          	public:
          		std::string getCurrentSong();
          		void loadSaveState();
          	        SaveState();
          };

          Then in SaveState.cpp:

          #include "json.hpp"
          ....

          Note how this solution still uses a json* pointer instead of a json object as a member to SaveState. This will avoid the issue you reported:

          nickcamillo when I try to use the fwd declarations I get this error on the definition of the state variable:

          implicit instantiation of undefined template 'nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char>, bool, long long, unsigned long long, double, std::allocator, adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> >, void>' column: 18, line: 12

          In general, when using forward declarations you cannot use objects in files that only see the fwd declaration. You can only use them (and/or call any method(including contstructor/destructor) on them) on files that see the full declaration.

          5 days later

          @giuliomoro Alright, I think I'm on the verge of getting this working. I did what you outline above, the only difference being I kept the return value of getCurrentSong as nlohmann::json* because it's an object. Also, currentSongId is another private int added to the header along with a simple setter.

          Everything compiles but then I get a runtime error the first time I try call getCurrentSong. When I parse the JSON on startup I'm setting the private state variable like so:

          void SaveState::loadSaveState() {
          	
          	std::ifstream f(kStateJsonFilepath);
          	nlohmann::json tempState = json::parse(f);
          	
          	state = &tempState;
          	
          	int currentSongId = state->value(kCurrentSongId, kIdNotFound);
          	
          	rt_printf("CURRENT SONG ID: %d\n", currentSongId);
          	
          	if (currentSongId != kIdNotFound) {
          		setCurrentSongId(currentSongId);
          	}
          	rt_printf("state: %s\n", state->dump(-1).c_str());
          }

          At the end of that same function, I've verified that the json has been properly parsed via:

          rt_printf("state: %s\n", state->dump(-1).c_str());

          which looks exactly as I'd expect.

          However, when I call getCurrentSong and try to run the same print statement from there, I'm getting mixed bad results:

          nlohmann::json* SaveState::getCurrentSong() {	
          	rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
          	
          	for(auto &song : (*state)[kSongs]) {
          	    int songId = song[kSongId];
          	    
          	    if (songId == currentSongId) {
          	    	return &song;
          	    }
          	}
          	
          	return &kIdNotFound;
          } 

          The first time it's called I get

          rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
          //  null

          The second time I get

          rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
          // {"songs": null} 

          There are no places in which that private state variable are being set or modified except for in that initial load function where I am successfully setting/reading from/printing out the dereferenced variable. The getCurrentSong function works perfectly when the state variable is not a pointer. The only change I made to the loadSaveState function was to first load the parsed JSON data into a tempState variable before assigning the class instance's state variable to the pointer. I only did this because trying to do it directly like so

          state = &(json::parse(f));

          produced an error: `Taking the address of a temporary object of type 'nlohmann::json...etc'

          Am I doing anything obviously wrong here? It seems to me like maybe that tempState variable is getting discarded at the end of the function call and so when I try to read the state variable later it is null? Although I don't see why I'd get two different results when trying to parse the state. I'm not sure I can see the way around this without declaring another private variable in the header that is not a pointer. Thoughts?

            nickcamillo nlohmann::json tempState = json::parse(f);

            state = &tempState;

            When tempState goes out of scope- at the end of the function- the pointer stored in state becomes invalid and should no longer be used.

            nickcamillo There are no places in which that private state variable are being set or modified except for in that initial load function where I am successfully setting/reading from/printing out the dereferenced variable.

            The pointer doesn't change, but the content of the memory it points to may, once the object that was originally stored there gets destroyed.

              giuliomoro so does that mean I woud need to do i/o and re-parse the file every time I need to access the current state? or does this mean that instead of leaving it as the nlohmann::json type I should parse, then assign to a private struct that I define myself that will remain in memory?

              you need to make a copy of the memory instead of the pointer to the memory, which means you need to allocate memory for it. I think something like this may work

              delete tempState; // delete old object
              state = new json(json::parse(f));

              you'll also need to set tempState = nullptr in the constructor or in the class declaration and to call delete state in the destructor. The former is important so the first call to delete is successful (it's valid to call delete on a nullptr); the latter is important so that the json object is properly destroyed and its memory released when the SaveState object is destroyed.

                Not sure I understand why it would... Does the change I suggested work?

                  giuliomoro I don't understand how the constructor would know about the tempState variable without declaring it in the header. Using state = new json(json::parse(f)) seems to work without the tempState variable.

                  oh I see, my bad. It should be:

                  	delete state; // delete old object
                  	state = new json(json::parse(f));

                  you'll also need to set state = nullptr in the constructor or in the class declaration.