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