diff --git a/CHANGELOG b/CHANGELOG index 9a7b7e80..8fd20f48 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,16 @@ +1.6.0-bacon4 + QWERTY keyboard name entry for new projects + * Press A on the project name field to enter QWERTY keyboard mode + * Navigate the on-screen keyboard with D-PAD/arrows + * Press A to input the selected character + * Press B to backspace + * Press L/R to move the text cursor left/right + * Press START or select OK to exit keyboard mode + * All keyboard navigation logic extracted to reusable KeyboardLayout.h utility + 1.6.0-bacon2 + ProjectView Render item + When in project view, it's now possible to initiate rendering Interpolate values in tables and phrases R + B on single-column selection will fill values from lowest to highest @@ -9,6 +21,7 @@ Fixes: Add 64 bit soundfont support (#211) Skip randomly generated project name if a directory with that name already exists (#175) + Save Song As" saves to current as well as new project (#212) 1.5.0-bacon3 Contributions: diff --git a/docs/LittlePiggyTrackerConf.md b/docs/LittlePiggyTrackerConf.md index 6f6acfd1..cb6bd4aa 100644 --- a/docs/LittlePiggyTrackerConf.md +++ b/docs/LittlePiggyTrackerConf.md @@ -13,7 +13,6 @@ 06. [Key and Button Mapping](#key-and-button-mapping) 07. [Auto Repeat](#auto-repeat) 08. [Path](#path) -09. [Rendering](#rendering) 10. [Volume](#volume) 11. [Audio Configuration](#audio-configuration) 12. [MIDI Configuration](#midi-configuration) @@ -246,18 +245,6 @@ You can tweak two different path: ``` -## Rendering - -Additionally to playing the song, LittleGPTracker can be used to render the audio to file. To control file rendering, the variable `RENDER` can be set to either `FILE`,`FILESPLIT`,`FILERT`,`FILESPLITRT`. Note that there's a small issue with the speed when using `FILE`/`FILESPLIT` so the xxRT seem like the best choice at the moment -The xxRT options render in real time -The xSPLITx options render separate files for the channels (stems) - -```xml - - - -``` - ## Volume For \[**GP2X**/**Dingoo**\] only diff --git a/docs/wiki/What-is-LittlePiggyTracker.md b/docs/wiki/What-is-LittlePiggyTracker.md index 52c6fd3c..ae2b04a5 100644 --- a/docs/wiki/What-is-LittlePiggyTracker.md +++ b/docs/wiki/What-is-LittlePiggyTracker.md @@ -51,8 +51,24 @@ After that you can copy additional wavs to the lgptRoot/lgptProject/samples dire ## New project -When creating a new project, use the Random button to generate a random name. Generate a new name with Random or edit it manually selecting characters with A and pressing up/down -Attempting to create a project with the same name in the same location produces a notification that this operation is denied +When creating a new project, you have several options for naming: + +**Random Name Generation:** +- Select the "Random" button and press A to generate a random name + +**QWERTY Keyboard Entry:** +- Move to the name field and press A to enter QWERTY keyboard mode +- An on-screen keyboard will appear with these controls: + - **D-PAD/Arrows:** Navigate the keyboard + - **A:** Input the selected character + - **B:** Backspace (delete character) + - **L/R:** Move the text cursor left/right within your project name + - **START or OK key:** Exit keyboard mode and return to the dialog +- The keyboard includes: + - Numbers (0-9) + - Uppercase and lowercase letters (A-Z, a-z) + - Special characters (@ | - _ < > ? ,) + - Space bar, backspace, and OK (done) buttons on the bottom row ## Multiple Projects @@ -591,20 +607,11 @@ RTRG 0101: does not do anything because after looping one tick, you move forward # Rendering Some people exploit the analog gap between their device's headphone output and whatever they are recording with. Alternately, you can start piggy in rendering mode so it will output 16bit, 44100Hz .WAV files. -Please note that RENDER mode is not intended to be functional on the GP2X Builds. -The following values can set for RENDER in the config.xml: +The following values can set for RENDER in the project view -- Standard mode: audio is played; no render. -- FILE: File rendering: Full speed (no audio) rendering of the stereo mixdown. -- FILESPLIT: File split rendering: Full speed (no audio) rendering of each channel separately. -- FILERT: Real Time file rendering: Renders the mixdown to file WHILE playing audio. This allow to render live mode tweaks directly. -- FILESPLITRT: Real Time file split: same except all channels are rendered separately. - -Here is an example of the proper XML syntax: (See [The config.xml setup guide](../LittlePiggyTrackerConf.md)) - -```xml - -``` +- Off: audio is played; no render. +- Stereo: Real Time file rendering: Renders the mixdown to file WHILE playing audio. This allow to render live mode tweaks directly. +- Stems: Real Time file split: same except all channels are rendered separately. Remember, any of the config.xml parameters can be specified to lgpt on the command line in this fashion: diff --git a/docs/wiki/config_xml.md b/docs/wiki/config_xml.md index 3687abba..b910ed38 100644 --- a/docs/wiki/config_xml.md +++ b/docs/wiki/config_xml.md @@ -199,16 +199,6 @@ You can tweak two different path: ``` -## Rendering - -Additionally to playing the song, LittleGPTracker can be used to render the audio to file. To control file rendering, the variable RENDER can be set to either FILE,FILESPLIT,FILERT,FILESPLITRT. Note that there's a small issue with the speed when using FILE/FILESPLIT so the xxRT seem like the best choice at the moment - -```xml - - - -``` - ## Volume This setting is for GP2X and Dingoo only. It is used to set the volume of the hardware at startup. In decimal (base 10). diff --git a/docs/wiki/tips_and_tricks.md b/docs/wiki/tips_and_tricks.md index 470e2a87..15d84b16 100644 --- a/docs/wiki/tips_and_tricks.md +++ b/docs/wiki/tips_and_tricks.md @@ -1,3 +1,20 @@ +# Project Naming with QWERTY Keyboard + +Since version 1.6.0, you can use an on-screen QWERTY keyboard when creating new projects: + +**Quick Tips:** +- Press A on the project name to enter keyboard mode +- The keyboard cursor will jump to the character under your text cursor unless it's a space +- Use L/R shoulder buttons to move through your project name +- Erase with B and exit with Start + +**Keyboard Layout:** +The on-screen keyboard is organized like this: +- Numbers +- Uppercase (A-Q + extra characters) +- Lowercase (a-q + extra characters) +- Special [Space] [Erase] [Done] + # Delays and Echoes ## Simulating LSDj's D command diff --git a/docs/wiki/ubuntu_install.md b/docs/wiki/ubuntu_install.md index fef0874f..c78ac58c 100644 --- a/docs/wiki/ubuntu_install.md +++ b/docs/wiki/ubuntu_install.md @@ -58,4 +58,3 @@ Please remember: ##### Use Piggy as your Midi Sequencer -##### Script piggy to run in render mode from a simple xterm command diff --git a/projects/lgpt.vcxproj b/projects/lgpt.vcxproj index e6f588df..24ee5329 100644 --- a/projects/lgpt.vcxproj +++ b/projects/lgpt.vcxproj @@ -440,6 +440,7 @@ + diff --git a/projects/lgptest.dev b/projects/lgptest.dev index e877c121..e3e0ef5b 100644 --- a/projects/lgptest.dev +++ b/projects/lgptest.dev @@ -2937,7 +2937,6 @@ Priority=1000 OverrideBuildCmd=0 BuildCmd= - [Unit288] FileName=..\sources\Application\utils\RandomNames.h CompileCpp=1 @@ -2948,3 +2947,13 @@ Priority=1000 OverrideBuildCmd=0 BuildCmd= +[Unit289] +FileName=..\sources\Application\utils\KeyboardLayout.h +CompileCpp=1 +Folder=Application/Utils +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/sources/Application/Mixer/MixerService.cpp b/sources/Application/Mixer/MixerService.cpp index abd6c722..daba8f7e 100644 --- a/sources/Application/Mixer/MixerService.cpp +++ b/sources/Application/Mixer/MixerService.cpp @@ -1,52 +1,34 @@ #include "MixerService.h" +#include "Application/Audio/DummyAudioOut.h" +#include "Application/Model/Config.h" +#include "Application/Model/Mixer.h" +#include "Application/Model/Project.h" #include "Services/Audio/Audio.h" #include "Services/Audio/AudioDriver.h" #include "Services/Midi/MidiService.h" #include "System/Console/Trace.h" -#include "Application/Model/Config.h" -#include "Application/Audio/DummyAudioOut.h" -#include "Application/Model/Mixer.h" -MixerService::MixerService(): - out_(0), - sync_(0) -{ - mode_=MSM_AUDIO ; - const char *render=Config::GetInstance()->GetValue("RENDER") ; - if (render) { - if (!strcmp(render,"FILERT")) { - mode_=MSM_FILERT ; - } ; - if (!strcmp(render,"FILE")) { - mode_=MSM_FILE ; - } ; - if (!strcmp(render,"FILESPLIT")) { - mode_=MSM_FILESPLIT ; - } ; - if (!strcmp(render,"FILESPLITRT")) { - mode_=MSM_FILESPLITRT ; - } ; - } ; -} ; +MixerService::MixerService() : out_(0), sync_(0), isRendering_(false) { + mode_ = MSRM_PLAYBACK; +}; -MixerService::~MixerService() { -} ; +MixerService::~MixerService() {}; /* * initializes the mixer service, config changes depending if we're in sequencer or render mode */ bool MixerService::Init() { - // create the output depending on rendering mode - out_ = 0; + // create the output depending on rendering mode + out_ = 0; switch (mode_) { - case MSM_FILE: - case MSM_FILESPLIT: - out_ = new DummyAudioOut(); - break; - default: - Audio *audio = Audio::GetInstance(); - out_ = audio->GetFirst(); - break; + case MSRM_STEREO: + case MSRM_STEMS: + out_ = new DummyAudioOut(); + break; + default: + Audio *audio = Audio::GetInstance(); + out_ = audio->GetFirst(); + break; } for (int i=0;iInsert(master_); } - switch(mode_) { - case MSM_AUDIO: - break ; - case MSM_FILERT: - case MSM_FILE: - out_->SetFileRenderer("project:mixdown.wav"); - break; - case MSM_FILESPLITRT: - case MSM_FILESPLIT: - for (int i=0;iAddObserver(*MidiService::GetInstance()); + initRendering(mode_); + out_->AddObserver(*MidiService::GetInstance()); } sync_=SDL_CreateMutex(); @@ -90,6 +57,23 @@ bool MixerService::Init() { return (result); }; +void MixerService::initRendering(MixerServiceRenderMode mode) { + switch(mode) { + case MSRM_PLAYBACK: + break; + case MSRM_STEREO: + out_->SetFileRenderer("project:mixdown.wav"); + break; + case MSRM_STEMS: + for (int i = 0; i < SONG_CHANNEL_COUNT; i++) { + char buffer[1024]; + sprintf(buffer, "project:channel%d.wav", i); + bus_[i].SetFileRenderer(buffer); + } + break; + } +} + void MixerService::Close() { if (out_) { out_->RemoveObserver(*MidiService::GetInstance()); @@ -98,21 +82,13 @@ void MixerService::Close() { master_.Empty() ; switch(mode_) { - case MSM_FILE: - case MSM_FILESPLIT: - SAFE_DELETE(out_) ; - break; - default: - break ; - } - switch(mode_) { - case MSM_FILESPLITRT: - case MSM_FILESPLIT: - break; - default: - break ; - } - } + case MSRM_STEMS: + case MSRM_STEREO: + break; + default: + break; + } + } for (int i=0;iStart() ; - if (out_) { - out_->AddObserver(*this) ; - out_->Start() ; + MidiService::GetInstance()->Start(); + if (out_) { + out_->AddObserver(*this); + out_->Start(); } return true ; } ; @@ -181,20 +163,22 @@ int MixerService::GetPlayedBufferPercentage() { } void MixerService::toggleRendering(bool enable) { - switch(mode_) { - case MSM_AUDIO: - break ; - case MSM_FILERT: - case MSM_FILE: - out_->EnableRendering(enable) ; - break ; - case MSM_FILESPLITRT: - case MSM_FILESPLIT: - for (int i=0;iEnableRendering(enable); + break; + case MSRM_STEMS: + initRendering(MSRM_STEMS); + for (int i = 0; i < SONG_CHANNEL_COUNT; i++) { + bus_[i].EnableRendering(enable); + }; + break; + } } void MixerService::OnPlayerStart() { diff --git a/sources/Application/Mixer/MixerService.h b/sources/Application/Mixer/MixerService.h index 8a7f6a5f..55de2f4a 100644 --- a/sources/Application/Mixer/MixerService.h +++ b/sources/Application/Mixer/MixerService.h @@ -13,13 +13,11 @@ #include "Services/Audio/AudioOut.h" #include "MixBus.h" -enum MixerServiceMode { - MSM_AUDIO, - MSM_FILE, - MSM_FILESPLIT, - MSM_FILERT, - MSM_FILESPLITRT -} ; +enum MixerServiceRenderMode { + MSRM_PLAYBACK, + MSRM_STEREO, + MSRM_STEMS, +}; #define MAX_BUS_COUNT 10 @@ -51,6 +49,8 @@ class MixerService: void SetPregain(int); void SetSoftclip(int, int); void SetMasterVolume(int); + void SetRenderMode(int); + bool IsRendering(); int GetPlayedBufferPercentage() ; virtual void Execute(FourCC id,float value) ; @@ -63,11 +63,12 @@ class MixerService: protected: void toggleRendering(bool enable) ; private: - AudioOut *out_ ; - MixBus master_ ; - MixBus bus_[MAX_BUS_COUNT] ; - MixerServiceMode mode_ ; - SDL_mutex *sync_ ; - + void initRendering(MixerServiceRenderMode); + AudioOut *out_; + MixBus master_; + MixBus bus_[MAX_BUS_COUNT]; + MixerServiceRenderMode mode_; + SDL_mutex *sync_; + bool isRendering_; } ; #endif diff --git a/sources/Application/Model/Project.cpp b/sources/Application/Model/Project.cpp index 784a21fb..56bcead3 100644 --- a/sources/Application/Model/Project.cpp +++ b/sources/Application/Model/Project.cpp @@ -41,6 +41,9 @@ tempoNudge_(0) new Variable("scale", VAR_SCALE, scaleNames, scaleCount, 0); this->Insert(scale); scale->SetInt(0); + Variable *renderMode = + new Variable("renderMode", VAR_RENDER, renderModes, MAX_RENDER_MODE, 0); + this->Insert(renderMode); // Reload the midi device list @@ -110,6 +113,12 @@ int Project::GetPregain() { return v->GetInt(); } +int Project::GetRenderMode() { + Variable *v = FindVariable(VAR_RENDER); + NAssert(v); + return v->GetInt(); +} + void Project::NudgeTempo(int value) { if((GetTempo() + tempoNudge_) > 0) tempoNudge_ += value; diff --git a/sources/Application/Model/Project.h b/sources/Application/Model/Project.h index 5b8f5c1a..f2336228 100644 --- a/sources/Application/Model/Project.h +++ b/sources/Application/Model/Project.h @@ -17,10 +17,11 @@ #define VAR_SOFTCLIP_GAIN MAKE_FOURCC('S', 'F', 'G', 'N') #define VAR_PREGAIN MAKE_FOURCC('P', 'R', 'G', 'N') #define VAR_SCALE MAKE_FOURCC('S', 'C', 'A', 'L') +#define VAR_RENDER MAKE_FOURCC('R', 'N', 'D', 'R') #define PROJECT_NUMBER "1" #define PROJECT_RELEASE "6" -#define BUILD_COUNT "0-bacon2" +#define BUILD_COUNT "0-bacon4" #define MAX_TAP 3 @@ -43,9 +44,10 @@ class Project: public Persistent,public VariableContainer,I_Observer { int GetSoftclip(); int GetSoftclipGain(); int GetPregain(); - + int GetRenderMode(); void Trigger(); + static const unsigned int MAX_RENDER_MODE = 3; // I_Observer virtual void Update(Observable &o, I_ObservableData *d); @@ -56,14 +58,14 @@ class Project: public Persistent,public VariableContainer,I_Observer { void LoadFirstGen(const char *root); protected: - void buildMidiDeviceList() ; -private: - InstrumentBank *instrumentBank_ ; - char **midiDeviceList_ ; - int midiDeviceListSize_ ; - int tempoNudge_ ; - unsigned long lastTap_[MAX_TAP] ; - unsigned int tempoTapCount_; -} ; + void buildMidiDeviceList(); +private: + InstrumentBank *instrumentBank_; + char **midiDeviceList_; + int midiDeviceListSize_; + int tempoNudge_; + unsigned long lastTap_[MAX_TAP]; + unsigned int tempoTapCount_; +}; #endif diff --git a/sources/Application/Model/ProjectDatas.h b/sources/Application/Model/ProjectDatas.h index e4eaff0f..0f70f880 100644 --- a/sources/Application/Model/ProjectDatas.h +++ b/sources/Application/Model/ProjectDatas.h @@ -1,2 +1,6 @@ -char *softclipStates[] = {"Bypass", "Subtle", "Medium", "Heavy", "Insane"}; -char *softclipGainStates[] = {"[unity]", "[boost]"}; +#ifndef _PROJECTDATAS_H_ +#define _PROJECTDATAS_H_ +static const char *softclipStates[] = {"Bypass", "Subtle", "Medium", "Heavy", "Insane"}; +static const char *softclipGainStates[] = {"[unity]", "[boost]"}; +static const char *renderModes[] = {"Off", "Stereo", "Stems"}; +#endif \ No newline at end of file diff --git a/sources/Application/Persistency/PersistencyService.cpp b/sources/Application/Persistency/PersistencyService.cpp index ebcaaed6..5b6ee1af 100644 --- a/sources/Application/Persistency/PersistencyService.cpp +++ b/sources/Application/Persistency/PersistencyService.cpp @@ -7,12 +7,12 @@ PersistencyService::PersistencyService():Service(MAKE_FOURCC('S','V','P','S')) { } ; -void PersistencyService::Save() { +void PersistencyService::Save(const char *name) { - Path filename("project:lgptsav.dat") ; + Path filename(name); - TiXmlDocument doc(filename.GetPath()) ; - TiXmlElement first("LITTLEGPTRACKER") ; + TiXmlDocument doc(filename.GetPath()); + TiXmlElement first("LITTLEGPTRACKER") ; TiXmlNode *node=doc.InsertEndChild(first) ; // Loop on all registered service @@ -25,7 +25,7 @@ void PersistencyService::Save() { } ; doc.SaveFile() ; -} ; +}; bool PersistencyService::Load() { diff --git a/sources/Application/Persistency/PersistencyService.h b/sources/Application/Persistency/PersistencyService.h index 27171df1..410f9cae 100644 --- a/sources/Application/Persistency/PersistencyService.h +++ b/sources/Application/Persistency/PersistencyService.h @@ -8,8 +8,8 @@ class PersistencyService: public Service,public T_Singleton { public: PersistencyService() ; - void Save() ; - bool Load() ; + void Save(const char *name = "project:lgptsav.dat"); + bool Load() ; } ; class PersistencyDocument: public TiXmlDocument { diff --git a/sources/Application/Utils/KeyboardLayout.h b/sources/Application/Utils/KeyboardLayout.h new file mode 100644 index 00000000..c260c1cc --- /dev/null +++ b/sources/Application/Utils/KeyboardLayout.h @@ -0,0 +1,100 @@ +#ifndef _KEYBOARD_LAYOUT_H_ +#define _KEYBOARD_LAYOUT_H_ + +#include + +// Keyboard layout configuration +#define SPACE_ROW 7 +#define KEYBOARD_ROWS (SPACE_ROW + 1) + +#define SPCE_START 0 +#define SPCE_END 3 +#define BACK_START 4 +#define BACK_END 6 +#define DONE_START 8 +#define DONE_END 10 + +static const char* keyboardLayout[] = { + "1234567890", + "QWERTYUIOP", + "ASDFGHJKL@", + "ZXCVBNM,?>", + "qwertyuiop", + "asdfghjkl|", + "zxcvbnm-_<", + "[_] <- OK" +}; + +// Helper functions for special row section detection +inline bool isInSpaceSection(int col) { return col < SPCE_END; } +inline bool isInBackSection(int col) { return col >= BACK_START && col < BACK_END; } +inline bool isInDoneSection(int col) { return col >= DONE_START; } + +// Get the character at a specific keyboard position +inline char getKeyAtPosition(int row, int col) { + if (row < 0 || row >= KEYBOARD_ROWS) return '\0'; + const char* rowStr = keyboardLayout[row]; + + // Handle special row with SPC, BACK, and DONE + if (row == SPACE_ROW) { + if (col >= 0 && col < SPCE_END) return ' '; // [_] (space) + if (col >= BACK_START && col < BACK_END) return '\b'; // <- (backspace) + if (col >= DONE_START && col < DONE_END) return '\r'; // OK (carriage return) + return '\0'; + } + + int len = strlen(rowStr); + if (col < 0 || col >= len) return '\0'; + return rowStr[col]; +} + +// Find a character's position in the keyboard layout +inline void findCharacterInKeyboard(char ch, int &outRow, int &outCol) { + if (ch == ' ') return; // Skip space character + + // Search for character in keyboard layout (excluding special row) + for (int row = 0; row < SPACE_ROW; row++) { + const char* rowStr = keyboardLayout[row]; + int len = strlen(rowStr); + for (int col = 0; col < len; col++) { + if (rowStr[col] == ch) { + outRow = row; + outCol = col; + return; + } + } + } + // Character not found, don't change position +} + +// Clamp keyboard column to valid range for current row +inline void clampKeyboardColumn(int row, int& col) { + if (row == SPACE_ROW) { + if (col < SPCE_END) col = SPCE_START; + else if (col <= BACK_END) col = BACK_START; + else col = DONE_START; + } else { + int maxCol = strlen(keyboardLayout[row]) - 1; + if (col > maxCol) col = 0; + } +} + +// Cycle keyboard column left (-1) or right (+1) within current row +inline void cycleKeyboardColumn(int row, int direction, int& col) { + if (row == SPACE_ROW) { + if (direction < 0) { // LEFT + if (isInSpaceSection(col)) col = DONE_START; + else if (isInBackSection(col)) col = SPCE_START; + else col = BACK_START; + } else { // RIGHT + if (isInBackSection(col)) col = DONE_START; + else if (isInDoneSection(col)) col = SPCE_START; + else col = BACK_START; + } + } else { + int maxCol = strlen(keyboardLayout[row]) - 1; + col = (col + direction + maxCol + 1) % (maxCol + 1); + } +} + +#endif diff --git a/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp b/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp index 132a63ca..48366734 100644 --- a/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp +++ b/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp @@ -1,5 +1,6 @@ #include "NewProjectDialog.h" +#include "Application/Utils/KeyboardLayout.h" #include "Application/Utils/RandomNames.h" static char *buttonText[BUTTONS_LENGTH] = {(char *)"Random", (char *)"Ok", @@ -12,39 +13,87 @@ NewProjectDialog::NewProjectDialog(View &view, Path currentPath) NewProjectDialog::~NewProjectDialog() {} +// Move text cursor left (-1) or right (+1) and update keyboard position +void NewProjectDialog::moveCursor(int direction) { + int newPos = currentChar_ + direction; + if (newPos >= 0 && newPos < MAX_NAME_LENGTH) { + currentChar_ = newPos; + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, keyboardCol_); + } +} + void NewProjectDialog::DrawView() { - SetWindow(DIALOG_WIDTH,5) ; + SetWindow(DIALOG_WIDTH, keyboardMode_ ? 15 : 5); - GUITextProperties props ; + GUITextProperties props; - SetColor(CD_NORMAL) ; + SetColor(CD_NORMAL); // Draw string int x = (DIALOG_WIDTH - MAX_NAME_LENGTH) / 3; char buffer[2]; - buffer[1]=0 ; - for (int i=0;i 0) { + currentChar_--; + name_[currentChar_] = ' '; + } + } else if (ch == '\r') { + // END key: exit keyboard mode (same as START) + keyboardMode_ = false; + isDirty_ = true; + // EndModal(0); + return; + } else if (ch != '\0') { + name_[currentChar_] = ch; + lastChar_ = ch; + if (currentChar_ < MAX_NAME_LENGTH - 1) { + currentChar_++; + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, + keyboardCol_); + } + } + isDirty_ = true; + return; + } else if (mask == EPBM_B) { + // Backspace: delete character and move cursor left + if (currentChar_ > 0) { + currentChar_--; + name_[currentChar_] = ' '; + isDirty_ = true; + } + return; + } else if (mask == EPBM_L) { + // Move cursor left + moveCursor(-1); + isDirty_ = true; + return; + } else if (mask == EPBM_R) { + // Move cursor right + moveCursor(1); + isDirty_ = true; + return; + } else if (mask == EPBM_UP) { + keyboardRow_ = (keyboardRow_ - 1 + KEYBOARD_ROWS) % KEYBOARD_ROWS; + clampKeyboardColumn(keyboardRow_, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_DOWN) { + keyboardRow_ = (keyboardRow_ + 1) % KEYBOARD_ROWS; + clampKeyboardColumn(keyboardRow_, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_LEFT) { + cycleKeyboardColumn(keyboardRow_, -1, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_RIGHT) { + cycleKeyboardColumn(keyboardRow_, 1, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_START) { + keyboardMode_ = false; + isDirty_ = true; + return; + } + return; + } else if (mask & EPBM_A) { + if (mask == EPBM_A) { + std::string randomName = ""; + switch (selected_) { + case 0: + // Toggle keyboard mode + keyboardMode_ = !keyboardMode_; + // When entering keyboard mode, jump to current character + if (keyboardMode_) { + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, + keyboardCol_); + } + isDirty_ = true; + break; + case 1: + do { + randomName = getRandomName(); + std::fill(name_ + randomName.length(), + name_ + sizeof(name_) / sizeof(name_[0]), ' '); + strncpy(name_, randomName.c_str(), randomName.length()); + lastChar_ = currentChar_ = randomName.length() - 1; + } while (currentPath_.Descend(GetName()).Exists()); + isDirty_ = true; + break; + case 2: + if (currentPath_.Descend(GetName()).Exists()) { + std::string res("Name " + std::string(name_) + " busy"); + View::SetNotification(res.c_str(), -6); + } else { + EndModal(1); + } + break; + case 3: + EndModal(0); + break; + } + } + if (mask & EPBM_UP) { + name_[currentChar_]+=1; lastChar_=name_[currentChar_] ; isDirty_=true ; - } - if (mask&EPBM_DOWN) { + } + if (mask&EPBM_DOWN) { name_[currentChar_]-=1; lastChar_=name_[currentChar_] ; isDirty_=true ; } - } else { - - // R Modifier - - if (mask & EPBM_R) { - } else { - // No modifier - if (mask == EPBM_UP) { - selected_ = (selected_ == 0) ? 1 : 0; - isDirty_ = true; - } - if (mask == EPBM_DOWN) { - selected_ = (selected_ == 0) ? 1 : 0; - isDirty_ = true; - } - - if (mask == EPBM_LEFT) { - switch (selected_) { - case 0: - if (currentChar_ > 0) - currentChar_--; - break; - case 1: - case 2: - case 3: - if (selected_ > 0) - selected_--; - break; - } - isDirty_ = true; - } - if (mask == EPBM_RIGHT) { - switch (selected_) { - case 0: - if (currentChar_ < MAX_NAME_LENGTH - 1) - currentChar_++; - else - selected_++; - break; - case 1: - case 2: - case 3: - if (selected_ < BUTTONS_LENGTH) - selected_++; - break; - } - isDirty_ = true; - } - } - } + } else { + + // A modifier + if (mask & EPBM_A) { + if (mask == EPBM_A) { + std::string randomName = getRandomName(); + switch (selected_) { + case 0: + if (name_[currentChar_] == ' ') { + name_[currentChar_] = lastChar_; + } + isDirty_ = true; + break; + case 1: + std::fill(name_ + randomName.length(), + name_ + sizeof(name_) / sizeof(name_[0]), ' '); + strncpy(name_, randomName.c_str(), randomName.length()); + lastChar_ = currentChar_ = randomName.length() - 1; + isDirty_ = true; + break; + case 2: + EndModal(1); + break; + case 3: + EndModal(0); + break; + } + } + } else { + + // R Modifier + + if (mask & EPBM_R) { + } else { + // No modifier + if (mask == EPBM_UP) { + selected_ = (selected_ == 0) ? 1 : 0; + isDirty_ = true; + } + if (mask == EPBM_DOWN) { + selected_ = (selected_ == 0) ? 1 : 0; + isDirty_ = true; + } + + if (mask == EPBM_LEFT) { + switch (selected_) { + case 0: + if (currentChar_ > 0) + currentChar_--; + break; + case 1: + case 2: + case 3: + if (selected_ > 0) + selected_--; + break; + } + isDirty_ = true; + } + if (mask == EPBM_RIGHT) { + switch (selected_) { + case 0: + if (currentChar_ < MAX_NAME_LENGTH - 1) + currentChar_++; + else + selected_++; + break; + case 1: + case 2: + case 3: + if (selected_ < BUTTONS_LENGTH) + selected_++; + break; + } + isDirty_ = true; + } + } + } } }; - std::string NewProjectDialog::GetName() { for (int i = MAX_NAME_LENGTH; i >= 0; i--) { if (name_[i]==' ') { diff --git a/sources/Application/Views/ModalDialogs/NewProjectDialog.h b/sources/Application/Views/ModalDialogs/NewProjectDialog.h index 457f8692..917e2953 100644 --- a/sources/Application/Views/ModalDialogs/NewProjectDialog.h +++ b/sources/Application/Views/ModalDialogs/NewProjectDialog.h @@ -1,6 +1,7 @@ #ifndef _NEW_PROJECT_DIALOG_H_ #define _NEW_PROJECT_DIALOG_H_ +#include "Application/Utils/KeyboardLayout.h" #include "Application/Views/BaseClasses/ModalView.h" #include @@ -25,6 +26,9 @@ class NewProjectDialog:public ModalView { int lastChar_; char name_[MAX_NAME_LENGTH + 1]; int currentChar_; -} ; - + bool keyboardMode_; + int keyboardRow_; + int keyboardCol_ ; + void moveCursor(int direction); +}; #endif diff --git a/sources/Application/Views/ProjectView.cpp b/sources/Application/Views/ProjectView.cpp index 2afe9516..0674049a 100644 --- a/sources/Application/Views/ProjectView.cpp +++ b/sources/Application/Views/ProjectView.cpp @@ -1,4 +1,6 @@ #include "ProjectView.h" +#include "Application/Mixer/MixerService.h" +#include "Application/Model/ProjectDatas.h" #include "Application/Model/Scale.h" #include "Application/Persistency/PersistencyService.h" #include "Application/Views/ModalDialogs/MessageBox.h" @@ -38,8 +40,8 @@ static void SaveAsProjectCallback(View &v,ModalView &dialog) { Path path_dstprjdir = Path(str_dstprjdir); Path path_dstsmpdir = Path(str_dstsmpdir); - Path path_srclgptdatsav = path_srcprjdir.GetPath() + "lgptsav.dat"; - Path path_dstlgptdatsav = path_dstprjdir.GetPath() + "/lgptsav.dat"; + Path path_srclgptdatsav = path_srcprjdir.GetPath() + "lgptsav_tmp.dat"; + Path path_dstlgptdatsav = path_dstprjdir.GetPath() + "/lgptsav.dat"; if (path_dstprjdir.Exists()) { Trace::Log("ProjectView", "Dst Dir '%s' Exist == true", @@ -55,10 +57,13 @@ static void SaveAsProjectCallback(View &v,ModalView &dialog) { return; }; - FSS.Copy(path_srclgptdatsav,path_dstlgptdatsav); + if (FSS.Copy(path_srclgptdatsav, path_dstlgptdatsav) > -1) { + FSS.Delete(path_srclgptdatsav); + } - I_Dir *idir_srcsmpdir=FileSystem::GetInstance()->Open(path_srcsmpdir.GetPath().c_str()); - if (idir_srcsmpdir) { + I_Dir *idir_srcsmpdir = + FileSystem::GetInstance()->Open(path_srcsmpdir.GetPath().c_str()); + if (idir_srcsmpdir) { idir_srcsmpdir->GetContent("*"); idir_srcsmpdir->Sort(); IteratorPtrit(idir_srcsmpdir->GetIterator()); @@ -78,13 +83,15 @@ static void SaveAsProjectCallback(View &v,ModalView &dialog) { } static void LoadCallback(View &v,ModalView &dialog) { - if (dialog.GetReturnCode()==MBL_YES) { + MixerService::GetInstance()->SetRenderMode(0); + if (dialog.GetReturnCode()==MBL_YES) { ((ProjectView &)v).OnLoadProject() ; } } ; static void QuitCallback(View &v,ModalView &dialog) { - if (dialog.GetReturnCode()==MBL_YES) { + MixerService::GetInstance()->SetRenderMode(0); + if (dialog.GetReturnCode()==MBL_YES) { ((ProjectView &)v).OnQuit() ; } } ; @@ -95,8 +102,8 @@ static void PurgeCallback(View &v,ModalView &dialog) { ProjectView::ProjectView(GUIWindow &w,ViewData *data):FieldView(w,data) { - lastClock_=0 ; - lastTick_=0 ; + lastClock_ = 0; + lastTick_ = 0; project_=data->project_ ; @@ -180,6 +187,13 @@ ProjectView::ProjectView(GUIWindow &w,ViewData *data):FieldView(w,data) { MidiService::GetInstance()->Size(), 1, 1); T_SimpleList::Insert(field); + position._y += 2; + v = project_->FindVariable(VAR_RENDER); + NAssert(v); + field = new UIIntVarField(position, *v, "Render: %s", 0, + project_->MAX_RENDER_MODE - 1, 1, 2); + T_SimpleList::Insert(field); + position._y += 2; a1 = new UIActionField("Exit", ACTION_QUIT, position); a1->AddObserver(*this); @@ -192,23 +206,34 @@ ProjectView::~ProjectView() { void ProjectView::ProcessButtonMask(unsigned short mask,bool pressed) { - if (!pressed) return ; + if (!pressed) + return; - FieldView::ProcessButtonMask(mask) ; + FieldView::ProcessButtonMask(mask); - if (mask&EPBM_R) { - if (mask&EPBM_DOWN) { + if (mask & EPBM_R) { + if (mask&EPBM_DOWN) { ViewType vt=VT_SONG; ViewEvent ve(VET_SWITCH_VIEW,&vt) ; SetChanged(); - NotifyObservers(&ve) ; - } - } else { - if (mask&EPBM_START) { - Player *player=Player::GetInstance() ; + NotifyObservers(&ve); + } + } else { + if (mask&EPBM_START) { + Player *player = Player::GetInstance(); + + int renderMode = viewData_->renderMode_; + if (renderMode > 0 && !player->IsRunning()) { + viewData_->isRendering_ = true; + View::SetNotification("Rendering started!"); + } else if (viewData_->isRendering_ && player->IsRunning()) { + viewData_->isRendering_ = false; + View::SetNotification("Rendering done!"); + } + player->OnStartButton(PM_SONG,viewData_->songX_,false,viewData_->songX_) ; } - } ; + }; } ; void ProjectView::DrawView() { @@ -227,8 +252,22 @@ void ProjectView::DrawView() { SetColor(CD_NORMAL); DrawString(pos._x,pos._y,projectString,props) ; - FieldView::Redraw() ; - drawMap() ; + FieldView::Redraw(); + drawMap(); + + int currentMode = project_->GetRenderMode(); + if ((viewData_->renderMode_ != currentMode) && !MixerService::GetInstance()->IsRendering()) { + // Mode changed + if (currentMode > 0 && viewData_->renderMode_ == 0) { + View::SetNotification("Rendering on, press start"); + } else if (currentMode == 0 && viewData_->renderMode_ > 0) { + View::SetNotification("Rendering off"); + } + viewData_->renderMode_ = currentMode; + MixerService::GetInstance()->SetRenderMode(currentMode); + } + + View::EnableNotification(); } ; void ProjectView::Update(Observable &,I_ObservableData *data) { @@ -240,11 +279,11 @@ void ProjectView::Update(Observable &,I_ObservableData *data) { # ifdef _64BIT int fourcc=*((int*)data); #else - int fourcc=(unsigned int)data ; + int fourcc = (unsigned int)data; #endif - UIField *focus=GetFocus() ; - if (fourcc!=ACTION_TEMPO_CHANGED) { + UIField *focus = GetFocus(); + if (fourcc!= ACTION_TEMPO_CHANGED) { focus->ClearFocus() ; focus->Draw(w_) ; w_.Flush() ; @@ -264,13 +303,14 @@ void ProjectView::Update(Observable &,I_ObservableData *data) { break ; } case ACTION_SAVE: { + MixerService::GetInstance()->SetRenderMode(0); PersistencyService *service = PersistencyService::GetInstance(); service->Save(); break; } case ACTION_SAVE_AS: { PersistencyService *service = PersistencyService::GetInstance(); - service->Save(); + service->Save("project:lgptsav_tmp.dat"); NewProjectDialog *mb = new NewProjectDialog(*this); DoModal(mb, SaveAsProjectCallback); break; diff --git a/sources/Application/Views/ProjectView.h b/sources/Application/Views/ProjectView.h index 1f9474cf..352a28e4 100644 --- a/sources/Application/Views/ProjectView.h +++ b/sources/Application/Views/ProjectView.h @@ -27,10 +27,10 @@ class ProjectView: public FieldView,public I_Observer { protected: private: - Project *project_ ; -// Debug - unsigned long lastTick_ ; - unsigned long lastClock_ ; - UIField *tempoField_ ; + Project *project_; + // Debug + unsigned long lastTick_; + unsigned long lastClock_; + UIField *tempoField_; } ; #endif diff --git a/sources/Application/Views/SongView.cpp b/sources/Application/Views/SongView.cpp index e184195a..39c8a59d 100644 --- a/sources/Application/Views/SongView.cpp +++ b/sources/Application/Views/SongView.cpp @@ -1,5 +1,7 @@ #include "SongView.h" #include "Application/Commands/ApplicationCommandDispatcher.h" +#include "Application/Mixer/MixerService.h" +#include "Application/Model/ProjectDatas.h" #include "Application/Player/Player.h" #include "Application/Utils/char.h" #include "System/Console/Trace.h" @@ -485,6 +487,14 @@ void SongView::onStart() { from = r.Left(); to = r.Right(); } + int renderMode = viewData_->renderMode_; + if (renderMode > 0 && !player->IsRunning()) { + viewData_->isRendering_ = true; + View::SetNotification("Rendering started!"); + } else if (viewData_->isRendering_ && player->IsRunning()) { + viewData_->isRendering_ = false; + View::SetNotification("Rendering done!"); + } player->OnSongStartButton(from, to, false, false); }; @@ -511,6 +521,7 @@ void SongView::onStop() { from = r.Left(); to = r.Right(); } + player->OnSongStartButton(from, to, true, false); }; diff --git a/sources/Application/Views/ViewData.cpp b/sources/Application/Views/ViewData.cpp index 11376496..f4dca9ff 100644 --- a/sources/Application/Views/ViewData.cpp +++ b/sources/Application/Views/ViewData.cpp @@ -17,6 +17,8 @@ ViewData::ViewData(Project *project) { currentGroove_=0 ; mixerCol_=0 ; mixerRow_=0 ; + renderMode_ = 0; + isRendering_ = false; } ; ViewData::~ViewData() { diff --git a/sources/Application/Views/ViewData.h b/sources/Application/Views/ViewData.h index 08da6f19..26be7b9d 100644 --- a/sources/Application/Views/ViewData.h +++ b/sources/Application/Views/ViewData.h @@ -74,6 +74,12 @@ class ViewData { int mixerCol_ ; // int mixerRow_ ; + + // Render Settings + + int renderMode_ ; // Current render mode (0=audio, 1=mixdown, 2=split) + bool isRendering_ ; // True when actively rendering (playing with mode > 0) + // Player Settings PlayMode playMode_ ; diff --git a/sources/System/FileSystem/FileSystem.cpp b/sources/System/FileSystem/FileSystem.cpp index 168c793f..78884dcc 100644 --- a/sources/System/FileSystem/FileSystem.cpp +++ b/sources/System/FileSystem/FileSystem.cpp @@ -215,4 +215,20 @@ int FileSystemService::Copy(const Path &src,const Path &dst) isrc->Close(); idst->Close(); return nbwrite; +} + +int FileSystemService::Delete(const Path &path) { + int result = -1; + std::string pathString = path.GetPath(); + FileSystem * fs = FileSystem::GetInstance(); + + if (fs->GetFileType(pathString.c_str()) != FT_UNKNOWN) { + fs->Delete(pathString.c_str()); + result += 1; + Trace::Log("FileSystemService"," Delete %s ", pathString.c_str()); + } else { + Trace::Log("FS Delete","path does not exist: %s", pathString.c_str()); + } + + return result; } \ No newline at end of file diff --git a/sources/System/FileSystem/FileSystem.h b/sources/System/FileSystem/FileSystem.h index a915ffc8..4f36388d 100644 --- a/sources/System/FileSystem/FileSystem.h +++ b/sources/System/FileSystem/FileSystem.h @@ -111,6 +111,7 @@ class FileSystem: public T_Factory { class FileSystemService { public: int Copy(const Path &src,const Path &dst); + int Delete(const Path &path); }; #endif