diff --git a/EXTENSIONS.md b/EXTENSIONS.md new file mode 100644 index 0000000..538b71c --- /dev/null +++ b/EXTENSIONS.md @@ -0,0 +1,30 @@ +# Extensions + +Prototracker can be extended (compile-time) with modular extensions. The exensions can provide new UI components ("Editors") and the main synth engine. The idea is that any forked trackers can pick and choose interesting features while avoiding feature bloat. + +The basic Prototracker functionality is in `src/BaseExtension`, you should replace this with your own if you choose to fork Prototracker and name it descriptively. + +The main `Makefile` needs to be updated to enable extensions. I.e. locate the following line and add your extension to it: + + ... + + EXTENSIONS=BaseExtension YourExtension + + ... + +## Extension lifecycle + + 1. `Extension::Extension()` will be called when a new extension is loaded by `Prototracker::loadExtension()` and if it was not already constructed by earlier loads. + 2. `Extension::init()` will be called some time after `Prototracker::loadExtension()` has been called. If you add a dependency in `Extension::init()`, it is ensured that it has been constructed but it is not ensured the init method for the extension has been called. You should only store the reference to the dependency here and use it later in the other lifecycle methods. + 3. `Extension::registerSynth()` will be called if an extension hasn't registered a synth yet - not a problem as only one extension should provide a synth. + 4. `Extension::registerUIComponents()` will be called. + 5. `Extension::registerSectionListeners()` will be called. + 6. `Extension::deinit()` (and `Extension::~Extension()`) will be called when Prototracker is shut down. + +## Dependencies + +`Extension::init()` is the place to load new dependency extensions with `Prototracker::loadExtension()` (the Prototracker instance is passed as the first argument to `Extension::init()`). This makes sure the dependency is loaded and not duplicated (in case multiple extensions depend on the same extension) and returns a reference to the extension. Please take care of the fact the dependency has been loaded but is not necessarily initialized at this point. + +## Problems + +While many changes to the main Prototracker fork can be isolated to extensions, it may be necessary to edit e.g. the TrackState class (to add effects) and those changes will not be automatically included when reusing an extension in another fork. The extension mechanism will still be useful for many features. diff --git a/Makefile b/Makefile index e0082a7..c739ac2 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,16 @@ PLAT=none PLATS=mingw linux chip8 emscripten +EXTENSIONS=BaseExtension + +export SRC=src/*.cpp $(foreach ext,$(EXTENSIONS),src/$(ext)/*.cpp) +export SRC_H=src/*.h $(foreach ext,$(EXTENSIONS),src/$(ext)/*.h) + all: $(PLAT) $(PLATS) clean: $(MAKE) -f Makefile.$@ - + none: @echo Build with 'make PLATFORM'. The supported platforms are: @echo $(PLATS) diff --git a/Makefile.chip8 b/Makefile.chip8 index 7944117..5d44a8f 100644 --- a/Makefile.chip8 +++ b/Makefile.chip8 @@ -4,9 +4,6 @@ # to other optimizations. Fullscreen mode is forced. OUTPUT=prototracker -SRC=src/*.cpp -$(OUTPUT): $(SRC) src/*.h +$(OUTPUT): $(SRC) $(SRC_H) g++ -Wformat -s -std=c++11 -o $@ $(SRC) -lSDL2_image -lSDL2main `sdl2-config --cflags --libs` -march=armv7-a -mtune=cortex-a7 -ffast-math -Ofast -DSCALE=1 -DOVERSAMPLE=0 -DFULLSCREEN=1 -DSAMPLERATE=22050 - - diff --git a/Makefile.emscripten b/Makefile.emscripten index 8d9ebc6..cc4ad50 100644 --- a/Makefile.emscripten +++ b/Makefile.emscripten @@ -5,14 +5,13 @@ # command line. OUTPUT=prototracker -SRC=src/*.cpp -$(OUTPUT).html: $(SRC) src/*.h +$(OUTPUT).html: $(SRC) $(SRC_H) emcc -std=c++11 --preload-file assets -O3 -s NO_EXIT_RUNTIME=0 \ -s EXPORTED_FUNCTIONS='["_main"]' -s SDL2_IMAGE_FORMATS='["png"]' \ -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "FS"]' -DSCALE=2 -DSAMPLERATE=22050 \ -s ALLOW_MEMORY_GROWTH=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s BINARYEN_TRAP_MODE=clamp \ - -s WASM=1 -s MODULARIZE=1 $(wildcard $(SRC)) -o $@ -s ASSERTIONS=1 + -s WASM=1 -s MODULARIZE=0 $(wildcard $(SRC)) -o $@ -s ASSERTIONS=1 clean: rm $(OUTPUT).html $(OUTPUT).js $(OUTPUT).data $(OUTPUT).wasm diff --git a/Makefile.linux b/Makefile.linux index 1195b5b..a016242 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,5 +1,4 @@ OUTPUT=prototracker -SRC=src/*.cpp -$(OUTPUT): $(SRC) src/*.h +$(OUTPUT): $(SRC) $(SRC_H) g++ -O3 -Wformat -std=c++11 -o $@ $(SRC) -lSDL2_image -lSDL2main `sdl2-config --cflags --libs` -s -DSCALE=2 diff --git a/Makefile.mingw b/Makefile.mingw index cb53dcb..01c229d 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,5 +1,4 @@ OUTPUT=prototracker.exe -SRC=src/*.cpp -$(OUTPUT): $(SRC) src/*.h +$(OUTPUT): $(SRC) $(SRC_H) g++ -DSCALE=2 -O3 -Wformat -std=c++11 -o $@ $(SRC) -lmingw32 -lSDL2_image -lSDL2main -lSDL2 -Ic:/mingw/include/SDL2 -Ic:/tdm-gcc-32/include/SDL2 -s -DENABLE_AUDIO_QUEUE=1 diff --git a/src/BaseExtension/BaseExtension.cpp b/src/BaseExtension/BaseExtension.cpp new file mode 100644 index 0000000..bea73b1 --- /dev/null +++ b/src/BaseExtension/BaseExtension.cpp @@ -0,0 +1,26 @@ +#include "BaseExtension.h" +#include "../UIComponentFactory.h" +#include "WaveView.h" + +void BaseExtension::init(Prototracker& prototracker, IPlayer& player, const Song& song) +{ + mSynth = new Synth(); +} + + +void BaseExtension::deinit() +{ + delete mSynth; +} + + +ISynth* BaseExtension::registerSynth() +{ + return mSynth; +} + + +void BaseExtension::registerUIComponents(UIComponentFactory& factory, EditorState& editorState) +{ + factory.registerComponent("WaveView", [&](const Theme::Element& element){ return new WaveView(editorState, mSynth->getWaveStore()); }); +} diff --git a/src/BaseExtension/BaseExtension.h b/src/BaseExtension/BaseExtension.h new file mode 100644 index 0000000..09fe9a6 --- /dev/null +++ b/src/BaseExtension/BaseExtension.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Extension.h" +#include "Synth.h" + +struct IPlayer; + +class BaseExtension: public Extension +{ + Synth *mSynth; +public: + virtual void init(Prototracker& prototracker, IPlayer& player, const Song& song); + virtual void deinit(); + virtual ISynth* registerSynth(); + virtual void registerUIComponents(UIComponentFactory& factory, EditorState& editorState); +}; diff --git a/src/Oscillator.cpp b/src/BaseExtension/Oscillator.cpp similarity index 97% rename from src/Oscillator.cpp rename to src/BaseExtension/Oscillator.cpp index c8ccef2..a7cc0b4 100644 --- a/src/Oscillator.cpp +++ b/src/BaseExtension/Oscillator.cpp @@ -2,11 +2,11 @@ #include #include #include "SDL.h" -#include "Sample.h" +#include "../Sample.h" #include "WaveStore.h" -#include "SequenceRow.h" +#include "../SequenceRow.h" #include "Wave.h" -#include "TrackState.h" +#include "../TrackState.h" Oscillator::Oscillator() : IOscillator(), mWaveStore(NULL), mWave(0), mSpeed(0), mVolume(1.0), mPosition(0) diff --git a/src/Oscillator.h b/src/BaseExtension/Oscillator.h similarity index 96% rename from src/Oscillator.h rename to src/BaseExtension/Oscillator.h index 615bda1..4d510b1 100644 --- a/src/Oscillator.h +++ b/src/BaseExtension/Oscillator.h @@ -1,6 +1,6 @@ #pragma once -#include "IOscillator.h" +#include "../IOscillator.h" struct WaveStore; @@ -11,12 +11,12 @@ struct WaveStore; class Oscillator: public IOscillator { const WaveStore* mWaveStore; - + int mWave, mQueuedWave; int mSpeed; int mPosition; int mVolume; - + public: static const int volumeResolution = 8192; static const int oscillatorLength = 256; @@ -24,14 +24,14 @@ class Oscillator: public IOscillator Oscillator(); virtual ~Oscillator(); - + virtual void triggerNote(); virtual void handleTrackState(ITrackState& trackState); void setWaveStore(const WaveStore& sampleStore); void setPosition(int newPosition); void setWave(int wave); void queueWave(int wave); - + virtual void setFrequency(float frequency); virtual void setVolume(int volume); virtual void update(int numSamples); diff --git a/src/Random.cpp b/src/BaseExtension/Random.cpp similarity index 100% rename from src/Random.cpp rename to src/BaseExtension/Random.cpp diff --git a/src/Random.h b/src/BaseExtension/Random.h similarity index 100% rename from src/Random.h rename to src/BaseExtension/Random.h diff --git a/src/Synth.cpp b/src/BaseExtension/Synth.cpp similarity index 88% rename from src/Synth.cpp rename to src/BaseExtension/Synth.cpp index 2549b7c..30118a6 100644 --- a/src/Synth.cpp +++ b/src/BaseExtension/Synth.cpp @@ -1,21 +1,21 @@ #include "Synth.h" #include "Oscillator.h" -#include "SequenceRow.h" -#include "Sample.h" +#include "../SequenceRow.h" +#include "../Sample.h" #include "WaveStore.h" #include "SDL.h" Synth::Synth() - : ISynth() + : SynthBase() { mWaveStore = new WaveStore(); - - /* - + + /* + Initialize the audio tracks. - + */ - + for (int i = 0 ; i < SequenceRow::maxTracks ; ++i) { Oscillator *oscillator = new Oscillator(); @@ -28,12 +28,12 @@ Synth::Synth() Synth::~Synth() { delete mWaveStore; - + /* - + NOTE: ~ISynth() will delete the Oscillator objects we initialized above! No need to cleanup yourself. - + */ } diff --git a/src/Synth.h b/src/BaseExtension/Synth.h similarity index 70% rename from src/Synth.h rename to src/BaseExtension/Synth.h index b96522a..553a817 100644 --- a/src/Synth.h +++ b/src/BaseExtension/Synth.h @@ -1,16 +1,16 @@ #pragma once -#include "ISynth.h" +#include "../SynthBase.h" struct WaveStore; -class Synth: public ISynth +class Synth: public SynthBase { WaveStore* mWaveStore; - + public: Synth(); virtual ~Synth(); - + const WaveStore& getWaveStore() const; }; diff --git a/src/Wave.cpp b/src/BaseExtension/Wave.cpp similarity index 100% rename from src/Wave.cpp rename to src/BaseExtension/Wave.cpp diff --git a/src/Wave.h b/src/BaseExtension/Wave.h similarity index 100% rename from src/Wave.h rename to src/BaseExtension/Wave.h diff --git a/src/WaveGen.cpp b/src/BaseExtension/WaveGen.cpp similarity index 100% rename from src/WaveGen.cpp rename to src/BaseExtension/WaveGen.cpp diff --git a/src/WaveGen.h b/src/BaseExtension/WaveGen.h similarity index 100% rename from src/WaveGen.h rename to src/BaseExtension/WaveGen.h diff --git a/src/WaveStore.cpp b/src/BaseExtension/WaveStore.cpp similarity index 100% rename from src/WaveStore.cpp rename to src/BaseExtension/WaveStore.cpp diff --git a/src/WaveStore.h b/src/BaseExtension/WaveStore.h similarity index 100% rename from src/WaveStore.h rename to src/BaseExtension/WaveStore.h diff --git a/src/WaveView.cpp b/src/BaseExtension/WaveView.cpp similarity index 97% rename from src/WaveView.cpp rename to src/BaseExtension/WaveView.cpp index 875ef9a..09ea8e0 100644 --- a/src/WaveView.cpp +++ b/src/BaseExtension/WaveView.cpp @@ -1,6 +1,6 @@ #include "WaveView.h" -#include "Renderer.h" -#include "Color.h" +#include "../Renderer.h" +#include "../Color.h" #include "SDL.h" #include "Wave.h" #include "WaveStore.h" diff --git a/src/WaveView.h b/src/BaseExtension/WaveView.h similarity index 93% rename from src/WaveView.h rename to src/BaseExtension/WaveView.h index 9c1371a..36610a7 100644 --- a/src/WaveView.h +++ b/src/BaseExtension/WaveView.h @@ -1,6 +1,6 @@ #pragma once -#include "Editor.h" +#include "../Editor.h" struct WaveStore; @@ -14,5 +14,3 @@ class WaveView: public Editor virtual void onDraw(Renderer& renderer, const SDL_Rect& area); virtual bool onEvent(SDL_Event& event); }; - - diff --git a/src/EditorState.cpp b/src/EditorState.cpp index d4f07ce..2872a42 100644 --- a/src/EditorState.cpp +++ b/src/EditorState.cpp @@ -21,9 +21,12 @@ FileSection * EditorState::pack() { FileSection * state = FileSection::createSection("STAT"); state->writeByte(0); - state->writeDword(macro); - state->writeDword(octave); - state->writeDword(editMode); + + #define EDITOR_STATE_VALUE(name) \ + state->writeDword(name); + EDITOR_STATE_VALUES + #undef EDITOR_STATE_VALUE + state->writeDword(followPlayPosition); /*state->writeDword(copyBuffer.getLength()); @@ -59,20 +62,12 @@ bool EditorState::unpack(const FileSection& section) if (version == FileSection::invalidRead) return false; - unsigned int macroNr = section.readDword(offset); - - if (macroNr == FileSection::invalidRead) - return false; - - unsigned int octaveNr = section.readDword(offset); - - if (octaveNr == FileSection::invalidRead) - return false; - - unsigned int editModeNr = section.readDword(offset); - - if (editModeNr == FileSection::invalidRead) - return false; + #define EDITOR_STATE_VALUE(name) \ + name = section.readDword(offset); \ + if (name == FileSection::invalidRead) \ + return false; + EDITOR_STATE_VALUES + #undef EDITOR_STATE_VALUE unsigned int followNr = section.readDword(offset); @@ -133,9 +128,6 @@ bool EditorState::unpack(const FileSection& section) if (!tempAudioDevice) return false; - macro = macroNr; - octave = octaveNr; - editMode = editModeNr; followPlayPosition = followNr; audioDevice = tempAudioDevice; @@ -222,8 +214,14 @@ void EditorState::reset() followPlayPosition = true; - macro = 0; + // Default all editor state values to zero, override below if needed + + #define EDITOR_STATE_VALUE(name) \ + name = 0; + EDITOR_STATE_VALUES + #undef EDITOR_STATE_VALUE + + // Override octave default octave = 4; - editMode = 0; audioDevice = ""; } diff --git a/src/EditorState.h b/src/EditorState.h index 8733cd9..004aa8c 100644 --- a/src/EditorState.h +++ b/src/EditorState.h @@ -21,11 +21,16 @@ struct TrackEditorState bool unpack(const FileSection& section); }; +#define EDITOR_STATE_VALUES \ + EDITOR_STATE_VALUE(macro) \ + EDITOR_STATE_VALUE(octave) \ + EDITOR_STATE_VALUE(editMode) + struct EditorState { - Value macro; - Value octave; - Value editMode; + #define EDITOR_STATE_VALUE(name) Value name; + EDITOR_STATE_VALUES + #undef EDITOR_STATE_VALUE TrackEditorState sequenceEditor; TrackEditorState patternEditor; diff --git a/src/Extension.cpp b/src/Extension.cpp new file mode 100644 index 0000000..f5ea4db --- /dev/null +++ b/src/Extension.cpp @@ -0,0 +1,36 @@ +#include "Extension.h" +#include + +Extension::~Extension() +{ + +} + + +void Extension::init(Prototracker& prototracker, IPlayer& player, const Song& song) +{ + +} + + +void Extension::deinit() +{ + +} + + +void Extension::registerUIComponents(UIComponentFactory& factory, EditorState& editorState) +{ +} + + +void Extension::registerSectionListeners(Song& song) +{ + +} + + +ISynth* Extension::registerSynth() +{ + return NULL; +} diff --git a/src/Extension.h b/src/Extension.h new file mode 100644 index 0000000..bd1ab90 --- /dev/null +++ b/src/Extension.h @@ -0,0 +1,44 @@ +#pragma once + +struct ISynth; +struct Song; +struct UIComponentFactory; +struct EditorState; +struct IPlayer; +struct Prototracker; + +class Extension +{ +public: + virtual ~Extension(); + + + /** + * Extension should initialise everything in init() + */ + + virtual void init(Prototracker& prototracker, IPlayer& player, const Song& song); + + /** + * Extension should deinitialise everything in deinit() + */ + + virtual void deinit(); + + /** + * Extension will register any new UI components when this is called + */ + + virtual void registerUIComponents(UIComponentFactory& factory, EditorState& editorState); + + /** + * Extension will register any Song FileSection listeners here + */ + + virtual void registerSectionListeners(Song& song); + + /** + * Return either an instance of ISynth to register a synth or NULL for no synth + */ + virtual ISynth* registerSynth(); +}; diff --git a/src/ISynth.h b/src/ISynth.h index 8eb31e5..eccaac9 100644 --- a/src/ISynth.h +++ b/src/ISynth.h @@ -3,46 +3,32 @@ struct IOscillator; struct Sample16; -/* - -ISynth handles the IOscillators and some things needed by the GUI -(namely, data for visualization). - -Override ISynth() to initialize your own IOscillators. - -*/ - class ISynth { -protected: - IOscillator **mOscillator; - Sample16 *mPreviousOscillatorOutput, *mTempBuffer; - int mProbePosition; - public: - ISynth(); - virtual ~ISynth(); - virtual void reset(); + virtual ~ISynth() {} + + virtual void reset() = 0; static const int oscillatorProbeLength = 128; - const Sample16* getOscillatorProbe(int oscillator) const; - IOscillator& getOscillator(int i); - int getProbePosition() const; - void setSampleRate(int rate); + virtual const Sample16* getOscillatorProbe(int oscillator) const = 0; + virtual IOscillator& getOscillator(int i) = 0; + virtual int getProbePosition() const = 0; + virtual void setSampleRate(int rate) = 0; // Update the synth state - virtual void update(int numSamples); + virtual void update(int numSamples) = 0; /* Get samples (does not change synth state) - Note: ISynth should overwrite the values in the buffer; + Note: SynthBase should overwrite the values in the buffer; it might contain random values. Fill with zeroes if the output should be silent. This should also update mPreviousOscillatorOutput for the GUI. */ - virtual void render(Sample16 *buffer, int numSamples); + virtual void render(Sample16 *buffer, int numSamples) = 0; }; diff --git a/src/Main.cpp b/src/Main.cpp index a15e4f4..27fa89c 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -2,6 +2,7 @@ #include "SDL_image.h" #include "Debug.h" #include "Prototracker.h" +#include "BaseExtension/BaseExtension.h" #include #ifdef __EMSCRIPTEN__ @@ -30,6 +31,7 @@ extern "C" int main(int argc, char **argv) atexit(IMG_Quit); Prototracker prototracker; + prototracker.loadExtension(); if (!prototracker.init()) { diff --git a/src/MainEditor.cpp b/src/MainEditor.cpp index 34cde0e..45c37fa 100644 --- a/src/MainEditor.cpp +++ b/src/MainEditor.cpp @@ -34,6 +34,8 @@ #include "MessageDisplayer.h" #include "TooltipManager.h" #include "TooltipDisplayer.h" +#include "UIComponentFactory.h" +#include "Debug.h" #include "App.h" #include "SDL.h" #include "Theme.h" @@ -711,7 +713,7 @@ void MainEditor::saveState() } -bool MainEditor::loadElements(const Theme& theme) +bool MainEditor::loadElements(const Theme& theme, const UIComponentFactory& componentFactory) { deleteChildren(); @@ -820,7 +822,20 @@ bool MainEditor::loadElements(const Theme& theme) break; case Theme::Unknown: - break; + { + Editor *component = componentFactory.createComponent(element.name, element); + + if (component != NULL) + { + addChild(component, element.parameters[0], element.parameters[1], element.parameters[2], element.parameters[3]); + } + else + { + showMessageV(MessageError, "Unknown element %s", element.name); + debug("Unknown element %s", element.name); + } + } + break; } } diff --git a/src/MainEditor.h b/src/MainEditor.h index 0bcb2ca..60c2791 100644 --- a/src/MainEditor.h +++ b/src/MainEditor.h @@ -22,6 +22,7 @@ struct TooltipDisplayer; struct AudioDeviceSelector; struct CommandSelector; struct CommandOptionSelector; +struct UIComponentFactory; class MainEditor: public Editor { @@ -115,6 +116,6 @@ class MainEditor: public Editor bool loadState(); void saveState(); - bool loadElements(const Theme& theme); + bool loadElements(const Theme& theme, const UIComponentFactory& componentFactory); }; diff --git a/src/Prototracker.cpp b/src/Prototracker.cpp index 78e366d..a480e48 100644 --- a/src/Prototracker.cpp +++ b/src/Prototracker.cpp @@ -2,7 +2,6 @@ #include "Prototracker.h" #include "Mixer.h" #include "Song.h" -#include "Synth.h" #include "Player.h" #include "EditorState.h" #include "Gamepad.h" @@ -10,6 +9,8 @@ #include "MainEditor.h" #include "Emscripten.h" #include "Debug.h" +#include "Extension.h" +#include "UIComponentFactory.h" #ifdef __EMSCRIPTEN__ #include @@ -23,18 +24,52 @@ Prototracker::Prototracker() Prototracker::~Prototracker() { + for (auto extension : mExtensions) + { + delete extension.second; + } } bool Prototracker::init() { + mGamepad = new Gamepad(); mEditorState = new EditorState(); mSong = new Song(); mPlayer = new Player(*mSong); - mGamepad = new Gamepad(); - mSynth = new Synth(); + mUIComponentFactory = new UIComponentFactory(); + + for (auto extension : mExtensions) + { + extension.second->init(*this, *mPlayer, *mSong); + } + + mSynth = NULL; + + for (auto extension : mExtensions) + { + ISynth *synth = extension.second->registerSynth(); + + if (synth != NULL) + { + mSynth = synth; + break; + } + } + + if (mSynth == NULL) + { + debug("No synth registered by any extension"); + } + mMixer = new Mixer(*mPlayer, *mSynth); mRenderer = new Renderer(); + for (auto extension : mExtensions) + { + extension.second->registerUIComponents(*mUIComponentFactory, *mEditorState); + extension.second->registerSectionListeners(*mSong); + } + mMainEditor = new MainEditor(*mEditorState, *mPlayer, mPlayer->getPlayerState(), *mSong, *mSynth, *mMixer); if (!initRenderer()) { @@ -42,10 +77,10 @@ bool Prototracker::init() } #ifndef __EMSCRIPTEN__ - initEditor(); + initEditor(); #endif - return true; + return true; } @@ -60,7 +95,6 @@ void Prototracker::deinit() delete mEditorState; delete mGamepad; delete mRenderer; - delete mSynth; } bool Prototracker::initRenderer() @@ -80,7 +114,7 @@ bool Prototracker::initRenderer() SDL_Rect area = {0, 0, theme.getWidth(), theme.getHeight()}; mMainEditor->setArea(area); - if (!mMainEditor->loadElements(theme)) + if (!mMainEditor->loadElements(theme, *mUIComponentFactory)) { return false; } @@ -92,7 +126,7 @@ bool Prototracker::initRenderer() void Prototracker::initEditor() { - // Emscripten needs an absolute path to filesystem root + // Emscripten needs an absolute path to filesystem root #ifdef __EMSCRIPTEN__ const char *gamepadPath = "/assets/gamecontrollerdb.txt"; const char *songPath = "/assets/dub.song"; diff --git a/src/Prototracker.h b/src/Prototracker.h index b17e266..ea34867 100644 --- a/src/Prototracker.h +++ b/src/Prototracker.h @@ -2,6 +2,9 @@ #include "SDL.h" #include +#include +#include +#include "Extension.h" struct EditorState; struct IPlayer; @@ -11,6 +14,7 @@ struct Mixer; struct MainEditor; struct Gamepad; struct Renderer; +struct UIComponentFactory; class Prototracker { Renderer *mRenderer; @@ -21,6 +25,9 @@ class Prototracker { Mixer *mMixer; MainEditor *mMainEditor; Gamepad *mGamepad; + UIComponentFactory *mUIComponentFactory; + + std::map mExtensions; Uint32 mPreviousTick; bool mReady; @@ -32,8 +39,31 @@ class Prototracker { Prototracker(); ~Prototracker(); + /** + * Register extension/dependency. + */ + + template + T& loadExtension(); + bool init(); void deinit(); bool handleEvents(); std::string getSongBase64() const; }; + + +template +T& Prototracker::loadExtension() { + auto typeName = typeid(T).name(); + auto foundExtension = mExtensions.find(typeName); + + if (foundExtension != mExtensions.end()) + { + return static_cast(*foundExtension->second); + } + + auto extension = new T(); + mExtensions[typeName] = extension; + return *extension; +} diff --git a/src/ISynth.cpp b/src/SynthBase.cpp similarity index 79% rename from src/ISynth.cpp rename to src/SynthBase.cpp index d81675c..ef648ca 100644 --- a/src/ISynth.cpp +++ b/src/SynthBase.cpp @@ -1,72 +1,72 @@ -#include "ISynth.h" +#include "SynthBase.h" #include "IOscillator.h" #include "SequenceRow.h" #include "Sample.h" #include "SDL.h" #include -const int ISynth::oscillatorProbeLength; +const int SynthBase::oscillatorProbeLength; -ISynth::ISynth() +SynthBase::SynthBase() : mProbePosition(0) { mOscillator = new IOscillator*[SequenceRow::maxTracks]; mPreviousOscillatorOutput = new Sample16[oscillatorProbeLength * SequenceRow::maxTracks]; mTempBuffer = new Sample16[2048]; - + SDL_memset(mPreviousOscillatorOutput, 0, sizeof(Sample16) * oscillatorProbeLength * SequenceRow::maxTracks); } -ISynth::~ISynth() +SynthBase::~SynthBase() { for (int i = 0 ; i < SequenceRow::maxTracks ; ++i) delete mOscillator[i]; - + delete[] mOscillator; delete[] mPreviousOscillatorOutput; } -IOscillator& ISynth::getOscillator(int i) +IOscillator& SynthBase::getOscillator(int i) { return *mOscillator[i]; } -const Sample16* ISynth::getOscillatorProbe(int oscillator) const +const Sample16* SynthBase::getOscillatorProbe(int oscillator) const { return &mPreviousOscillatorOutput[oscillatorProbeLength * oscillator]; } -void ISynth::update(int numSamples) +void SynthBase::update(int numSamples) { for (int i = 0 ; i < SequenceRow::maxTracks ; ++i) mOscillator[i]->update(numSamples); } -void ISynth::render(Sample16 *buffer, int numSamples) +void SynthBase::render(Sample16 *buffer, int numSamples) { SDL_memset(buffer, 0, sizeof(Sample16) * numSamples); - + int probeCount = std::min(numSamples, oscillatorProbeLength); - + for (int i = 0 ; i < SequenceRow::maxTracks ; ++i) { SDL_memset(mTempBuffer, 0, sizeof(Sample16) * numSamples); mOscillator[i]->render(mTempBuffer, numSamples); - + for (int p = 0 ; p < numSamples ; ++p) { buffer[p].left += mTempBuffer[p].left; buffer[p].right += mTempBuffer[p].right; } - + Sample16 *src = mTempBuffer + std::max(0, numSamples - oscillatorProbeLength); Sample16 *dest = mPreviousOscillatorOutput + oscillatorProbeLength * i; - + for (int p = 0 ; p < probeCount ; ++p) { Sample16& sample = dest[(p + mProbePosition) % oscillatorProbeLength]; @@ -75,24 +75,24 @@ void ISynth::render(Sample16 *buffer, int numSamples) src++; } } - + mProbePosition += probeCount; } -int ISynth::getProbePosition() const +int SynthBase::getProbePosition() const { return mProbePosition; } -void ISynth::setSampleRate(int rate) +void SynthBase::setSampleRate(int rate) { for (int i = 0 ; i < SequenceRow::maxTracks ; ++i) mOscillator[i]->setSampleRate(rate); } -void ISynth::reset() +void SynthBase::reset() { } diff --git a/src/SynthBase.h b/src/SynthBase.h new file mode 100644 index 0000000..d23f3eb --- /dev/null +++ b/src/SynthBase.h @@ -0,0 +1,47 @@ +#pragma once + +#include "ISynth.h" + +/* + +SynthBase handles the IOscillators and some things needed by the GUI +(namely, data for visualization). + +Override SynthBase() to initialize your own IOscillators. + +*/ + +class SynthBase: public ISynth +{ +protected: + IOscillator **mOscillator; + Sample16 *mPreviousOscillatorOutput, *mTempBuffer; + int mProbePosition; + +public: + SynthBase(); + virtual ~SynthBase(); + + virtual void reset(); + + static const int oscillatorProbeLength = 128; + + virtual const Sample16* getOscillatorProbe(int oscillator) const; + virtual IOscillator& getOscillator(int i); + virtual int getProbePosition() const; + virtual void setSampleRate(int rate); + + // Update the synth state + virtual void update(int numSamples); + + /* + Get samples (does not change synth state) + Note: SynthBase should overwrite the values in the buffer; + it might contain random values. Fill with zeroes if + the output should be silent. + + This should also update mPreviousOscillatorOutput for + the GUI. + */ + virtual void render(Sample16 *buffer, int numSamples); +}; diff --git a/src/Theme.cpp b/src/Theme.cpp index 24fd359..d45ff1a 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -225,6 +225,7 @@ bool Theme::loadDefinition(const std::string& path) { Element element; element.type = Theme::Unknown; + strncpy(element.name, elementName, sizeof(element.name) - 1); for (auto elementDef : elements) { @@ -235,16 +236,9 @@ bool Theme::loadDefinition(const std::string& path) } } - if (element.type != Theme::Unknown) - { - memcpy(element.parameters, parameters, sizeof(element.parameters)); - memcpy(element.strParameters, strParameters, sizeof(element.strParameters)); - mElement.push_back(element); - } - else - { - debug("Unknown element %s on line %d", elementName, lineCounter); - } + memcpy(element.parameters, parameters, sizeof(element.parameters)); + memcpy(element.strParameters, strParameters, sizeof(element.strParameters)); + mElement.push_back(element); } else { diff --git a/src/Theme.h b/src/Theme.h index 73e214f..a0150ba 100644 --- a/src/Theme.h +++ b/src/Theme.h @@ -26,6 +26,7 @@ class Theme struct Element { + char name[32]; ElementType type; int parameters[10]; char strParameters[10][50]; diff --git a/src/UIComponentFactory.cpp b/src/UIComponentFactory.cpp new file mode 100644 index 0000000..f771985 --- /dev/null +++ b/src/UIComponentFactory.cpp @@ -0,0 +1,19 @@ +#include "UIComponentFactory.h" + +void UIComponentFactory::registerComponent(const char *componentName, EditorCreatorFunc creatorFunc) +{ + mComponentPrototypes[componentName] = creatorFunc; +} + + +Editor* UIComponentFactory::createComponent(const char *componentName, const Theme::Element& element) const +{ + auto createComponent = mComponentPrototypes.find(componentName); + + if (createComponent == mComponentPrototypes.end()) + { + return NULL; + } + + return createComponent->second(element); +} diff --git a/src/UIComponentFactory.h b/src/UIComponentFactory.h new file mode 100644 index 0000000..01998b2 --- /dev/null +++ b/src/UIComponentFactory.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include "Theme.h" + +struct Editor; + +typedef std::function EditorCreatorFunc; + +class UIComponentFactory +{ + std::map mComponentPrototypes; +public: + void registerComponent(const char *componentName, EditorCreatorFunc creatorFunc); + Editor* createComponent(const char *componentName, const Theme::Element& element) const; +};