Where to put app resource files in Projucer for Android

Android super noob here! I’m looking for a way to include some midi, json and soundfont files in an Android app. While I found how to make it work for the iOS version, I cannot find where to put the folders that include the files for the Android version.

I guess that I’ll have to declare the path of the folders somewhere in Projucer; I’ve tried out all fields that seem relevant* in “Exporters/Android” (as well as in Debug and Release), but made no progress.

Thanks!

  • I’ve tried putting the absolute resources path in:
  • Exporters/Android -> Resources folders
    Here it complains about the files not being “.xml” so I guess this field is irrelevant.

  • Exporters/Android/Release & Debug -> Extra Android Raw Resources
    No error here but files are still not discoverable.

If the files only need to be accessed by the JUCE app, best way is to add them as Binary Data via Projucer.

Thank you so much for the quick response!

It appears that I have more fundamental questions regarding Projucer though…

I have already used the “+” icon in “File Explorer” to add the folders containing the files and when browsing those folders in Projucer all file have the “Binary Resource” ticked (if that means anything).

Would this be enough to constitute those files binary data or there is another process that I’m missing?

Thank you again!

Yes that’s pretty much it.

So your next port of call would be to check your BinaryData.h/cpp to make sure the data is present (it’s created when saving a project).
Then look up how to access the data, e.g. BinaryData::MyXmlFile_xml - plenty of examples around depending on how you are going to read/use the file data.

Thanks again for the response @adamski! Indeed I was ignoring the binary data process that goes on behind the scenes when importing data to Projucer.

After looking at numerous examples I figured out that the best (if not the only) way to include midi files in an Android app is to:

  1. Create a subfolder within the app’s main folder to keep the midi files in.

  2. Scan the binary files and look for “.mid” files.

  3. Create a file out of each binary within the midi subfolder created in step 1.

Then the app would use the midi folder created with this process and access each midi file separately. Since I developed the app based on its iOS version, the entire functionality of the app depends on a function that does all the above and returns the folder that includes the midi files:

const File getSoundfontsDirectory()
{
	# if JUCE_IOS
	    // iOS-related code here
	#if JUCE_ANDROID
            // create a folder for midi files
            String appDirPath = File::getSpecialLocation( File::SpecialLocationType::currentApplicationFile ).getFullPathName();
            const File midi_directory = File::getSpecialLocation (File::userApplicationDataDirectory).getChildFile ("PitchBop_MIDI_licks");
            bool dir_ok = false;
            if (midi_directory.isDirectory()){
                dir_ok = true;
                Logger::writeToLog("dir_ok - already directory");
            }else{
                dir_ok = midi_directory.createDirectory();
            }
            if(dir_ok){
                Logger::writeToLog("dir_ok");
            }else{
                Logger::writeToLog("dir NOT ok");
            }
            // get all binaries and check which correspond to midi
            int num_of_binaries = BinaryData::namedResourceListSize;
            for(int i=0; i<num_of_binaries; i++){
                String originalName = BinaryData::originalFilenames[i];
                if(originalName.endsWithIgnoreCase(".mid")){
                    // get size of this midi file
                    int tmp_file_size;
                    BinaryData::getNamedResource(BinaryData::namedResourceList[i], tmp_file_size);
                    // create a temporary file for this midi binary, within the directory created above
                    const File tmp_file = File(midi_directory.getChildFile(BinaryData::originalFilenames[i]));
                    bool file_ok = tmp_file.create();
                    if(file_ok){
                        Logger::writeToLog("file created OK");
                    }else{
                        Logger::writeToLog("file NOT created");
                    }
                    // create a file output stream to write the file
                    FileOutputStream output (tmp_file);
                    if (output.openedOk()){
                        Logger::writeToLog("file opened OK");
                        output.setPosition(0);
                        output.truncate();
                        output.write (BinaryData::namedResourceList[i], tmp_file_size);
                        output.flush();
                    }else{
                        Logger::writeToLog("file NOT opened");
                    }
                }
            }
            // sanity check: print how many files in directory
            Logger::writeToLog("number of files in midi_directory: "+String( midi_directory.getNumberOfChildFiles(2,"*.mid") ));
            return midi_directory;
        #endif
}

Here comes a test of the output of this function:

// the app has initialised a midi file
MidiFile pbMidiFile;
// call the function defined above to get the directory that includes the midi files
Array<File> midiFiles = getMidiDirectory().findChildFiles(File::findFiles, false, "*.mid");
// get a random midi file
File f = midiFiles[0];

// read the file and place it in the initialised midi file
FileInputStream theStream(f);
pbMidiFile.clear();
pbMidiFile.readFrom(theStream);
pbMidiFile.convertTimestampTicksToSeconds();
// find tempo events from initialised midi file
MidiMessageSequence tempoEvents;
pbMidiFile.findAllTempoEvents(tempoEvents);
MidiMessageSequence::MidiEventHolder *event = tempoEvents.getEventPointer(0);
MidiMessage message = event->message; // here it appears to be crushing...

When getting the folder that includes the midi files in iOS (with a process that is extremely much simpler), everything works fine, so I guess that the error is in the way that I try to create the folder and append files in there - using the function given in first part of code that I’ve pasted. I’ve also run in the same issue when trying to create a folder with sound font files.

This message was too big - sorry for that - but I think this was necessary for completeness purposes. Thank you everyone who had the time to have a look at it!

You could also store all your MIDI or SoundFont files in a dedicated zip file and add that to your binary data. Then you can treat it like a folder and avoid having to scan all your binary data. Check the ZipFile class.

Thank you again @adamski! Your suggestion to use a zipped folder with the files of interest worked!

I still wonder, though, why returning a directory created with “.createDirectory()” (and programmatically putting BinaryData files therein) didn’t work, while unzipping and returning a folder directly from BinaryData works.

Anyway, here’s the code based on @adamski 's idea that worked (replaces function / first part in my previous answer:):

const File getSoundfontsDirectory()
{
	# if JUCE_IOS
	    // iOS-related code here
	#if JUCE_ANDROID
            // create a folder for midi files
            String appDirPath = File::getSpecialLocation( File::SpecialLocationType::currentApplicationFile ).getFullPathName();
            // create file to put zipped folder in
            File tmp_file = File(File::getSpecialLocation (File::userApplicationDataDirectory).getChildFile ("unzippedFiles"));
            bool file_ok = tmp_file.create();
            file_ok ? Logger::writeToLog("file_ok") : Logger::writeToLog("file NOT ok");
	    // write zipped file in there
            FileOutputStream output (tmp_file);
            if (output.openedOk()){
                Logger::writeToLog("file opened OK");
                output.setPosition(0);  // default would append
                output.truncate();
                output.write ( BinaryData::all_resources_zip , BinaryData::all_resources_zipSize);
                output.flush();
            }else{
                Logger::writeToLog("file NOT opened");
            }
	    // get zip file in proper form for further processing
            ZipFile zipFile(tmp_file);
	    // eventually, the zipped folder "all_resources" was unzipped in a folder with the same name...
            const File all_resources = File::getSpecialLocation (File::userApplicationDataDirectory).getChildFile ("all_resources");
            bool dir_ok = all_resources.createDirectory();
            dir_ok ? Logger::writeToLog("dir_ok") : Logger::writeToLog("dir NOT ok");
	    // unzip
            bool unzip_ok = zipFile.uncompressTo(all_resources);
            unzip_ok ? Logger::writeToLog("unzip_ok") : Logger::writeToLog("unzip NOT ok");
            // return the directory now - works fine!
            File soundfonts_directory = all_resources.getChildFile("all_resources").getChildFile("Soundfonts");
            return soundfonts_directory;
        #endif
}
2 Likes