Hi Christian,

thanks a lot for the info!

Regarding regex:
I know regex, but was not too familiar with it.
>From what I understand, it is hard to foresee, in what order the
attributes are in the filename, which makes direct usage of regex
difficult.

I already toyed around a little bit and wrote a patch (2 files
attached), that will do the following:

- Use '-s' as a switch to use the 'filenamescanner'.
- You will be presented with some instructions on how to use it.
Basically, this works like formatting with printf. Also, the first
filename of the set will be printed.
- Enter a 'scheme' that will be used to parse the filenames.
- Eventually enter required attributes that are missing in the filename
(like e.g. velocity, that would be a constant for all samples then).
- Show the result based on the first filename in the set.
- Proceed, retry or abort.

It uses regex to parse the input-scheme and thereby create another
regex-string (that will then parse the filenames) plus mapping of the
found patterns to the corresponding attributes.
The code calculates NOTE_NRs out of NOTE_NAMES and vice versa, if
either one is missing.

The patch also adds
"
    } else {
        s->MIDIUnityNote = wav->note;
"
as you proposed.

To be done:
- code-cleanup
- test and implement checks against sane input (e.g. NOTE_NR has a
valid format) and make code more robust.
- 'hide' filename-path, only the filename itself is relevant.
(Although, expanding the usage of wav2gig to be able to create multiple
gig-files based on a directory-structure could be interesting.)

Wonnahave:
- A flag for automatically generating the .gig-filename like e.g.
'NAME1 - NAME2.gig'.
- The possibility, to automatically join two mono-wav-files into one
stereo-wav-file. Another specifier for identifying those in the
filename could be added, like %s. Currently, this has to be done
'manually' (e.g. with sox), bevor using wav2gig.
- a flag to expanse the created regions (with a minimum of pitch-
correction), so that a gapless, ready-to-use instrument is created.
I think, this would make the tool very valuable for creating
instruments from scratch.

As I said, this is my first contribution (and the first patch I ever
wrote), I still lack knowledge of versioning and writing code that is
e.g. windows-compatible (I'm on Archlinux, had to adjust the PKGBUILD
of libgig-svn since it woldn't compile due to pango-errors), so bare
with me... ;)

If you'd rather go into another direction, that is fine with me as
well, it still was a good exercise for me :)

Let me know what you think!

Cheers,
Kolja

P.S.: I put the functions for the filenamescanner into a separate file
(filenamescanner.cpp). Both files need to be in the directory of
wav2gig.cpp since I don't know how this is usually done.



Am Donnerstag, dem 26.08.2021 um 15:35 +0200 schrieb Christian
Schoenebeck:
> On Mittwoch, 25. August 2021 12:26:38 CEST Kolja Koch wrote:
> > Hello all,
> > 
> > I played around a little bit with the wav2gig tool, in order to get
> > samples extracted by akaiextract into a gig-file.
> > Currently, wav2gig only supports a hard coded naming convention for
> > the
> > wav-files in order to get the information needed for the import
> > process.
> > 
> > - Are there any plans to integrate the import-process into gigedit?
> 
> In libgig there is already a quite a collection of individual command
> line 
> tools. The plan is to first make these already existing tools available
> through a convenient API accessible from the libgig DLL itself. I've
> already 
> started work on this on libgig side (not committed yet).
> 
> As a next step GUI applications like GigEdit would be extended to call
> those 
> libgig functions to offer the respective tools like conversions,
> integrity 
> checks, and so forth. This could obviously then also be used by other
> GUI 
> apps.
> 
> > If so, we could present the user with an interface similar to the
> > "Fill
> > Tag" scanner in eastag:
> > http://src.gnu-darwin.org/ports/audio/easytag/work/easytag-2.1/doc/EasyTAG_D
> > ocumentation.html#vh_1_2_2 Note: Any characters in the filename that
> > should
> > be ignored can be entered directly and work like a delimiter, just
> > like the
> > " - " in the example.
> 
> wav2gig.cpp already uses regular expressions (RegEx) to extract info
> from the 
> individual file names. These RegEx patterns are currently hard coded in
> wav2gig.cpp, but the plan was to use them just as default RegEx
> patterns and 
> allowing to override them individually with custom RegEx patterns from
> the 
> command line if needed.
> 
> While the template format in EasyTag is easier to understand, it is
> also much 
> more limited than RegEx patterns.
> 
> Regular expressions are a common standard for text parsing tasks across
> all 
> programming languages, including shell scripts.
> 
> > Disadvantage is, that this will only work, if there are indeed
> > delimiters. In my case, the filenames look like this:
> > 'STFLSF 5   L.wav'
> > where 
> > 'ST' is the instrument, 
> > 'FLS' the articulation and 
> > 'F 5' is the note 'F#5'. 
> > The 'L' stands for 'left', as there is also a file for the right
> > channel.
> > 
> > Unfortunately, there is also
> > 'STFLSF5    L.wav'
> > where 'F5' is the note 'F5'.
> > Theoretically, there could also be a 'F -1' (= 'F#-1').
> > 
> > I know, we cannot cover any name-scheme one might (not) think of, but
> > as you can see, the only 'dynamic' part of the filename in my case is
> > the note-name, so using the 'easytag'-scanner I could enter
> > 'STFLS%n  '
> > where %n would refer to the note-name.
> 
> Yeah, but RegEx patterns can handle all of them, and also much more 
> complicated file name scenarios beyond that.
> 
> I highly recommend you to readup on RegEx patterns, as this is a highly
> valueable knowledge for any programmer, even if you are just writing
> shell 
> scripts. There are also plenty of sites online that allow you to toy
> arround 
> live with RegEx patterns and arbitrary input, with coloured visual
> diagnostics 
> how the example input text was exactly tokenized, syntax help and much
> more.
> 
> [...]
> > I'm willing to participate on this, though my programming-skills are
> > those of a layman... ;)
> 
> Well, in this particular case, all you need is inside wav2gig.cpp,
> which is a 
> understand and adjust this code, even for beginners.
> 
> CU
> Christian
> 
> 
> 
> 
> _______________________________________________
> Linuxsampler-devel mailing list
> Linuxsampler-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/linuxsampler-devel

#include<iostream>
#include <string>
#include <regex>

using namespace std;
				 
string fs_mapping[][2] = {
		{"n", "NAME1"},
		{"m", "NAME2"},
		{"v", "VELOCITY_NR"},
		{"r", "NOTE_NR"},
		{"a", "NOTE_NAME"}
};
const int ATTRIBUTES = sizeof(fs_mapping)/sizeof(fs_mapping[0]); 

// A NOTE_NAME consists of "note[sign]octave"
const string NOTES[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};



// This struct keeps/gets all required elements for scanning the filenames
// including all specifiers as of fs_mapping.
struct fs_struct {
	string filename_rx="";  	// regex-string to scan filenames
	struct attributes_struct {
		string spec;				// specifier as of fs_mapping
		string spec_name;		// specifier's name
		string str;				// resulting pattern from filename
		int map;				// n-th result in filename-regex-results
	} attr[ATTRIBUTES];
	char notenr;
	char notename;
	string sharpsign = "#";		// needed to eventually calculate
	string flatsign = "b";		// NOTE_NR from given NOTE_NAME or vis versa

	fs_struct() {
		for (int i = 0; i < ATTRIBUTES; i++) {
			attr[i].str = "";
			attr[i].map = -10;
			attr[i].spec = fs_mapping[i][0];
			attr[i].spec_name = fs_mapping[i][1];
			if (attr[i].spec_name == "NOTE_NR")
				notenr = i;
			else if (attr[i].spec_name == "NOTE_NAME")
				notename = i;
		}
	}
	
	void clear() {
		filename_rx="";
		for (int i = 0; i < ATTRIBUTES; i++) {
			attr[i].str = "";
			attr[i].map = -10;
		}
		sharpsign = "#";
		flatsign = "b";
	}
};

// return place in fs_mapping for given symbol
int getAttributeNumber(char symbol) {
	for (int i = 0; i < ATTRIBUTES; i++)
		if (fs_mapping[i][0].c_str()[0] == symbol)
			return i;
	return -1;
}

// generate NOTE_NR out of  Note_Name 
string fs_getNoteNr(struct fs_struct &fs) {
	string notename = fs.attr[fs.notename].str;
	int note = -1;
	int sign = 0;
	int oct = 0;
//	cout << "notename: " << (char)toupper(notename[0]) << endl;

	for (int i = 0; i < 12; i++)
		if (NOTES[i].size() == 1)
			if (toupper(notename[0]) == toupper(NOTES[i][0]))
				note = i;
	if (note == -1)
		return "-1";
	notename.erase(0,1);
//	cout << "sharpsign: " << fs.sharpsign << endl;
//	cout << "notename: " << notename << endl;
//	cout << "fs.attr[fs.notename].str[0]: " << fs.attr[fs.notename].str[0] << endl;
	if (notename.compare(0,fs.sharpsign.size(), fs.sharpsign) == 0) {
		sign = 1;
		notename.erase(0,fs.sharpsign.size());
	}
	else if (fs.attr[fs.notename].str.compare(1,fs.flatsign.size(), fs.flatsign) == 0) {
		sign = -1;
		notename.erase(0,fs.sharpsign.size());
	}

	oct = atoi(notename.c_str());
	
//	cout << "note: " << note << " sign: " << sign << " oct: " << oct << endl;
		
	return to_string(24 + note + sign + oct*12);
}

// generate NOTE_NAME out of  Note_NR
string fs_getNoteName(struct fs_struct &fs) {
	int notenr = atoi(fs.attr[fs.notenr].str.c_str());
	int note, oct;
	
	oct = notenr / 12 - 2;
	note = notenr % 12;
	
	return NOTES[note] + to_string(oct);;
}




// get fileinfo from file via fs
void get_fileinfo(string file, struct fs_struct &fs) {
   	const regex input_e (fs.filename_rx);
   	cmatch cm;
	regex_match (file.c_str(), cm, input_e);

	for (unsigned i=1; i<cm.size(); ++i)	// loop through regex-results
		for (int j = 0; j < ATTRIBUTES; j++)
			if (fs.attr[j].map == i - 1)
				fs.attr[j].str = cm[i];
	
	// Check if Note_NR is available
	if (fs.attr[fs.notenr].map == -10)
		fs.attr[fs.notenr].str = fs_getNoteNr(fs);
	
	// Check if Note_NAME is available
	if (fs.attr[fs.notename].map == -10)
		fs.attr[fs.notename].str = fs_getNoteName(fs);
	
//	for (int i = 0; i < ATTRIBUTES; i++)
//			cout << fs_mapping[i][1] << ":\t" << fs.attr[i].str << endl;
//	cout << endl;
}


// Enter scheme and thereof create regex-pattern and mapping to scan filenames
int schemeinput(string filename, struct fs_struct &fs) {
	cout << "\nFileNameScanner\n"
			<< "---------------\n"
			<< "You can enter a string in order to map patterns of the filenames\n"
			<< "to required attributes. The logic is similar to 'printf'.\n"
			<< endl
			<< "Use any number for [n] in order to specify the number of characters,\n"
			<< "or omit for a variable length.\n"
			<< "You can also enter (constant) substrings of the filename, these will\n"
			<< "be used as delimiters.\n"
			<< endl
			<< "Example 1:\n"
			<< "filename: Name1 - Name2 - 64 - 80\n"
			<< "scheme  : %n - %m - %v - %r\n"
			<< endl
			<< "Example 2:\n"
			<< "filename: /home/me/samples/ASTRA 2    L.wav\n"
			<< "scheme  : %ies/%2n%2m%3a%i\n"
			<< "Note: The string for the sign will have to be entered later on,\n"
			<< "' ' for 'sharp' in this case ('A 2' = A#2).\n"
			<< "The corresponding NOTE_NRs will be calculated based on this."
			<< endl;
	while (true) {
		cout << endl;
		cout << "Available specifiers:\n";
		for (int i = 0; i < ATTRIBUTES; i++)
			cout << "%[n]" << fs_mapping[i][0] << "\t" << fs_mapping[i][1] << endl;
		cout << "%[n]i\tignore" << endl;
		cout << endl;
		cout << "filename:" << filename << endl;		
		cout << "scheme  :";
		string scheme;
		getline (cin,scheme);
		
		// Construct regex-string for scanning input-scheme.
		// All characters from Array fs_mapping will be added, 
		// according to their order in the array, plus 'i' (ignore). 
		string rx_str = "([^%]+)|(%\\d*[";
		for (int i = 0; i < ATTRIBUTES; i++)
			rx_str += fs_mapping[i][0] + "|";
		rx_str += "i])";
		const regex e (rx_str);
	
		// scan scheme and create regex-pattern to scan filenames
		smatch m;
		int it = 0;
		fs.clear();
		while (regex_search (scheme,m,e)) {
			if (m.str(0)[0] == '%') {
				char symbol = m.str(0)[m.str(0).size()-1];
				if (m.str(0).size() > 2) {
					if (symbol == 'i')
						fs.filename_rx += ".{" + m.str(0).substr(1, m.str(0).size()-2) + "}";
					else {
					fs.filename_rx += "(.{" + m.str(0).substr(1, m.str(0).size()-2) + "})";
					fs.attr[getAttributeNumber(symbol)].map = it;
					it +=1;
					}
				} else {
					if (symbol == 'i')
						fs.filename_rx += ".*";
					else {
						fs.filename_rx += "(.*)";
						fs.attr[getAttributeNumber(symbol)].map = it;
						it+=1;
					}
				}
			} else {
				fs.filename_rx += m.str(0);
			}
			scheme = m.suffix().str();
		}
		
//		for (int i = 0; i < ATTRIBUTES; i++)
//			cout << fs.attr[i].map << endl;
		
//		fs.filename_rx += ")";

		// Have user to enter missing attributes
		
		//check if neither Snote nor noteName is available
		string s;
		if ((fs.attr[3].map == -10) and (fs.attr[4].map == -10)) {
			cout << "No pattern for Note_Nr nor Note_Name found,\n"
				 <<	"please enter a static value for Note_Nr, \n"
				 <<	"leave empty to enter a static Note_Name instead.\n"
				 << "Note_Nr: ";
			getline (cin,s);
	//		cout << endl;
			if (s != "") {
				fs.attr[3].str = s;
				fs.attr[3].map = -1;
			} else {
				cout << "Note_Name: ";
				getline (cin,s);
	//			cout << endl;
				if (s != "") {
					fs.attr[4].str = s;
					fs.attr[4].map = -1;
				}
				else {
					cout << "No Note_Nr nor Note_Name specified, aborting..." << endl;
					return 0;
				}
			}
		}
		// No sNote (pattern) given, need to calculate from noteName
		if (fs.attr[3].map == -10) {
	//		string sign;
			
			cout << "Calculating Note_Nr from given Note_Name.\n"
				 <<	"Please enter sign for 'sharp' (default: '#'): ";
			getline (cin,s);
//			cout << endl;
			if (s != "")
				fs.sharpsign = s; 
			
			cout <<	"Please enter sign for 'flat' (default: 'b'): ";
			getline (cin,s);
			cout << endl;
			if (s != "")
				fs.flatsign = s;
		}
		
		// check for missing attributes
		bool available = false;
		for (int i = 0; i < ATTRIBUTES; i++)
			if (
					(fs.attr[i].spec_name != "NOTE_NAME") and 	// not required yet (but will be calculated)
					(fs.attr[i].spec_name != "NAME2") and		// not required yet
					(fs.attr[i].spec_name != "NOTE_NR") and		// already took care of
					(fs.attr[i].spec_name != "NOTE_NAME") and	// already took care of
					(fs.attr[i].map == -10)) {			// no mapping given for attribute
				cout << "Enter static value for " << fs_mapping[i][1] << ":";
				getline (cin,fs.attr[i].str);
			}
//		cout << fs.filename_rx << endl;
//		for (int i = 0; i < ATTRIBUTES; i++)
//			cout << "attr[" << i << "].map: " << fs.attr[i].map << endl;
		
		// check against filename
		get_fileinfo(filename, fs);
		cout << endl;
		cout << "Result:" << endl;
		cout << endl;
		for (int i = 0; i < ATTRIBUTES; i++)
				cout << fs_mapping[i][1] << ":\t" << fs.attr[i].str << endl;
		cout << endl;
		cout << "Is this what you wanted?\n"
				<< "(Y)es, (r)etry, (a)bort: ";
		getline (cin,s);
		if ((s == "y") or (s == "Y") or (s == ""))
			return 1;
		else if (s == "a")
			return 0;
		
	}
	return 0;
}



//int main() {
//	
//	
//	cout << "filename:";
//	string input;
//	getline (cin,input);
//	
//	fs_struct fs;
//
//	schemeinput(input, fs);
//						 
//
//}
--- wav2gig.cpp	2021-08-20 15:44:36.000000000 +0200
+++ wav2gig_kk.cpp	2021-08-27 19:38:03.029243642 +0200
@@ -42,6 +42,8 @@
 #include "../gig.h"
 #include "../helper.h" // for ToString()
 
+#include "./filenamescanner.cpp"
+
 // only libsndfile is available for Windows, so we use that for writing the sound files
 #ifdef WIN32
 # define HAVE_SNDFILE 1
@@ -90,6 +92,8 @@
     cout << endl;
     cout << "  -r  Recurse through all subdirs of provided input WAV dirs." << endl;
     cout << endl;
+    cout << "  -s  Enter scheme to scan filenames for attributes" << endl;
+    cout << endl;
 }
 
 static bool beginsWith(const string& haystack, const string& needle) {
@@ -312,6 +316,43 @@
     return wav;
 }
 
+static WavInfo fs_getWavInfo(string filename, struct fs_struct &fs) {
+    WavInfo wav;
+    wav.fileName = filename;
+    wav.sfinfo = {};
+    {
+        SNDFILE* hFile = sf_open(filename.c_str(), SFM_READ, &wav.sfinfo);
+        if (!hFile) {
+            cerr << "Could not open input wav file \"" << filename << "\"" << endl;
+            exit(EXIT_FAILURE);
+        }
+        wav.hasSfInst = (sf_command(hFile, SFC_GET_INSTRUMENT,
+                                    &wav.sfinst, sizeof(wav.sfinst)) != SF_FALSE);
+        sf_close(hFile);
+        switch (wav.sfinfo.channels) {
+            case 1:
+            case 2:
+                break;
+            default:
+                cerr << int(wav.sfinfo.channels) << " audio channels in WAV file \"" << filename << "\"; this is not supported!" << endl;
+                exit(EXIT_FAILURE);
+        }
+    }
+    {
+    	get_fileinfo(filename, fs);
+    	
+        wav.name1    = fs.attr[0].str;
+        string sVelocity = fs.attr[2].str;
+        wav.velocity = atoi(sVelocity.c_str());
+        string sNote = fs.attr[3].str;
+        wav.note     = atoi(sNote.c_str());
+        wav.name2    = fs.attr[1].str;
+        wav.noteName = fs.attr[4].str;
+    }
+    return wav;
+}
+
+
 inline int getDimensionIndex(gig::Region* region, gig::dimension_t type) {
     for (int d = 0; d < region->Dimensions; ++d)
         if (region->pDimensionDefinitions[d].dimension == type)
@@ -344,7 +385,7 @@
 
     s->FrameSize = s->Channels * s->BitDepth / 8;
     if (wav->hasSfInst) {
-        s->MIDIUnityNote = wav->note;
+        s->MIDIUnityNote = wav->sfinst.basenote;
         s->FineTune      = wav->sfinst.detune;
         if (wav->sfinst.loop_count && wav->sfinst.loops[0].mode != SF_LOOP_NONE) {
             s->Loops = 1;
@@ -364,6 +405,8 @@
             s->LoopPlayCount = wav->sfinst.loops[0].count;
             s->LoopSize = s->LoopEnd - s->LoopStart + 1;
         }
+    } else {
+    	s->MIDIUnityNote = wav->note;
     }
 
     // schedule for resize (will be performed when gig->Save() is called)
@@ -375,7 +418,8 @@
 int main(int argc, char *argv[]) {
     bool bForce = false;
     bool bRecursive = false;
-
+    bool bfilescan = false;
+    
     // validate & parse arguments provided to this program
     int iArg;
     for (iArg = 1; iArg < argc; ++iArg) {
@@ -393,6 +437,8 @@
             bForce = true;
         } else if (opt == "-r") {
             bRecursive = true;
+        } else if (opt == "-s") {
+        	bfilescan = true;
         } else {
             cerr << "Unknown option '" << opt << "'" << endl;
             cerr << endl;
@@ -456,11 +502,24 @@
 
     // order all input wav files into regions and velocity splits
     WavInstrument wavInstrument;
+    
+    fs_struct fs;
+    if (bfilescan)
+    	if (schemeinput(*wavFileNames.begin(), fs) == 0) {
+    		cout << "aborting..." << endl;
+            exit(0);
+    	}
+    	
+    
     cout << "Preprocessing input WAV files by their names ... " << flush;
     for (set<string>::const_iterator it = wavFileNames.begin();
          it != wavFileNames.end(); ++it)
     {
-        WavInfo wavInfo = getWavInfo(*it);
+    	WavInfo wavInfo;
+    	if (bfilescan)
+    		wavInfo = fs_getWavInfo(*it,fs);
+    	else
+    		wavInfo = getWavInfo(*it);
         wavInfo.assertValid(); // make sure collected informations are OK
         if (wavInstrument[wavInfo.note].count(wavInfo.velocity)) {
             cerr << "Velocity conflict between file '" << wavInfo.fileName
_______________________________________________
Linuxsampler-devel mailing list
Linuxsampler-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxsampler-devel

Reply via email to