diff --git a/CHANGELOG b/CHANGELOG index 6e5979ef..e65be33f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ Various additions Fix regression caused in https://github.com/djdiskmachine/LittleGPTracker/pull/245 Fix introduced microtonality for single cycle osc, thank you @INFU-AV <3 Purge samples actually purges samples [author: maks](https://github.com/xiphonics/picoTracker/pull/313) + Clear SoundFonts on SamplePool reset 1.6.0-bacon11 Slices decoupled from loop mode diff --git a/docs/wiki/What-is-LittlePiggyTracker.md b/docs/wiki/What-is-LittlePiggyTracker.md index b05c0e7a..c7236b84 100644 --- a/docs/wiki/What-is-LittlePiggyTracker.md +++ b/docs/wiki/What-is-LittlePiggyTracker.md @@ -47,7 +47,7 @@ After that you can copy additional wavs to the lgptRoot/lgptProject/samples dire **Use 8 or 16 Bit wav files, any sampling frequency, mono or stereo**. 8bit samples are converted to 16bit at load time for compatibility with the engine (you can save space in storage but not in RAM). -**Piggy now supports .sf2 Soundfonts. You must add these by hand to your SAMPLES directory, use PROGRAM CHANGE commands to load different patches. Loop points are automatically loaded, but you'll need to make VOLM setting to adjust decay.** +**Piggy now supports .sf2 Soundfonts.** You must add these by hand to your SAMPLES directory, use PROGRAM CHANGE commands to load different patches. Loop points are automatically loaded, but you'll need to make VOLM setting to adjust decay. By default, at most 3 are loaded per project; this limit may be increased by recompiling LittleGPTracker with the compiler flag `-DMAXLOADEDBANKS=X`, where `X` is the desired limit. ## New project diff --git a/sources/Application/AppWindow.cpp b/sources/Application/AppWindow.cpp index 587ccc47..d1d30fa8 100644 --- a/sources/Application/AppWindow.cpp +++ b/sources/Application/AppWindow.cpp @@ -331,7 +331,7 @@ void AppWindow::LoadProject(const Path &p) { SamplePool *pool = SamplePool::GetInstance(); - pool->Load(); + unsigned int load_result = pool->Load(); Project *project = new Project(); @@ -397,6 +397,19 @@ void AppWindow::LoadProject(const Path &p) { _songView->DoModal(mb); } + // Report on sample & SoundFont load fails + if (load_result) { + const char *err_str = (load_result == SLOAD_ERR_MAX_SAMPLES) ? "Maximum number of samples exceeded" + : (load_result == SLOAD_ERR_MAX_SOUNDFONTS) ? "Maximum number of SoundFonts exceeded" + : (load_result == SLOAD_ERR_MAX_SAMPLES | SLOAD_ERR_MAX_SOUNDFONTS) ? "Maximum number of samples and SoundFonts exceeded" + : (load_result == SLOAD_ERR_INVALID_DIR) ? "Sample directory could not be opened" + : "Unknown error loading sample pool"; + Trace::Error(err_str) ; + MessageBox *mb = + new MessageBox(*_currentView, err_str); + _currentView->DoModal(mb); + } + Redraw(); } diff --git a/sources/Application/Instruments/SamplePool.cpp b/sources/Application/Instruments/SamplePool.cpp index 91d4815e..96cedcf1 100644 --- a/sources/Application/Instruments/SamplePool.cpp +++ b/sources/Application/Instruments/SamplePool.cpp @@ -42,19 +42,26 @@ void SamplePool::Reset() { SoundFontManager::GetInstance()->Reset() ; } ; -void SamplePool::Load() { +/* + Returns an element of + {SLOAD_OK, SLOAD_ERR_INVALID_DIR, SLOAD_ERR_MAX_SAMPLES, + SLOAD_ERR_MAX_SOUNDFONTS, SLOAD_ERR_MAX_SAMPLES | SLOAD_ERR_MAX_SOUNDFONTS}. +*/ +unsigned int SamplePool::Load() { - Path sampleDir("samples:"); + Path sampleDir("samples:"); - I_Dir *dir=FileSystem::GetInstance()->Open(sampleDir.GetPath().c_str()) ; - if (!dir) { - return ; - } + I_Dir *dir = FileSystem::GetInstance()->Open(sampleDir.GetPath().c_str()); + if (!dir) { + return SLOAD_ERR_INVALID_DIR; + } + + unsigned int result = SLOAD_OK; - // First, find all wav files + // First, find all wav files - dir->GetContent("*.wav") ; - IteratorPtr it(dir->GetIterator()) ; + dir->GetContent("*.wav"); + IteratorPtr it(dir->GetIterator()) ; count_=0 ; for(it->Begin();!it->IsDone();it->Next()) { @@ -62,8 +69,9 @@ void SamplePool::Load() { Trace::Log("Load", "%s", path.GetCanonicalPath().c_str()); loadSample(path.GetPath().c_str()) ; if (count_==MAX_PIG_SAMPLES) { - Trace::Error("Warning maximum sample count reached") ; - break ; + result |= SLOAD_ERR_MAX_SAMPLES; + Trace::Error("Warning maximum sample count reached"); + break; } ; } ; @@ -72,17 +80,26 @@ void SamplePool::Load() { dir->GetContent("*.sf2") ; IteratorPtr it2(dir->GetIterator()) ; + int sf_idx = 0; - for(it2->Begin();!it2->IsDone();it2->Next()) { - Path &path=it2->CurrentItem() ; - loadSoundFont(path.GetPath().c_str()) ; - } ; + for (it2->Begin(); !it2->IsDone(); it2->Next(), sf_idx++) { + Path &path=it2->CurrentItem() ; + Trace::Log("Load", "%s", path.GetCanonicalPath().c_str()); + loadSoundFont(path.GetPath().c_str()) ; + if (sf_idx == MAX_SOUNDFONTS) { + result |= SLOAD_ERR_MAX_SOUNDFONTS; + Trace::Error("Warning maximum soundfont count reached"); + break; + } ; + }; - delete dir ; + delete dir; - // now sort the samples + // now sort the samples Sort(); -} ; + + return result; +}; void SamplePool::Sort() { int rest=count_; @@ -124,41 +141,51 @@ int SamplePool::GetNameListSize() { return count_ ; } ; -bool SamplePool::loadSample(const char *path) { - - if (count_==MAX_PIG_SAMPLES) return false ; - - Path sPath(path) ; - Status::Set("Loading %s",sPath.GetName().c_str()) ; - Trace::Log("loadSample", "%s", path); - - Path wavPath(path); - WavFile *wave=WavFile::Open(path) ; - if (wave) { - wav_[count_]=wave ; - const std::string name=wavPath.GetName() ; - names_[count_]=(char*)SYS_MALLOC(name.length()+1) ; - strcpy(names_[count_],name.c_str()) ; - count_++ ; - wave->GetBuffer(0,wave->GetSize(-1)) ; - wave->Close() ; - return true ; - } else { - Trace::Error("Failed to load samples %s",wavPath.GetName().c_str()) ; - return false ; - } +/* + Returns an element of + {SLOAD_OK, SLOAD_ERR_MAX_SAMPLES, SLOAD_ERR_INPUT_FILE}. +*/ +int SamplePool::loadSample(const char *path) { + + if (count_==MAX_PIG_SAMPLES) return SLOAD_ERR_MAX_SAMPLES ; + +Path sPath(path) ; +Status::Set("Loading %s", sPath.GetName().c_str()); +Trace::Log("loadSample", "%s", path); + +Path wavPath(path); +WavFile *wave = WavFile::Open(path); +if (wave) { + wav_[count_] = wave; + const std::string name = wavPath.GetName(); + names_[count_] = (char *)SYS_MALLOC(name.length() + 1); + strcpy(names_[count_], name.c_str()); + count_++; + wave->GetBuffer(0, wave->GetSize(-1)); + wave->Close(); + return SLOAD_OK; +} else { + Trace::Error("Failed to load samples %s", wavPath.GetName().c_str()); + return SLOAD_ERR_INPUT_FILE; +} } #define IMPORT_CHUNK_SIZE 1000 +/* + Returns a nonnegative int or an element of + {-SLOAD_ERR_INVALID_DIR, -SLOAD_ERR_INPUT_FILE, -SLOAD_ERR_MAX_SAMPLES, + -SLOAD_ERR_MAX_SOUNDFONTS}. +*/ int SamplePool::ImportSample(Path &path) { - if (count_==MAX_PIG_SAMPLES) return -1 ; + if (count_ == MAX_PIG_SAMPLES) + return -SLOAD_ERR_MAX_SAMPLES; - // construct target path + // construct target path - std::string dpath="samples:" ; - dpath+=path.GetName() ; + std::string dpath = "samples:"; + dpath+=path.GetName() ; Path dstPath(dpath.c_str()) ; // Opens files @@ -167,23 +194,23 @@ int SamplePool::ImportSample(Path &path) { if (!fin) { Trace::Error("Failed to open input file %s", path.GetCanonicalPath().c_str()); - return -1; + return -SLOAD_ERR_INPUT_FILE; }; - fin->Seek(0,SEEK_END) ; - long size=fin->Tell() ; + fin->Seek(0, SEEK_END); + long size=fin->Tell() ; fin->Seek(0,SEEK_SET) ; I_File *fout=FileSystem::GetInstance()->Open(dstPath.GetPath().c_str(),"w") ; if (!fout) { fin->Close() ; - delete (fin) ; - return -1 ; + delete (fin); + return -SLOAD_ERR_OUTPUT_FILE ; } ; - // copy file to current project + // copy file to current project - char buffer[IMPORT_CHUNK_SIZE] ; - while (size>0) { + char buffer[IMPORT_CHUNK_SIZE]; + while (size>0) { int count=(size>IMPORT_CHUNK_SIZE)?IMPORT_CHUNK_SIZE:size ; fin->Read(buffer,1,count) ; fout->Write(buffer,1,count) ; @@ -195,16 +222,17 @@ int SamplePool::ImportSample(Path &path) { delete(fin) ; delete(fout) ; - // now load the sample - - bool status=loadSample(dstPath.GetPath().c_str()) ; + // now load the sample + int status = dstPath.Matches("*.wav") + ? loadSample(dstPath.GetPath().c_str()) + : loadSoundFont(dstPath.GetPath().c_str()); - SetChanged() ; - SamplePoolEvent ev ; + SetChanged(); + SamplePoolEvent ev ; ev.index_=count_-1 ; ev.type_=SPET_INSERT ; - NotifyObservers(&ev) ; - return status?(count_-1):-1 ; + NotifyObservers(&ev); + return !status ?(count_-1):(-status) ; }; bool SamplePool::IsImported(std::string name) { @@ -302,49 +330,57 @@ void SamplePool::unload(int i) { NotifyObservers(&ev) ; } -bool SamplePool::loadSoundFont(const char *path) { +/* + Returns an element of + {SLOAD_OK, SLOAD_ERR_MAX_SOUNDFONTS, SLOAD_ERR_INPUT_FILE}. +*/ +int SamplePool::loadSoundFont(const char *path) { - sfBankID id=SoundFontManager::GetInstance()->LoadBank(path) ; - if (id==-1) { - return false ; - } + sfBankID id = SoundFontManager::GetInstance()->LoadBank(path); + if (id==-SF_BANK_TABLE_FULL) { + return SLOAD_ERR_MAX_SOUNDFONTS ; + } else if (id < 0) { + return SLOAD_ERR_INPUT_FILE; + } - // Grab the sample offset + // Grab the sample offset - long offset=sfGetSMPLOffset(id) ; + long offset = sfGetSMPLOffset(id); - // Add all presets of the sf + // Add all presets of the sf - WORD presetCount=0 ; - SFPRESETHDRPTR pHeaders=sfGetPresetHdrs(id,&presetCount); + WORD presetCount = 0; + SFPRESETHDRPTR pHeaders=sfGetPresetHdrs(id,&presetCount); for (int i=0;i,public Observable { public: - void Load() ; - void Sort(); - SamplePool(); - void Reset() ; - ~SamplePool() ; - SoundSource *GetSource(int i) ; - char **GetNameList() ; - int GetNameListSize(); - int ImportSample(Path &path); - bool IsImported(std::string name); - // int InsertSample(const std::string& sampleName, bool imported, std::string fi); - int Reassign(std::string name, bool imported); - void PurgeSample(int i) ; - const char *GetSampleLib() ; + unsigned int Load(); + void Sort(); + SamplePool(); + void Reset(); + ~SamplePool(); + SoundSource *GetSource(int i); + char **GetNameList(); + int GetNameListSize(); + int ImportSample(Path &path); + bool IsImported(std::string name); + // int InsertSample(const std::string& sampleName, bool imported, std::string fi); + int Reassign(std::string name, bool imported); + void PurgeSample(int i); + const char *GetSampleLib(); protected: void unload(int i); - bool loadSample(const char *path); - bool loadSoundFont(const char *path); + int loadSample(const char *path); + int loadSoundFont(const char *path); int getIndexOf(const char *path); int count_; char *names_[MAX_PIG_SAMPLES]; diff --git a/sources/Application/Instruments/SoundFontManager.cpp b/sources/Application/Instruments/SoundFontManager.cpp index d56a4efc..ac011c3c 100644 --- a/sources/Application/Instruments/SoundFontManager.cpp +++ b/sources/Application/Instruments/SoundFontManager.cpp @@ -14,20 +14,30 @@ void SoundFontManager::Reset() { SAFE_FREE(*it) ; it=sampleData_.erase(it) ; } ; + + // Unload all SoundFonts + sfBankID i; + for(i = 0; i < MAX_SOUNDFONTS; i++) + sfUnloadSFBank(i); } ; +/* + Returns a nonnegative short or an element of + {-SF_BANK_TABLE_FULL, -SF_LOAD_ERROR, -SF_OPEN_ERROR}. + */ sfBankID SoundFontManager::LoadBank(const char *path) { sfBankID id=sfReadSFBFile((char *)path) ; if (id==-1) { - return -1 ; + enaErrors err_code = sfGetError(); + return -(err_code == enaLOADERROR ? SF_LOAD_ERROR : SF_BANK_TABLE_FULL); } // open the file I_File *fin=FileSystem::GetInstance()->Open(path,"r") ; if (!fin) { - return false; - } + return -SF_OPEN_ERROR; + } // Grab the sample offset diff --git a/sources/Application/Instruments/SoundFontManager.h b/sources/Application/Instruments/SoundFontManager.h index f0100200..9ae84ef0 100644 --- a/sources/Application/Instruments/SoundFontManager.h +++ b/sources/Application/Instruments/SoundFontManager.h @@ -5,8 +5,16 @@ #include "Externals/Soundfont/ENAB.H" #include -class SoundFontManager:public T_Singleton { -public: +#define MAX_SOUNDFONTS MAXLOADEDBANKS + +enum SFManagerError { + SF_BANK_TABLE_FULL = 1, + SF_LOAD_ERROR = 2, + SF_OPEN_ERROR = 3, +}; + +class SoundFontManager : public T_Singleton { + public: SoundFontManager() ; ~SoundFontManager() ; void Reset() ; diff --git a/sources/Application/Views/ModalDialogs/ImportSampleDialog.cpp b/sources/Application/Views/ModalDialogs/ImportSampleDialog.cpp index c9cfeb5a..60d18d21 100644 --- a/sources/Application/Views/ModalDialogs/ImportSampleDialog.cpp +++ b/sources/Application/Views/ModalDialogs/ImportSampleDialog.cpp @@ -1,6 +1,7 @@ #include "ImportSampleDialog.h" -#include "Application/Instruments/SamplePool.h" #include "Application/Instruments/SampleInstrument.h" +#include "Application/Instruments/SamplePool.h" +#include "Application/Views/ModalDialogs/MessageBox.h" #define LIST_SIZE 15 #define LIST_WIDTH 28 @@ -30,23 +31,23 @@ ImportSampleDialog::~ImportSampleDialog() { void ImportSampleDialog::DrawView() { - SetWindow(LIST_WIDTH,LIST_SIZE+3) ; + SetWindow(LIST_WIDTH, LIST_SIZE + 3); - GUITextProperties props ; + GUITextProperties props; -// Draw title + // Draw title -// char title[40] ; + // char title[40] ; - SetColor(CD_NORMAL) ; + SetColor(CD_NORMAL); -// sprintf(title,"Sample Import from %s",currentPath_.GetName()) ; -// w_.DrawString(title,pos,props) ; + // sprintf(title,"Sample Import from %s",currentPath_.GetName()) ; + // w_.DrawString(title,pos,props) ; -// Draw samples + // Draw samples - int x=1 ; - int y=1 ; + int x = 1; + int y=1 ; if (currentSample_project_->GetInstrumentBank()->GetNext() ; }; } else { - Trace::Error("failed to import sample") ; + const char *err_str = (sampleID == -SLOAD_ERR_MAX_SAMPLES) + ? "Maximum number of samples exceeded" + : (sampleID == -SLOAD_ERR_MAX_SOUNDFONTS) + ? "Maximum number of SoundFonts exceeded" + : (sampleID == -SLOAD_ERR_INVALID_DIR) + ? "Invalid directory" + : "Unable to open file"; + Trace::Error(err_str); + MessageBox *mb = new MessageBox(*this, err_str); + View::DoModal(mb); }; isDirty_=true ; } ; @@ -158,12 +168,14 @@ void ImportSampleDialog::ProcessButtonMask(unsigned short mask,bool pressed) { switch(selected_) { case 0: // preview - if(!element->IsDirectory()) { // Don't browse preview folders - preview(*element); - } - break ; - case 1: // import - if(!element->IsDirectory()) { // Don't browse import folders + if (!(element->IsDirectory() || + element->Matches( + "*.sf2"))) { // Don't preview folders or SoundFonts + preview(*element); + } + break; + case 1: // import + if(!element->IsDirectory()) { // Don't import folders import(*element); } break ; @@ -273,9 +285,9 @@ void ImportSampleDialog::setCurrentFolder(Path *path) { } } for (it->Begin();!it->IsDone();it->Next()) { - Path ¤t=it->CurrentItem() ; - if (!current.IsDirectory()) { - if (current.Matches("*.wav") && current.GetName()[0]!='.') { + Path ¤t = it->CurrentItem(); + if (!current.IsDirectory()) { + if ((current.Matches("*.wav") || current.Matches("*.sf2")) && current.GetName()[0]!='.') { Path *sample=new Path(current) ; sampleList_.Insert(sample) ; } diff --git a/sources/Externals/Soundfont/ENAB.H b/sources/Externals/Soundfont/ENAB.H index 1db993a5..79c85343 100644 --- a/sources/Externals/Soundfont/ENAB.H +++ b/sources/Externals/Soundfont/ENAB.H @@ -91,7 +91,9 @@ // the extent of the internal implementation, (array) of info nodes, one per // loaded banks. Need more than the default? Change this and you got 'em. +#ifndef MAXLOADEDBANKS # define MAXLOADEDBANKS 3 +#endif // This is the type of a sfBankID, -1 is an error condition. typedef SHORT sfBankID;