GUI pour Korg NTS-1 écrit en C++ avec la librairie GUI FLTK
Public: MusiciensKorg produit des synthétiseurs, dont le Korg NTS-1.
- Sorti en novembre 2019
- Processeur ARM Cortex-M4
- Arpégiateur, effets reverb/delay/chorus/flanger/phaser
- Synthé monophonique 1 oscillateur, mais on peut en charger 16, c'est un gain d'argent considérable
- On peut aussi charger d'autres effets (8 reverbs, 8 delays)
- On peut le programmer en C/C++ c'est un plus et Korg est le seul à avoir dévoilé les codes Open Source. Les oscillateurs sont compatibles avec le Minilogue XD (600€) et le Korg Prologue (1600€)
- Petit Haut-parleur intégré
- Prise de synchronisation
- Entrée et sortie avec un Câble 3.5mm TRS stereo
- Alimenté en micro-USB (par un PC, chargeur de téléphone, prise USB de voiture, ou Power bank)
- En vente sur Amazon pour 89€
Son entrée audio permet même de servir d'effets pour guitare avec des sons vraiment exceptionnels, par exemple sustain infini!
Doc/downloads
https://www.korg.com/us/support/download/product/0/832/
https://www.korg.com/fr/products/dj/nts_1/index.php
Oscillateurs et effets à télécharger
Comme il est programmable, on peut coder en C/C++ des oscillateurs, mods, delays, reverbs,
et les transférer avec l'application GUI Digital Librarian vers le synthétiseur via un câble USB.
Seulement voilà, cette application ne tourne que sur Windows et Mac.
C'est pourquoi j'ai créé une interface GUI avec la librairie FLTK, pour transférer/supprimer des effets depuis Linux vers le NTS-1.
On peut charger 16 oscillateurs utilisateur, en plus des trois internes sortis d'usine. Ici le mien est plein.
Cliquer sur un slot demande si on veut supprimer ou le remplacer par un nouveau:
Après avoir supprimé le dernier oscillateur, j'en charge un nouveau:
On peut charger 8 reverbs utilisateur:
Code source de cette fenêtre
digital-librarian-linux.cpp#include <stdio.h> #include <stdlib.h> #include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Box.H> #include <FL/Fl_Button.H> #include <FL/Fl_Double_Window.H> #include <FL/Fl_Browser.H> #include <FL/fl_draw.H> #include <FL/Fl_File_Chooser.H> /*=======================================================*/ // Demonstrate how to derive a class extending Fl_Browser with interactively resizable columns. erco 1.10 12/09/2005 class ColResizeBrowser : public Fl_Browser { Fl_Color _colsepcolor; // color of column separator lines int _showcolsep; // flag to enable drawing column separators Fl_Cursor _last_cursor; // saved cursor state info int _dragging; // 1=user dragging a column int _dragcol; // col# user is currently dragging int *_widths; // pointer to user's width[] array int _nowidths[1]; // default width array (non-const) // CHANGE CURSOR Does nothing if cursor already set to value specified. void change_cursor(Fl_Cursor newcursor) { if ( newcursor != _last_cursor ) { fl_cursor(newcursor, FL_BLACK, FL_WHITE); _last_cursor = newcursor; } } // RETURN THE COLUMN MOUSE IS 'NEAR' Returns -1 if none. int which_col_near_mouse() { int X,Y,W,H; Fl_Browser::bbox(X,Y,W,H); // area inside browser's box() // EVENT NOT INSIDE BROWSER AREA? (eg. on a scrollbar) if ( ! Fl::event_inside(X,Y,W,H) ) { return(-1); } int mousex = Fl::event_x() + hposition(); int colx = this->x(); for ( int t=0; _widths[t]; t++ ) { colx += _widths[t]; int diff = mousex - colx; // MOUSE 'NEAR' A COLUMN? Return column # if ( diff >= -4 && diff <= 4 ) { return(t); } } return(-1); } protected: // MANAGE EVENTS TO HANDLE COLUMN RESIZING int handle(int e) { // Not showing column separators? Use default Fl_Browser::handle() logic if ( ! showcolsep() ) return(Fl_Browser::handle(e)); // Handle column resizing int ret = 0; switch ( e ) { case FL_ENTER: { ret = 1; break; } case FL_MOVE: { if ( which_col_near_mouse() >= 0 ) { change_cursor(FL_CURSOR_WE); } else { change_cursor(FL_CURSOR_DEFAULT); } ret = 1; break; } case FL_PUSH: { int whichcol = which_col_near_mouse(); if ( whichcol >= 0 ) { // CLICKED ON RESIZER? START DRAGGING ret = 1; _dragging = 1; _dragcol = whichcol; change_cursor(FL_CURSOR_DEFAULT); } break; } case FL_DRAG: { if ( _dragging ) { ret = 1; // Sum up column widths to determine position int mousex = Fl::event_x() + hposition(); int newwidth = mousex - x(); for ( int t=0; _widths[t] && t<_dragcol; t++ ) { newwidth -= _widths[t]; } if ( newwidth > 0 ) { // Apply new width, redraw interface _widths[_dragcol] = newwidth; if ( _widths[_dragcol] < 2 ) { _widths[_dragcol] = 2; } redraw(); } } break; } case FL_LEAVE: case FL_RELEASE: { _dragging = 0; // disable drag mode change_cursor(FL_CURSOR_DEFAULT); // ensure normal cursor ret = 1; break; } } if ( _dragging ) return(1); // dragging? don't pass event to Fl_Browser return(Fl_Browser::handle(e) ? 1 : ret); } void draw() { // DRAW BROWSER Fl_Browser::draw(); if ( _showcolsep ) { // DRAW COLUMN SEPARATORS int colx = this->x() - hposition(); int X,Y,W,H; Fl_Browser::bbox(X,Y,W,H); fl_color(_colsepcolor); for ( int t=0; _widths[t]; t++ ) { colx += _widths[t]; if ( colx > X && colx < (X+W) ) { fl_line(colx, Y, colx, Y+H-1); } } } } public: // CTOR ColResizeBrowser(int X,int Y,int W,int H,const char*L=0) : Fl_Browser(X,Y,W,H,L) { _colsepcolor = Fl_Color(FL_GRAY); _last_cursor = FL_CURSOR_DEFAULT; _showcolsep = 0; _dragging = 0; _nowidths[0] = 0; _widths = _nowidths; } // GET/SET COLUMN SEPARATOR LINE COLOR Fl_Color colsepcolor() const { return(_colsepcolor); } void colsepcolor(Fl_Color val) { _colsepcolor = val; } // GET/SET DISPLAY OF COLUMN SEPARATOR LINES 1: show lines, 0: don't show lines int showcolsep() const { return(_showcolsep); } void showcolsep(int val) { _showcolsep = val; } // GET/SET COLUMN WIDTHS ARRAY Just like fltk method, but array is non-const. int *column_widths() const { return(_widths); } void column_widths(int *val) { _widths = val; Fl_Browser::column_widths(val); } }; /*=======================================================*/ char strmidi[32]; // exemple "hw:1,0,0" char in =-1; // souvent -i 1 char out=-1; // souvent -o 1 Fl_Button *but[4]; // boutons onglets char ongletSelectionne = -1; Fl_Color grisfonce = fl_rgb_color(50,50,50); //Fl_Color grisclair = fl_rgb_color(80,80,80); const char* types[]={"osc","modfx","delfx","revfx"}; const char* strtypes[]={"Choisir un Oscillateur:","Choisir un Mod:","Choisir un Delay:","Choisir une Reverb:"}; const char* dos[]={"osc","mod","delay","reverb"}; char selectedSlot = -1; Fl_Box *span; // info orange "connecté", "pas connecté" ColResizeBrowser* br; Fl_Window* winpopup; /*======================================================= Regarde si le NTS-1 est connecté avec la commande amidi -l. Si aucun engin MIDI n'est connecté la console dit seulement Dir Device Name\n Sinon amidi dit Dir Device Name\n IO hw:1,0,0 NTS-1 digital kit NTS-1 digital\n ← on veut retenir "hw:1,0,0" pour upload ultérieur ... et peut-être d'autres engins Donc compter le nombre de lignes. Si une seule ligne: aucun engin connecté. Sinon scanner chaque ligne, si elle contient "NTS-1" c'est la bonne, extraire "hw:1,0,0" de l'index 4 jusqu'au 1er espace rencontré. */ bool NTS1Connected() { //printf("Exec [%s]\n", cmd); char ligne[130]; char numligne=0; FILE *fp = popen("amidi -l","r"); if(fp){ while (fgets(ligne, sizeof ligne, fp)){ numligne++; //printf("%d %s", numligne, ligne); if(numligne>1){ int n=0; //scanne la ligne pour voir si elle contient "NTS-1" while(1){ if(ligne[n+4]=='\n') break; else if(ligne[n]=='N' && ligne[n+1]=='T' && ligne[n+2]=='S' && ligne[n+3]=='-' && ligne[n+4]=='1'){ //c'est la bonne ligne: extraire "hw:1,0,0" de l'index 4 jusqu'au 1er espace rencontré. //printf("ligne %d c'est la bonne\n", numligne); n=4; char mot[32]; char b=0; while(ligne[n]!=' '){ mot[b++]=ligne[n++]; } //mot[b]=0; printf("mot:{%s}\n",mot); for(n=0; n<b; n++) strmidi[n]=mot[n]; strmidi[n]=0; // pas besoin d'inclure <string.h> juste pour strcpy(strmidi,mot); break; } n++; } } } fclose(fp); } if(numligne==1) {printf("** NTS-1 non connecté **\n"); return false;} return true; } /*======================================================= Identifie in out pour logue-cli "-i 1 -o 1", pour ultérieurement obtenir la liste des oscillateurs ou en uploader un $ ./logue-cli probe -l Available MIDI inputs: in 0: Midi Through:Midi Through Port-0 14:0 in 1: NTS-1 digital kit:NTS-1 digital kit NTS-1 digital 20:0 Available MIDI ouputs: out 0: Midi Through:Midi Through Port-0 14:0 out 1: NTS-1 digital kit:NTS-1 digital kit NTS-1 digital 20:0 Scanne la ligne pour voir si elle contient "NTS-1" Si oui, regarde les 5e et 6e octets: si "in" saute 2 espaces et récupère la valeur in= jusqu'au : sinon si c'est "out", récupère out= */ void getInOutValues(){ char ligne[130]; FILE *fp = popen("./logue-cli probe -l","r"); if(fp){ while (fgets(ligne, sizeof ligne, fp)){ //printf("%s", ligne); int n=0; //scanne la ligne pour voir si elle contient "NTS-1" while(1){ if(ligne[n+4]=='\n') break; else if(ligne[n]=='N' && ligne[n+1]=='T' && ligne[n+2]=='S' && ligne[n+3]=='-' && ligne[n+4]=='1'){ //printf("c'est la bonne ligne NTS-1\n"); //c'est la bonne ligne: regarde les 5e et 6e octets pour voir si c'est "in" if(ligne[4]=='i' && ligne[5]=='n'){ n=8; char mot[16]; char b=0; // avance jusqu'au : pour récupérer la valeur while(ligne[n]!=':'){mot[b++]=ligne[n++];} mot[b]=0; //printf("in:{%s}\n",mot); in=atoi(mot); } else if(ligne[4]=='o' && ligne[5]=='u' && ligne[6]=='t'){ n=8; char mot[16]; char b=0; // avance jusqu'au : pour récupérer la valeur while(ligne[n]!=':'){mot[b++]=ligne[n++];} mot[b]=0; //printf("out:{%s}\n",mot); out=atoi(mot); } break; } n++; } } fclose(fp); } //printf("in:%d out:%d\n", in, out); } /*=======================================================*/ //0:OSC 1:MODFX 2:delay 3:reverb void onglet_click(char id) { //vide le tableau et remet les en-têtes br->clear(); br->add("\t@B89@C7@l@b@.NAME\t@B112@C7@b@l@.VERSION\t@B120@C7@b@l@.API\t"); char ligne[130]; char cmd [64]; int n = sprintf(cmd,"./logue-cli probe -m %s -i %d -o %d", types[id], in, out); //printf("click %s [%s]\n", types[id], cmd); // Remet en sombre l'ancien onglet slectionné. ça marche pas ça le met en gris clair, mais ne remet pas l'ancien en sombre. et redraw() plante //if(ongletSelectionne!=-1) but[ongletSelectionne]->color(grisfonce); //but[ongletSelectionne]->redraw(); //but[id]->color(grisclair); // et met celui-ci en plus clair ongletSelectionne=id; FILE *fp = popen(cmd,"r"); // envoie la commande if(fp){ while (fgets(ligne, sizeof ligne, fp)) { printf("%s", ligne); if(ligne[0]=='['){ // ignore les 4 première lignes qui commencent par > //012345 //[0]: "waves" v1.00-0 api:1.00-0 did:00000000 uid:00000000 //[1]: free. //[10]: free. //[10]: "waves" v1.00-0 api:1.00-0 did:00000000 uid:00000000 //printf("%s", ligne); char n=6;//à partir de quel octet sortir le nom, 7 si le slot a deux chiffres char slotvide=0; // récupère le numéro de slot char slot[3]; if(ligne[2]==']'){ // slot sur un seul chiffre 0/9 slot[0] = ligne[1]; slot[1]=0; if(ligne[5]=='f') slotvide=1; // le slot est vide si le caractère à l'index 5 est un f (de "free.") } else { // slot sur 2 chiffres 10+ slot[0]=ligne[1]; slot[1]=ligne[2]; slot[2]=0; n=7; if(ligne[6]=='f') slotvide=1; // le slot est vide si le caractère à l'index 6 est un f (de "free.") } //printf("slot (%s) ", slot); //récupère le nom/version/API, si le slot n'est pas vide if(slotvide!=1){ char nom[32]; char a=0; char version[20]; char b=0; char api[20]; char c=0; while(ligne[n]!='"') {nom[a++]=ligne[n++];} nom[a]=0; //printf("nom|%s| ", nom); n+=2; // saute " espace // prend la version while(ligne[n]!=' ') version[b++]=ligne[n++]; version[b]=0;//printf("version:[%s] ", version); // prend l'API n+=5; while(ligne[n]!=' ') api[c++]=ligne[n++]; api[c]=0;//printf("api:[%s]\n", api); //ajoute la ligne au tableau char tmp[200]; n = sprintf(tmp,"@C255@.%s\t@C255@l@.%s\t@C255@.%s\t@C255@.%s", slot, nom, version, api); br->add(tmp); } else{ //printf("%s -- -- --\n", slot); char tmp[200]; n = sprintf(tmp,"@C255@.%s\t@C255@l@.--\t@C255@.--\t@C255@.--", slot); br->add(tmp); } } } pclose(fp); } } /*=======================================================*/ void button_osc_click(Fl_Widget* w, void*) // Clic sur onglet Oscillateurs { onglet_click(0); } /*=======================================================*/ void button_mod_click(Fl_Widget* w, void*) // Clic sur onglet Mod { onglet_click(1); } /*=======================================================*/ void button_delay_click(Fl_Widget* w, void*) // Clic sur onglet Delay { onglet_click(2); } /*=======================================================*/ void button_reverb_click(Fl_Widget* w, void*)// Clic sur onglet Reverb { onglet_click(3); } /*=======================================================*/ void exec(char* cmd) { printf("Exec [%s]\n", cmd); char ligne[130]; FILE *fp = popen(cmd,"r"); if(fp){ while (fgets(ligne, sizeof ligne, fp)){ printf("%s", ligne); } fclose(fp); } } /*=======================================================*/ //Exemple supprime l'osc du slot 6: ./logue-cli clear -m osc -s 6 -i 1 -o 1 void button_suppr_click(Fl_Widget *w, void *data) { winpopup->hide(); char cmd [130]; int n = sprintf(cmd,"./logue-cli clear -m %s -s %d -i %d -o %d", types[ongletSelectionne], selectedSlot, in, out); printf("Suppr [%s]\n", cmd); exec(cmd); // Redemande au NTS-1 la liste des OSC/MOD/DELAY/ ou REVERB pour refaire la liste, normalement à jour onglet_click(ongletSelectionne); } /*=======================================================*/ void button_nouvo_click(Fl_Widget *w, void *data) { winpopup->hide(); //Choisir un fichier... Fl_File_Chooser chooser( ".", // directory "*.ntkdigunit\t*.mnlgxdunit\t*.prlgunit", Fl_File_Chooser::SINGLE, strtypes[ongletSelectionne]); chooser.preview(false); chooser.ok_label("Charger dans le NTS-1"); chooser.directory(dos[ongletSelectionne]); chooser.color(FL_BLACK); chooser.textfont(FL_HELVETICA); chooser.textcolor(FL_WHITE); chooser.textsize(20); chooser.show(); while (chooser.shown()) Fl::wait(); if (chooser.value() == NULL) return; // et le charger vers le NTS-1 printf("\nCharger %s [%s] dans le slot %d\n", types[ongletSelectionne], chooser.value(), selectedSlot); // Lance load.sh // exemple $ ./load.sh 1 1 hw:1,0,0 /media/149Go/C/FLTK/korg/osc/j6_v201.ntkdigunit 8 char cmd [256]; int n = sprintf(cmd,"./load.sh %d %d %s %s %d", in, out, strmidi, chooser.value(), selectedSlot); exec(cmd); // Redemande au NTS-1 la liste des OSC/MOD/DELAY/ ou REVERB pour refaire la liste, normalement à jour onglet_click(ongletSelectionne); } /*=======================================================*/ void ligne_click(Fl_Widget *w, void *data) { Fl_Browser *fbrow = (Fl_Browser*)w; int index = fbrow->value(); if (ongletSelectionne==-1 || index <= 1) return; selectedSlot = index-2; //printf("click %s slot %d\n", types[ongletSelectionne], selectedSlot); // Ouvre la fenêtre modale pour demander "supprimer ou charger un nouveau dans ce slot?" winpopup->show(); } /*=======================================================*/ int main(int argc, char **argv) { Fl_Window *win = new Fl_Window(820,700, "NTS-1 digital librarian"); win->color(FL_BLACK); win->position((Fl::w() - win->w())/2, (Fl::h() - win->h())/2); // Titre blanc Fl_Box *box = new Fl_Box(0,20,260,40,"NTS-1 digital"); box->box(FL_FLAT_BOX); box->color(FL_BLACK); box->labelcolor(FL_WHITE); box->labelfont(FL_BOLD); box->labelsize(30); // Span Info span = new Fl_Box(300,20,260,40,"Welcome"); span->box(FL_FLAT_BOX); span->labelsize(20); span->color(FL_BLACK); span->labelcolor(fl_rgb_color(245, 127, 23)); span->labelfont(FL_BOLD); // Boutons onglets static const char *captions[4]={"USER OSCILLATORS","USER MODULATION FX","USER DELAY FX","USER REVERB FX"}; for(char n=0; n<4; n++){ but[n] = new Fl_Button(n*5+(n*200),70, 200,30, captions[n]); but[n]->box(FL_FLAT_BOX); but[n]->color(grisfonce); but[n]->labelcolor(FL_WHITE); } but[0]->callback(button_osc_click); but[1]->callback(button_mod_click); but[2]->callback(button_delay_click); but[3]->callback(button_reverb_click); // Table int widths[] = {30,200,140,80}; // widths for each column br = new ColResizeBrowser(0,100, win->w(),win->h()-70); br->column_widths(widths); br->showcolsep(1); br->colsepcolor(FL_DARK1); br->column_char('\t'); // tabs as column delimiters br->type(FL_HOLD_BROWSER);//FL_MULTI_BROWSER); br->callback(ligne_click); br->color(FL_BLACK); //@l: large font @b: bold @B12: background-color 12 C255: text color white br->add("\t@B89@C7@l@b@.NAME\t@B112@C7@b@l@.VERSION\t@B120@C7@b@l@.API\t"); //br->add("@C255@.0\t@C255@l@.waves\t@C255@.v1.00-0\t@C255@.1.00-0"); //br->add("@C255@.1\t@C255@l@.Volca Bass\t@C255@.v1.00-1\t@C255@.1.01-0"); //br->add("@C255@.2\t@C255@l@.808bass\t@C255@.v0.01-0\t@C255@.1.01-0"); br->add("@C255@.0\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.1\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.2\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.3\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.4\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.5\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.6\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.7\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.8\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.9\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.10\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.11\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.12\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.13\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.14\t@C255@l@.--\t@C255@.--\t@C255@.--"); br->add("@C255@.15\t@C255@l@.--\t@C255@.--\t@C255@.--"); // Regarde si logue_cli est présent FILE *fp = fopen("logue-cli","r"); if(!fp) { span->label("logue_cli est introuvable"); //int a=system("xdg-open https://github.com/korginc/logue-sdk/blob/master/tools/logue-cli/get_logue_cli_linux.sh"); } else{ fclose(fp); // Regarde si le NTS-1 est branché if(!NTS1Connected()) span->label("Branchez le NTS-1 en USB\npuis relancez l'appli"); //printf("Aucun engin MIDI connecté. Branchez le NTS-1 en USB et redémarrez\n"); else{ span->label("Connecté");//printf("strMIDI:{%s}\n",strmidi); //Identifie les ports entrée/sortie pour renseigner les variables in out -i 1 -o 1 getInOutValues(); } } onglet_click(0); // Charge les oscillateurs win->end(); win->show(argc, argv); //Fenêtre popup modale cachée pour l'instant winpopup = new Fl_Window(310,70); winpopup->set_modal(); winpopup->color(FL_BLACK); Fl_Button *suppr = new Fl_Button(20,20, 100,30, "Supprimer"); suppr->box(FL_FLAT_BOX); suppr->color(grisfonce); suppr->labelcolor(FL_WHITE); suppr->callback(button_suppr_click); Fl_Button *nouvo = new Fl_Button(150,20, 150,30, "Charger nouveau"); nouvo->box(FL_FLAT_BOX); nouvo->color(grisfonce); nouvo->labelcolor(FL_WHITE); nouvo->callback(button_nouvo_click); return Fl::run(); }
Compilation de cette application
Pour compiler l'appli il faut installer la lib FLTK 1.3 et son paquet de développement:$ sudo apt install libftlk1.3 libfltk1.3-dev $ cd /home/joe/korg $ fltk-config --compile digital-librarian-linux.cppVous pouvez même la compiler sur Windows et Mac, en cas de problème de compilation regarder ici documentation FLTK 1.3
Si vous avez déja l'exécutable compilé, pour le déployer sur une autre machine Debian/Ubuntu il suffit d'installer la librairie FLTK 1.3:
$ sudo apt-get install libfltk1.3il faut aussi une copie de logue_cli (le mettre près de l'exé), qui permet de transférer en ligne de commande.
Un script shell est lancé par l'exécutable pour uploader les modules vers le synthétiseur:
load.sh
#!/usr/bin/env sh # Exemple to load OSC J6 in slot 8 # $1 $2 $3 $4 $5 #$ ./load.sh 1 1 hw:1,0,0 /home/joe/osc/j6_v201.ntkdigunit 8 ./logue-cli load -v -i $1 -o $2 -u $4 -s $5 -d > load.log tail 1 load.log| sed 's/,//g' | sed 's/}//g' | sed 's/{//g' | sed 's/>//g' | sed 's/^ *//g' > load.sysex amidi -p $3 -S `cat load.sysex` rm load.log rm load.sysex
amidi doit être installé, testez avec la commande
$ amidi -l $ sudo apt-get install amidiUn dossier osc, un dossier mod, un dossier delay, un dossier reverb
Ces dossiers contiennent les fichiers à transférer vers le NTS-1
Ce n'est pas obligatoire car les fichiers à transférer peuvent être n'importe où, mais c'est plus pratique.
Comment lancer cette application
Depuis une console:$ cd /home/joe/korg $ ./digital-librarian-linux ou $ ./home/joe/digital-librarian-linuxou double-cliquer sur l'icone de l'exécutable,
ou créer un lanceur dans la barre de lancement rapide en glissant/déplaçant l'exé sur la barre,
ou créer un fichier .dektop
Si vous avez un Korg NTS-1 et vous aimez Linux, je vous invite fortement à
Télécharger le kit complet: exécutable Ubuntu/source/scripts Shell/oscillateurs, mods, delay, reverbs (497Ko)
Le Korg NTS-1 est en vente sur Amazon pour 89€