| @@ -73,6 +73,10 @@ ALL_LIBS += $(MODULEDIR)/rtaudio.a | |||||
| ALL_LIBS += $(MODULEDIR)/rtmidi.a | ALL_LIBS += $(MODULEDIR)/rtmidi.a | ||||
| endif | endif | ||||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||||
| ALL_LIBS += $(MODULEDIR)/rtosc.a | |||||
| endif | |||||
| ifeq ($(HAVE_QT4),true) | ifeq ($(HAVE_QT4),true) | ||||
| ALL_LIBS += $(MODULEDIR)/theme.qt4.a | ALL_LIBS += $(MODULEDIR)/theme.qt4.a | ||||
| endif | endif | ||||
| @@ -534,9 +538,13 @@ endif | |||||
| bin/resources/nekofilter/*.png \ | bin/resources/nekofilter/*.png \ | ||||
| $(DESTDIR)$(PREFIX)/share/carla/resources/nekofilter/ | $(DESTDIR)$(PREFIX)/share/carla/resources/nekofilter/ | ||||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||||
| ifeq ($(HAVE_ZYN_UI_DEPS),true) | |||||
| install -m 644 \ | install -m 644 \ | ||||
| bin/resources/zynaddsubfx/*.png \ | bin/resources/zynaddsubfx/*.png \ | ||||
| $(DESTDIR)$(PREFIX)/share/carla/resources/zynaddsubfx/ | $(DESTDIR)$(PREFIX)/share/carla/resources/zynaddsubfx/ | ||||
| endif | |||||
| endif | |||||
| # Install resources (re-use python files) | # Install resources (re-use python files) | ||||
| $(LINK) $(PREFIX)/share/carla/carla_app.py $(DESTDIR)$(PREFIX)/share/carla/resources/ | $(LINK) $(PREFIX)/share/carla/carla_app.py $(DESTDIR)$(PREFIX)/share/carla/resources/ | ||||
| @@ -419,6 +419,9 @@ ifeq ($(HAVE_NTK),true) | |||||
| HAVE_ZYN_UI_DEPS = true | HAVE_ZYN_UI_DEPS = true | ||||
| endif | endif | ||||
| # TESTING | |||||
| HAVE_ZYN_UI_DEPS = false | |||||
| ifeq ($(HAVE_DGL),true) | ifeq ($(HAVE_DGL),true) | ||||
| NATIVE_PLUGINS_LIBS += $(DGL_LIBS) | NATIVE_PLUGINS_LIBS += $(DGL_LIBS) | ||||
| ifeq ($(HAVE_PROJECTM),true) | ifeq ($(HAVE_PROJECTM),true) | ||||
| @@ -429,7 +432,7 @@ endif | |||||
| ifeq ($(EXPERIMENTAL_PLUGINS),true) | ifeq ($(EXPERIMENTAL_PLUGINS),true) | ||||
| BASE_FLAGS += -DHAVE_EXPERIMENTAL_PLUGINS | BASE_FLAGS += -DHAVE_EXPERIMENTAL_PLUGINS | ||||
| NATIVE_PLUGINS_LIBS += -lclxclient -lclthreads -lzita-convolver -lzita-resampler | NATIVE_PLUGINS_LIBS += -lclxclient -lclthreads -lzita-convolver -lzita-resampler | ||||
| NATIVE_PLUGINS_LIBS += $(shell pkg-config --libs cairo libpng12 fftw3f x11 xft) | |||||
| NATIVE_PLUGINS_LIBS += $(shell pkg-config --libs cairo libpng12 fftw3f x11 xft zlib) | |||||
| endif | endif | ||||
| ifeq ($(HAVE_ZYN_DEPS),true) | ifeq ($(HAVE_ZYN_DEPS),true) | ||||
| @@ -52,6 +52,10 @@ STANDALONE_LIBS += $(MODULEDIR)/rtaudio.a | |||||
| STANDALONE_LIBS += $(MODULEDIR)/rtmidi.a | STANDALONE_LIBS += $(MODULEDIR)/rtmidi.a | ||||
| endif | endif | ||||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||||
| STANDALONE_LIBS += $(MODULEDIR)/rtosc.a | |||||
| endif | |||||
| UTILS_LIBS = $(MODULEDIR)/juce_audio_basics.a | UTILS_LIBS = $(MODULEDIR)/juce_audio_basics.a | ||||
| UTILS_LIBS += $(MODULEDIR)/juce_audio_formats.a | UTILS_LIBS += $(MODULEDIR)/juce_audio_formats.a | ||||
| UTILS_LIBS += $(MODULEDIR)/juce_core.a | UTILS_LIBS += $(MODULEDIR)/juce_core.a | ||||
| @@ -114,6 +114,10 @@ LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) | |||||
| endif | endif | ||||
| endif | endif | ||||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||||
| LIBS_native += $(MODULEDIR)/rtosc.a | |||||
| endif | |||||
| LINK_FLAGS += $(LIBLO_LIBS) | LINK_FLAGS += $(LIBLO_LIBS) | ||||
| ifeq ($(HAVE_X11),true) | ifeq ($(HAVE_X11),true) | ||||
| @@ -0,0 +1,61 @@ | |||||
| #!/usr/bin/make -f | |||||
| # Makefile for rtosc # | |||||
| # ------------------ # | |||||
| # Created by falkTX | |||||
| # | |||||
| CWD=../.. | |||||
| MODULENAME=rtosc | |||||
| include ../Makefile.mk | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||||
| BUILD_CXX_FLAGS += -I. | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||||
| OBJS = \ | |||||
| $(OBJDIR)/dispatch.c.o \ | |||||
| $(OBJDIR)/rtosc.c.o \ | |||||
| $(OBJDIR)/midimapper.cpp.o \ | |||||
| $(OBJDIR)/miditable.cpp.o \ | |||||
| $(OBJDIR)/ports.cpp.o \ | |||||
| $(OBJDIR)/subtree-serialize.cpp.o \ | |||||
| $(OBJDIR)/thread-link.cpp.o \ | |||||
| $(OBJDIR)/undo-history.cpp.o | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||||
| all: $(MODULEDIR)/$(MODULENAME).a | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||||
| clean: | |||||
| rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||||
| debug: | |||||
| $(MAKE) DEBUG=true | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||||
| $(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||||
| -@mkdir -p $(MODULEDIR) | |||||
| @echo "Creating $(MODULENAME).a" | |||||
| @rm -f $@ | |||||
| @$(AR) crs $@ $^ | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||||
| $(OBJDIR)/%.c.o: src/%.c | |||||
| -@mkdir -p $(OBJDIR) | |||||
| @echo "Compiling $<" | |||||
| @$(CC) $< $(BUILD_C_FLAGS) -c -o $@ | |||||
| $(OBJDIR)/%.cpp.o: src/cpp/%.cpp | |||||
| -@mkdir -p $(OBJDIR) | |||||
| @echo "Compiling $<" | |||||
| @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
| -include $(OBJS:%.o=%.d) | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||||
| @@ -0,0 +1,277 @@ | |||||
| /* | |||||
| * Copyright (c) 2012 Mark McCurry | |||||
| * | |||||
| * Permission is hereby granted, free of charge, to any person obtaining a | |||||
| * copy of this software and associated documentation files (the "Software"), | |||||
| * to deal in the Software without restriction, including without limitation | |||||
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||||
| * and/or sell copies of the Software, and to permit persons to whom the | |||||
| * Software is furnished to do so, subject to the following conditions: | |||||
| * | |||||
| * The above copyright notice and this permission notice (including the next | |||||
| * paragraph) shall be included in all copies or substantial portions of the | |||||
| * Software. | |||||
| * | |||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |||||
| * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
| * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |||||
| * DEALINGS IN THE SOFTWARE. | |||||
| */ | |||||
| #include "ports.h" | |||||
| #include <string.h> | |||||
| #include <algorithm> | |||||
| #include <map> | |||||
| #include <sstream> | |||||
| #include <deque> | |||||
| #include <utility> | |||||
| #include <cassert> | |||||
| namespace rtosc { | |||||
| /** | |||||
| * Module Overview | |||||
| * | |||||
| * Actions: | |||||
| * - Add a mapping {coarse/fine} [nRT] | |||||
| * - Delete a mapping {coarse/fine} [nRT] | |||||
| * - Transform mapping value based on passive observation [nRT] | |||||
| * - Find unused CC numbers [RT] | |||||
| * - Transform CC into event {coarse/fine} [RT] | |||||
| */ | |||||
| class MidiMapperStorage | |||||
| { | |||||
| public: | |||||
| //Almost immutable short vector class | |||||
| template<class T> | |||||
| class TinyVector { | |||||
| int n; | |||||
| T *t; | |||||
| public: | |||||
| TinyVector(void):n(0),t(0){} | |||||
| TinyVector(int i):n(i),t(new T[i]){} | |||||
| T&operator[](int i) {assert(i>=0 && i<n);return t[i];} | |||||
| T operator[](int i) const {assert(i>=0 && i<n);return t[i];} | |||||
| TinyVector insert(const T &t_) | |||||
| {TinyVector next(n+1); for(int i=0;i<n; ++i) next.t[i]=t[i]; next.t[n] = t_;return std::move(next);} | |||||
| TinyVector one_larger(void) | |||||
| {TinyVector next(n+1); for(int i=0;i<n + 1; ++i) next.t[i]=0; return std::move(next);} | |||||
| TinyVector sized_clone(void) | |||||
| {TinyVector next(n); for(int i=0;i<n; ++i) next.t[i]=0; return std::move(next);} | |||||
| TinyVector clone(void) | |||||
| {TinyVector next(n); for(int i=0;i<n; ++i) next.t[i]=t[i]; return std::move(next);} | |||||
| int size(void) const{return n;} | |||||
| }; | |||||
| typedef std::function<void(const char*)> write_cb; | |||||
| typedef std::function<void(int16_t,write_cb)> callback_t; | |||||
| //RT Read Only | |||||
| TinyVector<std::tuple<int, bool, int>> mapping;//CC->{coarse, val-cb offset} | |||||
| TinyVector<callback_t> callbacks; | |||||
| //RT RW | |||||
| TinyVector<int> values; | |||||
| bool handleCC(int ID, int val, write_cb write); | |||||
| //TODO try to change O(n^2) algorithm to O(n) | |||||
| void cloneValues(const MidiMapperStorage &storage); | |||||
| MidiMapperStorage *clone(void); | |||||
| }; | |||||
| struct MidiBijection | |||||
| { | |||||
| int mode;//0:linear,1:log | |||||
| float min; | |||||
| float max; | |||||
| int operator()(float x) const; | |||||
| float operator()(int x) const; | |||||
| }; | |||||
| #include <cassert> | |||||
| class MidiMappernRT | |||||
| { | |||||
| public: | |||||
| MidiMappernRT(void); | |||||
| void map(const char *addr, bool coarse = true); | |||||
| MidiMapperStorage *generateNewBijection(const Port &port, std::string); | |||||
| void addNewMapper(int ID, const Port &port, std::string addr); | |||||
| void addFineMapper(int ID, const Port &port, std::string addr); | |||||
| void useFreeID(int ID); | |||||
| void unMap(const char *addr, bool coarse); | |||||
| void delMapping(int ID, bool coarse, const char *addr); | |||||
| void replaceMapping(int, bool, const char *); | |||||
| std::map<std::string, std::string> getMidiMappingStrings(void); | |||||
| //unclear if this should be be here as a helper or not | |||||
| std::string getMappedString(std::string addr); | |||||
| MidiBijection getBijection(std::string s); | |||||
| void snoop(const char *msg); | |||||
| void apply_high(int v, int ID); | |||||
| void apply_low(int v, int ID); | |||||
| void apply_midi(int val, int ID); | |||||
| void setBounds(const char *str, float low, float high); | |||||
| std::tuple<float,float,float,float> getBounds(const char *str); | |||||
| bool has(std::string addr); | |||||
| bool hasPending(std::string addr); | |||||
| bool hasCoarse(std::string addr); | |||||
| bool hasFine(std::string addr); | |||||
| bool hasCoarsePending(std::string addr); | |||||
| bool hasFinePending(std::string addr); | |||||
| int getCoarse(std::string addr); | |||||
| int getFine(std::string addr); | |||||
| //(Location, Coarse, Fine, Bijection) | |||||
| std::map<std::string, std::tuple<int, int, int, MidiBijection>> inv_map; | |||||
| std::deque<std::pair<std::string,bool>> learnQueue; | |||||
| std::function<void(const char *)> rt_cb; | |||||
| MidiMapperStorage *storage; | |||||
| const Ports *base_ports; | |||||
| }; | |||||
| class MidiMapperRT | |||||
| { | |||||
| public: | |||||
| MidiMapperRT(void); | |||||
| void setBackendCb(std::function<void(const char*)> cb); | |||||
| void setFrontendCb(std::function<void(const char*)> cb); | |||||
| void handleCC(int ID, int val); | |||||
| void addWatch(void); | |||||
| void remWatch(void); | |||||
| Port addWatchPort(void); | |||||
| Port removeWatchPort(void); | |||||
| Port bindPort(void); | |||||
| //Fixed upper bounded size set of integer IDs | |||||
| class PendingQueue | |||||
| { | |||||
| public: | |||||
| PendingQueue() | |||||
| :pos_r(0), pos_w(0), size(0) | |||||
| { | |||||
| for(int i=0; i<32; ++i) | |||||
| vals[i] = -1; | |||||
| } | |||||
| void insert(int x) | |||||
| { | |||||
| if(has(x) || size > 31) | |||||
| return; | |||||
| vals[pos_w] = x; | |||||
| size++; | |||||
| pos_w = (pos_w+1)%32; | |||||
| } | |||||
| void pop(void) | |||||
| { | |||||
| if(size == 0) | |||||
| return; | |||||
| size--; | |||||
| vals[pos_r] = -1; | |||||
| pos_r = (1+pos_r)%32; | |||||
| } | |||||
| bool has(int x) | |||||
| { | |||||
| for(int i=0; i<32; ++i) | |||||
| if(vals[i] == x) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| int vals[32]; | |||||
| int pos_r; | |||||
| int pos_w; | |||||
| int size; | |||||
| }; | |||||
| /*************** | |||||
| * Member Data * | |||||
| ***************/ | |||||
| PendingQueue pending; | |||||
| MidiMapperStorage *storage; | |||||
| unsigned watchSize; | |||||
| std::function<void(const char*)> backend; | |||||
| std::function<void(const char*)> frontend; | |||||
| }; | |||||
| struct MidiAddr | |||||
| { | |||||
| //The midi values that map to the specified action | |||||
| uint8_t ch, ctl; | |||||
| //The type of the event 'f', 'i', 'T', 'c' | |||||
| char type; | |||||
| //The path of the event | |||||
| char *path; | |||||
| //The conversion function for 'f' types | |||||
| const char *conversion; | |||||
| }; | |||||
| /** | |||||
| * Table of midi mappings - Deprecated | |||||
| * | |||||
| */ | |||||
| class MidiTable | |||||
| { | |||||
| public: | |||||
| const Ports &dispatch_root; | |||||
| short unhandled_ch; | |||||
| short unhandled_ctl; | |||||
| char *unhandled_path; | |||||
| void (*error_cb)(const char *, const char *); | |||||
| void (*event_cb)(const char *); | |||||
| void (*modify_cb)(const char *, const char *, const char *, int, int); | |||||
| MidiTable(const Ports &_dispatch_root); | |||||
| ~MidiTable(); | |||||
| bool has(uint8_t ch, uint8_t ctl) const; | |||||
| MidiAddr *get(uint8_t ch, uint8_t ctl); | |||||
| const MidiAddr *get(uint8_t ch, uint8_t ctl) const; | |||||
| bool mash_port(MidiAddr &e, const Port &port); | |||||
| void addElm(uint8_t ch, uint8_t ctl, const char *path); | |||||
| void check_learn(void); | |||||
| void learn(const char *s); | |||||
| void clear_entry(const char *s); | |||||
| void process(uint8_t ch, uint8_t ctl, uint8_t val); | |||||
| Port learnPort(void); | |||||
| Port unlearnPort(void); | |||||
| Port registerPort(void); | |||||
| //TODO generalize to an addScalingFunction() system | |||||
| static float translate(uint8_t val, const char *meta); | |||||
| private: | |||||
| class MidiTable_Impl *impl; | |||||
| }; | |||||
| }; | |||||
| @@ -0,0 +1,371 @@ | |||||
| /* | |||||
| * Copyright (c) 2013 Mark McCurry | |||||
| * | |||||
| * Permission is hereby granted, free of charge, to any person obtaining a | |||||
| * copy of this software and associated documentation files (the "Software"), | |||||
| * to deal in the Software without restriction, including without limitation | |||||
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||||
| * and/or sell copies of the Software, and to permit persons to whom the | |||||
| * Software is furnished to do so, subject to the following conditions: | |||||
| * | |||||
| * The above copyright notice and this permission notice (including the next | |||||
| * paragraph) shall be included in all copies or substantial portions of the | |||||
| * Software. | |||||
| * | |||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |||||
| * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
| * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |||||
| * DEALINGS IN THE SOFTWARE. | |||||
| */ | |||||
| #ifndef RTOSC_PORT_SUGAR | |||||
| #define RTOSC_PORT_SUGAR | |||||
| //Hack to workaround old incomplete decltype implementations | |||||
| #ifdef __GNUC__ | |||||
| #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ <= 7) | |||||
| template<typename T> | |||||
| struct rtosc_hack_decltype_t | |||||
| { | |||||
| typedef T type; | |||||
| }; | |||||
| #define decltype(expr) rtosc_hack_decltype_t<decltype(expr)>::type | |||||
| #endif | |||||
| #endif | |||||
| //General macro utilities | |||||
| #define STRINGIFY2(a) #a | |||||
| #define STRINGIFY(a) STRINGIFY2(a) | |||||
| //Helper for documenting varargs | |||||
| #define IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9, N, ...) N | |||||
| #define LAST_IMP(...) IMPL(__VA_ARGS__,9,8,7,6,5,4,3,2,1,0,0,0,0) | |||||
| #define DOC_IMP9(a,b,c,d,e,f,g,h,i) a b c d e f g h rDoc(i) | |||||
| #define DOC_IMP8(a,b,c,d,e,f,g,h) a b c d e f g rDoc(h) | |||||
| #define DOC_IMP7(a,b,c,d,e,f,g) a b c d e f rDoc(g) | |||||
| #define DOC_IMP6(a,b,c,d,e,f) a b c d e rDoc(f) | |||||
| #define DOC_IMP5(a,b,c,d,e) a b c d rDoc(e) | |||||
| #define DOC_IMP4(a,b,c,d) a b c rDoc(d) | |||||
| #define DOC_IMP3(a,b,c) a b rDoc(c) | |||||
| #define DOC_IMP2(a,b) a rDoc(b) | |||||
| #define DOC_IMP1(a) rDoc(a) | |||||
| #define DOC_IMP0() YOU_MUST_DOCUMENT_YOUR_PORTS | |||||
| #define DOC_IMP(count, ...) DOC_IMP ##count(__VA_ARGS__) | |||||
| #define DOC_I(count, ...) DOC_IMP(count,__VA_ARGS__) | |||||
| #define DOC(...) DOC_I(LAST_IMP(__VA_ARGS__), __VA_ARGS__) | |||||
| //XXX Currently unused macro | |||||
| #define MAC_EACH_0(mac, x, ...) INSUFFICIENT_ARGUMENTS_PROVIDED_TO_MAC_EACH | |||||
| #define MAC_EACH_1(mac, x, ...) mac(x) | |||||
| #define MAC_EACH_2(mac, x, ...) mac(x) MAC_EACH_1(mac, __VA_ARGS__) | |||||
| #define MAC_EACH_3(mac, x, ...) mac(x) MAC_EACH_2(mac, __VA_ARGS__) | |||||
| #define MAC_EACH_4(mac, x, ...) mac(x) MAC_EACH_3(mac, __VA_ARGS__) | |||||
| #define MAC_EACH_5(mac, x, ...) mac(x) MAC_EACH_4(mac, __VA_ARGS__) | |||||
| #define MAC_EACH_6(mac, x, ...) mac(x) MAC_EACH_5(mac, __VA_ARGS__) | |||||
| #define MAC_EACH_7(mac, x, ...) mac(x) MAC_EACH_6(mac, __VA_ARGS__) | |||||
| #define MAC_EACH_8(mac, x, ...) mac(x) MAC_EACH_7(mac, __VA_ARGS__) | |||||
| #define MAC_EACH_9(mac, x, ...) mac(x) MAC_EACH_8(mac, __VA_ARGS__) | |||||
| #define MAC_EACH_IMP(mac, count, ...) MAC_EACH_ ##count(mac,__VA_ARGS__) | |||||
| #define MAC_EACH_I(mac, count, ...) MAC_EACH_IMP(mac, count, __VA_ARGS__) | |||||
| #define MAC_EACH(mac, ...) MAC_EACH_I(mac, LAST_IMP(__VA_ARGS__), __VA_ARGS__) | |||||
| #define OPTIONS_IMP9(a,b,c,d,e,f,g,h,i) \ | |||||
| rOpt(0,a) rOpt(1,b) rOpt(2,c) rOpt(3,d) rOpt(4,e) rOpt(5,f) rOpt(6,g) \ | |||||
| rOpt(7,h) rOpt(8,i) | |||||
| #define OPTIONS_IMP8(a,b,c,d,e,f,g,h) \ | |||||
| rOpt(0,a) rOpt(1,b) rOpt(2,c) rOpt(3,d) rOpt(4,e) rOpt(5,f) rOpt(6,g) \ | |||||
| rOpt(7,h) | |||||
| #define OPTIONS_IMP7(a,b,c,d,e,f,g) \ | |||||
| rOpt(0,a) rOpt(1,b) rOpt(2,c) rOpt(3,d) rOpt(4,e) rOpt(5,f) rOpt(6,g) | |||||
| #define OPTIONS_IMP6(a,b,c,d,e,f) \ | |||||
| rOpt(0,a) rOpt(1,b) rOpt(2,c) rOpt(3,d) rOpt(4,e) rOpt(5,f) | |||||
| #define OPTIONS_IMP5(a,b,c,d,e) \ | |||||
| rOpt(0,a) rOpt(1,b) rOpt(2,c) rOpt(3,d) rOpt(4,e) | |||||
| #define OPTIONS_IMP4(a,b,c,d) \ | |||||
| rOpt(0,a) rOpt(1,b) rOpt(2,c) rOpt(3,d) | |||||
| #define OPTIONS_IMP3(a,b,c) \ | |||||
| rOpt(0,a) rOpt(1,b) rOpt(2,c) | |||||
| #define OPTIONS_IMP2(a,b) \ | |||||
| rOpt(0,a) rOpt(1,b) | |||||
| #define OPTIONS_IMP1(a) \ | |||||
| rOpt(0,a) | |||||
| #define OPTIONS_IMP0() YOU_MUST_PROVIDE_OPTIONS | |||||
| #define OPTIONS_IMP(count, ...) OPTIONS_IMP ##count(__VA_ARGS__) | |||||
| #define OPTIONS_I(count, ...) OPTIONS_IMP(count, __VA_ARGS__) | |||||
| #define OPTIONS(...) OPTIONS_I(LAST_IMP(__VA_ARGS__), __VA_ARGS__) | |||||
| //Additional Change Callback (after parameters have been changed) | |||||
| //This can be used to queue up interpolation or parameter regen | |||||
| #define rChangeCb | |||||
| //Normal parameters | |||||
| #define rParam(name, ...) \ | |||||
| {STRINGIFY(name) "::c", rProp(parameter) rMap(min, 0) rMap(max, 127) DOC(__VA_ARGS__), NULL, rParamCb(name)} | |||||
| #define rParamF(name, ...) \ | |||||
| {STRINGIFY(name) "::f", rProp(parameter) DOC(__VA_ARGS__), NULL, rParamFCb(name)} | |||||
| #define rParamI(name, ...) \ | |||||
| {STRINGIFY(name) "::i", rProp(parameter) DOC(__VA_ARGS__), NULL, rParamICb(name)} | |||||
| #define rToggle(name, ...) \ | |||||
| {STRINGIFY(name) "::T:F",rProp(parameter) DOC(__VA_ARGS__), NULL, rToggleCb(name)} | |||||
| #define rOption(name, ...) \ | |||||
| {STRINGIFY(name) "::i:c",rProp(parameter) DOC(__VA_ARGS__), NULL, rOptionCb(name)} | |||||
| //Array operators | |||||
| #define rArrayF(name, length, ...) \ | |||||
| {STRINGIFY(name) "#" STRINGIFY(length) "::f", rProp(parameter) DOC(__VA_ARGS__), NULL, rArrayFCb(name)} | |||||
| #define rArray(name, length, ...) \ | |||||
| {STRINGIFY(name) "#" STRINGIFY(length) "::c", rProp(parameter) DOC(__VA_ARGS__), NULL, rArrayCb(name)} | |||||
| #define rArrayT(name, length, ...) \ | |||||
| {STRINGIFY(name) "#" STRINGIFY(length) "::T:F", rProp(parameter) DOC(__VA_ARGS__), NULL, rArrayTCb(name)} | |||||
| #define rArrayI(name, length, ...) \ | |||||
| {STRINGIFY(name) "#" STRINGIFY(length) "::i", rProp(parameter) DOC(__VA_ARGS__), NULL, rArrayICb(name)} | |||||
| //Method callback Actions | |||||
| #define rAction(name, ...) \ | |||||
| {STRINGIFY(name) ":", DOC(__VA_ARGS__), NULL, rActionCb(name)} | |||||
| #define rActioni(name, ...) \ | |||||
| {STRINGIFY(name) ":i", DOC(__VA_ARGS__), NULL, rActioniCb(name)} | |||||
| //Alias operators | |||||
| #define rParams(name, length, ...) \ | |||||
| rArray(name, length, __VA_ARGS__), \ | |||||
| {STRINGIFY(name) ":", rProp(alias), NULL, rParamsCb(name, length)} | |||||
| template<class T> constexpr T spice(T*t) {return *t;} | |||||
| //Recursion [two ports in one for pointer manipulation] | |||||
| #define rRecur(name, ...) \ | |||||
| {STRINGIFY(name) "/", DOC(__VA_ARGS__), &decltype(rObject::name)::ports, rRecurCb(name)}, \ | |||||
| {STRINGIFY(name) ":", rProp(internal), NULL, rRecurPtrCb(name)} | |||||
| #define rRecurp(name, ...) \ | |||||
| {STRINGIFY(name) "/", DOC(__VA_ARGS__), \ | |||||
| &decltype(spice(rObject::name))::ports, \ | |||||
| rRecurpCb(name)} | |||||
| #define rRecurs(name, length, ...) \ | |||||
| {STRINGIFY(name) "#" STRINGIFY(length)"/", DOC(__VA_ARGS__), \ | |||||
| &decltype(spice(&rObject::name[0]))::ports, \ | |||||
| rRecursCb(name, length)} | |||||
| //Technically this is a pointer pointer method... | |||||
| #define rRecursp(name, length, ...) \ | |||||
| {STRINGIFY(name)"#" STRINGIFY(length) "/", DOC(__VA_ARGS__), \ | |||||
| &decltype(spice(rObject::name[0]))::ports, \ | |||||
| rRecurspCb(name)} | |||||
| //{STRINGIFY(name) ":", rProp(internal), NULL, rRecurPtrCb(name)} | |||||
| //Misc | |||||
| #define rDummy(name, ...) {STRINIFY(name), rProp(dummy), NULL, [](msg_t, rtosc::RtData &){}} | |||||
| #define rString(name, len, ...) \ | |||||
| {STRINGIFY(name) "::s", rMap(length, len) DOC(__VA_ARGS__), NULL, rStringCb(name,len)} | |||||
| //General property operators | |||||
| #define rMap(name, value) ":" STRINGIFY(name) "\0=" STRINGIFY(value) "\0" | |||||
| #define rProp(name) ":" STRINGIFY(name) "\0" | |||||
| //Scaling property | |||||
| #define rLinear(min_, max_) rMap(min, min_) rMap(max, max_) rMap(scale, linear) | |||||
| #define rLog(min_, max_) rMap(min, min_) rMap(max, max_) rMap(scale, logarithmic) | |||||
| //Special values | |||||
| #define rSpecial(doc) ":special\0" STRINGIFY(doc) "\0" | |||||
| #define rCentered ":centered\0" | |||||
| //Misc properties | |||||
| #define rDoc(doc) ":documentation\0=" doc "\0" | |||||
| #define rOpt(numeric,symbolic) rMap(map numeric, symbolic) | |||||
| #define rOptions(...) OPTIONS(__VA_ARGS__) | |||||
| //Callback Implementations | |||||
| #define rBOIL_BEGIN [](const char *msg, rtosc::RtData &data) { \ | |||||
| (void) msg; (void) data; \ | |||||
| rObject *obj = (rObject*) data.obj;(void) obj; \ | |||||
| const char *args = rtosc_argument_string(msg); (void) args;\ | |||||
| const char *loc = data.loc; (void) loc;\ | |||||
| auto prop = data.port->meta(); (void) prop; | |||||
| #define rBOIL_END } | |||||
| #define rLIMIT(var, convert) \ | |||||
| if(prop["min"] && var < (decltype(var))convert(prop["min"])) \ | |||||
| var = convert(prop["min"]);\ | |||||
| if(prop["max"] && var > (decltype(var))convert(prop["max"])) \ | |||||
| var = convert(prop["max"]); | |||||
| #define rTYPE(n) decltype(obj->n) | |||||
| #define rAPPLY(n,t) if(obj->n != var) data.reply("undo_change", "s" #t #t, data.loc, obj->n, var); obj->n = var; | |||||
| #define rParamCb(name) rBOIL_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "c", obj->name); \ | |||||
| } else { \ | |||||
| rTYPE(name) var = rtosc_argument(msg, 0).i; \ | |||||
| rLIMIT(var, atoi) \ | |||||
| rAPPLY(name, c) \ | |||||
| data.broadcast(loc, "c", obj->name);\ | |||||
| rChangeCb \ | |||||
| } rBOIL_END | |||||
| #define rParamFCb(name) rBOIL_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "f", obj->name); \ | |||||
| } else { \ | |||||
| rTYPE(name) var = rtosc_argument(msg, 0).f; \ | |||||
| rLIMIT(var, atof) \ | |||||
| rAPPLY(name, f) \ | |||||
| data.broadcast(loc, "f", obj->name);\ | |||||
| rChangeCb \ | |||||
| } rBOIL_END | |||||
| #define rParamICb(name) rBOIL_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "i", obj->name); \ | |||||
| } else { \ | |||||
| rTYPE(name) var = rtosc_argument(msg, 0).i; \ | |||||
| rLIMIT(var, atoi) \ | |||||
| rAPPLY(name, i) \ | |||||
| data.broadcast(loc, "i", obj->name);\ | |||||
| rChangeCb \ | |||||
| } rBOIL_END | |||||
| //TODO finish me (include string mapper action?) | |||||
| #define rOptionCb(name) rBOIL_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "i", obj->name); \ | |||||
| } else { \ | |||||
| rTYPE(name) var = rtosc_argument(msg, 0).i; \ | |||||
| rLIMIT(var, atoi) \ | |||||
| rAPPLY(name, i) \ | |||||
| data.broadcast(loc, rtosc_argument_string(msg), obj->name);\ | |||||
| rChangeCb \ | |||||
| } rBOIL_END | |||||
| #define rToggleCb(name) rBOIL_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, obj->name ? "T" : "F"); \ | |||||
| } else { \ | |||||
| if(obj->name != rtosc_argument(msg, 0).T) { \ | |||||
| data.broadcast(loc, args);\ | |||||
| rChangeCb \ | |||||
| } \ | |||||
| obj->name = rtosc_argument(msg, 0).T; \ | |||||
| } rBOIL_END | |||||
| #define SNIP \ | |||||
| while(*msg && *msg!='/') ++msg; \ | |||||
| msg = *msg ? msg+1 : msg; | |||||
| #define rRecurCb(name) rBOIL_BEGIN \ | |||||
| data.obj = &obj->name; \ | |||||
| SNIP \ | |||||
| decltype(obj->name)::ports.dispatch(msg, data); \ | |||||
| rBOIL_END | |||||
| #define rRecurPtrCb(name) rBOIL_BEGIN \ | |||||
| void *ptr = &obj->name; \ | |||||
| data.reply(loc, "b", sizeof(void*), &ptr); \ | |||||
| rBOIL_END | |||||
| #define rRecurpCb(name) rBOIL_BEGIN \ | |||||
| if(obj->name == NULL) return; \ | |||||
| data.obj = obj->name; \ | |||||
| SNIP \ | |||||
| decltype(spice(rObject::name))::ports.dispatch(msg, data); \ | |||||
| rBOIL_END | |||||
| #define rRecursCb(name, length) rBOILS_BEGIN \ | |||||
| data.obj = &obj->name[idx]; \ | |||||
| SNIP \ | |||||
| decltype(spice(rObject::name))::ports.dispatch(msg, data); \ | |||||
| rBOILS_END | |||||
| #define rRecurspCb(name) rBOILS_BEGIN \ | |||||
| data.obj = obj->name[idx]; \ | |||||
| SNIP \ | |||||
| decltype(spice(rObject::name[0]))::ports.dispatch(msg, data); \ | |||||
| rBOILS_END | |||||
| #define rActionCb(name) rBOIL_BEGIN obj->name(); rBOIL_END | |||||
| #define rActioniCb(name) rBOIL_BEGIN \ | |||||
| obj->name(rtosc_argument(msg,0).i); rBOIL_END | |||||
| //Array ops | |||||
| #define rBOILS_BEGIN rBOIL_BEGIN \ | |||||
| const char *mm = msg; \ | |||||
| while(*mm && !isdigit(*mm)) ++mm; \ | |||||
| unsigned idx = atoi(mm); | |||||
| #define rBOILS_END rBOIL_END | |||||
| #define rArrayCb(name) rBOILS_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "c", obj->name[idx]); \ | |||||
| } else { \ | |||||
| char var = rtosc_argument(msg, 0).i; \ | |||||
| rLIMIT(var, atoi) \ | |||||
| rAPPLY(name[idx], c) \ | |||||
| data.broadcast(loc, "c", obj->name[idx]);\ | |||||
| rChangeCb \ | |||||
| } rBOILS_END | |||||
| #define rArrayFCb(name) rBOILS_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "f", obj->name[idx]); \ | |||||
| } else { \ | |||||
| float var = rtosc_argument(msg, 0).f; \ | |||||
| rLIMIT(var, atof) \ | |||||
| rAPPLY(name[idx], f) \ | |||||
| data.broadcast(loc, "f", obj->name[idx]);\ | |||||
| } rBOILS_END | |||||
| #define rArrayTCb(name) rBOILS_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, obj->name[idx] ? "T" : "F"); \ | |||||
| } else { \ | |||||
| if(obj->name[idx] != rtosc_argument(msg, 0).T) { \ | |||||
| data.broadcast(loc, args);\ | |||||
| rChangeCb \ | |||||
| } \ | |||||
| obj->name[idx] = rtosc_argument(msg, 0).T; \ | |||||
| } rBOILS_END | |||||
| #define rArrayICb(name) rBOILS_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "i", obj->name[idx]); \ | |||||
| } else { \ | |||||
| char var = rtosc_argument(msg, 0).i; \ | |||||
| rLIMIT(var, atoi) \ | |||||
| rAPPLY(name[idx], i) \ | |||||
| data.broadcast(loc, "i", obj->name[idx]);\ | |||||
| rChangeCb \ | |||||
| } rBOILS_END | |||||
| #define rParamsCb(name, length) rBOIL_BEGIN \ | |||||
| data.reply(loc, "b", length, obj->name); rBOIL_END | |||||
| #define rStringCb(name, length) rBOIL_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "s", obj->name); \ | |||||
| } else { \ | |||||
| strncpy(obj->name, rtosc_argument(msg, 0).s, length); \ | |||||
| data.broadcast(loc, "s", obj->name);\ | |||||
| rChangeCb \ | |||||
| } rBOIL_END | |||||
| #endif | |||||
| @@ -0,0 +1,219 @@ | |||||
| /* | |||||
| * Copyright (c) 2012 Mark McCurry | |||||
| * | |||||
| * Permission is hereby granted, free of charge, to any person obtaining a | |||||
| * copy of this software and associated documentation files (the "Software"), | |||||
| * to deal in the Software without restriction, including without limitation | |||||
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||||
| * and/or sell copies of the Software, and to permit persons to whom the | |||||
| * Software is furnished to do so, subject to the following conditions: | |||||
| * | |||||
| * The above copyright notice and this permission notice (including the next | |||||
| * paragraph) shall be included in all copies or substantial portions of the | |||||
| * Software. | |||||
| * | |||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |||||
| * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
| * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |||||
| * DEALINGS IN THE SOFTWARE. | |||||
| */ | |||||
| #ifndef RTOSC_PORTS | |||||
| #define RTOSC_PORTS | |||||
| #include <vector> | |||||
| #include <functional> | |||||
| #include <initializer_list> | |||||
| #include "rtosc.h" | |||||
| #include <cstring> | |||||
| #include <cctype> | |||||
| #include <cstdlib> | |||||
| #include <cstdio> | |||||
| #include <string> | |||||
| namespace rtosc { | |||||
| //First define all types | |||||
| typedef const char *msg_t; | |||||
| struct Port; | |||||
| struct Ports; | |||||
| struct RtData | |||||
| { | |||||
| RtData(void); | |||||
| char *loc; | |||||
| size_t loc_size; | |||||
| void *obj; | |||||
| int matches; | |||||
| const Port *port; | |||||
| virtual void reply(const char *path, const char *args, ...); | |||||
| virtual void reply(const char *msg); | |||||
| virtual void broadcast(const char *path, const char *args, ...); | |||||
| virtual void broadcast(const char *msg); | |||||
| }; | |||||
| /** | |||||
| * Port in rtosc dispatching hierarchy | |||||
| */ | |||||
| struct Port { | |||||
| const char *name; //< Pattern for messages to match | |||||
| const char *metadata;//< Statically accessable data about port | |||||
| const Ports *ports; //< Pointer to further ports | |||||
| std::function<void(msg_t, RtData&)> cb;//< Callback for matching functions | |||||
| class MetaIterator | |||||
| { | |||||
| public: | |||||
| MetaIterator(const char *str); | |||||
| //A bit odd to return yourself, but it seems to work for this | |||||
| //context | |||||
| const MetaIterator& operator*(void) const {return *this;} | |||||
| const MetaIterator* operator->(void) const {return this;} | |||||
| bool operator==(MetaIterator a) {return title == a.title;} | |||||
| bool operator!=(MetaIterator a) {return title != a.title;} | |||||
| MetaIterator& operator++(void); | |||||
| const char *title; | |||||
| const char *value; | |||||
| }; | |||||
| class MetaContainer | |||||
| { | |||||
| public: | |||||
| MetaContainer(const char *str_); | |||||
| MetaIterator begin(void) const; | |||||
| MetaIterator end(void) const; | |||||
| MetaIterator find(const char *str) const; | |||||
| size_t length(void) const; | |||||
| const char *operator[](const char *str) const; | |||||
| const char *str_ptr; | |||||
| }; | |||||
| MetaContainer meta(void) const | |||||
| { | |||||
| if(metadata && *metadata == ':') | |||||
| return MetaContainer(metadata+1); | |||||
| else | |||||
| return MetaContainer(metadata); | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * Ports - a dispatchable collection of Port entries | |||||
| * | |||||
| * This structure makes it somewhat easier to perform actions on collections of | |||||
| * port entries and it is responsible for the dispatching of OSC messages to | |||||
| * their respective ports. | |||||
| * That said, it is a very simple structure, which uses a stl container to store | |||||
| * all data in a simple dispatch table. | |||||
| * All methods post-initialization are RT safe (assuming callbacks are RT safe) | |||||
| */ | |||||
| struct Ports | |||||
| { | |||||
| std::vector<Port> ports; | |||||
| typedef std::vector<Port>::const_iterator itr_t; | |||||
| /**Forwards to builtin container*/ | |||||
| itr_t begin() const {return ports.begin();} | |||||
| /**Forwards to builtin container*/ | |||||
| itr_t end() const {return ports.end();} | |||||
| /**Forwards to builtin container*/ | |||||
| size_t size() const {return ports.size();} | |||||
| /**Forwards to builtin container*/ | |||||
| const Port &operator[](unsigned i) const {return ports[i];} | |||||
| Ports(std::initializer_list<Port> l); | |||||
| ~Ports(void); | |||||
| Ports(const Ports&) = delete; | |||||
| /** | |||||
| * Dispatches message to all matching ports. | |||||
| * This uses simple pattern matching available in rtosc::match. | |||||
| * | |||||
| * @param m a valid OSC message | |||||
| * @param d The RtData object shall contain a path buffer (or null), the length of | |||||
| * the buffer, a pointer to data. | |||||
| */ | |||||
| void dispatch(const char *m, RtData &d) const; | |||||
| /** | |||||
| * Retrieve local port by name | |||||
| * TODO implement full matching | |||||
| */ | |||||
| const Port *operator[](const char *name) const; | |||||
| /** | |||||
| * Find the best match for a given path | |||||
| * | |||||
| * @parameter path partial OSC path | |||||
| * @returns first path prefixed by the argument | |||||
| * | |||||
| * Example usage: | |||||
| * @code | |||||
| * Ports p = {{"foo",0,0,dummy_method}, | |||||
| * {"flam",0,0,dummy_method}, | |||||
| * {"bar",0,0,dummy_method}}; | |||||
| * p.apropos("/b")->name;//bar | |||||
| * p.apropos("/f")->name;//foo | |||||
| * p.apropos("/fl")->name;//flam | |||||
| * p.apropos("/gg");//NULL | |||||
| * @endcode | |||||
| */ | |||||
| const Port *apropos(const char *path) const; | |||||
| private: | |||||
| //Performance hacks | |||||
| class Port_Matcher *impl; | |||||
| unsigned elms; | |||||
| }; | |||||
| /********************* | |||||
| * Port walking code * | |||||
| *********************/ | |||||
| //typedef std::function<void(const Port*,const char*)> port_walker_t; | |||||
| typedef void(*port_walker_t)(const Port*,const char*,void*); | |||||
| void walk_ports(const Ports *base, | |||||
| char *name_buffer, | |||||
| size_t buffer_size, | |||||
| void *data, | |||||
| port_walker_t walker); | |||||
| /********************* | |||||
| * Port Dumping code * | |||||
| *********************/ | |||||
| struct OscDocFormatter | |||||
| { | |||||
| const Ports *p; | |||||
| std::string prog_name; | |||||
| std::string uri; | |||||
| std::string doc_origin; | |||||
| std::string author_first; | |||||
| std::string author_last; | |||||
| //TODO extend this some more | |||||
| }; | |||||
| std::ostream &operator<<(std::ostream &o, OscDocFormatter &formatter); | |||||
| }; | |||||
| #endif | |||||
| @@ -0,0 +1,238 @@ | |||||
| /* | |||||
| * Copyright (c) 2012 Mark McCurry | |||||
| * | |||||
| * Permission is hereby granted, free of charge, to any person obtaining a | |||||
| * copy of this software and associated documentation files (the "Software"), | |||||
| * to deal in the Software without restriction, including without limitation | |||||
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||||
| * and/or sell copies of the Software, and to permit persons to whom the | |||||
| * Software is furnished to do so, subject to the following conditions: | |||||
| * | |||||
| * The above copyright notice and this permission notice (including the next | |||||
| * paragraph) shall be included in all copies or substantial portions of the | |||||
| * Software. | |||||
| * | |||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |||||
| * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
| * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |||||
| * DEALINGS IN THE SOFTWARE. | |||||
| * | |||||
| * @file rtosc.h | |||||
| */ | |||||
| #ifndef RTOSC_H | |||||
| #define RTOSC_H | |||||
| #include <stdarg.h> | |||||
| #include <stdint.h> | |||||
| #include <stddef.h> | |||||
| #include <stdbool.h> | |||||
| #ifdef __cplusplus | |||||
| extern "C" { | |||||
| #endif | |||||
| typedef struct { | |||||
| int32_t len; | |||||
| uint8_t *data; | |||||
| } rtosc_blob_t; | |||||
| typedef union { | |||||
| int32_t i; //i,c,r | |||||
| char T; //I,T,F,N | |||||
| float f; //f | |||||
| double d; //d | |||||
| int64_t h; //h | |||||
| uint64_t t; //t | |||||
| uint8_t m[4];//m | |||||
| const char *s; //s,S | |||||
| rtosc_blob_t b; //b | |||||
| } rtosc_arg_t; | |||||
| /** | |||||
| * Write OSC message to fixed length buffer | |||||
| * | |||||
| * On error, buffer will be zeroed. | |||||
| * When buffer is NULL, the function returns the size of the buffer required to | |||||
| * store the message | |||||
| * | |||||
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||||
| * //Example messages | |||||
| * char buffer[128]; | |||||
| * rtosc_message(buffer,128,"/path","TFI"); | |||||
| * rtosc_message(buffer,128,"/path","s","foobar"); | |||||
| * rtosc_message(buffer,128,"/path","i",128); | |||||
| * rtosc_message(buffer,128,"/path","f",128.0); | |||||
| * const char blob[4] = {'a','b','c','d'}; | |||||
| * rtosc_message(buffer,128,"/path","b",4,blob); | |||||
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||||
| * | |||||
| * @param buffer Memory to write to | |||||
| * @param len Length of buffer | |||||
| * @param address OSC pattern to send message to | |||||
| * @param arguments String consisting of the types of the following arguments | |||||
| * @param ... OSC arguments to pass forward | |||||
| * @returns length of resulting message or zero if bounds exceeded | |||||
| */ | |||||
| size_t rtosc_message(char *buffer, | |||||
| size_t len, | |||||
| const char *address, | |||||
| const char *arguments, | |||||
| ...); | |||||
| /** | |||||
| * @see rtosc_message() | |||||
| */ | |||||
| size_t rtosc_vmessage(char *buffer, | |||||
| size_t len, | |||||
| const char *address, | |||||
| const char *arguments, | |||||
| va_list va); | |||||
| /** | |||||
| * @see rtosc_message() | |||||
| */ | |||||
| size_t rtosc_amessage(char *buffer, | |||||
| size_t len, | |||||
| const char *address, | |||||
| const char *arguments, | |||||
| const rtosc_arg_t *args); | |||||
| /** | |||||
| * Returns the number of arguments found in a given message | |||||
| * | |||||
| * @param msg well formed OSC message | |||||
| * @returns number of arguments in message | |||||
| */ | |||||
| unsigned rtosc_narguments(const char *msg); | |||||
| /** | |||||
| * @param msg well formed OSC message | |||||
| * @param i index of argument | |||||
| * @returns the type of the ith argument in msg | |||||
| */ | |||||
| char rtosc_type(const char *msg, unsigned i); | |||||
| /** | |||||
| * Blob data may be safely written to | |||||
| * @param msg OSC message | |||||
| * @param i index of argument | |||||
| * @returns an argument by value via the rtosc_arg_t union | |||||
| */ | |||||
| rtosc_arg_t rtosc_argument(const char *msg, unsigned i); | |||||
| /** | |||||
| * @param msg OSC message | |||||
| * @param len Message length upper bound | |||||
| * @returns the size of a message given a chunk of memory. | |||||
| */ | |||||
| size_t rtosc_message_length(const char *msg, size_t len); | |||||
| typedef struct { | |||||
| char *data; | |||||
| size_t len; | |||||
| } ring_t; | |||||
| /** | |||||
| * Finds the length of the next message inside a ringbuffer structure. | |||||
| * | |||||
| * @param ring The addresses and lengths of the split buffer, in a compatible | |||||
| * format to jack's ringbuffer | |||||
| * @returns size of message stored in ring datastructure | |||||
| */ | |||||
| size_t rtosc_message_ring_length(ring_t *ring); | |||||
| /** | |||||
| * Validate if an arbitrary byte sequence is an OSC message. | |||||
| * @param msg pointer to memory buffer | |||||
| * @param len length of buffer | |||||
| */ | |||||
| bool rtosc_valid_message_p(const char *msg, size_t len); | |||||
| /** | |||||
| * @param OSC message | |||||
| * @returns the argument string of a given message | |||||
| */ | |||||
| const char *rtosc_argument_string(const char *msg); | |||||
| /** | |||||
| * Generate a bundle from sub-messages | |||||
| * | |||||
| * @param buffer Destination buffer | |||||
| * @param len Length of buffer | |||||
| * @param tt OSC time tag | |||||
| * @param elms Number of sub messages | |||||
| * @param ... Messages | |||||
| * @returns legnth of generated bundle or zero on failure | |||||
| */ | |||||
| size_t rtosc_bundle(char *buffer, size_t len, uint64_t tt, int elms, ...); | |||||
| /** | |||||
| * Find the elements in a bundle | |||||
| * | |||||
| * @param msg OSC bundle | |||||
| * @param len Upper bound on the length of the bundle | |||||
| * @returns The number of messages contained within the bundle | |||||
| */ | |||||
| size_t rtosc_bundle_elements(const char *msg, size_t len); | |||||
| /** | |||||
| * Fetch a message within the bundle | |||||
| * | |||||
| * @param msg OSC bundle | |||||
| * @param i index of sub-message | |||||
| * @returns The ith message within the bundle | |||||
| */ | |||||
| const char *rtosc_bundle_fetch(const char *msg, unsigned i); | |||||
| /** | |||||
| * Get the size of a particular bundle element | |||||
| * | |||||
| * @param msg OSC bundle | |||||
| * @param i Index of sub-message | |||||
| * @returns The size of the ith sub-message in bytes | |||||
| */ | |||||
| size_t rtosc_bundle_size(const char *msg, unsigned i); | |||||
| /** | |||||
| * Test if the buffer contains a bundle | |||||
| * | |||||
| * @param msg OSC message | |||||
| * @returns true if message is a bundle | |||||
| */ | |||||
| int rtosc_bundle_p(const char *msg); | |||||
| /** | |||||
| * @returns Time Tag for a bundle | |||||
| */ | |||||
| uint64_t rtosc_bundle_timetag(const char *msg); | |||||
| /** | |||||
| * This is a non-compliant pattern matcher for dispatching OSC messages | |||||
| * | |||||
| * Overall the pattern specification is | |||||
| * (normal-path)(\#digit-specifier)?(/)?(:argument-restrictor)* | |||||
| * | |||||
| * @param pattern The pattern string stored in the Port | |||||
| * @param msg The OSC message to be matched | |||||
| * @returns true if a normal match and false if unmatched | |||||
| */ | |||||
| bool rtosc_match(const char *pattern, const char *msg); | |||||
| /** | |||||
| * Attempt to match a rtosc style path while ignoring arguments | |||||
| * | |||||
| * @param pattern rtosc pattern | |||||
| * @param msg a normal C string or a rtosc message | |||||
| */ | |||||
| const char *rtosc_match_path(const char *pattern, const char *msg); | |||||
| #ifdef __cplusplus | |||||
| }; | |||||
| #endif | |||||
| #endif | |||||
| @@ -0,0 +1,533 @@ | |||||
| #include "ports.h" | |||||
| #include <cstring> | |||||
| #include <algorithm> | |||||
| #include <map> | |||||
| #include <sstream> | |||||
| #include <deque> | |||||
| #include <utility> | |||||
| #include <cassert> | |||||
| #include "miditable.h" | |||||
| using namespace rtosc; | |||||
| using std::string; | |||||
| using std::get; | |||||
| using std::tuple; | |||||
| using std::make_tuple; | |||||
| /******************** | |||||
| * Helper Templates * | |||||
| ********************/ | |||||
| template<class T, class U> | |||||
| bool has_t(const T &t, const U&u) | |||||
| {return t.find(u) != t.end();} | |||||
| template<class T, class U> | |||||
| bool has2(const T &t, const U&u) { | |||||
| for(const U&uu:t) | |||||
| if(uu==u) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| template<class T,class U> | |||||
| int getInd(const T&t,const U&u){ | |||||
| int i=0; | |||||
| for(const U&uu:t) { | |||||
| if(uu==u) | |||||
| return i; | |||||
| else | |||||
| i++; | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| /*********** | |||||
| * Storage * | |||||
| ***********/ | |||||
| bool MidiMapperStorage::handleCC(int ID, int val, write_cb write) | |||||
| { | |||||
| for(int i=0; i<mapping.size(); ++i) | |||||
| { | |||||
| if(std::get<0>(mapping[i]) == ID) | |||||
| { | |||||
| bool coarse = std::get<1>(mapping[i]); | |||||
| int ind = std::get<2>(mapping[i]); | |||||
| if(coarse) | |||||
| values[ind] = (val<<7)|(values[ind]&0x7f); | |||||
| else | |||||
| values[ind] = val|(values[ind]&0x3f80); | |||||
| callbacks[ind](values[ind],write); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| //TODO try to change O(n^2) algorithm to O(n) | |||||
| void MidiMapperStorage::cloneValues(const MidiMapperStorage &storage) | |||||
| { | |||||
| //XXX this method is SUPER error prone | |||||
| for(int i=0; i<values.size(); ++i) | |||||
| values[i] = 0; | |||||
| for(int i=0; i<mapping.size(); ++i) { | |||||
| for(int j=0; j<storage.mapping.size(); ++j) { | |||||
| if(std::get<0>(mapping[i]) == std::get<0>(storage.mapping[j])) { | |||||
| bool coarse_src = std::get<1>(storage.mapping[j]); | |||||
| int ind_src = std::get<2>(storage.mapping[j]); | |||||
| bool coarse_dest = std::get<1>(mapping[i]); | |||||
| int ind_dest = std::get<2>(mapping[i]); | |||||
| int val = 0; | |||||
| //Extract | |||||
| if(coarse_src) | |||||
| val = storage.values[ind_src]>>7; | |||||
| else | |||||
| val = storage.values[ind_src]&0x7f; | |||||
| //Blit | |||||
| if(coarse_dest) | |||||
| values[ind_dest] = (val<<7)|(values[ind_dest]&0x7f); | |||||
| else | |||||
| values[ind_dest] = val|(values[ind_dest]&0x3f80); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| MidiMapperStorage *MidiMapperStorage::clone(void) | |||||
| { | |||||
| MidiMapperStorage *nstorage = new MidiMapperStorage(); | |||||
| nstorage->values = values.sized_clone(); | |||||
| nstorage->mapping = mapping.clone(); | |||||
| nstorage->callbacks = callbacks.clone(); | |||||
| return nstorage; | |||||
| } | |||||
| int MidiBijection::operator()(float x) const { | |||||
| if(mode == 0) | |||||
| return ((x-min)/(max-min))*(1<<14); | |||||
| else | |||||
| return 0; | |||||
| } | |||||
| float MidiBijection::operator()(int x) const { | |||||
| if(mode == 0) | |||||
| return x/((1<<14)*1.0)*(max-min)+min; | |||||
| else | |||||
| return 0; | |||||
| } | |||||
| /************************ | |||||
| * Non realtime portion * | |||||
| ************************/ | |||||
| MidiMappernRT::MidiMappernRT(void) | |||||
| :storage(0),base_ports(0) | |||||
| {} | |||||
| void MidiMappernRT::map(const char *addr, bool coarse) | |||||
| { | |||||
| for(auto x:learnQueue) | |||||
| if(x.first == addr && x.second == coarse) | |||||
| return; | |||||
| unMap(addr, coarse); | |||||
| learnQueue.push_back(std::make_pair(addr,coarse)); | |||||
| char buf[1024]; | |||||
| rtosc_message(buf, 1024, "/midi-add-watch",""); | |||||
| rt_cb(buf); | |||||
| } | |||||
| MidiMapperStorage *MidiMappernRT::generateNewBijection(const Port &port, std::string addr) | |||||
| { | |||||
| MidiBijection bi; | |||||
| bi.mode = 0; | |||||
| bi.min = atof(port.meta()["min"]); | |||||
| bi.max = atof(port.meta()["max"]); | |||||
| auto tmp = [bi,addr](int16_t x, MidiMapperStorage::write_cb cb) { | |||||
| float out = bi(x); | |||||
| char buf[1024]; | |||||
| rtosc_message(buf, 1024, addr.c_str(), "f", out); | |||||
| cb(buf); | |||||
| }; | |||||
| MidiMapperStorage *nstorage = new MidiMapperStorage(); | |||||
| if(storage) { | |||||
| //XXX not quite | |||||
| nstorage->values = storage->values.one_larger(); | |||||
| nstorage->mapping = storage->mapping.clone();//insert(std::make_tuple(ID, true, storage->callbacks.size())); | |||||
| nstorage->callbacks = storage->callbacks.insert(tmp); | |||||
| } else { | |||||
| nstorage->values = nstorage->values.insert(0); | |||||
| nstorage->mapping = nstorage->mapping.clone();//insert(std::make_tuple(ID, true, 0)); | |||||
| nstorage->callbacks = nstorage->callbacks.insert(tmp); | |||||
| } | |||||
| inv_map[addr] = std::make_tuple(nstorage->callbacks.size()-1, -1,-1,bi); | |||||
| return nstorage; | |||||
| } | |||||
| void MidiMappernRT::addNewMapper(int ID, const Port &port, std::string addr) | |||||
| { | |||||
| MidiBijection bi; | |||||
| bi.mode = 0; | |||||
| bi.min = atof(port.meta()["min"]); | |||||
| bi.max = atof(port.meta()["max"]); | |||||
| auto tmp = [bi,addr](int16_t x, MidiMapperStorage::write_cb cb) { | |||||
| float out = bi(x); | |||||
| char buf[1024]; | |||||
| rtosc_message(buf, 1024, addr.c_str(), "f", out); | |||||
| cb(buf); | |||||
| }; | |||||
| MidiMapperStorage *nstorage = new MidiMapperStorage(); | |||||
| if(storage) { | |||||
| //XXX not quite | |||||
| nstorage->values = storage->values.one_larger(); | |||||
| nstorage->mapping = storage->mapping.insert(std::make_tuple(ID, true, storage->callbacks.size())); | |||||
| nstorage->callbacks = storage->callbacks.insert(tmp); | |||||
| } else { | |||||
| nstorage->values = nstorage->values.insert(0); | |||||
| nstorage->mapping = nstorage->mapping.insert(std::make_tuple(ID, true, 0)); | |||||
| nstorage->callbacks = nstorage->callbacks.insert(tmp); | |||||
| } | |||||
| storage = nstorage; | |||||
| inv_map[addr] = std::make_tuple(storage->callbacks.size()-1, ID,-1,bi); | |||||
| } | |||||
| void MidiMappernRT::addFineMapper(int ID, const Port &port, std::string addr) | |||||
| { | |||||
| (void) port; | |||||
| //TODO asserts | |||||
| //Bijection already created | |||||
| //Coarse node already active | |||||
| //Value already allocated | |||||
| //Find mapping | |||||
| int mapped_ID = std::get<0>(inv_map[addr]); | |||||
| std::get<2>(inv_map[addr]) = ID; | |||||
| MidiMapperStorage *nstorage = new MidiMapperStorage(); | |||||
| nstorage->values = storage->values.sized_clone(); | |||||
| nstorage->mapping = storage->mapping.insert(std::make_tuple(ID, false, mapped_ID)); | |||||
| nstorage->callbacks = storage->callbacks.insert(storage->callbacks[mapped_ID]); | |||||
| storage = nstorage; | |||||
| } | |||||
| void killMap(int ID, MidiMapperStorage &m) | |||||
| { | |||||
| MidiMapperStorage::TinyVector<tuple<int, bool, int>> nmapping(m.mapping.size()-1); | |||||
| int j=0; | |||||
| for(int i=0; i<m.mapping.size(); i++) | |||||
| if(get<0>(m.mapping[i]) != ID) | |||||
| nmapping[j++] = m.mapping[i]; | |||||
| assert(j == nmapping.size()); | |||||
| m.mapping = nmapping; | |||||
| } | |||||
| void MidiMappernRT::useFreeID(int ID) | |||||
| { | |||||
| if(learnQueue.empty()) | |||||
| return; | |||||
| std::string addr = std::get<0>(learnQueue.front()); | |||||
| bool coarse = std::get<1>(learnQueue.front()); | |||||
| learnQueue.pop_front(); | |||||
| assert(base_ports); | |||||
| const rtosc::Port *p = base_ports->apropos(addr.c_str()); | |||||
| assert(p); | |||||
| MidiMapperStorage *nstorage; | |||||
| if(inv_map.find(addr) == inv_map.end()) | |||||
| nstorage = generateNewBijection(*p, addr); | |||||
| else | |||||
| nstorage = storage->clone(); | |||||
| auto imap = inv_map[addr]; | |||||
| int mapped_ID = std::get<0>(imap); | |||||
| nstorage->mapping = nstorage->mapping.insert(make_tuple(ID, coarse, mapped_ID)); | |||||
| if(coarse) { | |||||
| if(get<1>(imap) != -1) | |||||
| killMap(get<1>(imap), *nstorage); | |||||
| inv_map[addr] = make_tuple(get<0>(imap), ID, get<2>(imap), get<3>(imap)); | |||||
| } else { | |||||
| if(get<2>(imap) != -1) | |||||
| killMap(get<1>(imap), *nstorage); | |||||
| inv_map[addr] = make_tuple(get<0>(imap), get<1>(imap), ID, get<3>(imap)); | |||||
| } | |||||
| storage = nstorage; | |||||
| //TODO clean up unused value and callback objects | |||||
| char buf[1024]; | |||||
| rtosc_message(buf, 1024, "/midi-bind", "b", sizeof(storage), &storage); | |||||
| rt_cb(buf); | |||||
| }; | |||||
| void MidiMappernRT::unMap(const char *addr, bool coarse) | |||||
| { | |||||
| printf("Unmapping('%s',%d)\n",addr,coarse); | |||||
| if(inv_map.find(addr) == inv_map.end()) | |||||
| return; | |||||
| auto imap = inv_map[addr]; | |||||
| int kill_id = -1; | |||||
| if(coarse) { | |||||
| kill_id = get<1>(imap); | |||||
| inv_map[addr] = make_tuple(get<0>(imap), -1, get<2>(imap), get<3>(imap)); | |||||
| } else { | |||||
| kill_id = get<2>(imap); | |||||
| inv_map[addr] = make_tuple(get<0>(imap), get<1>(imap), -1, get<3>(imap)); | |||||
| } | |||||
| if(kill_id == -1) | |||||
| return; | |||||
| MidiMapperStorage *nstorage = storage->clone(); | |||||
| killMap(kill_id, *nstorage); | |||||
| storage = nstorage; | |||||
| //TODO clean up unused value and callback objects | |||||
| char buf[1024]; | |||||
| rtosc_message(buf, 1024, "/midi-bind", "b", sizeof(storage), &storage); | |||||
| rt_cb(buf); | |||||
| } | |||||
| void MidiMappernRT::delMapping(int ID, bool coarse, const char *addr){ | |||||
| (void) ID; | |||||
| (void) coarse; | |||||
| (void) addr; | |||||
| }; | |||||
| void MidiMappernRT::replaceMapping(int, bool, const char *){}; | |||||
| std::map<std::string, std::string> MidiMappernRT::getMidiMappingStrings(void) | |||||
| { | |||||
| std::map<std::string, std::string> result; | |||||
| for(auto s:inv_map) | |||||
| result[s.first] = getMappedString(s.first); | |||||
| char ID = 'A'; | |||||
| for(auto s:learnQueue) | |||||
| { | |||||
| if(s.second == false) | |||||
| result[s.first] += std::string(":")+ID++; | |||||
| else | |||||
| result[s.first] = ID++; | |||||
| } | |||||
| return result; | |||||
| } | |||||
| //unclear if this should be be here as a helper or not | |||||
| std::string MidiMappernRT::getMappedString(std::string addr) | |||||
| { | |||||
| std::stringstream out; | |||||
| //find coarse | |||||
| if(has_t(inv_map,addr)) { | |||||
| if(std::get<1>(inv_map[addr]) != -1) | |||||
| out << std::get<1>(inv_map[addr]); | |||||
| }else if(has2(learnQueue, make_pair(addr,true))) | |||||
| out << getInd(learnQueue,std::make_pair(addr,true)); | |||||
| //find Fine | |||||
| if(has_t(inv_map,addr)) { | |||||
| if(std::get<2>(inv_map[addr]) != -1) | |||||
| out << ":" << std::get<2>(inv_map[addr]); | |||||
| } else if(has2(learnQueue, make_pair(addr,false))) | |||||
| out << getInd(learnQueue,std::make_pair(addr,false)); | |||||
| return out.str(); | |||||
| } | |||||
| MidiBijection MidiMappernRT::getBijection(std::string s) | |||||
| { | |||||
| return std::get<3>(inv_map[s]); | |||||
| } | |||||
| void MidiMappernRT::snoop(const char *msg) | |||||
| { | |||||
| if(inv_map.find(msg) != inv_map.end()) | |||||
| { | |||||
| auto apple = inv_map[msg]; | |||||
| MidiBijection bi = getBijection(msg); | |||||
| float value = 0; | |||||
| std::string args = rtosc_argument_string(msg); | |||||
| if(args == "f") | |||||
| value = rtosc_argument(msg, 0).f; | |||||
| else if(args == "i") | |||||
| value = rtosc_argument(msg, 0).i; | |||||
| else if(args == "T") | |||||
| value = 1.0; | |||||
| else if(args == "F") | |||||
| value = 0.0; | |||||
| else | |||||
| return; | |||||
| int new_midi = bi(value); | |||||
| //printf("--------------------------------------------\n"); | |||||
| //printf("msg = '%s'\n", msg); | |||||
| //printf("--------------------------------------------\n"); | |||||
| //printf("new midi value: %f->'%x'\n", value, new_midi); | |||||
| if(std::get<1>(apple) != -1) | |||||
| apply_high(new_midi,std::get<1>(apple)); | |||||
| if(std::get<2>(apple) != -1) | |||||
| apply_low(new_midi,std::get<2>(apple)); | |||||
| } | |||||
| }; | |||||
| void MidiMappernRT::apply_high(int v, int ID) { apply_midi(v>>7,ID); } | |||||
| void MidiMappernRT::apply_low(int v, int ID) { apply_midi(0x7f&v,ID);} | |||||
| void MidiMappernRT::apply_midi(int val, int ID) | |||||
| { | |||||
| char buf[1024]; | |||||
| rtosc_message(buf,1024,"/virtual_midi_cc","ii",val,ID); | |||||
| rt_cb(buf); | |||||
| } | |||||
| void MidiMappernRT::setBounds(const char *str, float low, float high) | |||||
| { | |||||
| if(inv_map.find(str) == inv_map.end()) | |||||
| return; | |||||
| string addr = str; | |||||
| auto imap = inv_map[str]; | |||||
| auto newBi = MidiBijection{0,low,high}; | |||||
| inv_map[str] = make_tuple(get<0>(imap),get<1>(imap),get<2>(imap),newBi); | |||||
| MidiMapperStorage *nstorage = storage->clone(); | |||||
| nstorage->callbacks[get<0>(imap)] = [newBi,addr](int16_t x, MidiMapperStorage::write_cb cb) { | |||||
| float out = newBi(x); | |||||
| char buf[1024]; | |||||
| rtosc_message(buf, 1024, addr.c_str(), "f", out); | |||||
| cb(buf); | |||||
| }; | |||||
| storage = nstorage; | |||||
| char buf[1024]; | |||||
| rtosc_message(buf, 1024, "/midi-bind", "b", sizeof(storage), &storage); | |||||
| rt_cb(buf); | |||||
| } | |||||
| std::tuple<float,float,float,float> MidiMappernRT::getBounds(const char *str) | |||||
| { | |||||
| const rtosc::Port *p = base_ports->apropos(str); | |||||
| assert(p); | |||||
| float min_val = atof(p->meta()["min"]); | |||||
| float max_val = atof(p->meta()["max"]); | |||||
| if(inv_map.find(str) != inv_map.end()) { | |||||
| auto elm = std::get<3>(inv_map[str]); | |||||
| return std::make_tuple(min_val, max_val,elm.min,elm.max); | |||||
| } | |||||
| return std::make_tuple(min_val, max_val,-1.0f,-1.0f); | |||||
| } | |||||
| bool MidiMappernRT::has(std::string addr) | |||||
| { | |||||
| return inv_map.find(addr) != inv_map.end(); | |||||
| } | |||||
| bool MidiMappernRT::hasPending(std::string addr) | |||||
| { | |||||
| for(auto s:learnQueue) | |||||
| if(s.first == addr) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| bool MidiMappernRT::hasCoarse(std::string addr) | |||||
| { | |||||
| if(!has(addr)) | |||||
| return false; | |||||
| auto e = inv_map[addr]; | |||||
| return std::get<1>(e) != -1; | |||||
| } | |||||
| bool MidiMappernRT::hasFine(std::string addr) | |||||
| { | |||||
| if(!has(addr)) | |||||
| return false; | |||||
| auto e = inv_map[addr]; | |||||
| return std::get<2>(e) != -1; | |||||
| } | |||||
| bool MidiMappernRT::hasCoarsePending(std::string addr) | |||||
| { | |||||
| for(auto s:learnQueue) | |||||
| if(s.first == addr && s.second) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| bool MidiMappernRT::hasFinePending(std::string addr) | |||||
| { | |||||
| for(auto s:learnQueue) | |||||
| if(s.first == addr && !s.second) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| int MidiMappernRT::getCoarse(std::string addr) | |||||
| { | |||||
| if(!has(addr)) | |||||
| return -1; | |||||
| auto e = inv_map[addr]; | |||||
| return std::get<1>(e); | |||||
| } | |||||
| int MidiMappernRT::getFine(std::string addr) | |||||
| { | |||||
| if(!has(addr)) | |||||
| return -1; | |||||
| auto e = inv_map[addr]; | |||||
| return std::get<2>(e); | |||||
| } | |||||
| /***************** | |||||
| * Realtime code * | |||||
| *****************/ | |||||
| MidiMapperRT::MidiMapperRT(void) | |||||
| :storage(NULL), watchSize(0) | |||||
| {} | |||||
| void MidiMapperRT::setBackendCb(std::function<void(const char*)> cb) {backend = cb;} | |||||
| void MidiMapperRT::setFrontendCb(std::function<void(const char*)> cb) {frontend = cb;} | |||||
| void MidiMapperRT::handleCC(int ID, int val) { | |||||
| if((!storage || !storage->handleCC(ID, val, backend)) && !pending.has(ID) && watchSize) { | |||||
| watchSize--; | |||||
| pending.insert(ID); | |||||
| char msg[1024]; | |||||
| rtosc_message(msg, 1024, "/midi-use-CC", "i", ID); | |||||
| frontend(msg); | |||||
| } | |||||
| } | |||||
| void MidiMapperRT::addWatch(void) {watchSize++;} | |||||
| void MidiMapperRT::remWatch(void) {if(watchSize) watchSize--;} | |||||
| Port MidiMapperRT::addWatchPort(void) { | |||||
| return Port{"midi-add-watch","",0, [this](msg_t, RtData&) { | |||||
| this->addWatch(); | |||||
| }}; | |||||
| } | |||||
| Port MidiMapperRT::removeWatchPort(void) { | |||||
| return Port{"midi-remove-watch","",0, [this](msg_t, RtData&) { | |||||
| this->remWatch(); | |||||
| }}; | |||||
| } | |||||
| Port MidiMapperRT::bindPort(void) { | |||||
| return Port{"midi-bind:b","",0, [this](msg_t msg, RtData&) { | |||||
| pending.pop(); | |||||
| MidiMapperStorage *nstorage = | |||||
| *(MidiMapperStorage**)rtosc_argument(msg,0).b.data; | |||||
| if(storage) { | |||||
| nstorage->cloneValues(*storage); | |||||
| storage = nstorage; | |||||
| } else | |||||
| storage = nstorage; | |||||
| //TODO memory deallocation | |||||
| }}; | |||||
| } | |||||
| @@ -0,0 +1,267 @@ | |||||
| #include <math.h> | |||||
| #include "miditable.h" | |||||
| using namespace rtosc; | |||||
| #define RTOSC_INVALID_MIDI 255 | |||||
| class rtosc::MidiTable_Impl | |||||
| { | |||||
| public: | |||||
| MidiTable_Impl(unsigned len, unsigned elms) | |||||
| :len(len), elms(elms) | |||||
| { | |||||
| table = new MidiAddr[elms]; | |||||
| for(unsigned i=0; i<elms; ++i) { | |||||
| table[i].ch = RTOSC_INVALID_MIDI; | |||||
| table[i].ctl = RTOSC_INVALID_MIDI; | |||||
| table[i].path = new char[len]; | |||||
| table[i].conversion = NULL; | |||||
| } | |||||
| //TODO initialize all elms | |||||
| } | |||||
| ~MidiTable_Impl() | |||||
| { | |||||
| for(unsigned i=0; i<elms; ++i) { | |||||
| delete [] table[i].path; | |||||
| } | |||||
| delete [] table; | |||||
| } | |||||
| MidiAddr *begin(void) {return table;} | |||||
| MidiAddr *end(void) {return table + elms;} | |||||
| unsigned len; | |||||
| unsigned elms; | |||||
| MidiAddr *table; | |||||
| }; | |||||
| //MidiAddr::MidiAddr(void) | |||||
| // :ch(RTOSC_INVALID_MIDI),ctl(RTOSC_INVALID_MIDI) | |||||
| //{} | |||||
| static void black_hole3 (const char *, const char *, const char *, int, int) | |||||
| {} | |||||
| static void black_hole2(const char *a, const char *b) | |||||
| {printf("'%s' and '%s'\n", a,b);} | |||||
| static void black_hole1(const char *a) | |||||
| {printf("'%s'\n", a);} | |||||
| #define MAX_UNHANDLED_PATH 128 | |||||
| MidiTable::MidiTable(const Ports &_dispatch_root) | |||||
| :dispatch_root(_dispatch_root), unhandled_ch(RTOSC_INVALID_MIDI), unhandled_ctl(RTOSC_INVALID_MIDI), | |||||
| error_cb(black_hole2), event_cb(black_hole1), modify_cb(black_hole3) | |||||
| { | |||||
| impl = new MidiTable_Impl(128,128); | |||||
| unhandled_path = new char[MAX_UNHANDLED_PATH]; | |||||
| memset(unhandled_path, 0, MAX_UNHANDLED_PATH); | |||||
| } | |||||
| MidiTable::~MidiTable() | |||||
| { | |||||
| delete impl; | |||||
| delete [] unhandled_path; | |||||
| } | |||||
| bool MidiTable::has(uint8_t ch, uint8_t ctl) const | |||||
| { | |||||
| for(auto e: *impl) { | |||||
| if(e.ch == ch && e.ctl == ctl) | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| MidiAddr *MidiTable::get(uint8_t ch, uint8_t ctl) | |||||
| { | |||||
| for(auto &e: *impl) | |||||
| if(e.ch==ch && e.ctl == ctl) | |||||
| return &e; | |||||
| return NULL; | |||||
| } | |||||
| const MidiAddr *MidiTable::get(uint8_t ch, uint8_t ctl) const | |||||
| { | |||||
| for(auto &e:*impl) | |||||
| if(e.ch==ch && e.ctl == ctl) | |||||
| return &e; | |||||
| return NULL; | |||||
| } | |||||
| bool MidiTable::mash_port(MidiAddr &e, const Port &port) | |||||
| { | |||||
| const char *args = strchr(port.name, ':'); | |||||
| if(!args) | |||||
| return false; | |||||
| //Consider a path to be typed based upon the argument restrictors | |||||
| if(strchr(args, 'f')) { | |||||
| e.type = 'f'; | |||||
| e.conversion = port.metadata; | |||||
| } else if(strchr(args, 'i')) | |||||
| e.type = 'i'; | |||||
| else if(strchr(args, 'T')) | |||||
| e.type = 'T'; | |||||
| else if(strchr(args, 'c')) | |||||
| e.type = 'c'; | |||||
| else | |||||
| return false; | |||||
| return true; | |||||
| } | |||||
| void MidiTable::addElm(uint8_t ch, uint8_t ctl, const char *path) | |||||
| { | |||||
| const Port *port = dispatch_root.apropos(path); | |||||
| if(!port || port->ports) {//missing or directory node | |||||
| error_cb("Bad path", path); | |||||
| return; | |||||
| } | |||||
| if(MidiAddr *e = this->get(ch,ctl)) { | |||||
| strncpy(e->path,path,impl->len); | |||||
| if(!mash_port(*e, *port)) { | |||||
| e->ch = RTOSC_INVALID_MIDI; | |||||
| e->ctl = RTOSC_INVALID_MIDI; | |||||
| error_cb("Failed to read metadata", path); | |||||
| } | |||||
| modify_cb("REPLACE", path, e->conversion, (int) ch, (int) ctl); | |||||
| return; | |||||
| } | |||||
| for(MidiAddr &e:*impl) { | |||||
| if(e.ch == RTOSC_INVALID_MIDI) {//free spot | |||||
| e.ch = ch; | |||||
| e.ctl = ctl; | |||||
| strncpy(e.path,path,impl->len); | |||||
| if(!mash_port(e, *port)) { | |||||
| e.ch = RTOSC_INVALID_MIDI; | |||||
| e.ctl = RTOSC_INVALID_MIDI; | |||||
| error_cb("Failed to read metadata", path); | |||||
| } | |||||
| modify_cb("ADD", path, e.conversion, (int) ch, (int) ctl); | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| void MidiTable::check_learn(void) | |||||
| { | |||||
| if(unhandled_ctl == RTOSC_INVALID_MIDI || unhandled_path[0] == '\0') | |||||
| return; | |||||
| addElm(unhandled_ch, unhandled_ctl, unhandled_path); | |||||
| unhandled_ch = unhandled_ctl = RTOSC_INVALID_MIDI; | |||||
| memset(unhandled_path, 0, MAX_UNHANDLED_PATH); | |||||
| } | |||||
| void MidiTable::learn(const char *s) | |||||
| { | |||||
| if(strlen(s) > impl->len) { | |||||
| error_cb("String too long", s); | |||||
| return; | |||||
| } | |||||
| clear_entry(s); | |||||
| strncpy(unhandled_path, s, MAX_UNHANDLED_PATH); | |||||
| unhandled_path[MAX_UNHANDLED_PATH-1] = '\0'; | |||||
| check_learn(); | |||||
| } | |||||
| void MidiTable::clear_entry(const char *s) | |||||
| { | |||||
| for(unsigned i=0; i<impl->elms; ++i) { | |||||
| if(!strcmp(impl->table[i].path, s)) { | |||||
| //Invalidate | |||||
| impl->table[i].ch = RTOSC_INVALID_MIDI; | |||||
| impl->table[i].ctl = RTOSC_INVALID_MIDI; | |||||
| modify_cb("DEL", s, "", -1, -1); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| void MidiTable::process(uint8_t ch, uint8_t ctl, uint8_t val) | |||||
| { | |||||
| const MidiAddr *addr = get(ch,ctl); | |||||
| if(!addr) { | |||||
| unhandled_ctl = ctl; | |||||
| unhandled_ch = ch; | |||||
| check_learn(); | |||||
| return; | |||||
| } | |||||
| char buffer[1024]; | |||||
| switch(addr->type) | |||||
| { | |||||
| case 'f': | |||||
| rtosc_message(buffer, 1024, addr->path, | |||||
| "f", translate(val,addr->conversion)); | |||||
| break; | |||||
| case 'i': | |||||
| rtosc_message(buffer, 1024, addr->path, | |||||
| "i", val); | |||||
| break; | |||||
| case 'T': | |||||
| rtosc_message(buffer, 1024, addr->path, | |||||
| (val<64 ? "F" : "T")); | |||||
| break; | |||||
| case 'c': | |||||
| rtosc_message(buffer, 1024, addr->path, | |||||
| "c", val); | |||||
| } | |||||
| event_cb(buffer); | |||||
| } | |||||
| Port MidiTable::learnPort(void) | |||||
| { | |||||
| return Port{"learn:s", "", 0, [this](msg_t m, RtData&){ | |||||
| this->learn(rtosc_argument(m,0).s); | |||||
| }}; | |||||
| } | |||||
| Port MidiTable::unlearnPort(void) | |||||
| { | |||||
| return Port{"unlearn:s", "", 0, [this](msg_t m, RtData&){ | |||||
| this->clear_entry(rtosc_argument(m,0).s); | |||||
| }}; | |||||
| } | |||||
| Port MidiTable::registerPort(void) | |||||
| { | |||||
| return Port{"register:iis","", 0, [this](msg_t m,RtData&){ | |||||
| const char *pos = rtosc_argument(m,2).s; | |||||
| while(*pos) putchar(*pos++); | |||||
| this->addElm(rtosc_argument(m,0).i,rtosc_argument(m,1).i,rtosc_argument(m,2).s);}}; | |||||
| } | |||||
| //TODO generalize to an addScalingFunction() system | |||||
| float MidiTable::translate(uint8_t val, const char *meta_) | |||||
| { | |||||
| //Allow for middle value to be set | |||||
| //TODO consider the centered trait for this op | |||||
| float x = val!=64.0 ? val/127.0 : 0.5; | |||||
| Port::MetaContainer meta(meta_); | |||||
| if(!meta["min"] || !meta["max"] || !meta["scale"]) { | |||||
| fprintf(stderr, "failed to get properties\n"); | |||||
| return 0.0f; | |||||
| } | |||||
| const float min = atof(meta["min"]); | |||||
| const float max = atof(meta["max"]); | |||||
| const char *scale = meta["scale"]; | |||||
| if(!strcmp(scale,"linear")) | |||||
| return x*(max-min)+min; | |||||
| else if(!strcmp(scale,"logarithmic")) { | |||||
| const float b = log(min); | |||||
| const float a = log(max)-b; | |||||
| return expf(a*x+b); | |||||
| } | |||||
| return 0.0f; | |||||
| } | |||||
| @@ -0,0 +1,828 @@ | |||||
| #include "ports.h" | |||||
| #include <ostream> | |||||
| #include <cassert> | |||||
| #include <climits> | |||||
| #include <cstring> | |||||
| #include <string> | |||||
| using namespace rtosc; | |||||
| static inline void scat(char *dest, const char *src) | |||||
| { | |||||
| while(*dest) dest++; | |||||
| if(*dest) dest++; | |||||
| while(*src && *src!=':') *dest++ = *src++; | |||||
| *dest = 0; | |||||
| } | |||||
| RtData::RtData(void) | |||||
| :loc(NULL), loc_size(0), obj(NULL), matches(0) | |||||
| {} | |||||
| void RtData::reply(const char *path, const char *args, ...) | |||||
| { | |||||
| va_list va; | |||||
| va_start(va,args); | |||||
| char buffer[1024]; | |||||
| rtosc_vmessage(buffer,1024,path,args,va); | |||||
| reply(buffer); | |||||
| va_end(va); | |||||
| }; | |||||
| void RtData::reply(const char *msg) | |||||
| {(void)msg;}; | |||||
| void RtData::broadcast(const char *path, const char *args, ...) | |||||
| { | |||||
| va_list va; | |||||
| va_start(va,args); | |||||
| char buffer[1024]; | |||||
| rtosc_vmessage(buffer,1024,path,args,va); | |||||
| broadcast(buffer); | |||||
| va_end(va); | |||||
| } | |||||
| void RtData::broadcast(const char *msg) | |||||
| {reply(msg);}; | |||||
| void metaiterator_advance(const char *&title, const char *&value) | |||||
| { | |||||
| if(!title || !*title) { | |||||
| value = NULL; | |||||
| return; | |||||
| } | |||||
| //Try to find "\0=" after title string | |||||
| value = title; | |||||
| while(*value) | |||||
| ++value; | |||||
| if(*++value != '=') | |||||
| value = NULL; | |||||
| else | |||||
| value++; | |||||
| } | |||||
| Port::MetaIterator::MetaIterator(const char *str) | |||||
| :title(str), value(NULL) | |||||
| { | |||||
| metaiterator_advance(title, value); | |||||
| } | |||||
| Port::MetaIterator& Port::MetaIterator::operator++(void) | |||||
| { | |||||
| if(!title || !*title) { | |||||
| title = NULL; | |||||
| return *this; | |||||
| } | |||||
| //search for next parameter start | |||||
| //aka "\0:" unless "\0\0" is seen | |||||
| char prev = 0; | |||||
| while(prev || (*title && *title != ':')) | |||||
| prev = *title++; | |||||
| if(!*title) | |||||
| title = NULL; | |||||
| else | |||||
| ++title; | |||||
| metaiterator_advance(title, value); | |||||
| return *this; | |||||
| } | |||||
| Port::MetaContainer::MetaContainer(const char *str_) | |||||
| :str_ptr(str_) | |||||
| {} | |||||
| Port::MetaIterator Port::MetaContainer::begin(void) const | |||||
| { | |||||
| if(str_ptr && *str_ptr == ':') | |||||
| return Port::MetaIterator(str_ptr+1); | |||||
| else | |||||
| return Port::MetaIterator(str_ptr); | |||||
| } | |||||
| Port::MetaIterator Port::MetaContainer::end(void) const | |||||
| { | |||||
| return MetaIterator(NULL); | |||||
| } | |||||
| Port::MetaIterator Port::MetaContainer::find(const char *str) const | |||||
| { | |||||
| for(const auto x : *this) | |||||
| if(!strcmp(x.title, str)) | |||||
| return x; | |||||
| return NULL; | |||||
| } | |||||
| size_t Port::MetaContainer::length(void) const | |||||
| { | |||||
| if(!str_ptr || !*str_ptr) | |||||
| return 0; | |||||
| char prev = 0; | |||||
| const char *itr = str_ptr; | |||||
| while(prev || *itr) | |||||
| prev = *itr++; | |||||
| return 2+(itr-str_ptr); | |||||
| } | |||||
| const char *Port::MetaContainer::operator[](const char *str) const | |||||
| { | |||||
| for(const auto x : *this) | |||||
| if(!strcmp(x.title, str)) | |||||
| return x.value; | |||||
| return NULL; | |||||
| } | |||||
| //Match the arg string or fail | |||||
| inline bool arg_matcher(const char *pattern, const char *args) | |||||
| { | |||||
| //match anything if now arg restriction is present (ie the ':') | |||||
| if(*pattern++ != ':') | |||||
| return true; | |||||
| const char *arg_str = args; | |||||
| bool arg_match = *pattern || *pattern == *arg_str; | |||||
| while(*pattern && *pattern != ':') | |||||
| arg_match &= (*pattern++==*arg_str++); | |||||
| if(*pattern==':') { | |||||
| if(arg_match && !*arg_str) | |||||
| return true; | |||||
| else | |||||
| return arg_matcher(pattern, args); //retry | |||||
| } | |||||
| return arg_match; | |||||
| } | |||||
| inline bool scmp(const char *a, const char *b) | |||||
| { | |||||
| while(*a && *a == *b) a++, b++; | |||||
| return a[0] == b[0]; | |||||
| } | |||||
| typedef std::vector<std::string> words_t; | |||||
| typedef std::vector<std::string> svec_t; | |||||
| typedef std::vector<const char *> cvec_t; | |||||
| typedef std::vector<int> ivec_t; | |||||
| typedef std::vector<int> tuple_t; | |||||
| typedef std::vector<tuple_t> tvec_t; | |||||
| namespace rtosc{ | |||||
| class Port_Matcher | |||||
| { | |||||
| public: | |||||
| bool *enump; | |||||
| svec_t fixed; | |||||
| cvec_t arg_spec; | |||||
| ivec_t pos; | |||||
| ivec_t assoc; | |||||
| ivec_t remap; | |||||
| bool rtosc_match_args(const char *pattern, const char *msg) | |||||
| { | |||||
| //match anything if now arg restriction is present | |||||
| //(ie the ':') | |||||
| if(*pattern++ != ':') | |||||
| return true; | |||||
| const char *arg_str = rtosc_argument_string(msg); | |||||
| bool arg_match = *pattern || *pattern == *arg_str; | |||||
| while(*pattern && *pattern != ':') | |||||
| arg_match &= (*pattern++==*arg_str++); | |||||
| if(*pattern==':') { | |||||
| if(arg_match && !*arg_str) | |||||
| return true; | |||||
| else | |||||
| return rtosc_match_args(pattern, msg); //retry | |||||
| } | |||||
| return arg_match; | |||||
| } | |||||
| bool hard_match(int i, const char *msg) | |||||
| { | |||||
| if(strncmp(msg, fixed[i].c_str(), fixed[i].length())) | |||||
| return false; | |||||
| if(arg_spec[i]) | |||||
| return rtosc_match_args(arg_spec[i], msg); | |||||
| else | |||||
| return true; | |||||
| } | |||||
| }; | |||||
| } | |||||
| tvec_t do_hash(const words_t &strs, const ivec_t &pos) | |||||
| { | |||||
| tvec_t tvec; | |||||
| for(auto &s:strs) { | |||||
| tuple_t tuple; | |||||
| tuple.push_back(s.length()); | |||||
| for(const auto &p:pos) | |||||
| if(p < (int)s.size()) | |||||
| tuple.push_back(s[p]); | |||||
| tvec.push_back(std::move(tuple)); | |||||
| } | |||||
| return tvec; | |||||
| } | |||||
| template<class T> | |||||
| int count_dups(std::vector<T> &t) | |||||
| { | |||||
| int dups = 0; | |||||
| int N = t.size(); | |||||
| bool mark[t.size()]; | |||||
| memset(mark, 0, N); | |||||
| for(int i=0; i<N; ++i) { | |||||
| if(mark[i]) | |||||
| continue; | |||||
| for(int j=i+1; j<N; ++j) { | |||||
| if(t[i] == t[j]) { | |||||
| dups++; | |||||
| mark[j] = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| return dups; | |||||
| } | |||||
| template<class T, class Z> | |||||
| bool has(T &t, Z&z) | |||||
| { | |||||
| for(auto tt:t) | |||||
| if(tt==z) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| int max(int a, int b) { return a<b?b:a;} | |||||
| ivec_t find_pos(words_t &strs) | |||||
| { | |||||
| ivec_t pos; | |||||
| int current_dups = strs.size(); | |||||
| int N = 0; | |||||
| for(auto w:strs) | |||||
| N = max(N,w.length()); | |||||
| int pos_best = -1; | |||||
| int pos_best_val = INT_MAX; | |||||
| while(true) | |||||
| { | |||||
| for(int i=0; i<N; ++i) { | |||||
| ivec_t npos = pos; | |||||
| if(has(pos, i)) | |||||
| continue; | |||||
| npos.push_back(i); | |||||
| auto hashed = do_hash(strs, npos); | |||||
| int d = count_dups(hashed); | |||||
| if(d < pos_best_val) { | |||||
| pos_best_val = d; | |||||
| pos_best = i; | |||||
| } | |||||
| } | |||||
| if(pos_best_val >= current_dups) | |||||
| break; | |||||
| current_dups = pos_best_val; | |||||
| pos.push_back(pos_best); | |||||
| } | |||||
| auto hashed = do_hash(strs, pos); | |||||
| int d = count_dups(hashed); | |||||
| //printf("Total Dups: %d\n", d); | |||||
| if(d != 0) | |||||
| pos.clear(); | |||||
| return pos; | |||||
| } | |||||
| ivec_t do_hash(const words_t &strs, const ivec_t &pos, const ivec_t &assoc) | |||||
| { | |||||
| ivec_t ivec; | |||||
| ivec.reserve(strs.size()); | |||||
| for(auto &s:strs) { | |||||
| int t = s.length(); | |||||
| for(auto p:pos) | |||||
| if(p < (int)s.size()) | |||||
| t += assoc[s[p]]; | |||||
| ivec.push_back(t); | |||||
| } | |||||
| return ivec; | |||||
| } | |||||
| ivec_t find_assoc(const words_t &strs, const ivec_t &pos) | |||||
| { | |||||
| ivec_t assoc; | |||||
| int current_dups = strs.size(); | |||||
| int N = 127; | |||||
| std::vector<char> useful_chars; | |||||
| for(auto w:strs) | |||||
| for(auto c:w) | |||||
| if(!has(useful_chars, c)) | |||||
| useful_chars.push_back(c); | |||||
| for(int i=0; i<N; ++i) | |||||
| assoc.push_back(0); | |||||
| int assoc_best = -1; | |||||
| int assoc_best_val = INT_MAX; | |||||
| for(int k=0; k<4; ++k) | |||||
| { | |||||
| for(int i:useful_chars) { | |||||
| assoc_best_val = INT_MAX; | |||||
| for(int j=0; j<100; ++j) { | |||||
| //printf("."); | |||||
| assoc[i] = j; | |||||
| auto hashed = do_hash(strs, pos, assoc); | |||||
| //for(int i=0; i<hashed.size(); ++i) | |||||
| // printf("%d ", hashed[i]); | |||||
| //printf("\n"); | |||||
| int d = count_dups(hashed); | |||||
| //printf("dup %d\n",d); | |||||
| if(d < assoc_best_val) { | |||||
| assoc_best_val = d; | |||||
| assoc_best = j; | |||||
| } | |||||
| } | |||||
| assoc[i] = assoc_best; | |||||
| } | |||||
| if(assoc_best_val >= current_dups) | |||||
| break; | |||||
| current_dups = assoc_best_val; | |||||
| } | |||||
| auto hashed = do_hash(strs, pos, assoc); | |||||
| //int d = count_dups(hashed); | |||||
| //printf("Total Dups Assoc: %d\n", d); | |||||
| return assoc; | |||||
| } | |||||
| ivec_t find_remap(words_t &strs, ivec_t &pos, ivec_t &assoc) | |||||
| { | |||||
| ivec_t remap; | |||||
| auto hashed = do_hash(strs, pos, assoc); | |||||
| //for(int i=0; i<strs.size(); ++i) | |||||
| // printf("%d) '%s'\n", hashed[i], strs[i].c_str()); | |||||
| int N = 0; | |||||
| for(auto h:hashed) | |||||
| N = max(N,h+1); | |||||
| for(int i=0; i<N; ++i) | |||||
| remap.push_back(0); | |||||
| for(int i=0; i<(int)hashed.size(); ++i) | |||||
| remap[hashed[i]] = i; | |||||
| return remap; | |||||
| } | |||||
| void generate_minimal_hash(std::vector<std::string> str, Port_Matcher &pm) | |||||
| { | |||||
| pm.pos = find_pos(str); | |||||
| if(pm.pos.empty()) { | |||||
| fprintf(stderr, "rtosc: Failed to generate minimal hash\n"); | |||||
| return; | |||||
| } | |||||
| pm.assoc = find_assoc(str, pm.pos); | |||||
| pm.remap = find_remap(str, pm.pos, pm.assoc); | |||||
| } | |||||
| void generate_minimal_hash(Ports &p, Port_Matcher &pm) | |||||
| { | |||||
| svec_t keys; | |||||
| cvec_t args; | |||||
| bool enump = false; | |||||
| for(unsigned i=0; i<p.ports.size(); ++i) | |||||
| if(strchr(p.ports[i].name, '#')) | |||||
| enump = true; | |||||
| if(enump) | |||||
| return; | |||||
| for(unsigned i=0; i<p.ports.size(); ++i) | |||||
| { | |||||
| std::string tmp = p.ports[i].name; | |||||
| const char *arg = NULL; | |||||
| int idx = tmp.find(':'); | |||||
| if(idx > 0) { | |||||
| arg = p.ports[i].name+idx; | |||||
| tmp = tmp.substr(0,idx); | |||||
| } | |||||
| keys.push_back(tmp); | |||||
| args.push_back(arg); | |||||
| } | |||||
| pm.fixed = keys; | |||||
| pm.arg_spec = args; | |||||
| generate_minimal_hash(keys, pm); | |||||
| } | |||||
| Ports::Ports(std::initializer_list<Port> l) | |||||
| :ports(l), impl(new Port_Matcher) | |||||
| { | |||||
| generate_minimal_hash(*this, *impl); | |||||
| impl->enump = new bool[ports.size()]; | |||||
| for(int i=0; i<(int)ports.size(); ++i) | |||||
| impl->enump[i] = strchr(ports[i].name, '#'); | |||||
| elms = ports.size(); | |||||
| } | |||||
| Ports::~Ports() | |||||
| { | |||||
| delete []impl->enump; | |||||
| delete impl; | |||||
| } | |||||
| #if !defined(__GNUC__) | |||||
| #define __builtin_expect(a,b) a | |||||
| #endif | |||||
| void Ports::dispatch(const char *m, rtosc::RtData &d) const | |||||
| { | |||||
| void *obj = d.obj; | |||||
| //simple case | |||||
| if(!d.loc || !d.loc_size) { | |||||
| for(const Port &port: ports) { | |||||
| if(rtosc_match(port.name,m)) | |||||
| d.port = &port, port.cb(m,d), d.obj = obj; | |||||
| } | |||||
| } else { | |||||
| //TODO this function is certainly buggy at the moment, some tests | |||||
| //are needed to make it clean | |||||
| //XXX buffer_size is not properly handled yet | |||||
| if(__builtin_expect(d.loc[0] == 0, 0)) { | |||||
| memset(d.loc, 0, d.loc_size); | |||||
| d.loc[0] = '/'; | |||||
| } | |||||
| char *old_end = d.loc; | |||||
| while(*old_end) ++old_end; | |||||
| if(impl->pos.empty()) { //No perfect minimal hash function | |||||
| for(unsigned i=0; i<elms; ++i) { | |||||
| const Port &port = ports[i]; | |||||
| if(!rtosc_match(port.name, m)) | |||||
| continue; | |||||
| if(!port.ports) | |||||
| d.matches++; | |||||
| //Append the path | |||||
| if(strchr(port.name,'#')) { | |||||
| const char *msg = m; | |||||
| char *pos = old_end; | |||||
| while(*msg && *msg != '/') | |||||
| *pos++ = *msg++; | |||||
| if(strchr(port.name, '/')) | |||||
| *pos++ = '/'; | |||||
| *pos = '\0'; | |||||
| } else | |||||
| scat(d.loc, port.name); | |||||
| d.port = &port; | |||||
| //Apply callback | |||||
| port.cb(m,d), d.obj = obj; | |||||
| //Remove the rest of the path | |||||
| char *tmp = old_end; | |||||
| while(*tmp) *tmp++=0; | |||||
| } | |||||
| } else { | |||||
| //Define string to be hashed | |||||
| unsigned len=0; | |||||
| const char *tmp = m; | |||||
| while(*tmp && *tmp != '/') | |||||
| tmp++; | |||||
| if(*tmp == '/') | |||||
| tmp++; | |||||
| len = tmp-m; | |||||
| //Compute the hash | |||||
| int t = len; | |||||
| for(auto p:impl->pos) | |||||
| if(p < (int)len) | |||||
| t += impl->assoc[m[p]]; | |||||
| if(t >= (int)impl->remap.size()) | |||||
| return; | |||||
| int port_num = impl->remap[t]; | |||||
| //Verify the chosen port is correct | |||||
| if(__builtin_expect(impl->hard_match(port_num, m), 1)) { | |||||
| const Port &port = ports[impl->remap[t]]; | |||||
| if(!port.ports) | |||||
| d.matches++; | |||||
| //Append the path | |||||
| if(impl->enump[port_num]) { | |||||
| const char *msg = m; | |||||
| char *pos = old_end; | |||||
| while(*msg && *msg != '/') | |||||
| *pos++ = *msg++; | |||||
| if(strchr(port.name, '/')) | |||||
| *pos++ = '/'; | |||||
| *pos = '\0'; | |||||
| } else | |||||
| memcpy(old_end, impl->fixed[port_num].c_str(), | |||||
| impl->fixed[port_num].length()+1); | |||||
| d.port = &port; | |||||
| //Apply callback | |||||
| port.cb(m,d), d.obj = obj; | |||||
| //Remove the rest of the path | |||||
| old_end[0] = '\0'; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| const Port *Ports::operator[](const char *name) const | |||||
| { | |||||
| for(const Port &port:ports) { | |||||
| const char *_needle = name, | |||||
| *_haystack = port.name; | |||||
| while(*_needle && *_needle==*_haystack)_needle++,_haystack++; | |||||
| if(*_needle == 0 && (*_haystack == ':' || *_haystack == '\0')) { | |||||
| return &port; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| static msg_t snip(msg_t m) | |||||
| { | |||||
| while(*m && *m != '/') ++m; | |||||
| return m+1; | |||||
| } | |||||
| const Port *Ports::apropos(const char *path) const | |||||
| { | |||||
| if(path && path[0] == '/') | |||||
| ++path; | |||||
| for(const Port &port: ports) | |||||
| if(strchr(port.name,'/') && rtosc_match_path(port.name,path)) | |||||
| return (strchr(path,'/')[1]==0) ? &port : | |||||
| port.ports->apropos(snip(path)); | |||||
| //This is the lowest level, now find the best port | |||||
| for(const Port &port: ports) | |||||
| if(*path && strstr(port.name, path)==port.name) | |||||
| return &port; | |||||
| return NULL; | |||||
| } | |||||
| void rtosc::walk_ports(const Ports *base, | |||||
| char *name_buffer, | |||||
| size_t buffer_size, | |||||
| void *data, | |||||
| port_walker_t walker) | |||||
| { | |||||
| assert(name_buffer); | |||||
| //XXX buffer_size is not properly handled yet | |||||
| if(name_buffer[0] == 0) | |||||
| name_buffer[0] = '/'; | |||||
| char *old_end = name_buffer; | |||||
| while(*old_end) ++old_end; | |||||
| for(const Port &p: *base) { | |||||
| if(strchr(p.name, '/')) {//it is another tree | |||||
| if(strchr(p.name,'#')) { | |||||
| const char *name = p.name; | |||||
| char *pos = old_end; | |||||
| while(*name != '#') *pos++ = *name++; | |||||
| const unsigned max = atoi(name+1); | |||||
| for(unsigned i=0; i<max; ++i) | |||||
| { | |||||
| sprintf(pos,"%d",i); | |||||
| //Ensure the result is a path | |||||
| if(strrchr(name_buffer, '/')[1] != '/') | |||||
| strcat(name_buffer, "/"); | |||||
| //Recurse | |||||
| rtosc::walk_ports(p.ports, name_buffer, buffer_size, | |||||
| data, walker); | |||||
| } | |||||
| } else { | |||||
| //Append the path | |||||
| scat(name_buffer, p.name); | |||||
| //Recurse | |||||
| rtosc::walk_ports(p.ports, name_buffer, buffer_size, | |||||
| data, walker); | |||||
| } | |||||
| } else { | |||||
| if(strchr(p.name,'#')) { | |||||
| const char *name = p.name; | |||||
| char *pos = old_end; | |||||
| while(*name != '#') *pos++ = *name++; | |||||
| const unsigned max = atoi(name+1); | |||||
| for(unsigned i=0; i<max; ++i) | |||||
| { | |||||
| sprintf(pos,"%d",i); | |||||
| //Apply walker function | |||||
| walker(&p, name_buffer, data); | |||||
| } | |||||
| } else { | |||||
| //Append the path | |||||
| scat(name_buffer, p.name); | |||||
| //Apply walker function | |||||
| walker(&p, name_buffer, data); | |||||
| } | |||||
| } | |||||
| //Remove the rest of the path | |||||
| char *tmp = old_end; | |||||
| while(*tmp) *tmp++=0; | |||||
| } | |||||
| } | |||||
| void walk_ports2(const rtosc::Ports *base, | |||||
| char *name_buffer, | |||||
| size_t buffer_size, | |||||
| void *data, | |||||
| rtosc::port_walker_t walker) | |||||
| { | |||||
| assert(name_buffer); | |||||
| //XXX buffer_size is not properly handled yet | |||||
| if(name_buffer[0] == 0) | |||||
| name_buffer[0] = '/'; | |||||
| char *old_end = name_buffer; | |||||
| while(*old_end) ++old_end; | |||||
| for(const rtosc::Port &p: *base) { | |||||
| if(strchr(p.name, '/')) {//it is another tree | |||||
| if(strchr(p.name,'#')) { | |||||
| const char *name = p.name; | |||||
| char *pos = old_end; | |||||
| while(*name != '#') *pos++ = *name++; | |||||
| const unsigned max = atoi(name+1); | |||||
| //for(unsigned i=0; i<max; ++i) | |||||
| { | |||||
| sprintf(pos,"[0,%d]",max); | |||||
| //Ensure the result is a path | |||||
| if(strrchr(name_buffer, '/')[1] != '/') | |||||
| strcat(name_buffer, "/"); | |||||
| //Recurse | |||||
| walk_ports2(p.ports, name_buffer, buffer_size, | |||||
| data, walker); | |||||
| } | |||||
| } else { | |||||
| //Append the path | |||||
| scat(name_buffer, p.name); | |||||
| //Recurse | |||||
| walk_ports2(p.ports, name_buffer, buffer_size, | |||||
| data, walker); | |||||
| } | |||||
| } else { | |||||
| if(strchr(p.name,'#')) { | |||||
| const char *name = p.name; | |||||
| char *pos = old_end; | |||||
| while(*name != '#') *pos++ = *name++; | |||||
| const unsigned max = atoi(name+1); | |||||
| //for(unsigned i=0; i<max; ++i) | |||||
| { | |||||
| sprintf(pos,"[0,%d]",max); | |||||
| //Apply walker function | |||||
| walker(&p, name_buffer, data); | |||||
| } | |||||
| } else { | |||||
| //Append the path | |||||
| scat(name_buffer, p.name); | |||||
| //Apply walker function | |||||
| walker(&p, name_buffer, data); | |||||
| } | |||||
| } | |||||
| //Remove the rest of the path | |||||
| char *tmp = old_end; | |||||
| while(*tmp) *tmp++=0; | |||||
| } | |||||
| } | |||||
| static void units(std::ostream &o, const char *u) | |||||
| { | |||||
| if(!u) | |||||
| return; | |||||
| o << " units=\"" << u << "\""; | |||||
| } | |||||
| void dump_ports_cb(const rtosc::Port *p, const char *name, void *v) | |||||
| { | |||||
| std::ostream &o = *(std::ostream*)v; | |||||
| auto meta = p->meta(); | |||||
| if(meta.find("parameter") != p->meta().end()) { | |||||
| char type = 0; | |||||
| const char *foo = strchr(p->name, ':'); | |||||
| if(strchr(foo, 'f')) | |||||
| type = 'f'; | |||||
| else if(strchr(foo, 'i')) | |||||
| type = 'i'; | |||||
| else if(strchr(foo, 'c')) | |||||
| type = 'c'; | |||||
| else if(strchr(foo, 'T')) | |||||
| type = 't'; | |||||
| if(!type) { | |||||
| fprintf(stderr, "rtosc port dumper: Cannot handle '%s'\n", p->name); | |||||
| return; | |||||
| } | |||||
| if(type == 't') | |||||
| { | |||||
| o << " <message_in pattern=\"" << name << "\" typetag=\"T\">\n"; | |||||
| o << " <desc>Enable " << p->meta()["documentation"] << "</desc>\n"; | |||||
| o << " <param_T symbol=\"x\"/>\n"; | |||||
| o << " </message_in>\n"; | |||||
| o << " <message_in pattern=\"" << name << "\" typetag=\"F\">\n"; | |||||
| o << " <desc>Disable " << p->meta()["documentation"] << "</desc>\n"; | |||||
| o << " <param_F symbol=\"x\"/>\n"; | |||||
| o << " </message_in>\n"; | |||||
| o << " <message_in pattern=\"" << name << "\" typetag=\"\">\n"; | |||||
| o << " <desc>Get state of " << p->meta()["documentation"] << "</desc>\n"; | |||||
| o << " </message_in>\n"; | |||||
| o << " <message_out pattern=\"" << name << "\" typetag=\"T\">\n"; | |||||
| o << " <desc>Value of " << p->meta()["documentation"] << "</desc>\n"; | |||||
| o << " <param_T symbol=\"x\"/>"; | |||||
| o << " </message_out>\n"; | |||||
| o << " <message_out pattern=\"" << name << "\" typetag=\"F\">\n"; | |||||
| o << " <desc>Value of %s</desc>\n", p->meta()["documentation"]; | |||||
| o << " <param_F symbol=\"x\"/>"; | |||||
| o << " </message_out>\n"; | |||||
| return; | |||||
| } | |||||
| o << " <message_in pattern=\"" << name << "\" typetag=\"" << type << "\">\n"; | |||||
| o << " <desc>Set Value of " << p->meta()["documentation"] << "</desc>\n"; | |||||
| if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c') | |||||
| { | |||||
| o << " <param_" << type << " symbol=\"x\""; | |||||
| units(o, meta["unit"]); | |||||
| o << ">\n"; | |||||
| o << " <range_min_max " << (type == 'f' ? "lmin=\"[\" lmax=\"]\"" : ""); | |||||
| o << " min=\"" << meta["min"] << "\" max=\"" << meta["max"] << "\"/>\n"; | |||||
| o << " </param_" << type << ">"; | |||||
| } else { | |||||
| o << " <param_" << type << " symbol=\"x\""; | |||||
| units(o, meta["unit"]); | |||||
| o << "/>\n"; | |||||
| } | |||||
| o << " </message_in>\n"; | |||||
| o << " <message_in pattern=\"" << name << "\" typetag=\"\">\n"; | |||||
| o << " <desc>Get Value of " << p->meta()["documentation"] << "</desc>\n"; | |||||
| o << " </message_in>\n"; | |||||
| o << " <message_out pattern=\"" << name << "\" typetag=\"" << type << "\">\n"; | |||||
| o << " <desc>Value of " << p->meta()["documentation"] << "</desc>\n"; | |||||
| if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c') | |||||
| { | |||||
| o << " <param_" << type << " symbol=\"x\""; | |||||
| units(o, meta["unit"]); | |||||
| o << ">\n"; | |||||
| o << " <range_min_max " << (type == 'f' ? "lmin=\"[\" lmax=\"]\"" : ""); | |||||
| o << " min=\"" << meta["min"] << "\" max=\"" << meta["max"] << "\"/>\n"; | |||||
| o << " </param_" << type << ">\n"; | |||||
| } else { | |||||
| o << " <param_" << type << " symbol=\"x\""; | |||||
| units(o, meta["unit"]); | |||||
| o << "/>\n"; | |||||
| } | |||||
| o << " </message_out>\n"; | |||||
| }// else if(meta.find("documentation") != meta.end()) | |||||
| // fprintf(stderr, "Skipping \"%s\"\n", name); | |||||
| //else | |||||
| // fprintf(stderr, "Skipping [UNDOCUMENTED] \"%s\"\n", name); | |||||
| } | |||||
| std::ostream &rtosc::operator<<(std::ostream &o, rtosc::OscDocFormatter &formatter) | |||||
| { | |||||
| o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; | |||||
| o << "<osc_unit format_version=\"1.0\">\n"; | |||||
| o << " <meta>\n"; | |||||
| o << " <name>" << formatter.prog_name << "</name>\n"; | |||||
| o << " <uri>" << formatter.uri << "</uri>\n"; | |||||
| o << " <doc_origin>" << formatter.doc_origin << "</doc_origin>\n"; | |||||
| o << " <author><firstname>" << formatter.author_first; | |||||
| o << "</firstname><lastname>" << formatter.author_last << "</lastname></author>\n"; | |||||
| o << " </meta>\n"; | |||||
| char buffer[1024]; | |||||
| memset(buffer, 0, sizeof(buffer)); | |||||
| walk_ports2(formatter.p, buffer, 1024, &o, dump_ports_cb); | |||||
| o << "</osc_unit>\n"; | |||||
| return o; | |||||
| } | |||||
| @@ -0,0 +1,144 @@ | |||||
| #include "subtree-serialize.h" | |||||
| #include "ports.h" | |||||
| #include "rtosc.h" | |||||
| #include <cstring> | |||||
| #include <cassert> | |||||
| using namespace rtosc; | |||||
| /* | |||||
| * Append another message onto a bundle if the space permits it. | |||||
| * If insufficient space is available, then zero is returned and the buffer is | |||||
| * untouched. | |||||
| * | |||||
| * If this is useful it may be generalized to rtosc_bundle_append() | |||||
| */ | |||||
| static size_t append_bundle(char *dst, const char *src, | |||||
| size_t max_len, size_t dst_len, size_t src_len) | |||||
| { | |||||
| assert(rtosc_message_length(src,src_len) == src_len); | |||||
| //Handle Edge case alignment | |||||
| //if(rtosc_bundle_elements(dst, dst_len) == 0) | |||||
| // dst_len -= 4; | |||||
| if(max_len < dst_len + src_len + 4 || dst_len == 0 || src_len == 0) | |||||
| return 0; | |||||
| *(int32_t*)(dst+dst_len) = (int32_t)src_len; | |||||
| memcpy(dst+dst_len+4, src, src_len); | |||||
| return dst_len + src_len + 4; | |||||
| } | |||||
| //This object captures the output of any given port by calling it through a no | |||||
| //argument message | |||||
| //Assuming that the loc field is set correctly the message stored here will be | |||||
| //able to be replayed to get an object to a previous state | |||||
| class VarCapture : public RtData | |||||
| { | |||||
| public: | |||||
| char buf[128]; | |||||
| char location[128]; | |||||
| char msg[128]; | |||||
| const char *dummy; | |||||
| bool success; | |||||
| VarCapture(void) | |||||
| :dummy("/ser\0\0\0\0,\0\0\0") | |||||
| { | |||||
| memset(buf, 0, sizeof(buf)); | |||||
| memset(location, 0, sizeof(buf)); | |||||
| this->loc = location; | |||||
| success = false; | |||||
| } | |||||
| const char *capture(const Ports *p, const char *path, void *obj_) | |||||
| { | |||||
| this->loc = location; | |||||
| assert(this->loc == location); | |||||
| this->obj = obj_; | |||||
| location[0] = '/'; | |||||
| strcpy(location+1, path); | |||||
| success = false; | |||||
| size_t len = rtosc_message(msg, 128, path, ""); | |||||
| (void) len; | |||||
| assert(len); | |||||
| assert(!strchr(path, ':')); | |||||
| p->dispatch(msg, *this); | |||||
| return success ? buf : NULL; | |||||
| } | |||||
| virtual void reply(const char *path, const char *args, ...) | |||||
| { | |||||
| assert(!success); | |||||
| assert(*path); | |||||
| va_list va; | |||||
| va_start(va, args); | |||||
| size_t len = rtosc_vmessage(buf, 128, path, args, va); | |||||
| (void) len; | |||||
| assert(len != 0); | |||||
| success = true; | |||||
| va_end(va); | |||||
| } | |||||
| virtual void broadcast(const char *msg) | |||||
| { | |||||
| (void) msg; | |||||
| } | |||||
| }; | |||||
| struct subtree_args_t | |||||
| { | |||||
| VarCapture v, vv; | |||||
| size_t len; | |||||
| char *buffer; | |||||
| size_t buffer_size; | |||||
| void *object; | |||||
| rtosc::Ports *ports; | |||||
| }; | |||||
| size_t subtree_serialize(char *buffer, size_t buffer_size, | |||||
| void *object, rtosc::Ports *ports) | |||||
| { | |||||
| (void) object; | |||||
| assert(buffer); | |||||
| assert(ports); | |||||
| subtree_args_t args; | |||||
| args.v.obj = object; | |||||
| args.len = rtosc_bundle(buffer, buffer_size, 0xdeadbeef0a0b0c0dULL, 0); | |||||
| args.buffer = buffer; | |||||
| args.buffer_size = buffer_size; | |||||
| args.object = object; | |||||
| args.ports = ports; | |||||
| //TODO FIXME this is not currently RT safe at the moment | |||||
| walk_ports(ports, args.v.loc, 128, &args, [](const Port *p, const char *, void *dat) { | |||||
| if(p->meta().find("internal") != p->meta().end()) | |||||
| return; | |||||
| subtree_args_t *args = (subtree_args_t*) dat; | |||||
| const char *buf = args->vv.capture(args->ports, args->v.loc+1, args->object); | |||||
| if(buf) | |||||
| args->len = append_bundle(args->buffer, buf, args->buffer_size, args->len, | |||||
| rtosc_message_length(buf, 128)); | |||||
| }); | |||||
| return args.len; | |||||
| } | |||||
| void subtree_deserialize(char *buffer, size_t buffer_size, | |||||
| void *object, rtosc::Ports *ports, RtData &d) | |||||
| { | |||||
| d.obj = object; | |||||
| //simply replay all objects seen here | |||||
| for(unsigned i=0; i<rtosc_bundle_elements(buffer, buffer_size); ++i) { | |||||
| const char *msg = rtosc_bundle_fetch(buffer, i); | |||||
| ports->dispatch(msg+1, d); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,180 @@ | |||||
| #include "thread-link.h" | |||||
| namespace rtosc { | |||||
| #ifdef off_t | |||||
| #undef off_t | |||||
| #endif | |||||
| #define off_t signed long | |||||
| //Ringbuffer internal structure | |||||
| //XXX possible undefined behavior depending on future semantics of volatile | |||||
| struct internal_ringbuffer_t { | |||||
| char *buffer; | |||||
| volatile off_t write; | |||||
| volatile off_t read; | |||||
| size_t size; | |||||
| }; | |||||
| typedef internal_ringbuffer_t ringbuffer_t; | |||||
| #define static | |||||
| static size_t ring_read_size(ringbuffer_t *ring) | |||||
| { | |||||
| const size_t w = ring->write; | |||||
| const size_t r = ring->read; | |||||
| return (w-r+ring->size) % ring->size; | |||||
| } | |||||
| static size_t ring_write_size(ringbuffer_t *ring) | |||||
| { | |||||
| //leave one forbidden element | |||||
| const size_t w = ring->write; | |||||
| const size_t r = ring->read; | |||||
| if(r == w) | |||||
| return ring->size - 1; | |||||
| return ((r - w + ring->size) % ring->size) - 1; | |||||
| } | |||||
| static void ring_write(ringbuffer_t *ring, const char *data, size_t len) | |||||
| { | |||||
| assert(ring_write_size(ring) >= len); | |||||
| const off_t next_write = (ring->write + len)%ring->size; | |||||
| //discontinuous write | |||||
| if(next_write < ring->write) { | |||||
| const size_t w1 = ring->size - ring->write - 1; | |||||
| const size_t w2 = len - w1; | |||||
| memcpy(ring->buffer+ring->write, data, w1); | |||||
| memcpy(ring->buffer, data+w1, w2); | |||||
| } else { //contiguous | |||||
| memcpy(ring->buffer+ring->write, data, len); | |||||
| } | |||||
| ring->write = next_write; | |||||
| } | |||||
| static void ring_read(ringbuffer_t *ring, char *data, size_t len) | |||||
| { | |||||
| assert(ring_read_size(ring) >= len); | |||||
| const off_t next_read = (ring->read + len)%ring->size; | |||||
| //discontinuous read | |||||
| if(next_read < ring->read) { | |||||
| const size_t r1 = ring->size - ring->read - 1; | |||||
| const size_t r2 = len - r1; | |||||
| memcpy(data, ring->buffer+ring->read, r1); | |||||
| memcpy(data+r1, ring->buffer, r2); | |||||
| } else { //contiguous | |||||
| memcpy(data, ring->buffer+ring->read, len); | |||||
| } | |||||
| ring->read = next_read; | |||||
| } | |||||
| static void ring_read_vector(ringbuffer_t *ring, ring_t *r) | |||||
| { | |||||
| assert(r); | |||||
| size_t read_size = ring_read_size(ring); | |||||
| off_t read = ring->read; | |||||
| r[0].data = ring->buffer+ring->read; | |||||
| if(read_size+read > ring->size) { //discontinuous | |||||
| size_t r2 = (read_size+1+read)%ring->size; | |||||
| size_t r1 = read_size - r2; | |||||
| r[0].len = r1; | |||||
| r[1].data = ring->buffer; | |||||
| r[1].len = r2; | |||||
| } else { | |||||
| r[0].len = read_size; | |||||
| r[1].data = NULL; | |||||
| r[1].len = 0; | |||||
| } | |||||
| } | |||||
| ThreadLink::ThreadLink(size_t max_message_length, size_t max_messages) | |||||
| :MaxMsg(max_message_length), | |||||
| BufferSize(MaxMsg*max_messages), | |||||
| write_buffer(new char[MaxMsg]), | |||||
| read_buffer(new char[MaxMsg]), | |||||
| ring(new ringbuffer_t) | |||||
| { | |||||
| ring->buffer = new char[BufferSize]; | |||||
| ring->size = BufferSize; | |||||
| ring->read = 0; | |||||
| ring->write = 0; | |||||
| memset(write_buffer, 0, MaxMsg); | |||||
| memset(read_buffer, 0, MaxMsg); | |||||
| } | |||||
| ThreadLink::~ThreadLink(void) | |||||
| { | |||||
| delete[] ring->buffer; | |||||
| delete ring; | |||||
| delete[] write_buffer; | |||||
| delete[] read_buffer; | |||||
| } | |||||
| void ThreadLink::write(const char *dest, const char *args, ...) | |||||
| { | |||||
| va_list va; | |||||
| va_start(va,args); | |||||
| const size_t len = | |||||
| rtosc_vmessage(write_buffer,MaxMsg,dest,args,va); | |||||
| va_end(va); | |||||
| if(ring_write_size(ring) >= len) | |||||
| ring_write(ring,write_buffer,len); | |||||
| } | |||||
| void ThreadLink::writeArray(const char *dest, const char *args, const rtosc_arg_t *aargs) | |||||
| { | |||||
| const size_t len = | |||||
| rtosc_amessage(write_buffer, MaxMsg, dest, args, aargs); | |||||
| if(ring_write_size(ring) >= len) | |||||
| ring_write(ring,write_buffer,len); | |||||
| } | |||||
| /** | |||||
| * Directly write message to ringbuffer | |||||
| */ | |||||
| void ThreadLink::raw_write(const char *msg) | |||||
| { | |||||
| const size_t len = rtosc_message_length(msg, -1);//assumed valid | |||||
| if(ring_write_size(ring) >= len) | |||||
| ring_write(ring,msg,len); | |||||
| } | |||||
| /** | |||||
| * @returns true iff there is another message to be read in the buffer | |||||
| */ | |||||
| bool ThreadLink::hasNext(void) const | |||||
| { | |||||
| return ring_read_size(ring); | |||||
| } | |||||
| /** | |||||
| * Read a new message from the ringbuffer | |||||
| */ | |||||
| msg_t ThreadLink::read(void) { | |||||
| ring_t r[2]; | |||||
| ring_read_vector(ring,r); | |||||
| const size_t len = | |||||
| rtosc_message_ring_length(r); | |||||
| assert(ring_read_size(ring) >= len); | |||||
| assert(len <= MaxMsg); | |||||
| ring_read(ring, read_buffer, len); | |||||
| return read_buffer; | |||||
| } | |||||
| /** | |||||
| * Peak at last message read without reading another | |||||
| */ | |||||
| msg_t ThreadLink::peak(void) const | |||||
| { | |||||
| return read_buffer; | |||||
| } | |||||
| /** | |||||
| * Raw write buffer access for more complicated task | |||||
| */ | |||||
| char *ThreadLink::buffer(void) {return write_buffer;} | |||||
| /** | |||||
| * Access to write buffer length | |||||
| */ | |||||
| size_t ThreadLink::buffer_size(void) const {return BufferSize;} | |||||
| }; | |||||
| @@ -0,0 +1,174 @@ | |||||
| #include <deque> | |||||
| #include <cstring> | |||||
| #include <cstdio> | |||||
| #include <cassert> | |||||
| #include <ctime> | |||||
| #include "rtosc.h" | |||||
| #include "undo-history.h" | |||||
| using std::pair; | |||||
| using std::make_pair; | |||||
| namespace rtosc { | |||||
| class UndoHistoryImpl | |||||
| { | |||||
| public: | |||||
| UndoHistoryImpl(void) | |||||
| :max_history_size(20) | |||||
| {} | |||||
| std::deque<pair<time_t, const char *>> history; | |||||
| long history_pos; | |||||
| unsigned max_history_size;//XXX Expose this via a public API | |||||
| std::function<void(const char*)> cb; | |||||
| void rewind(const char *msg); | |||||
| void replay(const char *msg); | |||||
| bool mergeEvent(time_t t, const char *msg, char *buf, size_t N); | |||||
| }; | |||||
| UndoHistory::UndoHistory(void) | |||||
| { | |||||
| impl = new UndoHistoryImpl; | |||||
| impl->history_pos = 0; | |||||
| } | |||||
| void UndoHistory::recordEvent(const char *msg) | |||||
| { | |||||
| //TODO Properly account for when you have traveled back in time. | |||||
| //while this could result in another branch of history, the simple method | |||||
| //would be to kill off any future redos when new history is recorded | |||||
| if(impl->history.size() != (unsigned) impl->history_pos) { | |||||
| impl->history.resize(impl->history_pos); | |||||
| } | |||||
| size_t len = rtosc_message_length(msg, -1); | |||||
| char *data = new char[len]; | |||||
| time_t now = time(NULL); | |||||
| //printf("now = '%ld'\n", now); | |||||
| if(!impl->mergeEvent(now, msg, data, len)) { | |||||
| memcpy(data, msg, len); | |||||
| impl->history.push_back(make_pair(now, data)); | |||||
| impl->history_pos++; | |||||
| if(impl->history.size() > impl->max_history_size) | |||||
| { | |||||
| delete[] impl->history[0].second; | |||||
| impl->history.pop_front(); | |||||
| impl->history_pos--; | |||||
| } | |||||
| } | |||||
| } | |||||
| void UndoHistory::showHistory(void) const | |||||
| { | |||||
| int i = 0; | |||||
| for(auto s : impl->history) | |||||
| printf("#%d type: %s dest: %s arguments: %s\n", i++, | |||||
| s.second, rtosc_argument(s.second, 0).s, rtosc_argument_string(s.second)); | |||||
| } | |||||
| static char tmp[256]; | |||||
| void UndoHistoryImpl::rewind(const char *msg) | |||||
| { | |||||
| memset(tmp, 0, sizeof(tmp)); | |||||
| printf("rewind('%s')\n", msg); | |||||
| rtosc_arg_t arg = rtosc_argument(msg,1); | |||||
| rtosc_amessage(tmp, 256, rtosc_argument(msg,0).s, | |||||
| rtosc_argument_string(msg)+2, | |||||
| &arg); | |||||
| cb(tmp); | |||||
| } | |||||
| void UndoHistoryImpl::replay(const char *msg) | |||||
| { | |||||
| printf("replay...'%s'\n", msg); | |||||
| rtosc_arg_t arg = rtosc_argument(msg,2); | |||||
| printf("replay address: '%s'\n", rtosc_argument(msg, 0).s); | |||||
| int len = rtosc_amessage(tmp, 256, rtosc_argument(msg,0).s, | |||||
| rtosc_argument_string(msg)+2, | |||||
| &arg); | |||||
| if(len) | |||||
| cb(tmp); | |||||
| } | |||||
| const char *getUndoAddress(const char *msg) | |||||
| { | |||||
| return rtosc_argument(msg,0).s; | |||||
| } | |||||
| bool UndoHistoryImpl::mergeEvent(time_t now, const char *msg, char *buf, size_t N) | |||||
| { | |||||
| if(history_pos == 0) | |||||
| return false; | |||||
| for(int i=history_pos-1; i>=0; --i) { | |||||
| if(difftime(now, history[i].first) > 2) | |||||
| break; | |||||
| if(!strcmp(getUndoAddress(msg), | |||||
| getUndoAddress(history[i].second))) | |||||
| { | |||||
| //We can splice events together, merging them into one event | |||||
| rtosc_arg_t args[3]; | |||||
| args[0] = rtosc_argument(msg, 0); | |||||
| args[1] = rtosc_argument(history[i].second,1); | |||||
| args[2] = rtosc_argument(msg, 2); | |||||
| rtosc_amessage(buf, N, msg, rtosc_argument_string(msg), args); | |||||
| delete [] history[i].second; | |||||
| history[i].second = buf; | |||||
| history[i].first = now; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| void UndoHistory::seekHistory(int distance) | |||||
| { | |||||
| //TODO print out the events that would need to take place to get to the | |||||
| //final destination | |||||
| //TODO limit the distance to be to applicable sizes | |||||
| //ie ones that do not exceed the known history/future | |||||
| long dest = impl->history_pos + distance; | |||||
| if(dest < 0) | |||||
| distance -= dest; | |||||
| if(dest > (long) impl->history.size()) | |||||
| distance = impl->history.size() - impl->history_pos; | |||||
| if(!distance) | |||||
| return; | |||||
| printf("distance == '%d'\n", distance); | |||||
| printf("history_pos == '%ld'\n", impl->history_pos); | |||||
| //TODO account for traveling back in time | |||||
| if(distance<0) | |||||
| while(distance++) | |||||
| impl->rewind(impl->history[--impl->history_pos].second); | |||||
| else | |||||
| while(distance--) | |||||
| impl->replay(impl->history[impl->history_pos++].second); | |||||
| } | |||||
| unsigned UndoHistory::getPos(void) const | |||||
| { | |||||
| return impl->history_pos; | |||||
| } | |||||
| const char *UndoHistory::getHistory(int i) const | |||||
| { | |||||
| return impl->history[i].second; | |||||
| } | |||||
| size_t UndoHistory::size() const | |||||
| { | |||||
| return impl->history.size(); | |||||
| } | |||||
| void UndoHistory::setCallback(std::function<void(const char*)> cb) | |||||
| { | |||||
| impl->cb = cb; | |||||
| } | |||||
| }; | |||||
| @@ -0,0 +1,76 @@ | |||||
| #include "rtosc.h" | |||||
| #include <ctype.h> | |||||
| #include <stdlib.h> | |||||
| static bool rtosc_match_number(const char **pattern, const char **msg) | |||||
| { | |||||
| //Verify both hold digits | |||||
| if(!isdigit(**pattern) || !isdigit(**msg)) | |||||
| return false; | |||||
| //Read in both numeric values | |||||
| unsigned max = atoi(*pattern); | |||||
| unsigned val = atoi(*msg); | |||||
| ////Advance pointers | |||||
| while(isdigit(**pattern))++*pattern; | |||||
| while(isdigit(**msg))++*msg; | |||||
| //Match iff msg number is strictly less than pattern | |||||
| return val < max; | |||||
| } | |||||
| const char *rtosc_match_path(const char *pattern, const char *msg) | |||||
| { | |||||
| while(1) { | |||||
| //Check for special characters | |||||
| if(*pattern == ':' && !*msg) | |||||
| return pattern; | |||||
| else if(*pattern == '/' && *msg == '/') | |||||
| return ++pattern; | |||||
| else if(*pattern == '#') { | |||||
| ++pattern; | |||||
| if(!rtosc_match_number(&pattern, &msg)) | |||||
| return NULL; | |||||
| } else if((*pattern == *msg)) { //verbatim compare | |||||
| if(*msg) | |||||
| ++pattern, ++msg; | |||||
| else | |||||
| return pattern; | |||||
| } else | |||||
| return NULL; | |||||
| } | |||||
| } | |||||
| //Match the arg string or fail | |||||
| static bool rtosc_match_args(const char *pattern, const char *msg) | |||||
| { | |||||
| //match anything if now arg restriction is present (ie the ':') | |||||
| if(*pattern++ != ':') | |||||
| return true; | |||||
| const char *arg_str = rtosc_argument_string(msg); | |||||
| bool arg_match = *pattern || *pattern == *arg_str; | |||||
| while(*pattern && *pattern != ':') | |||||
| arg_match &= (*pattern++==*arg_str++); | |||||
| if(*pattern==':') { | |||||
| if(arg_match && !*arg_str) | |||||
| return true; | |||||
| else | |||||
| return rtosc_match_args(pattern, msg); //retry | |||||
| } | |||||
| return arg_match; | |||||
| } | |||||
| bool rtosc_match(const char *pattern, const char *msg) | |||||
| { | |||||
| const char *arg_pattern = rtosc_match_path(pattern, msg); | |||||
| if(!arg_pattern) | |||||
| return false; | |||||
| else if(*arg_pattern == ':') | |||||
| return rtosc_match_args(arg_pattern, msg); | |||||
| return true; | |||||
| } | |||||
| @@ -0,0 +1,673 @@ | |||||
| #include <stdint.h> | |||||
| #include <stdio.h> | |||||
| #include <string.h> | |||||
| #include <stdarg.h> | |||||
| #include <stdbool.h> | |||||
| #include <ctype.h> | |||||
| #include <assert.h> | |||||
| #include "rtosc.h" | |||||
| const char *rtosc_argument_string(const char *msg) | |||||
| { | |||||
| assert(msg && *msg); | |||||
| while(*++msg); //skip pattern | |||||
| while(!*++msg);//skip null | |||||
| return msg+1; //skip comma | |||||
| } | |||||
| unsigned rtosc_narguments(const char *msg) | |||||
| { | |||||
| const char *args = rtosc_argument_string(msg); | |||||
| int nargs = 0; | |||||
| while(*args++) | |||||
| nargs += (*args == ']' || *args == '[') ? 0 : 1; | |||||
| return nargs; | |||||
| } | |||||
| static int has_reserved(char type) | |||||
| { | |||||
| switch(type) | |||||
| { | |||||
| case 'i'://official types | |||||
| case 's': | |||||
| case 'b': | |||||
| case 'f': | |||||
| case 'h'://unofficial | |||||
| case 't': | |||||
| case 'd': | |||||
| case 'S': | |||||
| case 'r': | |||||
| case 'm': | |||||
| case 'c': | |||||
| return 1; | |||||
| case 'T': | |||||
| case 'F': | |||||
| case 'N': | |||||
| case 'I': | |||||
| case '[': | |||||
| case ']': | |||||
| return 0; | |||||
| } | |||||
| //Should not happen | |||||
| return 0; | |||||
| } | |||||
| static unsigned nreserved(const char *args) | |||||
| { | |||||
| unsigned res = 0; | |||||
| for(;*args;++args) | |||||
| res += has_reserved(*args); | |||||
| return res; | |||||
| } | |||||
| char rtosc_type(const char *msg, unsigned nargument) | |||||
| { | |||||
| assert(nargument < rtosc_narguments(msg)); | |||||
| const char *arg = rtosc_argument_string(msg); | |||||
| while(1) { | |||||
| if(*arg == '[' || *arg == ']') | |||||
| ++arg; | |||||
| else if(!nargument || !*arg) | |||||
| return *arg; | |||||
| else | |||||
| ++arg, --nargument; | |||||
| } | |||||
| } | |||||
| static unsigned arg_off(const char *msg, unsigned idx) | |||||
| { | |||||
| if(!has_reserved(rtosc_type(msg,idx))) | |||||
| return 0; | |||||
| //Iterate to the right position | |||||
| const uint8_t *args = (const uint8_t*) rtosc_argument_string(msg); | |||||
| const uint8_t *aligned_ptr = args-1; | |||||
| const uint8_t *arg_pos = args; | |||||
| while(*++arg_pos); | |||||
| //Alignment | |||||
| arg_pos += 4-(arg_pos-((uint8_t*)aligned_ptr))%4; | |||||
| //ignore any leading '[' or ']' | |||||
| while(*args == '[' || *args == ']') | |||||
| ++args; | |||||
| while(idx--) { | |||||
| uint32_t bundle_length = 0; | |||||
| switch(*args++) | |||||
| { | |||||
| case 'h': | |||||
| case 't': | |||||
| case 'd': | |||||
| arg_pos +=8; | |||||
| break; | |||||
| case 'm': | |||||
| case 'r': | |||||
| case 'f': | |||||
| case 'c': | |||||
| case 'i': | |||||
| arg_pos += 4; | |||||
| break; | |||||
| case 'S': | |||||
| case 's': | |||||
| while(*++arg_pos); | |||||
| arg_pos += 4-(arg_pos-((uint8_t*)aligned_ptr))%4; | |||||
| break; | |||||
| case 'b': | |||||
| bundle_length |= (*arg_pos++ << 24); | |||||
| bundle_length |= (*arg_pos++ << 16); | |||||
| bundle_length |= (*arg_pos++ << 8); | |||||
| bundle_length |= (*arg_pos++); | |||||
| if(bundle_length%4) | |||||
| bundle_length += 4-bundle_length%4; | |||||
| arg_pos += bundle_length; | |||||
| break; | |||||
| case '[': | |||||
| case ']': | |||||
| //completely ignore array chars | |||||
| ++idx; | |||||
| break; | |||||
| case 'T': | |||||
| case 'F': | |||||
| case 'I': | |||||
| ; | |||||
| } | |||||
| } | |||||
| return arg_pos-(uint8_t*)msg; | |||||
| } | |||||
| size_t rtosc_message(char *buffer, | |||||
| size_t len, | |||||
| const char *address, | |||||
| const char *arguments, | |||||
| ...) | |||||
| { | |||||
| va_list va; | |||||
| va_start(va, arguments); | |||||
| size_t result = rtosc_vmessage(buffer, len, address, arguments, va); | |||||
| va_end(va); | |||||
| return result; | |||||
| } | |||||
| //Calculate the size of the message without writing to a buffer | |||||
| static size_t vsosc_null(const char *address, | |||||
| const char *arguments, | |||||
| const rtosc_arg_t *args) | |||||
| { | |||||
| unsigned pos = 0; | |||||
| pos += strlen(address); | |||||
| pos += 4-pos%4;//get 32 bit alignment | |||||
| pos += 1+strlen(arguments); | |||||
| pos += 4-pos%4; | |||||
| unsigned toparse = nreserved(arguments); | |||||
| unsigned arg_pos = 0; | |||||
| //Take care of varargs | |||||
| while(toparse) | |||||
| { | |||||
| char arg = *arguments++; | |||||
| assert(arg); | |||||
| int i; | |||||
| const char *s; | |||||
| switch(arg) { | |||||
| case 'h': | |||||
| case 't': | |||||
| case 'd': | |||||
| ++arg_pos; | |||||
| pos += 8; | |||||
| --toparse; | |||||
| break; | |||||
| case 'm': | |||||
| case 'r': | |||||
| case 'c': | |||||
| case 'f': | |||||
| case 'i': | |||||
| ++arg_pos; | |||||
| pos += 4; | |||||
| --toparse; | |||||
| break; | |||||
| case 's': | |||||
| case 'S': | |||||
| s = args[arg_pos++].s; | |||||
| assert(s && "Input strings CANNOT be NULL"); | |||||
| pos += strlen(s); | |||||
| pos += 4-pos%4; | |||||
| --toparse; | |||||
| break; | |||||
| case 'b': | |||||
| i = args[arg_pos++].b.len; | |||||
| pos += 4 + i; | |||||
| if(pos%4) | |||||
| pos += 4-pos%4; | |||||
| --toparse; | |||||
| break; | |||||
| default: | |||||
| ; | |||||
| } | |||||
| } | |||||
| return pos; | |||||
| } | |||||
| size_t rtosc_vmessage(char *buffer, | |||||
| size_t len, | |||||
| const char *address, | |||||
| const char *arguments, | |||||
| va_list ap) | |||||
| { | |||||
| const unsigned nargs = nreserved(arguments); | |||||
| if(!nargs) | |||||
| return rtosc_amessage(buffer,len,address,arguments,NULL); | |||||
| rtosc_arg_t args[nargs]; | |||||
| unsigned arg_pos = 0; | |||||
| const char *arg_str = arguments; | |||||
| uint8_t *midi_tmp; | |||||
| while(arg_pos < nargs) | |||||
| { | |||||
| switch(*arg_str++) { | |||||
| case 'h': | |||||
| case 't': | |||||
| args[arg_pos++].h = va_arg(ap, int64_t); | |||||
| break; | |||||
| case 'd': | |||||
| args[arg_pos++].d = va_arg(ap, double); | |||||
| break; | |||||
| case 'c': | |||||
| case 'i': | |||||
| case 'r': | |||||
| args[arg_pos++].i = va_arg(ap, int); | |||||
| break; | |||||
| case 'm': | |||||
| midi_tmp = va_arg(ap, uint8_t *); | |||||
| args[arg_pos].m[0] = midi_tmp[0]; | |||||
| args[arg_pos].m[1] = midi_tmp[1]; | |||||
| args[arg_pos].m[2] = midi_tmp[2]; | |||||
| args[arg_pos++].m[3] = midi_tmp[3]; | |||||
| break; | |||||
| case 'S': | |||||
| case 's': | |||||
| args[arg_pos++].s = va_arg(ap, const char *); | |||||
| break; | |||||
| case 'b': | |||||
| args[arg_pos].b.len = va_arg(ap, int); | |||||
| args[arg_pos].b.data = va_arg(ap, unsigned char *); | |||||
| arg_pos++; | |||||
| break; | |||||
| case 'f': | |||||
| args[arg_pos++].f = va_arg(ap, double); | |||||
| break; | |||||
| default: | |||||
| ; | |||||
| } | |||||
| } | |||||
| return rtosc_amessage(buffer,len,address,arguments,args); | |||||
| } | |||||
| size_t rtosc_amessage(char *buffer, | |||||
| size_t len, | |||||
| const char *address, | |||||
| const char *arguments, | |||||
| const rtosc_arg_t *args) | |||||
| { | |||||
| const size_t total_len = vsosc_null(address, arguments, args); | |||||
| if(!buffer) | |||||
| return total_len; | |||||
| //Abort if the message cannot fit | |||||
| if(total_len>len) { | |||||
| memset(buffer, 0, len); | |||||
| return 0; | |||||
| } | |||||
| memset(buffer, 0, total_len); | |||||
| unsigned pos = 0; | |||||
| while(*address) | |||||
| buffer[pos++] = *address++; | |||||
| //get 32 bit alignment | |||||
| pos += 4-pos%4; | |||||
| buffer[pos++] = ','; | |||||
| const char *arg_str = arguments; | |||||
| while(*arg_str) | |||||
| buffer[pos++] = *arg_str++; | |||||
| pos += 4-pos%4; | |||||
| unsigned toparse = nreserved(arguments); | |||||
| unsigned arg_pos = 0; | |||||
| while(toparse) | |||||
| { | |||||
| char arg = *arguments++; | |||||
| assert(arg); | |||||
| int32_t i; | |||||
| int64_t d; | |||||
| const uint8_t *m; | |||||
| const char *s; | |||||
| const unsigned char *u; | |||||
| rtosc_blob_t b; | |||||
| switch(arg) { | |||||
| case 'h': | |||||
| case 't': | |||||
| case 'd': | |||||
| d = args[arg_pos++].t; | |||||
| buffer[pos++] = ((d>>56) & 0xff); | |||||
| buffer[pos++] = ((d>>48) & 0xff); | |||||
| buffer[pos++] = ((d>>40) & 0xff); | |||||
| buffer[pos++] = ((d>>32) & 0xff); | |||||
| buffer[pos++] = ((d>>24) & 0xff); | |||||
| buffer[pos++] = ((d>>16) & 0xff); | |||||
| buffer[pos++] = ((d>>8) & 0xff); | |||||
| buffer[pos++] = (d & 0xff); | |||||
| --toparse; | |||||
| break; | |||||
| case 'r': | |||||
| case 'f': | |||||
| case 'c': | |||||
| case 'i': | |||||
| i = args[arg_pos++].i; | |||||
| buffer[pos++] = ((i>>24) & 0xff); | |||||
| buffer[pos++] = ((i>>16) & 0xff); | |||||
| buffer[pos++] = ((i>>8) & 0xff); | |||||
| buffer[pos++] = (i & 0xff); | |||||
| --toparse; | |||||
| break; | |||||
| case 'm': | |||||
| //TODO verify ordering of spec | |||||
| m = args[arg_pos++].m; | |||||
| buffer[pos++] = m[0]; | |||||
| buffer[pos++] = m[1]; | |||||
| buffer[pos++] = m[2]; | |||||
| buffer[pos++] = m[3]; | |||||
| --toparse; | |||||
| break; | |||||
| case 'S': | |||||
| case 's': | |||||
| s = args[arg_pos++].s; | |||||
| while(*s) | |||||
| buffer[pos++] = *s++; | |||||
| pos += 4-pos%4; | |||||
| --toparse; | |||||
| break; | |||||
| case 'b': | |||||
| b = args[arg_pos++].b; | |||||
| i = b.len; | |||||
| buffer[pos++] = ((i>>24) & 0xff); | |||||
| buffer[pos++] = ((i>>16) & 0xff); | |||||
| buffer[pos++] = ((i>>8) & 0xff); | |||||
| buffer[pos++] = (i & 0xff); | |||||
| u = b.data; | |||||
| if(u) { | |||||
| while(i--) | |||||
| buffer[pos++] = *u++; | |||||
| } | |||||
| else | |||||
| pos += i; | |||||
| if(pos%4) | |||||
| pos += 4-pos%4; | |||||
| --toparse; | |||||
| break; | |||||
| default: | |||||
| ; | |||||
| } | |||||
| } | |||||
| return pos; | |||||
| } | |||||
| rtosc_arg_t rtosc_argument(const char *msg, unsigned idx) | |||||
| { | |||||
| rtosc_arg_t result = {0}; | |||||
| char type = rtosc_type(msg, idx); | |||||
| //trivial case | |||||
| if(!has_reserved(type)) { | |||||
| switch(type) | |||||
| { | |||||
| case 'T': | |||||
| result.T = true; | |||||
| break; | |||||
| case 'F': | |||||
| result.T = false; | |||||
| break; | |||||
| default: | |||||
| ; | |||||
| } | |||||
| } else { | |||||
| const unsigned char *arg_pos = (const unsigned char*)msg+arg_off(msg,idx); | |||||
| switch(type) | |||||
| { | |||||
| case 'h': | |||||
| case 't': | |||||
| case 'd': | |||||
| result.t |= (((uint64_t)*arg_pos++) << 56); | |||||
| result.t |= (((uint64_t)*arg_pos++) << 48); | |||||
| result.t |= (((uint64_t)*arg_pos++) << 40); | |||||
| result.t |= (((uint64_t)*arg_pos++) << 32); | |||||
| result.t |= (((uint64_t)*arg_pos++) << 24); | |||||
| result.t |= (((uint64_t)*arg_pos++) << 16); | |||||
| result.t |= (((uint64_t)*arg_pos++) << 8); | |||||
| result.t |= (((uint64_t)*arg_pos++)); | |||||
| break; | |||||
| case 'r': | |||||
| case 'f': | |||||
| case 'c': | |||||
| case 'i': | |||||
| result.i |= (*arg_pos++ << 24); | |||||
| result.i |= (*arg_pos++ << 16); | |||||
| result.i |= (*arg_pos++ << 8); | |||||
| result.i |= (*arg_pos++); | |||||
| break; | |||||
| case 'm': | |||||
| result.m[0] = *arg_pos++; | |||||
| result.m[1] = *arg_pos++; | |||||
| result.m[2] = *arg_pos++; | |||||
| result.m[3] = *arg_pos++; | |||||
| break; | |||||
| case 'b': | |||||
| result.b.len |= (*arg_pos++ << 24); | |||||
| result.b.len |= (*arg_pos++ << 16); | |||||
| result.b.len |= (*arg_pos++ << 8); | |||||
| result.b.len |= (*arg_pos++); | |||||
| result.b.data = (unsigned char *)arg_pos; | |||||
| break; | |||||
| case 'S': | |||||
| case 's': | |||||
| result.s = (char *)arg_pos; | |||||
| break; | |||||
| } | |||||
| } | |||||
| return result; | |||||
| } | |||||
| static unsigned char deref(unsigned pos, ring_t *ring) | |||||
| { | |||||
| return pos<ring[0].len ? ring[0].data[pos] : | |||||
| ((pos-ring[0].len)<ring[1].len ? ring[1].data[pos-ring[0].len] : 0x00); | |||||
| } | |||||
| static size_t bundle_ring_length(ring_t *ring) | |||||
| { | |||||
| unsigned pos = 8+8;//goto first length field | |||||
| uint32_t advance = 0; | |||||
| do { | |||||
| advance = deref(pos+0, ring) << (8*0) | | |||||
| deref(pos+1, ring) << (8*1) | | |||||
| deref(pos+2, ring) << (8*2) | | |||||
| deref(pos+3, ring) << (8*3); | |||||
| if(advance) | |||||
| pos += 4+advance; | |||||
| } while(advance); | |||||
| return pos <= (ring[0].len+ring[1].len) ? pos : 0; | |||||
| } | |||||
| //Zero means no full message present | |||||
| size_t rtosc_message_ring_length(ring_t *ring) | |||||
| { | |||||
| //Check if the message is a bundle | |||||
| if(deref(0,ring) == '#' && | |||||
| deref(1,ring) == 'b' && | |||||
| deref(2,ring) == 'u' && | |||||
| deref(3,ring) == 'n' && | |||||
| deref(4,ring) == 'd' && | |||||
| deref(5,ring) == 'l' && | |||||
| deref(6,ring) == 'e' && | |||||
| deref(7,ring) == '\0') | |||||
| return bundle_ring_length(ring); | |||||
| //Proceed for normal messages | |||||
| //Consume path | |||||
| unsigned pos = 0; | |||||
| while(deref(pos++,ring)); | |||||
| pos--; | |||||
| //Travel through the null word end [1..4] bytes | |||||
| for(int i=0; i<4; ++i) | |||||
| if(deref(++pos, ring)) | |||||
| break; | |||||
| if(deref(pos, ring) != ',') | |||||
| return 0; | |||||
| unsigned aligned_pos = pos; | |||||
| int arguments = pos+1; | |||||
| while(deref(++pos,ring)); | |||||
| pos += 4-(pos-aligned_pos)%4; | |||||
| unsigned toparse = 0; | |||||
| { | |||||
| int arg = arguments-1; | |||||
| while(deref(++arg,ring)) | |||||
| toparse += has_reserved(deref(arg,ring)); | |||||
| } | |||||
| //Take care of varargs | |||||
| while(toparse) | |||||
| { | |||||
| char arg = deref(arguments++,ring); | |||||
| assert(arg); | |||||
| uint32_t i; | |||||
| switch(arg) { | |||||
| case 'h': | |||||
| case 't': | |||||
| case 'd': | |||||
| pos += 8; | |||||
| --toparse; | |||||
| break; | |||||
| case 'm': | |||||
| case 'r': | |||||
| case 'c': | |||||
| case 'f': | |||||
| case 'i': | |||||
| pos += 4; | |||||
| --toparse; | |||||
| break; | |||||
| case 'S': | |||||
| case 's': | |||||
| while(deref(++pos,ring)); | |||||
| pos += 4-(pos-aligned_pos)%4; | |||||
| --toparse; | |||||
| break; | |||||
| case 'b': | |||||
| i = 0; | |||||
| i |= (deref(pos++,ring) << 24); | |||||
| i |= (deref(pos++,ring) << 16); | |||||
| i |= (deref(pos++,ring) << 8); | |||||
| i |= (deref(pos++,ring)); | |||||
| pos += i; | |||||
| if((pos-aligned_pos)%4) | |||||
| pos += 4-(pos-aligned_pos)%4; | |||||
| --toparse; | |||||
| break; | |||||
| default: | |||||
| ; | |||||
| } | |||||
| } | |||||
| return pos <= (ring[0].len+ring[1].len) ? pos : 0; | |||||
| } | |||||
| size_t rtosc_message_length(const char *msg, size_t len) | |||||
| { | |||||
| ring_t ring[2] = {{(char*)msg,len},{NULL,0}}; | |||||
| return rtosc_message_ring_length(ring); | |||||
| } | |||||
| bool rtosc_valid_message_p(const char *msg, size_t len) | |||||
| { | |||||
| //Validate Path Characters (assumes printable characters are sufficient) | |||||
| if(*msg != '/') | |||||
| return false; | |||||
| const char *tmp = msg; | |||||
| for(unsigned i=0; i<len; ++i) { | |||||
| if(*tmp == 0) | |||||
| break; | |||||
| if(!isprint(*tmp)) | |||||
| return false; | |||||
| tmp++; | |||||
| } | |||||
| //tmp is now either pointing to a null or the end of the string | |||||
| const size_t offset1 = tmp-msg; | |||||
| size_t offset2 = tmp-msg; | |||||
| for(; offset2<len; offset2++) { | |||||
| if(*tmp == ',') | |||||
| break; | |||||
| tmp++; | |||||
| } | |||||
| //Too many NULL bytes | |||||
| if(offset2-offset1 > 4) | |||||
| return false; | |||||
| if((offset2 % 4) != 0) | |||||
| return false; | |||||
| size_t observed_length = rtosc_message_length(msg, len); | |||||
| return observed_length == len; | |||||
| } | |||||
| size_t rtosc_bundle(char *buffer, size_t len, uint64_t tt, int elms, ...) | |||||
| { | |||||
| char *_buffer = buffer; | |||||
| memset(buffer, 0, len); | |||||
| strcpy(buffer, "#bundle"); | |||||
| buffer += 8; | |||||
| (*(uint64_t*)buffer) = tt; | |||||
| buffer +=8; | |||||
| va_list va; | |||||
| va_start(va, elms); | |||||
| for(int i=0; i<elms; ++i) { | |||||
| const char *msg = va_arg(va, const char*); | |||||
| //It is assumed that any passed message/bundle is valid | |||||
| size_t size = rtosc_message_length(msg, -1); | |||||
| *(uint32_t*)buffer = size; | |||||
| buffer += 4; | |||||
| memcpy(buffer, msg, size); | |||||
| buffer+=size; | |||||
| } | |||||
| va_end(va); | |||||
| return buffer-_buffer; | |||||
| } | |||||
| #define POS ((size_t)(((const char *)lengths) - buffer)) | |||||
| size_t rtosc_bundle_elements(const char *buffer, size_t len) | |||||
| { | |||||
| const uint32_t *lengths = (const uint32_t*) (buffer+16); | |||||
| size_t elms = 0; | |||||
| //TODO | |||||
| while(POS < len && *lengths) { | |||||
| lengths += *lengths/4+1; | |||||
| if(POS > len) | |||||
| break; | |||||
| ++elms; | |||||
| } | |||||
| return elms; | |||||
| } | |||||
| #undef POS | |||||
| const char *rtosc_bundle_fetch(const char *buffer, unsigned elm) | |||||
| { | |||||
| const uint32_t *lengths = (const uint32_t*) (buffer+16); | |||||
| size_t elm_pos = 0; | |||||
| while(elm_pos!=elm && *lengths) ++elm_pos, lengths+=*lengths/4+1; | |||||
| return (const char*) (elm==elm_pos?lengths+1:NULL); | |||||
| } | |||||
| size_t rtosc_bundle_size(const char *buffer, unsigned elm) | |||||
| { | |||||
| const uint32_t *lengths = (const uint32_t*) (buffer+16); | |||||
| size_t elm_pos = 0; | |||||
| size_t last_len = 0; | |||||
| while(elm_pos!=elm && *lengths) { | |||||
| last_len = *lengths; | |||||
| ++elm_pos, lengths+=*lengths/4+1; | |||||
| } | |||||
| return last_len; | |||||
| } | |||||
| int rtosc_bundle_p(const char *msg) | |||||
| { | |||||
| return !strcmp(msg,"#bundle"); | |||||
| } | |||||
| uint64_t rtosc_bundle_timetag(const char *msg) | |||||
| { | |||||
| return *(uint64_t*)(msg+8); | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| #include <cstddef> | |||||
| namespace rtosc{struct Ports; struct RtData;} | |||||
| size_t subtree_serialize(char *buffer, size_t buffer_size, | |||||
| void *object, rtosc::Ports *ports); | |||||
| void subtree_deserialize(char *buffer, size_t buffer_size, | |||||
| void *object, rtosc::Ports *ports, rtosc::RtData &d); | |||||
| @@ -0,0 +1,97 @@ | |||||
| /* | |||||
| * Copyright (c) 2012 Mark McCurry | |||||
| * | |||||
| * Permission is hereby granted, free of charge, to any person obtaining a | |||||
| * copy of this software and associated documentation files (the "Software"), | |||||
| * to deal in the Software without restriction, including without limitation | |||||
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||||
| * and/or sell copies of the Software, and to permit persons to whom the | |||||
| * Software is furnished to do so, subject to the following conditions: | |||||
| * | |||||
| * The above copyright notice and this permission notice (including the next | |||||
| * paragraph) shall be included in all copies or substantial portions of the | |||||
| * Software. | |||||
| * | |||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |||||
| * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
| * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |||||
| * DEALINGS IN THE SOFTWARE. | |||||
| */ | |||||
| #ifndef RTOSC_THREAD_LINK | |||||
| #define RTOSC_THREAD_LINK | |||||
| #include <cstring> | |||||
| #include <cassert> | |||||
| #include <cstdio> | |||||
| #include "rtosc.h" | |||||
| namespace rtosc { | |||||
| typedef const char *msg_t; | |||||
| /** | |||||
| * ThreadLink - A simple wrapper around jack's ringbuffers desinged to make | |||||
| * sending messages via rt-osc trivial. | |||||
| * This class provides the basics of reading and writing events via fixed sized | |||||
| * buffers, which can be specified at compile time. | |||||
| */ | |||||
| class ThreadLink | |||||
| { | |||||
| public: | |||||
| ThreadLink(size_t max_message_length, size_t max_messages); | |||||
| ~ThreadLink(void); | |||||
| /** | |||||
| * Write message to ringbuffer | |||||
| * @see rtosc_message() | |||||
| */ | |||||
| void write(const char *dest, const char *args, ...); | |||||
| /** | |||||
| * Write an arary of arguments to ringbuffer | |||||
| * @see rtosc_amessage() | |||||
| */ | |||||
| void writeArray(const char *dest, const char *args, const rtosc_arg_t *aargs); | |||||
| /** | |||||
| * Directly write message to ringbuffer | |||||
| */ | |||||
| void raw_write(const char *msg); | |||||
| /** | |||||
| * @returns true iff there is another message to be read in the buffer | |||||
| */ | |||||
| bool hasNext(void) const; | |||||
| /** | |||||
| * Read a new message from the ringbuffer | |||||
| */ | |||||
| msg_t read(void); | |||||
| /** | |||||
| * Peak at last message read without reading another | |||||
| */ | |||||
| msg_t peak(void) const; | |||||
| /** | |||||
| * Raw write buffer access for more complicated task | |||||
| */ | |||||
| char *buffer(void); | |||||
| /** | |||||
| * Access to write buffer length | |||||
| */ | |||||
| size_t buffer_size(void) const; | |||||
| private: | |||||
| const size_t MaxMsg; | |||||
| const size_t BufferSize; | |||||
| char *write_buffer; | |||||
| char *read_buffer; | |||||
| struct internal_ringbuffer_t *ring; | |||||
| }; | |||||
| }; | |||||
| #endif | |||||
| @@ -0,0 +1,116 @@ | |||||
| #include "rtosc.h" | |||||
| #include <type_traits> | |||||
| #include <stdexcept> | |||||
| namespace rtosc | |||||
| { | |||||
| template<class... Types> class rtMsg; | |||||
| // empty tuple | |||||
| template<> class rtMsg<> | |||||
| { | |||||
| public: | |||||
| rtMsg(const char *arg = NULL, const char *spec=NULL, bool _=false) | |||||
| :msg(arg) | |||||
| { | |||||
| if(arg && spec && !rtosc_match_path(spec, arg)) | |||||
| msg = NULL; | |||||
| (void)_; | |||||
| } | |||||
| operator bool(void){return this->msg;} | |||||
| const char *msg; | |||||
| }; | |||||
| template<class T> | |||||
| bool valid_char(char) { return false;} | |||||
| template<> | |||||
| bool valid_char<const char*>(char c) { return c=='s' || c=='S'; }; | |||||
| template<> | |||||
| bool valid_char<int32_t>(char c) { return c=='i'; }; | |||||
| template<> | |||||
| bool valid_char<float>(char c) { return c=='f'; }; | |||||
| template<int i> | |||||
| bool validate(const char *arg) | |||||
| { | |||||
| return rtosc_narguments(arg) == i; | |||||
| } | |||||
| template<int i, class This, class... Rest> | |||||
| bool validate(const char *arg) | |||||
| { | |||||
| if(!valid_char<This>(rtosc_type(arg,i))) | |||||
| return false; | |||||
| else | |||||
| return validate<i+1,Rest...>(arg); | |||||
| } | |||||
| //Tuple Like Template Class Definition | |||||
| template<class This, class... Rest> | |||||
| class rtMsg<This, Rest...>:public rtMsg<Rest...> | |||||
| { | |||||
| public: | |||||
| typedef rtMsg<Rest...> T; | |||||
| rtMsg(const char *arg = NULL, const char *spec=NULL) | |||||
| :T(arg, spec, false) | |||||
| { | |||||
| if(this->msg && !validate<0,This,Rest...>(this->msg)) | |||||
| this->msg = NULL; | |||||
| } | |||||
| rtMsg(const char *arg, const char *spec, bool) | |||||
| :T(arg, spec, false) | |||||
| {} | |||||
| }; | |||||
| // tuple_element | |||||
| template<size_t Index, class Tuple> struct osc_element; | |||||
| // select first element | |||||
| template<class This, class... Rest> | |||||
| struct osc_element<0, rtMsg<This, Rest...>> | |||||
| { | |||||
| typedef This type; | |||||
| }; | |||||
| // recursive tuple_element definition | |||||
| template <size_t Index, class This, class... Rest> | |||||
| struct osc_element<Index, rtMsg<This, Rest...>> | |||||
| : public osc_element<Index - 1, rtMsg<Rest...>> | |||||
| { | |||||
| }; | |||||
| template<class T> | |||||
| T rt_get_impl(const char *msg, size_t i); | |||||
| template<> | |||||
| const char *rt_get_impl(const char *msg, size_t i) | |||||
| { | |||||
| return rtosc_argument(msg,i).s; | |||||
| } | |||||
| template<> | |||||
| int32_t rt_get_impl(const char *msg, size_t i) | |||||
| { | |||||
| return rtosc_argument(msg,i).i; | |||||
| } | |||||
| // get reference to _Index element of tuple | |||||
| template<size_t Index, class... Types> inline | |||||
| typename osc_element<Index, rtMsg<Types...>>::type | |||||
| get(rtMsg<Types...>& Tuple) | |||||
| { | |||||
| if(!Tuple.msg) | |||||
| throw std::invalid_argument("Message Does Not Match Spec"); | |||||
| typedef typename std::remove_reference<typename osc_element<Index, rtMsg<Types...>>::type>::type T; | |||||
| return rt_get_impl<T>(Tuple.msg, Index); | |||||
| } | |||||
| }; | |||||
| @@ -0,0 +1,34 @@ | |||||
| #pragma once | |||||
| #include <functional> | |||||
| namespace rtosc | |||||
| { | |||||
| /** | |||||
| * Known event types: | |||||
| * /undo_change /path/location old-data new-data | |||||
| */ | |||||
| class UndoHistory | |||||
| { | |||||
| //TODO think about the consequences of largish loads | |||||
| public: | |||||
| UndoHistory(void); | |||||
| //Records any undoable event | |||||
| void recordEvent(const char *msg); | |||||
| //Prints out a history | |||||
| void showHistory(void) const; | |||||
| //Seek to another point in history relative to the current one | |||||
| //Negative values mean undo, positive values mean redo | |||||
| void seekHistory(int distance); | |||||
| unsigned getPos(void) const; | |||||
| const char *getHistory(int i) const; | |||||
| size_t size(void) const; | |||||
| void setCallback(std::function<void(const char*)> cb); | |||||
| private: | |||||
| class UndoHistoryImpl *impl; | |||||
| }; | |||||
| }; | |||||
| @@ -19,21 +19,20 @@ BUILD_CXX_FLAGS += -I.. -isystem $(CWD)/modules -I$(CWD)/modules/distrho | |||||
| ifeq ($(HAVE_ZYN_DEPS),true) | ifeq ($(HAVE_ZYN_DEPS),true) | ||||
| ZYN_CXX_FLAGS = $(BUILD_CXX_FLAGS) -isystem zynaddsubfx | ZYN_CXX_FLAGS = $(BUILD_CXX_FLAGS) -isystem zynaddsubfx | ||||
| ZYN_CXX_FLAGS += $(shell pkg-config --cflags fftw3 mxml zlib) | ZYN_CXX_FLAGS += $(shell pkg-config --cflags fftw3 mxml zlib) | ||||
| ifneq ($(MACOS),true) | |||||
| ZYN_CXX_FLAGS += -DHAVE_SCHEDULER | |||||
| endif | |||||
| ifeq ($(HAVE_ZYN_UI_DEPS),true) | ifeq ($(HAVE_ZYN_UI_DEPS),true) | ||||
| ifeq ($(HAVE_NTK),true) | ifeq ($(HAVE_NTK),true) | ||||
| FLUID = ntk-fluid | FLUID = ntk-fluid | ||||
| ZYN_CXX_FLAGS += -DNTK_GUI | ZYN_CXX_FLAGS += -DNTK_GUI | ||||
| ZYN_CXX_FLAGS += $(shell pkg-config --cflags ntk_images ntk) | ZYN_CXX_FLAGS += $(shell pkg-config --cflags ntk_images ntk) | ||||
| else | |||||
| else # HAVE_NTK | |||||
| FLUID = fluid | FLUID = fluid | ||||
| ZYN_CXX_FLAGS += -DFLTK_GUI | ZYN_CXX_FLAGS += -DFLTK_GUI | ||||
| ZYN_CXX_FLAGS += $(shell fltk-config --use-images --cxxflags) | ZYN_CXX_FLAGS += $(shell fltk-config --use-images --cxxflags) | ||||
| endif | |||||
| endif | |||||
| endif | |||||
| endif # HAVE_NTK | |||||
| else # HAVE_ZYN_UI_DEPS | |||||
| ZYN_CXX_FLAGS += -DNO_UI | |||||
| endif # HAVE_ZYN_UI_DEPS | |||||
| endif # HAVE_ZYN_DEPS | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | # ---------------------------------------------------------------------------------------------------------------------------- | ||||
| # Set objects | # Set objects | ||||
| @@ -1,6 +1,6 @@ | |||||
| /* | /* | ||||
| * Carla Native Plugins | * Carla Native Plugins | ||||
| * Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> | |||||
| * Copyright (C) 2012-2015 Filipe Coelho <falktx@falktx.com> | |||||
| * | * | ||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
| * modify it under the terms of the GNU General Public License as | * modify it under the terms of the GNU General Public License as | ||||
| @@ -25,13 +25,16 @@ | |||||
| #include "Effects/Echo.h" | #include "Effects/Echo.h" | ||||
| #include "Effects/Phaser.h" | #include "Effects/Phaser.h" | ||||
| #include "Effects/Reverb.h" | #include "Effects/Reverb.h" | ||||
| #include "Misc/Allocator.h" | |||||
| #include "juce_audio_basics.h" | #include "juce_audio_basics.h" | ||||
| using juce::roundToIntAccurate; | using juce::roundToIntAccurate; | ||||
| using juce::FloatVectorOperations; | using juce::FloatVectorOperations; | ||||
| using juce::SharedResourcePointer; | |||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| template<class ZynFX> | |||||
| class FxAbstractPlugin : public NativePluginClass | class FxAbstractPlugin : public NativePluginClass | ||||
| { | { | ||||
| protected: | protected: | ||||
| @@ -39,18 +42,21 @@ protected: | |||||
| : NativePluginClass(host), | : NativePluginClass(host), | ||||
| fParamCount(paramCount-2), // volume and pan handled by host | fParamCount(paramCount-2), // volume and pan handled by host | ||||
| fProgramCount(programCount), | fProgramCount(programCount), | ||||
| fBufferSize(getBufferSize()), | |||||
| fSampleRate(getSampleRate()), | |||||
| fEffect(nullptr), | fEffect(nullptr), | ||||
| efxoutl(nullptr), | efxoutl(nullptr), | ||||
| efxoutr(nullptr), | efxoutr(nullptr), | ||||
| leakDetector_FxAbstractPlugin() | leakDetector_FxAbstractPlugin() | ||||
| { | { | ||||
| const int bufferSize(static_cast<int>(getBufferSize())); | |||||
| const int ibufferSize(static_cast<int>(fBufferSize)); | |||||
| efxoutl = new float[bufferSize]; | |||||
| efxoutr = new float[bufferSize]; | |||||
| efxoutl = new float[fBufferSize]; | |||||
| efxoutr = new float[fBufferSize]; | |||||
| FloatVectorOperations::clear(efxoutl, ibufferSize); | |||||
| FloatVectorOperations::clear(efxoutr, ibufferSize); | |||||
| FloatVectorOperations::clear(efxoutl, bufferSize); | |||||
| FloatVectorOperations::clear(efxoutr, bufferSize); | |||||
| doReinit(true); | |||||
| } | } | ||||
| ~FxAbstractPlugin() override | ~FxAbstractPlugin() override | ||||
| @@ -147,7 +153,11 @@ protected: | |||||
| void bufferSizeChanged(const uint32_t bufferSize) final | void bufferSizeChanged(const uint32_t bufferSize) final | ||||
| { | { | ||||
| const int ibufferSize(static_cast<int>(getBufferSize())); | |||||
| if (fBufferSize == bufferSize) | |||||
| return; | |||||
| fBufferSize = bufferSize; | |||||
| const int ibufferSize(static_cast<int>(fBufferSize)); | |||||
| delete[] efxoutl; | delete[] efxoutl; | ||||
| delete[] efxoutr; | delete[] efxoutr; | ||||
| @@ -156,52 +166,74 @@ protected: | |||||
| FloatVectorOperations::clear(efxoutl, ibufferSize); | FloatVectorOperations::clear(efxoutl, ibufferSize); | ||||
| FloatVectorOperations::clear(efxoutr, ibufferSize); | FloatVectorOperations::clear(efxoutr, ibufferSize); | ||||
| doReinit(getSampleRate(), bufferSize); | |||||
| doReinit(false); | |||||
| } | } | ||||
| void sampleRateChanged(const double sampleRate) final | void sampleRateChanged(const double sampleRate) final | ||||
| { | { | ||||
| doReinit(sampleRate, getBufferSize()); | |||||
| if (fSampleRate == sampleRate) | |||||
| return; | |||||
| fSampleRate = sampleRate; | |||||
| doReinit(false); | |||||
| } | } | ||||
| void doReinit(const double sampleRate, const uint32_t bufferSize) | |||||
| void doReinit(const bool firstInit) | |||||
| { | { | ||||
| uchar params[fParamCount]; | uchar params[fParamCount]; | ||||
| for (int i=0, count=static_cast<int>(fParamCount); i<count; ++i) | |||||
| params[i] = fEffect->getpar(i+2); | |||||
| if (fEffect != nullptr) | |||||
| { | |||||
| for (int i=0, count=static_cast<int>(fParamCount); i<count; ++i) | |||||
| params[i] = fEffect->getpar(i+2); | |||||
| reinit(static_cast<uint>(sampleRate), static_cast<int>(bufferSize)); | |||||
| delete fEffect; | |||||
| } | |||||
| for (int i=0, count=static_cast<int>(fParamCount); i<count; ++i) | |||||
| fEffect->changepar(i+2, params[i]); | |||||
| } | |||||
| EffectParams pars(fAllocator.getObject(), false, efxoutl, efxoutr, 0, static_cast<uint>(fSampleRate), static_cast<int>(fBufferSize)); | |||||
| fEffect = new ZynFX(pars); | |||||
| if (firstInit) | |||||
| { | |||||
| fEffect->setpreset(0); | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i=0, count=static_cast<int>(fParamCount); i<count; ++i) | |||||
| fEffect->changepar(i+2, params[i]); | |||||
| } | |||||
| virtual void reinit(const uint sampleRate, const int bufferSize) = 0; | |||||
| // reset volume and pan | |||||
| fEffect->changepar(0, 127); | |||||
| fEffect->changepar(1, 64); | |||||
| } | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| const uint32_t fParamCount; | const uint32_t fParamCount; | ||||
| const uint32_t fProgramCount; | const uint32_t fProgramCount; | ||||
| uint32_t fBufferSize; | |||||
| double fSampleRate; | |||||
| Effect* fEffect; | Effect* fEffect; | ||||
| float* efxoutl; | float* efxoutl; | ||||
| float* efxoutr; | float* efxoutr; | ||||
| SharedResourcePointer<Allocator> fAllocator; | |||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxAbstractPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxAbstractPlugin) | ||||
| }; | }; | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| class FxAlienWahPlugin : public FxAbstractPlugin | |||||
| class FxAlienWahPlugin : public FxAbstractPlugin<Alienwah> | |||||
| { | { | ||||
| public: | public: | ||||
| FxAlienWahPlugin(const NativeHostDescriptor* const host) | FxAlienWahPlugin(const NativeHostDescriptor* const host) | ||||
| : FxAbstractPlugin(host, 11, 4), | : FxAbstractPlugin(host, 11, 4), | ||||
| leakDetector_FxAlienWahPlugin() | |||||
| { | |||||
| fEffect = new Alienwah(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||||
| } | |||||
| leakDetector_FxAlienWahPlugin() {} | |||||
| protected: | protected: | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -327,27 +359,18 @@ protected: | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| void reinit(const uint sampleRate, const int bufferSize) override | |||||
| { | |||||
| delete fEffect; | |||||
| fEffect = new Alienwah(false, efxoutl, efxoutr, sampleRate, bufferSize); | |||||
| } | |||||
| PluginClassEND(FxAlienWahPlugin) | PluginClassEND(FxAlienWahPlugin) | ||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxAlienWahPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxAlienWahPlugin) | ||||
| }; | }; | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| class FxChorusPlugin : public FxAbstractPlugin | |||||
| class FxChorusPlugin : public FxAbstractPlugin<Chorus> | |||||
| { | { | ||||
| public: | public: | ||||
| FxChorusPlugin(const NativeHostDescriptor* const host) | FxChorusPlugin(const NativeHostDescriptor* const host) | ||||
| : FxAbstractPlugin(host, 12, 10), | : FxAbstractPlugin(host, 12, 10), | ||||
| leakDetector_FxChorusPlugin() | |||||
| { | |||||
| fEffect = new Chorus(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||||
| } | |||||
| leakDetector_FxChorusPlugin() {} | |||||
| protected: | protected: | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -497,27 +520,18 @@ protected: | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| void reinit(const uint sampleRate, const int bufferSize) override | |||||
| { | |||||
| delete fEffect; | |||||
| fEffect = new Chorus(false, efxoutl, efxoutr, sampleRate, bufferSize); | |||||
| } | |||||
| PluginClassEND(FxChorusPlugin) | PluginClassEND(FxChorusPlugin) | ||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxChorusPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxChorusPlugin) | ||||
| }; | }; | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| class FxDistortionPlugin : public FxAbstractPlugin | |||||
| class FxDistortionPlugin : public FxAbstractPlugin<Distorsion> | |||||
| { | { | ||||
| public: | public: | ||||
| FxDistortionPlugin(const NativeHostDescriptor* const host) | FxDistortionPlugin(const NativeHostDescriptor* const host) | ||||
| : FxAbstractPlugin(host, 11, 6), | : FxAbstractPlugin(host, 11, 6), | ||||
| leakDetector_FxDistortionPlugin() | |||||
| { | |||||
| fEffect = new Distorsion(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||||
| } | |||||
| leakDetector_FxDistortionPlugin() {} | |||||
| protected: | protected: | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -675,27 +689,18 @@ protected: | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| void reinit(const uint sampleRate, const int bufferSize) override | |||||
| { | |||||
| delete fEffect; | |||||
| fEffect = new Distorsion(false, efxoutl, efxoutr, sampleRate, bufferSize); | |||||
| } | |||||
| PluginClassEND(FxDistortionPlugin) | PluginClassEND(FxDistortionPlugin) | ||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxDistortionPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxDistortionPlugin) | ||||
| }; | }; | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| class FxDynamicFilterPlugin : public FxAbstractPlugin | |||||
| class FxDynamicFilterPlugin : public FxAbstractPlugin<DynamicFilter> | |||||
| { | { | ||||
| public: | public: | ||||
| FxDynamicFilterPlugin(const NativeHostDescriptor* const host) | FxDynamicFilterPlugin(const NativeHostDescriptor* const host) | ||||
| : FxAbstractPlugin(host, 10, 5), | : FxAbstractPlugin(host, 10, 5), | ||||
| leakDetector_FxDynamicFilterPlugin() | |||||
| { | |||||
| fEffect = new DynamicFilter(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||||
| } | |||||
| leakDetector_FxDynamicFilterPlugin() {} | |||||
| protected: | protected: | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -819,27 +824,18 @@ protected: | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| void reinit(const uint sampleRate, const int bufferSize) override | |||||
| { | |||||
| delete fEffect; | |||||
| fEffect = new DynamicFilter(false, efxoutl, efxoutr, sampleRate, bufferSize); | |||||
| } | |||||
| PluginClassEND(FxDynamicFilterPlugin) | PluginClassEND(FxDynamicFilterPlugin) | ||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxDynamicFilterPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxDynamicFilterPlugin) | ||||
| }; | }; | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| class FxEchoPlugin : public FxAbstractPlugin | |||||
| class FxEchoPlugin : public FxAbstractPlugin<Echo> | |||||
| { | { | ||||
| public: | public: | ||||
| FxEchoPlugin(const NativeHostDescriptor* const host) | FxEchoPlugin(const NativeHostDescriptor* const host) | ||||
| : FxAbstractPlugin(host, 7, 9), | : FxAbstractPlugin(host, 7, 9), | ||||
| leakDetector_FxEchoPlugin() | |||||
| { | |||||
| fEffect = new Echo(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||||
| } | |||||
| leakDetector_FxEchoPlugin() {} | |||||
| protected: | protected: | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -951,27 +947,18 @@ protected: | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| void reinit(const uint sampleRate, const int bufferSize) override | |||||
| { | |||||
| delete fEffect; | |||||
| fEffect = new Echo(false, efxoutl, efxoutr, sampleRate, bufferSize); | |||||
| } | |||||
| PluginClassEND(FxEchoPlugin) | PluginClassEND(FxEchoPlugin) | ||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxEchoPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxEchoPlugin) | ||||
| }; | }; | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| class FxPhaserPlugin : public FxAbstractPlugin | |||||
| class FxPhaserPlugin : public FxAbstractPlugin<Phaser> | |||||
| { | { | ||||
| public: | public: | ||||
| FxPhaserPlugin(const NativeHostDescriptor* const host) | FxPhaserPlugin(const NativeHostDescriptor* const host) | ||||
| : FxAbstractPlugin(host, 15, 12), | : FxAbstractPlugin(host, 15, 12), | ||||
| leakDetector_FxPhaserPlugin() | |||||
| { | |||||
| fEffect = new Phaser(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||||
| } | |||||
| leakDetector_FxPhaserPlugin() {} | |||||
| protected: | protected: | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -1144,27 +1131,18 @@ protected: | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| void reinit(const uint sampleRate, const int bufferSize) override | |||||
| { | |||||
| delete fEffect; | |||||
| fEffect = new Phaser(false, efxoutl, efxoutr, sampleRate, bufferSize); | |||||
| } | |||||
| PluginClassEND(FxPhaserPlugin) | PluginClassEND(FxPhaserPlugin) | ||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxPhaserPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxPhaserPlugin) | ||||
| }; | }; | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| class FxReverbPlugin : public FxAbstractPlugin | |||||
| class FxReverbPlugin : public FxAbstractPlugin<Reverb> | |||||
| { | { | ||||
| public: | public: | ||||
| FxReverbPlugin(const NativeHostDescriptor* const host) | FxReverbPlugin(const NativeHostDescriptor* const host) | ||||
| : FxAbstractPlugin(host, 13, 13), | : FxAbstractPlugin(host, 13, 13), | ||||
| leakDetector_FxReverbPlugin() | |||||
| { | |||||
| fEffect = new Reverb(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||||
| } | |||||
| leakDetector_FxReverbPlugin() {} | |||||
| protected: | protected: | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -1323,12 +1301,6 @@ protected: | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| void reinit(const uint sampleRate, const int bufferSize) override | |||||
| { | |||||
| delete fEffect; | |||||
| fEffect = new Reverb(false, efxoutl, efxoutr, sampleRate, bufferSize); | |||||
| } | |||||
| PluginClassEND(FxReverbPlugin) | PluginClassEND(FxReverbPlugin) | ||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxReverbPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxReverbPlugin) | ||||
| }; | }; | ||||
| @@ -1,6 +1,6 @@ | |||||
| /* | /* | ||||
| * Carla Native Plugins | * Carla Native Plugins | ||||
| * Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> | |||||
| * Copyright (C) 2012-2015 Filipe Coelho <falktx@falktx.com> | |||||
| * | * | ||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
| * modify it under the terms of the GNU General Public License as | * modify it under the terms of the GNU General Public License as | ||||
| @@ -22,6 +22,13 @@ | |||||
| #define warnx(...) | #define warnx(...) | ||||
| #endif | #endif | ||||
| #define PLUGINVERSION | |||||
| #include "zynaddsubfx/tlsf/tlsf.h" | |||||
| extern "C" { | |||||
| #include "zynaddsubfx/tlsf/tlsf.c" | |||||
| } | |||||
| // zynaddsubfx includes | // zynaddsubfx includes | ||||
| #include "zynaddsubfx/DSP/AnalogFilter.cpp" | #include "zynaddsubfx/DSP/AnalogFilter.cpp" | ||||
| #include "zynaddsubfx/DSP/FFTwrapper.cpp" | #include "zynaddsubfx/DSP/FFTwrapper.cpp" | ||||
| @@ -40,12 +47,14 @@ | |||||
| #include "zynaddsubfx/Effects/EQ.cpp" | #include "zynaddsubfx/Effects/EQ.cpp" | ||||
| #include "zynaddsubfx/Effects/Phaser.cpp" | #include "zynaddsubfx/Effects/Phaser.cpp" | ||||
| #include "zynaddsubfx/Effects/Reverb.cpp" | #include "zynaddsubfx/Effects/Reverb.cpp" | ||||
| #include "zynaddsubfx/Misc/Allocator.cpp" | |||||
| #include "zynaddsubfx/Misc/Bank.cpp" | #include "zynaddsubfx/Misc/Bank.cpp" | ||||
| #include "zynaddsubfx/Misc/Config.cpp" | #include "zynaddsubfx/Misc/Config.cpp" | ||||
| #include "zynaddsubfx/Misc/Dump.cpp" | |||||
| #include "zynaddsubfx/Misc/Master.cpp" | #include "zynaddsubfx/Misc/Master.cpp" | ||||
| #include "zynaddsubfx/Misc/Microtonal.cpp" | #include "zynaddsubfx/Misc/Microtonal.cpp" | ||||
| #include "zynaddsubfx/Misc/MiddleWare.cpp" | |||||
| #include "zynaddsubfx/Misc/Part.cpp" | #include "zynaddsubfx/Misc/Part.cpp" | ||||
| #include "zynaddsubfx/Misc/PresetExtractor.cpp" | |||||
| #include "zynaddsubfx/Misc/Recorder.cpp" | #include "zynaddsubfx/Misc/Recorder.cpp" | ||||
| //#include "zynaddsubfx/Misc/Stereo.cpp" | //#include "zynaddsubfx/Misc/Stereo.cpp" | ||||
| #include "zynaddsubfx/Misc/Util.cpp" | #include "zynaddsubfx/Misc/Util.cpp" | ||||
| @@ -71,21 +80,25 @@ | |||||
| #include "zynaddsubfx/Synth/SUBnote.cpp" | #include "zynaddsubfx/Synth/SUBnote.cpp" | ||||
| #include "zynaddsubfx/Synth/SynthNote.cpp" | #include "zynaddsubfx/Synth/SynthNote.cpp" | ||||
| #ifdef NO_UI | |||||
| #include "zynaddsubfx/UI/ConnectionDummy.cpp" | |||||
| #endif | |||||
| // Dummy variables and functions for linking purposes | // Dummy variables and functions for linking purposes | ||||
| const char* instance_name = nullptr; | |||||
| SYNTH_T* synth = nullptr; | |||||
| // const char* instance_name = nullptr; | |||||
| // SYNTH_T* synth = nullptr; | |||||
| class WavFile; | class WavFile; | ||||
| namespace Nio { | namespace Nio { | ||||
| bool start(void){return 1;} | |||||
| void stop(void){} | |||||
| bool setSource(std::string){return true;} | |||||
| bool setSink(std::string){return true;} | |||||
| std::set<std::string> getSources(void){return std::set<std::string>();} | |||||
| std::set<std::string> getSinks(void){return std::set<std::string>();} | |||||
| std::string getSource(void){return "";} | |||||
| std::string getSink(void){return "";} | |||||
| // bool start(void){return 1;} | |||||
| // void stop(void){} | |||||
| // bool setSource(std::string){return true;} | |||||
| // bool setSink(std::string){return true;} | |||||
| // std::set<std::string> getSources(void){return std::set<std::string>();} | |||||
| // std::set<std::string> getSinks(void){return std::set<std::string>();} | |||||
| // std::string getSource(void){return "";} | |||||
| // std::string getSink(void){return "";} | |||||
| void waveNew(WavFile*){} | void waveNew(WavFile*){} | ||||
| void waveStart(void){} | |||||
| void waveStop(void){} | |||||
| void waveEnd(void){} | |||||
| void waveStart(){} | |||||
| void waveStop(){} | |||||
| // void waveEnd(void){} | |||||
| } | } | ||||
| @@ -1,6 +1,6 @@ | |||||
| /* | /* | ||||
| * Carla Native Plugins | * Carla Native Plugins | ||||
| * Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> | |||||
| * Copyright (C) 2012-2015 Filipe Coelho <falktx@falktx.com> | |||||
| * | * | ||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
| * modify it under the terms of the GNU General Public License as | * modify it under the terms of the GNU General Public License as | ||||
| @@ -111,12 +111,10 @@ public: | |||||
| return; | return; | ||||
| fInitiated = true; | fInitiated = true; | ||||
| Master& master(Master::getInstance()); | |||||
| pthread_mutex_lock(&master.mutex); | |||||
| fPrograms.append(new ProgramInfo(0, 0, "default")); | fPrograms.append(new ProgramInfo(0, 0, "default")); | ||||
| Master& master(getMasterInstance()); | |||||
| // refresh banks | // refresh banks | ||||
| master.bank.rescanforbanks(); | master.bank.rescanforbanks(); | ||||
| @@ -137,48 +135,43 @@ public: | |||||
| fPrograms.append(new ProgramInfo(i+1, instrument, insName.c_str())); | fPrograms.append(new ProgramInfo(i+1, instrument, insName.c_str())); | ||||
| } | } | ||||
| } | } | ||||
| pthread_mutex_unlock(&master.mutex); | |||||
| } | } | ||||
| void load(Master* const master, const uint8_t channel, const uint32_t bank, const uint32_t program) | |||||
| void load(Master* const master, CarlaMutex& mutex, const uint8_t channel, const uint32_t bank, const uint32_t program) | |||||
| { | { | ||||
| if (bank == 0) | if (bank == 0) | ||||
| { | { | ||||
| pthread_mutex_lock(&master->mutex); | |||||
| if (program != 0) | |||||
| return; | |||||
| const CarlaMutexLocker cml(mutex); | |||||
| master->partonoff(channel, 1); | master->partonoff(channel, 1); | ||||
| master->part[channel]->defaults(); | master->part[channel]->defaults(); | ||||
| master->part[channel]->applyparameters(false); | master->part[channel]->applyparameters(false); | ||||
| pthread_mutex_unlock(&master->mutex); | |||||
| return; | return; | ||||
| } | } | ||||
| const std::string& bankdir(master->bank.banks[bank-1].dir); | |||||
| if (! bankdir.empty()) | |||||
| { | |||||
| pthread_mutex_lock(&master->mutex); | |||||
| master->partonoff(channel, 1); | |||||
| const Master& gmaster(getMasterInstance()); | |||||
| const std::string& bankdir(gmaster.bank.banks[bank-1].dir); | |||||
| master->bank.loadbank(bankdir); | |||||
| master->bank.loadfromslot(program, master->part[channel]); | |||||
| if (bankdir.empty()) | |||||
| return; | |||||
| master->part[channel]->applyparameters(false); | |||||
| const CarlaMutexLocker cml(mutex); | |||||
| pthread_mutex_unlock(&master->mutex); | |||||
| } | |||||
| master->partonoff(channel, 1); | |||||
| master->bank.loadbank(bankdir); | |||||
| master->bank.loadfromslot(program, master->part[channel]); | |||||
| master->part[channel]->applyparameters(false); | |||||
| } | } | ||||
| uint32_t count() const noexcept | |||||
| uint32_t getNativeMidiProgramCount() const noexcept | |||||
| { | { | ||||
| return static_cast<uint32_t>(fPrograms.count()); | return static_cast<uint32_t>(fPrograms.count()); | ||||
| } | } | ||||
| const NativeMidiProgram* getInfo(const uint32_t index) const noexcept | |||||
| const NativeMidiProgram* getNativeMidiProgramInfo(const uint32_t index) const noexcept | |||||
| { | { | ||||
| if (index >= fPrograms.count()) | if (index >= fPrograms.count()) | ||||
| return nullptr; | return nullptr; | ||||
| @@ -193,6 +186,13 @@ public: | |||||
| return &fRetProgram; | return &fRetProgram; | ||||
| } | } | ||||
| uint32_t getZynBankCount() const | |||||
| { | |||||
| const Master& master(getMasterInstance()); | |||||
| return master.bank.banks.size(); | |||||
| } | |||||
| private: | private: | ||||
| struct ProgramInfo { | struct ProgramInfo { | ||||
| uint32_t bank; | uint32_t bank; | ||||
| @@ -226,140 +226,28 @@ private: | |||||
| mutable NativeMidiProgram fRetProgram; | mutable NativeMidiProgram fRetProgram; | ||||
| LinkedList<const ProgramInfo*> fPrograms; | LinkedList<const ProgramInfo*> fPrograms; | ||||
| CARLA_PREVENT_HEAP_ALLOCATION | |||||
| CARLA_DECLARE_NON_COPY_CLASS(ZynAddSubFxPrograms) | |||||
| }; | |||||
| static ZynAddSubFxPrograms sPrograms; | |||||
| // ----------------------------------------------------------------------- | |||||
| class ZynAddSubFxInstanceCount | |||||
| { | |||||
| public: | |||||
| ZynAddSubFxInstanceCount() | |||||
| : fCount(0), | |||||
| fMutex() {} | |||||
| ~ZynAddSubFxInstanceCount() | |||||
| static Master& getMasterInstance() | |||||
| { | { | ||||
| CARLA_SAFE_ASSERT(fCount == 0); | |||||
| } | |||||
| void addOne(const NativeHostDescriptor* const host) | |||||
| { | |||||
| if (fCount++ != 0) | |||||
| return; | |||||
| const CarlaMutexLocker cml(fMutex); | |||||
| CARLA_SAFE_ASSERT(synth == nullptr); | |||||
| CARLA_SAFE_ASSERT(denormalkillbuf == nullptr); | |||||
| reinit(host); | |||||
| #ifdef WANT_ZYNADDSUBFX_UI | |||||
| if (gPixmapPath.isEmpty()) | |||||
| { | |||||
| gPixmapPath = host->resourceDir; | |||||
| gPixmapPath += "/zynaddsubfx/"; | |||||
| gUiPixmapPath = gPixmapPath; | |||||
| } | |||||
| #endif | |||||
| } | |||||
| void removeOne() | |||||
| { | |||||
| if (--fCount != 0) | |||||
| return; | |||||
| const CarlaMutexLocker cml(fMutex); | |||||
| CARLA_SAFE_ASSERT(synth != nullptr); | |||||
| CARLA_SAFE_ASSERT(denormalkillbuf != nullptr); | |||||
| Master::deleteInstance(); | |||||
| delete[] denormalkillbuf; | |||||
| denormalkillbuf = nullptr; | |||||
| delete synth; | |||||
| synth = nullptr; | |||||
| } | |||||
| void maybeReinit(const NativeHostDescriptor* const host) | |||||
| { | |||||
| if (static_cast< int>(host->get_buffer_size(host->handle)) == synth->buffersize && | |||||
| static_cast<uint>(host->get_sample_rate(host->handle)) == synth->samplerate) | |||||
| return; | |||||
| const CarlaMutexLocker cml(fMutex); | |||||
| reinit(host); | |||||
| } | |||||
| CarlaMutex& getLock() noexcept | |||||
| { | |||||
| return fMutex; | |||||
| } | |||||
| private: | |||||
| int fCount; | |||||
| CarlaMutex fMutex; | |||||
| void reinit(const NativeHostDescriptor* const host) | |||||
| { | |||||
| Master::deleteInstance(); | |||||
| if (denormalkillbuf != nullptr) | |||||
| { | |||||
| delete[] denormalkillbuf; | |||||
| denormalkillbuf = nullptr; | |||||
| } | |||||
| if (synth != nullptr) | |||||
| { | |||||
| delete synth; | |||||
| synth = nullptr; | |||||
| } | |||||
| synth = new SYNTH_T(); | |||||
| synth->buffersize = static_cast<int>(host->get_buffer_size(host->handle)); | |||||
| synth->samplerate = static_cast<uint>(host->get_sample_rate(host->handle)); | |||||
| config.init(); | |||||
| config.cfg.SoundBufferSize = synth->buffersize; | |||||
| config.cfg.SampleRate = static_cast<int>(synth->samplerate); | |||||
| config.cfg.GzipCompression = 0; | |||||
| sprng(static_cast<prng_t>(std::time(nullptr))); | |||||
| denormalkillbuf = new float[synth->buffersize]; | |||||
| for (int i=0; i < synth->buffersize; ++i) | |||||
| denormalkillbuf[i] = (RND - 0.5f) * 1e-16f; | |||||
| if (synth->buffersize > 32) | |||||
| synth->buffersize = 32; | |||||
| synth->alias(); | |||||
| Master::getInstance(); | |||||
| static SYNTH_T synth; | |||||
| static Master master(synth); | |||||
| return master; | |||||
| } | } | ||||
| CARLA_PREVENT_HEAP_ALLOCATION | CARLA_PREVENT_HEAP_ALLOCATION | ||||
| CARLA_DECLARE_NON_COPY_CLASS(ZynAddSubFxInstanceCount) | |||||
| CARLA_DECLARE_NON_COPY_CLASS(ZynAddSubFxPrograms) | |||||
| }; | }; | ||||
| static ZynAddSubFxInstanceCount sInstanceCount; | |||||
| static ZynAddSubFxPrograms sPrograms; | |||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| class ZynAddSubFxThread : public CarlaThread | class ZynAddSubFxThread : public CarlaThread | ||||
| { | { | ||||
| public: | public: | ||||
| ZynAddSubFxThread(Master* const master, const NativeHostDescriptor* const host) | |||||
| ZynAddSubFxThread(Master* const master, CarlaMutex& mutex, const NativeHostDescriptor* const host) | |||||
| : CarlaThread("ZynAddSubFxThread"), | : CarlaThread("ZynAddSubFxThread"), | ||||
| fMaster(master), | fMaster(master), | ||||
| fMutex(mutex), | |||||
| kHost(host), | kHost(host), | ||||
| #ifdef WANT_ZYNADDSUBFX_UI | #ifdef WANT_ZYNADDSUBFX_UI | ||||
| fUi(nullptr), | fUi(nullptr), | ||||
| @@ -475,12 +363,11 @@ protected: | |||||
| if (fUi == nullptr) | if (fUi == nullptr) | ||||
| { | { | ||||
| fUiClosed = 0; | fUiClosed = 0; | ||||
| fUi = new MasterUI(fMaster, &fUiClosed); | |||||
| fUi = new MasterUI(&fUiClosed, &fOscIface); | |||||
| fUi->masterwindow->label(kHost->uiName); | fUi->masterwindow->label(kHost->uiName); | ||||
| fUi->showUI(); | |||||
| } | } | ||||
| else | |||||
| fUi->showUI(); | |||||
| fUi->showUI(1); | |||||
| } | } | ||||
| else if (fNextUiAction == 0) // close | else if (fNextUiAction == 0) // close | ||||
| { | { | ||||
| @@ -509,7 +396,7 @@ protected: | |||||
| if (fChangeProgram) | if (fChangeProgram) | ||||
| { | { | ||||
| fChangeProgram = false; | fChangeProgram = false; | ||||
| sPrograms.load(fMaster, fNextChannel, fNextBank, fNextProgram); | |||||
| sPrograms.load(fMaster, fMutex, fNextChannel, fNextBank, fNextProgram); | |||||
| fNextChannel = 0; | fNextChannel = 0; | ||||
| fNextBank = 0; | fNextBank = 0; | ||||
| fNextProgram = 0; | fNextProgram = 0; | ||||
| @@ -545,12 +432,14 @@ protected: | |||||
| private: | private: | ||||
| Master* fMaster; | Master* fMaster; | ||||
| CarlaMutex& fMutex; | |||||
| const NativeHostDescriptor* const kHost; | const NativeHostDescriptor* const kHost; | ||||
| #ifdef WANT_ZYNADDSUBFX_UI | #ifdef WANT_ZYNADDSUBFX_UI | ||||
| MasterUI* fUi; | MasterUI* fUi; | ||||
| int fUiClosed; | int fUiClosed; | ||||
| volatile int fNextUiAction; | volatile int fNextUiAction; | ||||
| Fl_Osc_Interface fOscIface; | |||||
| #endif | #endif | ||||
| volatile bool fChangeProgram; | volatile bool fChangeProgram; | ||||
| @@ -580,9 +469,10 @@ public: | |||||
| ZynAddSubFxPlugin(const NativeHostDescriptor* const host) | ZynAddSubFxPlugin(const NativeHostDescriptor* const host) | ||||
| : NativePluginClass(host), | : NativePluginClass(host), | ||||
| fMaster(nullptr), | fMaster(nullptr), | ||||
| fSampleRate(static_cast<uint>(getSampleRate())), | |||||
| fSynth(), | |||||
| fIsActive(false), | fIsActive(false), | ||||
| fThread(nullptr, host), | |||||
| fMutex(), | |||||
| fThread(nullptr, fMutex, host), | |||||
| leakDetector_ZynAddSubFxPlugin() | leakDetector_ZynAddSubFxPlugin() | ||||
| { | { | ||||
| // init parameters to default | // init parameters to default | ||||
| @@ -593,7 +483,19 @@ public: | |||||
| fParameters[kParamResCenter] = 64.0f; | fParameters[kParamResCenter] = 64.0f; | ||||
| fParameters[kParamResBandwidth] = 64.0f; | fParameters[kParamResBandwidth] = 64.0f; | ||||
| fSynth.buffersize = static_cast<int>(getBufferSize()); | |||||
| fSynth.samplerate = static_cast<uint>(getSampleRate()); | |||||
| //if (fSynth.buffersize > 32) | |||||
| // fSynth.buffersize = 32; | |||||
| fSynth.alias(); | |||||
| // FIXME | |||||
| fSynth.samplerate_f = getSampleRate(); | |||||
| _initMaster(); | _initMaster(); | ||||
| sPrograms.initIfNeeded(); | sPrograms.initIfNeeded(); | ||||
| } | } | ||||
| @@ -668,14 +570,14 @@ protected: | |||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| // Plugin midi-program calls | // Plugin midi-program calls | ||||
| uint32_t getMidiProgramCount() const override | |||||
| uint32_t getMidiProgramCount() const noexcept override | |||||
| { | { | ||||
| return sPrograms.count(); | |||||
| return sPrograms.getNativeMidiProgramCount(); | |||||
| } | } | ||||
| const NativeMidiProgram* getMidiProgramInfo(const uint32_t index) const override | |||||
| const NativeMidiProgram* getMidiProgramInfo(const uint32_t index) const noexcept override | |||||
| { | { | ||||
| return sPrograms.getInfo(index); | |||||
| return sPrograms.getNativeMidiProgramInfo(index); | |||||
| } | } | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -699,14 +601,14 @@ protected: | |||||
| void setMidiProgram(const uint8_t channel, const uint32_t bank, const uint32_t program) override | void setMidiProgram(const uint8_t channel, const uint32_t bank, const uint32_t program) override | ||||
| { | { | ||||
| if (bank >= fMaster->bank.banks.size()) | |||||
| if (bank >= sPrograms.getZynBankCount()) | |||||
| return; | return; | ||||
| if (program >= BANK_SIZE) | if (program >= BANK_SIZE) | ||||
| return; | return; | ||||
| if (isOffline() || ! fIsActive) | if (isOffline() || ! fIsActive) | ||||
| { | { | ||||
| sPrograms.load(fMaster, channel, bank, program); | |||||
| sPrograms.load(fMaster, fMutex, channel, bank, program); | |||||
| #ifdef WANT_ZYNADDSUBFX_UI | #ifdef WANT_ZYNADDSUBFX_UI | ||||
| fThread.uiRepaint(); | fThread.uiRepaint(); | ||||
| #endif | #endif | ||||
| @@ -721,7 +623,7 @@ protected: | |||||
| CARLA_SAFE_ASSERT_RETURN(key != nullptr,); | CARLA_SAFE_ASSERT_RETURN(key != nullptr,); | ||||
| CARLA_SAFE_ASSERT_RETURN(value != nullptr,); | CARLA_SAFE_ASSERT_RETURN(value != nullptr,); | ||||
| pthread_mutex_lock(&fMaster->mutex); | |||||
| const CarlaMutexLocker cml(fMutex); | |||||
| /**/ if (std::strcmp(key, "CarlaAlternateFile1") == 0) // xmz | /**/ if (std::strcmp(key, "CarlaAlternateFile1") == 0) // xmz | ||||
| { | { | ||||
| @@ -734,9 +636,7 @@ protected: | |||||
| fMaster->part[0]->loadXMLinstrument(value); | fMaster->part[0]->loadXMLinstrument(value); | ||||
| } | } | ||||
| fMaster->applyparameters(false); | |||||
| pthread_mutex_unlock(&fMaster->mutex); | |||||
| fMaster->applyparameters(); | |||||
| } | } | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| @@ -754,13 +654,15 @@ protected: | |||||
| void process(float**, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override | void process(float**, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override | ||||
| { | { | ||||
| const CarlaMutexTryLocker cmtl(sInstanceCount.getLock()); | |||||
| if (cmtl.wasNotLocked() || pthread_mutex_trylock(&fMaster->mutex) != 0) | |||||
| if (! fMutex.tryLock()) | |||||
| { | { | ||||
| FloatVectorOperations::clear(outBuffer[0], static_cast<int>(frames)); | |||||
| FloatVectorOperations::clear(outBuffer[1], static_cast<int>(frames)); | |||||
| return; | |||||
| if (! isOffline()) | |||||
| { | |||||
| FloatVectorOperations::clear(outBuffer[0], static_cast<int>(frames)); | |||||
| FloatVectorOperations::clear(outBuffer[1], static_cast<int>(frames)); | |||||
| return; | |||||
| } | |||||
| fMutex.lock(); | |||||
| } | } | ||||
| for (uint32_t i=0; i < midiEventCount; ++i) | for (uint32_t i=0; i < midiEventCount; ++i) | ||||
| @@ -811,9 +713,9 @@ protected: | |||||
| } | } | ||||
| } | } | ||||
| fMaster->GetAudioOutSamples(frames, fSampleRate, outBuffer[0], outBuffer[1]); | |||||
| fMaster->GetAudioOutSamples(frames, fSynth.samplerate, outBuffer[0], outBuffer[1]); | |||||
| pthread_mutex_unlock(&fMaster->mutex); | |||||
| fMutex.unlock(); | |||||
| } | } | ||||
| #ifdef WANT_ZYNADDSUBFX_UI | #ifdef WANT_ZYNADDSUBFX_UI | ||||
| @@ -834,8 +736,6 @@ protected: | |||||
| char* getState() const override | char* getState() const override | ||||
| { | { | ||||
| config.save(); | |||||
| char* data = nullptr; | char* data = nullptr; | ||||
| fMaster->getalldata(&data); | fMaster->getalldata(&data); | ||||
| return data; | return data; | ||||
| @@ -844,43 +744,53 @@ protected: | |||||
| void setState(const char* const data) override | void setState(const char* const data) override | ||||
| { | { | ||||
| fThread.stopLoadProgramLater(); | fThread.stopLoadProgramLater(); | ||||
| const CarlaMutexLocker cml(fMutex); | |||||
| fMaster->putalldata(const_cast<char*>(data), 0); | fMaster->putalldata(const_cast<char*>(data), 0); | ||||
| fMaster->applyparameters(true); | |||||
| fMaster->applyparameters(); | |||||
| } | } | ||||
| // ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
| // Plugin dispatcher | // Plugin dispatcher | ||||
| void bufferSizeChanged(const uint32_t) final | |||||
| void bufferSizeChanged(const uint32_t bufferSize) final | |||||
| { | { | ||||
| char* const state(getState()); | char* const state(getState()); | ||||
| _deleteMaster(); | _deleteMaster(); | ||||
| sInstanceCount.maybeReinit(getHostHandle()); | |||||
| fSynth.buffersize = static_cast<int>(bufferSize); | |||||
| fSynth.alias(); | |||||
| _initMaster(); | _initMaster(); | ||||
| if (state != nullptr) | if (state != nullptr) | ||||
| { | { | ||||
| fMaster->putalldata(state, 0); | fMaster->putalldata(state, 0); | ||||
| fMaster->applyparameters(true); | |||||
| fMaster->applyparameters(); | |||||
| std::free(state); | std::free(state); | ||||
| } | } | ||||
| } | } | ||||
| void sampleRateChanged(const double sampleRate) final | void sampleRateChanged(const double sampleRate) final | ||||
| { | { | ||||
| fSampleRate = static_cast<uint>(sampleRate); | |||||
| char* const state(getState()); | char* const state(getState()); | ||||
| _deleteMaster(); | _deleteMaster(); | ||||
| sInstanceCount.maybeReinit(getHostHandle()); | |||||
| fSynth.samplerate = static_cast<uint>(sampleRate); | |||||
| fSynth.alias(); | |||||
| // FIXME | |||||
| fSynth.samplerate_f = sampleRate; | |||||
| _initMaster(); | _initMaster(); | ||||
| if (state != nullptr) | if (state != nullptr) | ||||
| { | { | ||||
| fMaster->putalldata(state, 0); | fMaster->putalldata(state, 0); | ||||
| fMaster->applyparameters(true); | |||||
| fMaster->applyparameters(); | |||||
| std::free(state); | std::free(state); | ||||
| } | } | ||||
| } | } | ||||
| @@ -896,35 +806,13 @@ protected: | |||||
| private: | private: | ||||
| Master* fMaster; | Master* fMaster; | ||||
| uint fSampleRate; | |||||
| SYNTH_T fSynth; | |||||
| bool fIsActive; | bool fIsActive; | ||||
| float fParameters[kParamCount]; | float fParameters[kParamCount]; | ||||
| CarlaMutex fMutex; | |||||
| ZynAddSubFxThread fThread; | ZynAddSubFxThread fThread; | ||||
| /* | |||||
| static Parameters getParameterFromZynIndex(const MidiControllers index) | |||||
| { | |||||
| switch (index) | |||||
| { | |||||
| case C_filtercutoff: | |||||
| return kParamFilterCutoff; | |||||
| case C_filterq: | |||||
| return kParamFilterQ; | |||||
| case C_bandwidth: | |||||
| return kParamBandwidth; | |||||
| case C_fmamp: | |||||
| return kParamModAmp; | |||||
| case C_resonance_center: | |||||
| return kParamResCenter; | |||||
| case C_resonance_bandwidth: | |||||
| return kParamResBandwidth; | |||||
| default: | |||||
| return kParamCount; | |||||
| } | |||||
| } | |||||
| */ | |||||
| static uint getZynParameterFromIndex(const uint index) | static uint getZynParameterFromIndex(const uint index) | ||||
| { | { | ||||
| switch (index) | switch (index) | ||||
| @@ -952,7 +840,7 @@ private: | |||||
| void _initMaster() | void _initMaster() | ||||
| { | { | ||||
| fMaster = new Master(); | |||||
| fMaster = new Master(fSynth); | |||||
| fThread.setMaster(fMaster); | fThread.setMaster(fMaster); | ||||
| fThread.startThread(); | fThread.startThread(); | ||||
| @@ -971,8 +859,6 @@ private: | |||||
| void _deleteMaster() | void _deleteMaster() | ||||
| { | { | ||||
| //ensure that everything has stopped | //ensure that everything has stopped | ||||
| pthread_mutex_lock(&fMaster->mutex); | |||||
| pthread_mutex_unlock(&fMaster->mutex); | |||||
| fThread.stopThread(-1); | fThread.stopThread(-1); | ||||
| delete fMaster; | delete fMaster; | ||||
| @@ -984,14 +870,33 @@ private: | |||||
| public: | public: | ||||
| static NativePluginHandle _instantiate(const NativeHostDescriptor* host) | static NativePluginHandle _instantiate(const NativeHostDescriptor* host) | ||||
| { | { | ||||
| sInstanceCount.addOne(host); | |||||
| static bool needsInit = true; | |||||
| if (needsInit) | |||||
| { | |||||
| needsInit = false; | |||||
| config.init(); | |||||
| sprng(static_cast<prng_t>(std::time(nullptr))); | |||||
| // FIXME - kill this | |||||
| denormalkillbuf = new float[8192]; | |||||
| for (int i=0; i < 8192; ++i) | |||||
| denormalkillbuf[i] = (RND - 0.5f) * 1e-16f; | |||||
| #ifdef WANT_ZYNADDSUBFX_UI | |||||
| gPixmapPath = host->resourceDir; | |||||
| gPixmapPath += "/zynaddsubfx/"; | |||||
| gUiPixmapPath = gPixmapPath; | |||||
| #endif | |||||
| } | |||||
| return new ZynAddSubFxPlugin(host); | return new ZynAddSubFxPlugin(host); | ||||
| } | } | ||||
| static void _cleanup(NativePluginHandle handle) | static void _cleanup(NativePluginHandle handle) | ||||
| { | { | ||||
| delete (ZynAddSubFxPlugin*)handle; | delete (ZynAddSubFxPlugin*)handle; | ||||
| sInstanceCount.removeOne(); | |||||
| } | } | ||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZynAddSubFxPlugin) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZynAddSubFxPlugin) | ||||
| @@ -1,6 +1,6 @@ | |||||
| /* | /* | ||||
| * Carla Native Plugins | * Carla Native Plugins | ||||
| * Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> | |||||
| * Copyright (C) 2012-2015 Filipe Coelho <falktx@falktx.com> | |||||
| * | * | ||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
| * modify it under the terms of the GNU General Public License as | * modify it under the terms of the GNU General Public License as | ||||
| @@ -24,21 +24,21 @@ | |||||
| CarlaString gUiPixmapPath; | CarlaString gUiPixmapPath; | ||||
| // zynaddsubfx ui includes | // zynaddsubfx ui includes | ||||
| #include "zynaddsubfx/UI/NioUI.cpp" | |||||
| #include "zynaddsubfx/UI/WidgetPDial.cpp" | |||||
| #include "zynaddsubfx/UI/ADnoteUI.cpp" | |||||
| #include "zynaddsubfx/UI/BankUI.cpp" | |||||
| #include "zynaddsubfx/UI/ConfigUI.cpp" | |||||
| #include "zynaddsubfx/UI/EffUI.cpp" | |||||
| #include "zynaddsubfx/UI/EnvelopeUI.cpp" | |||||
| #include "zynaddsubfx/UI/FilterUI.cpp" | |||||
| #include "zynaddsubfx/UI/LFOUI.cpp" | |||||
| #include "zynaddsubfx/UI/MasterUI.cpp" | |||||
| #include "zynaddsubfx/UI/MicrotonalUI.cpp" | |||||
| #include "zynaddsubfx/UI/OscilGenUI.cpp" | |||||
| #include "zynaddsubfx/UI/PADnoteUI.cpp" | |||||
| #include "zynaddsubfx/UI/PartUI.cpp" | |||||
| #include "zynaddsubfx/UI/PresetsUI.cpp" | |||||
| #include "zynaddsubfx/UI/ResonanceUI.cpp" | |||||
| #include "zynaddsubfx/UI/SUBnoteUI.cpp" | |||||
| #include "zynaddsubfx/UI/VirKeyboard.cpp" | |||||
| // #include "zynaddsubfx/UI/NioUI.cpp" | |||||
| // #include "zynaddsubfx/UI/WidgetPDial.cpp" | |||||
| // #include "zynaddsubfx/UI/ADnoteUI.cpp" | |||||
| // #include "zynaddsubfx/UI/BankUI.cpp" | |||||
| // #include "zynaddsubfx/UI/ConfigUI.cpp" | |||||
| // #include "zynaddsubfx/UI/EffUI.cpp" | |||||
| // #include "zynaddsubfx/UI/EnvelopeUI.cpp" | |||||
| // #include "zynaddsubfx/UI/FilterUI.cpp" | |||||
| // #include "zynaddsubfx/UI/LFOUI.cpp" | |||||
| // #include "zynaddsubfx/UI/MasterUI.cpp" | |||||
| // #include "zynaddsubfx/UI/MicrotonalUI.cpp" | |||||
| // #include "zynaddsubfx/UI/OscilGenUI.cpp" | |||||
| // #include "zynaddsubfx/UI/PADnoteUI.cpp" | |||||
| // #include "zynaddsubfx/UI/PartUI.cpp" | |||||
| // #include "zynaddsubfx/UI/PresetsUI.cpp" | |||||
| // #include "zynaddsubfx/UI/ResonanceUI.cpp" | |||||
| // #include "zynaddsubfx/UI/SUBnoteUI.cpp" | |||||
| // #include "zynaddsubfx/UI/VirKeyboard.cpp" | |||||
| @@ -1,384 +0,0 @@ | |||||
| #checking include/library paths | |||||
| message(STATUS "Checking Include Path" $ENV{CMAKE_INCLUDE_PATH} ${CMAKE_INCLUDE_PATH}) | |||||
| message(STATUS "Checking Library Path" $ENV{CMAKE_LIBRARY_PATH} ${CMAKE_LIBRARY_PATH}) | |||||
| #Dependency check | |||||
| find_package(PkgConfig REQUIRED) | |||||
| find_package(zlib REQUIRED) | |||||
| pkg_check_modules(FFTW REQUIRED fftw3) | |||||
| pkg_check_modules(MXML REQUIRED mxml) | |||||
| find_package(Threads REQUIRED) | |||||
| find_package(OSS) | |||||
| find_package(Alsa) | |||||
| pkg_check_modules(JACK jack) | |||||
| pkg_check_modules(PORTAUDIO portaudio-2.0>=19) | |||||
| set(FLTK_SKIP_OPENGL true) | |||||
| pkg_check_modules(NTK ntk) | |||||
| pkg_check_modules(NTK_IMAGES ntk_images) | |||||
| find_package(FLTK) | |||||
| find_package(OpenGL) #for FLTK | |||||
| find_package(CxxTest) | |||||
| if(CXXTEST_FOUND) | |||||
| set(CXXTEST_USE_PYTHON TRUE) | |||||
| endif() | |||||
| # lash | |||||
| pkg_search_module(LASH lash-1.0) | |||||
| mark_as_advanced(LASH_LIBRARIES) | |||||
| pkg_search_module(DSSI dssi>=0.9.0) | |||||
| mark_as_advanced(DSSI_LIBRARIES) | |||||
| pkg_search_module(LIBLO liblo>=0.26) | |||||
| mark_as_advanced(LIBLO_LIBRARIES) | |||||
| CHECK_FUNCTION_EXISTS(sched_setscheduler HAVE_SCHEDULER) | |||||
| execute_process(COMMAND echo fistpl 0 | |||||
| COMMAND as - | |||||
| ERROR_VARIABLE AVOID_ASM) | |||||
| ######### Settings ########### | |||||
| # NOTE: These cache variables should normally not be changed in this | |||||
| # file, but either in in CMakeCache.txt before compile, or by passing | |||||
| # parameters directly into cmake using the -D flag. | |||||
| SET (GuiModule fltk CACHE STRING "GUI module, either fltk, ntk or off") | |||||
| SET (CompileTests ${CXXTEST_FOUND} CACHE BOOL "whether tests should be compiled in or not") | |||||
| SET (AlsaEnable ${ALSA_FOUND} CACHE BOOL | |||||
| "Enable support for Advanced Linux Sound Architecture") | |||||
| SET (JackEnable ${JACK_FOUND} CACHE BOOL | |||||
| "Enable support for JACK Audio Connection toolKit") | |||||
| SET (OssEnable ${OSS_FOUND} CACHE BOOL | |||||
| "Enable support for Open Sound System") | |||||
| SET (PaEnable ${PORTAUDIO_FOUND} CACHE BOOL | |||||
| "Enable support for Port Audio System") | |||||
| SET (LashEnable ${LASH_FOUND} CACHE BOOL | |||||
| "Enable LASH Audio Session Handler") | |||||
| SET (DssiEnable ${DSSI_FOUND} CACHE BOOL | |||||
| "Enable DSSI Plugin compilation") | |||||
| SET (LibloEnable ${LIBLO_FOUND} CACHE BOOL | |||||
| "Enable Liblo") | |||||
| # Now, handle the incoming settings and set define flags/variables based | |||||
| # on this | |||||
| # Add version information | |||||
| add_definitions(-DVERSION="${VERSION}") | |||||
| message(STATUS "Building on a '${CMAKE_SYSTEM_NAME}' System") | |||||
| if(NOT "Darwin" STREQUAL ${CMAKE_SYSTEM_NAME}) | |||||
| # Add scheduler function existance info (OSX compatiability) | |||||
| add_definitions(-DHAVE_SCHEDULER=${HAVE_SCHEDULER}) | |||||
| endif() | |||||
| # Give a good guess on the best Input/Output default backends | |||||
| if (JackEnable) | |||||
| SET (DefaultOutput jack CACHE STRING | |||||
| "Default Output module: [null, alsa, oss, jack, portaudio]") | |||||
| # Override with perhaps more helpful midi backends | |||||
| if (AlsaEnable) | |||||
| SET (DefaultInput alsa CACHE STRING | |||||
| "Default Input module: [null, alsa, oss, jack]") | |||||
| elseif (OssEnable) | |||||
| SET (DefaultInput oss CACHE STRING | |||||
| "Default Input module: [null, alsa, oss, jack]") | |||||
| else () | |||||
| SET (DefaultInput jack CACHE STRING | |||||
| "Default Input module: [null, alsa, oss, jack]") | |||||
| endif () | |||||
| elseif (AlsaEnable) | |||||
| SET (DefaultOutput alsa CACHE STRING | |||||
| "Default Output module: [null, alsa, oss, jack, portaudio]") | |||||
| SET (DefaultInput alsa CACHE STRING | |||||
| "Default Input module: [null, alsa, oss, jack]") | |||||
| elseif (OssEnable) | |||||
| SET (DefaultOutput oss CACHE STRING | |||||
| "Default Output module: [null, alsa, oss, jack, portaudio]") | |||||
| SET (DefaultInput oss CACHE STRING | |||||
| "Default Input module: [null, alsa, oss, jack]") | |||||
| else() | |||||
| SET (DefaultOutput null CACHE STRING | |||||
| "Default Output module: [null, alsa, oss, jack, portaudio]") | |||||
| SET (DefaultInput null CACHE STRING | |||||
| "Default Input module: [null, alsa, oss, jack]") | |||||
| endif() | |||||
| if (GuiModule STREQUAL qt AND QT_FOUND) | |||||
| set (QtGui TRUE) | |||||
| elseif(GuiModule STREQUAL ntk AND NTK_FOUND) | |||||
| set (NtkGui TRUE) | |||||
| elseif(GuiModule STREQUAL fltk AND FLTK_FOUND) | |||||
| set (FltkGui TRUE) | |||||
| elseif(GuiModule STREQUAL off) | |||||
| add_definitions(-DDISABLE_GUI) | |||||
| else () | |||||
| set (GuiModule off CACHE STRING "GUI module, either fltk, qt or off") | |||||
| add_definitions(-DDISABLE_GUI) | |||||
| message(STATUS "GUI module defaulting to off") | |||||
| endif() | |||||
| #Build Flags | |||||
| option (BuildForAMD_X86_64 "Build for AMD x86_64 system" OFF) | |||||
| option (BuildForCore2_X86_64 "Build for Intel Core2 x86_64 system" OFF) | |||||
| option (BuildForDebug "Include gdb debugging support" OFF) | |||||
| set(CMAKE_BUILD_TYPE "Release") | |||||
| set (BuildOptions_x86_64AMD | |||||
| "-O3 -march=athlon64 -m64 -Wall -ffast-math -fno-finite-math-only -fomit-frame-pointer" | |||||
| CACHE STRING "X86_64 compiler options" | |||||
| ) | |||||
| set (BuildOptions_X86_64Core2 | |||||
| "-O3 -march=core2 -m64 -Wall -ffast-math -fno-finite-math-only -fomit-frame-pointer" | |||||
| CACHE STRING "X86_64 compiler options" | |||||
| ) | |||||
| set (BuildOptionsBasic | |||||
| "-O3 -msse -msse2 -mfpmath=sse -ffast-math -fomit-frame-pointer" | |||||
| CACHE STRING "basic X86 complier options" | |||||
| ) | |||||
| set (BuildOptionsDebug | |||||
| "-O0 -g3 -ggdb -Wall -Wpointer-arith" CACHE STRING "Debug build flags") | |||||
| ########### Settings dependant code ########### | |||||
| # From here on, the setting variables have been prepared so concentrate | |||||
| # on the actual compiling. | |||||
| if(AlsaEnable) | |||||
| list(APPEND AUDIO_LIBRARIES ${ASOUND_LIBRARY}) | |||||
| list(APPEND AUDIO_LIBRARY_DIRS ${ASOUND_LIBRARY_DIRS}) | |||||
| add_definitions(-DALSA=1) | |||||
| endif(AlsaEnable) | |||||
| if(JackEnable) | |||||
| list(APPEND AUDIO_LIBRARIES ${JACK_LIBRARIES}) | |||||
| list(APPEND AUDIO_LIBRARY_DIRS ${JACK_LIBRARY_DIRS}) | |||||
| add_definitions(-DJACK=1) | |||||
| endif(JackEnable) | |||||
| if(OssEnable) | |||||
| add_definitions(-DOSS=1) | |||||
| endif(OssEnable) | |||||
| if(PaEnable) | |||||
| include_directories(${PORTAUDIO_INCLUDE_DIR}) | |||||
| add_definitions(-DPORTAUDIO=1) | |||||
| list(APPEND AUDIO_LIBRARIES ${PORTAUDIO_LIBRARIES}) | |||||
| list(APPEND AUDIO_LIBRARY_DIRS ${PORTAUDIO_LIBRARY_DIRS}) | |||||
| endif() | |||||
| if (CompileTests) | |||||
| ENABLE_TESTING() | |||||
| endif() | |||||
| if(LashEnable) | |||||
| include_directories(${LASH_INCLUDE_DIRS}) | |||||
| add_definitions(-DLASH=1) | |||||
| list(APPEND AUDIO_LIBRARIES ${LASH_LIBRARIES}) | |||||
| list(APPEND AUDIO_LIBRARY_DIRS ${LASH_LIBRARY_DIRS}) | |||||
| message(STATUS "Compiling with lash") | |||||
| endif() | |||||
| if(LibloEnable) | |||||
| include_directories(${LIBLO_INCLUDE_DIRS}) | |||||
| add_definitions(-DUSE_NSM=1) | |||||
| list(APPEND AUDIO_LIBRARIES ${LIBLO_LIBRARIES}) | |||||
| list(APPEND AUDIO_LIBRARY_DIRS ${LIBLO_LIBRARY_DIRS}) | |||||
| message(STATUS "Compiling with liblo") | |||||
| endif() | |||||
| # other include directories | |||||
| include_directories(${ZLIB_INCLUDE_DIRS} ${MXML_INCLUDE_DIRS}) | |||||
| add_definitions( | |||||
| -g #TODO #todo put in a better location | |||||
| -Wall | |||||
| -Wextra | |||||
| ) | |||||
| if(NOT AVOID_ASM) | |||||
| message(STATUS "Compiling with x86 opcode support") | |||||
| add_definitions(-DASM_F2I_YES) | |||||
| endif() | |||||
| if (BuildForDebug) | |||||
| set (CMAKE_BUILD_TYPE "Debug") | |||||
| set (CMAKE_CXX_FLAGS_DEBUG ${BuildOptionsDebug}) | |||||
| message (STATUS "Building for ${CMAKE_BUILD_TYPE}, flags: ${CMAKE_CXX_FLAGS_DEBUG}") | |||||
| else (BuildForDebug) | |||||
| set (CMAKE_BUILD_TYPE "Release") | |||||
| if (BuildForAMD_X86_64) | |||||
| set (CMAKE_CXX_FLAGS_RELEASE ${BuildOptions_x86_64AMD}) | |||||
| else (BuildForAMD_X86_64) | |||||
| if (BuildForCore2_X86_64) | |||||
| set (CMAKE_CXX_FLAGS_RELEASE ${BuildOptions_X86_64Core2}) | |||||
| else (BuildForCore2_X86_64) | |||||
| set (CMAKE_CXX_FLAGS_RELEASE ${BuildOptionsBasic}) | |||||
| endif (BuildForCore2_X86_64) | |||||
| endif (BuildForAMD_X86_64) | |||||
| message (STATUS "Building for ${CMAKE_BUILD_TYPE}, flags: ${CMAKE_CXX_FLAGS_RELEASE}") | |||||
| endif (BuildForDebug) | |||||
| add_definitions(-fPIC) | |||||
| if(FLTK_FOUND) | |||||
| mark_as_advanced(FORCE FLTK_BASE_LIBRARY) | |||||
| mark_as_advanced(FORCE FLTK_CONFIG_SCRIPT) | |||||
| mark_as_advanced(FORCE FLTK_DIR) | |||||
| mark_as_advanced(FORCE FLTK_FLUID_EXECUTABLE) | |||||
| mark_as_advanced(FORCE FLTK_FORMS_LIBRARY) | |||||
| mark_as_advanced(FORCE FLTK_GL_LIBRARY) | |||||
| mark_as_advanced(FORCE FLTK_IMAGES_LIBRARY) | |||||
| mark_as_advanced(FORCE FLTK_INCLUDE_DIR) | |||||
| mark_as_advanced(FORCE FLTK_MATH_LIBRARY) | |||||
| endif(FLTK_FOUND) | |||||
| if(NTK_FOUND) | |||||
| mark_as_advanced(FORCE NTK_BASE_LIBRARY) | |||||
| mark_as_advanced(FORCE NTK_CONFIG_SCRIPT) | |||||
| mark_as_advanced(FORCE NTK_DIR) | |||||
| mark_as_advanced(FORCE FLTK_FLUID_EXECUTABLE) | |||||
| mark_as_advanced(FORCE NTK_FORMS_LIBRARY) | |||||
| mark_as_advanced(FORCE NTK_GL_LIBRARY) | |||||
| mark_as_advanced(FORCE NTK_IMAGES_LIBRARY) | |||||
| mark_as_advanced(FORCE NTK_INCLUDE_DIR) | |||||
| mark_as_advanced(FORCE NTK_MATH_LIBRARY) | |||||
| endif(NTK_FOUND) | |||||
| if(FltkGui) | |||||
| #UGLY WORKAROUND | |||||
| find_program (FLTK_CONFIG fltk-config) | |||||
| if (FLTK_CONFIG) | |||||
| execute_process (COMMAND ${FLTK_CONFIG} --use-images --ldflags OUTPUT_VARIABLE FLTK_LDFLAGS) | |||||
| string(STRIP ${FLTK_LDFLAGS} FLTK_LIBRARIES) | |||||
| endif() | |||||
| message(STATUS ${FLTK_LDFLAGS}) | |||||
| set(GUI_LIBRARIES ${FLTK_LIBRARIES} ${FLTK_LIBRARIES} ${OPENGL_LIBRARIES} zynaddsubfx_gui) | |||||
| add_definitions(-DFLTK_GUI) | |||||
| message(STATUS "Will build FLTK gui") | |||||
| include_directories( | |||||
| ${FLTK_INCLUDE_DIR} | |||||
| "${CMAKE_CURRENT_SOURCE_DIR}/UI" | |||||
| "${CMAKE_CURRENT_BINARY_DIR}/UI" | |||||
| ) | |||||
| add_subdirectory(UI) | |||||
| endif() | |||||
| if(NtkGui) | |||||
| find_program( FLTK_FLUID_EXECUTABLE ntk-fluid) | |||||
| message(STATUS ${NTK_LDFLAGS} ${NTK_IMAGES_LDFLAGS}) | |||||
| set(GUI_LIBRARIES ${NTK_LIBRARIES} ${NTK_IMAGES_LIBRARIES} ${OPENGL_LIBRARIES} zynaddsubfx_gui) | |||||
| add_definitions(-DNTK_GUI) | |||||
| message(STATUS "Will build NTK gui") | |||||
| include_directories( | |||||
| ${NTK_INCLUDE_DIRS} | |||||
| "${CMAKE_CURRENT_SOURCE_DIR}/UI" | |||||
| "${CMAKE_CURRENT_BINARY_DIR}/UI" | |||||
| ) | |||||
| add_subdirectory(UI) | |||||
| endif() | |||||
| ########### General section ############## | |||||
| # Following this should be only general compilation code, and no mention | |||||
| # of module-specific variables | |||||
| link_directories(${AUDIO_LIBRARY_DIRS} ${ZLIB_LIBRARY_DIRS} ${FFTW_LIBRARY_DIRS} ${MXML_LIBRARY_DIRS} ${FLTK_LIBRARY_DIRS} ${NTK_LIBRARY_DIRS}) | |||||
| include_directories( | |||||
| ${CMAKE_CURRENT_SOURCE_DIR} | |||||
| ${CMAKE_CURRENT_BINARY_DIR} | |||||
| ) | |||||
| set(NONGUI_LIBRARIES | |||||
| zynaddsubfx_misc | |||||
| zynaddsubfx_synth | |||||
| zynaddsubfx_effect | |||||
| zynaddsubfx_params | |||||
| zynaddsubfx_dsp | |||||
| zynaddsubfx_nio | |||||
| ) | |||||
| add_subdirectory(Misc) | |||||
| add_subdirectory(Synth) | |||||
| add_subdirectory(Effects) | |||||
| add_subdirectory(Params) | |||||
| add_subdirectory(DSP) | |||||
| add_subdirectory(Nio) | |||||
| add_library(zynaddsubfx_core STATIC | |||||
| ${zynaddsubfx_dsp_SRCS} | |||||
| ${zynaddsubfx_effect_SRCS} | |||||
| ${zynaddsubfx_misc_SRCS} | |||||
| ${zynaddsubfx_params_SRCS} | |||||
| ${zynaddsubfx_synth_SRCS} | |||||
| ) | |||||
| target_link_libraries(zynaddsubfx_core | |||||
| ${ZLIB_LIBRARIES} | |||||
| ${FFTW_LIBRARIES} | |||||
| ${MXML_LIBRARIES} | |||||
| ${OS_LIBRARIES} | |||||
| pthread) | |||||
| if(CompileTests) | |||||
| add_subdirectory(Tests) | |||||
| endif(CompileTests) | |||||
| message(STATUS "using link directories: ${AUDIO_LIBRARY_DIRS} ${ZLIB_LIBRARY_DIRS} ${FFTW_LIBRARY_DIRS} ${MXML_LIBRARY_DIRS} ${FLTK_LIBRARY_DIRS}") | |||||
| add_executable(zynaddsubfx main.cpp) | |||||
| target_link_libraries(zynaddsubfx | |||||
| zynaddsubfx_core | |||||
| zynaddsubfx_nio | |||||
| ${GUI_LIBRARIES} | |||||
| ${NIO_LIBRARIES} | |||||
| ${AUDIO_LIBRARIES} | |||||
| ) | |||||
| if (DssiEnable) | |||||
| add_library(zynaddsubfx_dssi SHARED | |||||
| Output/DSSIaudiooutput.cpp | |||||
| ) | |||||
| target_link_libraries(zynaddsubfx_dssi | |||||
| zynaddsubfx_core | |||||
| ${OS_LIBRARIES} | |||||
| ) | |||||
| if (${CMAKE_SIZEOF_VOID_P} EQUAL "8") | |||||
| install(TARGETS zynaddsubfx_dssi LIBRARY DESTINATION lib64/dssi/) | |||||
| else () | |||||
| install(TARGETS zynaddsubfx_dssi LIBRARY DESTINATION lib/dssi/) | |||||
| endif () | |||||
| endif() | |||||
| message(STATUS "Link libraries: ${ZLIB_LIBRARY} ${FFTW_LIBRARY} ${MXML_LIBRARIES} ${AUDIO_LIBRARIES} ${OS_LIBRARIES}") | |||||
| install(TARGETS zynaddsubfx | |||||
| RUNTIME DESTINATION bin | |||||
| ) | |||||
| if(NtkGui) | |||||
| install(DIRECTORY ../pixmaps DESTINATION share/zynaddsubfx) | |||||
| add_definitions(-DPIXMAP_PATH="${CMAKE_INSTALL_PREFIX}/share/zynaddsubfx/pixmaps/") | |||||
| add_definitions(-DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") | |||||
| endif(NtkGui) | |||||
| include(CTest) | |||||
| @@ -213,7 +213,6 @@ void AnalogFilter::computefiltercoefs(void) | |||||
| case 7: //Low Shelf - 2 poles | case 7: //Low Shelf - 2 poles | ||||
| if(!zerocoefs) { | if(!zerocoefs) { | ||||
| tmpq = sqrtf(tmpq); | tmpq = sqrtf(tmpq); | ||||
| alpha = sn / (2.0f * tmpq); | |||||
| beta = sqrtf(tmpgain) / tmpq; | beta = sqrtf(tmpgain) / tmpq; | ||||
| tmp = (tmpgain + 1.0f) + (tmpgain - 1.0f) * cs + beta * sn; | tmp = (tmpgain + 1.0f) + (tmpgain - 1.0f) * cs + beta * sn; | ||||
| @@ -239,7 +238,6 @@ void AnalogFilter::computefiltercoefs(void) | |||||
| case 8: //High Shelf - 2 poles | case 8: //High Shelf - 2 poles | ||||
| if(!zerocoefs) { | if(!zerocoefs) { | ||||
| tmpq = sqrtf(tmpq); | tmpq = sqrtf(tmpq); | ||||
| alpha = sn / (2.0f * tmpq); | |||||
| beta = sqrtf(tmpgain) / tmpq; | beta = sqrtf(tmpgain) / tmpq; | ||||
| tmp = (tmpgain + 1.0f) - (tmpgain - 1.0f) * cs + beta * sn; | tmp = (tmpgain + 1.0f) - (tmpgain - 1.0f) * cs + beta * sn; | ||||
| @@ -49,16 +49,18 @@ class AnalogFilter:public Filter | |||||
| float H(float freq); //Obtains the response for a given frequency | float H(float freq); //Obtains the response for a given frequency | ||||
| struct Coeff { | |||||
| float c[3], //Feed Forward | |||||
| d[3]; //Feed Back | |||||
| } coeff, oldCoeff; | |||||
| private: | private: | ||||
| struct fstage { | struct fstage { | ||||
| float x1, x2; //Input History | float x1, x2; //Input History | ||||
| float y1, y2; //Output History | float y1, y2; //Output History | ||||
| } history[MAX_FILTER_STAGES + 1], oldHistory[MAX_FILTER_STAGES + 1]; | } history[MAX_FILTER_STAGES + 1], oldHistory[MAX_FILTER_STAGES + 1]; | ||||
| struct Coeff { | |||||
| float c[3], //Feed Forward | |||||
| d[3]; //Feed Back | |||||
| } coeff, oldCoeff; | |||||
| //old coeffs are used for interpolation when paremeters change quickly | //old coeffs are used for interpolation when paremeters change quickly | ||||
| //Apply IIR filter to Samples, with coefficients, and past history | //Apply IIR filter to Samples, with coefficients, and past history | ||||
| @@ -23,13 +23,24 @@ | |||||
| #include <cmath> | #include <cmath> | ||||
| #include <cassert> | #include <cassert> | ||||
| #include <cstring> | #include <cstring> | ||||
| #include <pthread.h> | |||||
| #include "FFTwrapper.h" | #include "FFTwrapper.h" | ||||
| static pthread_mutex_t *mutex = NULL; | |||||
| FFTwrapper::FFTwrapper(int fftsize_) | FFTwrapper::FFTwrapper(int fftsize_) | ||||
| { | { | ||||
| //first one will spawn the mutex (yeah this may be a race itself) | |||||
| if(!mutex) { | |||||
| mutex = new pthread_mutex_t; | |||||
| pthread_mutex_init(mutex, NULL); | |||||
| } | |||||
| fftsize = fftsize_; | fftsize = fftsize_; | ||||
| time = new fftw_real[fftsize]; | time = new fftw_real[fftsize]; | ||||
| fft = new fftw_complex[fftsize + 1]; | fft = new fftw_complex[fftsize + 1]; | ||||
| pthread_mutex_lock(mutex); | |||||
| planfftw = fftw_plan_dft_r2c_1d(fftsize, | planfftw = fftw_plan_dft_r2c_1d(fftsize, | ||||
| time, | time, | ||||
| fft, | fft, | ||||
| @@ -38,12 +49,15 @@ FFTwrapper::FFTwrapper(int fftsize_) | |||||
| fft, | fft, | ||||
| time, | time, | ||||
| FFTW_ESTIMATE); | FFTW_ESTIMATE); | ||||
| pthread_mutex_unlock(mutex); | |||||
| } | } | ||||
| FFTwrapper::~FFTwrapper() | FFTwrapper::~FFTwrapper() | ||||
| { | { | ||||
| pthread_mutex_lock(mutex); | |||||
| fftw_destroy_plan(planfftw); | fftw_destroy_plan(planfftw); | ||||
| fftw_destroy_plan(planfftw_inv); | fftw_destroy_plan(planfftw_inv); | ||||
| pthread_mutex_unlock(mutex); | |||||
| delete [] time; | delete [] time; | ||||
| delete [] fft; | delete [] fft; | ||||
| @@ -82,4 +96,7 @@ void FFTwrapper::freqs2smps(const fft_t *freqs, float *smps) | |||||
| void FFT_cleanup() | void FFT_cleanup() | ||||
| { | { | ||||
| fftw_cleanup(); | fftw_cleanup(); | ||||
| pthread_mutex_destroy(mutex); | |||||
| delete mutex; | |||||
| mutex = NULL; | |||||
| } | } | ||||
| @@ -24,8 +24,7 @@ | |||||
| #define FFT_WRAPPER_H | #define FFT_WRAPPER_H | ||||
| #include <fftw3.h> | #include <fftw3.h> | ||||
| #include <complex> | #include <complex> | ||||
| typedef double fftw_real; | |||||
| typedef std::complex<fftw_real> fft_t; | |||||
| #include "../globals.h" | |||||
| /**A wrapper for the FFTW library (Fast Fourier Transforms)*/ | /**A wrapper for the FFTW library (Fast Fourier Transforms)*/ | ||||
| class FFTwrapper | class FFTwrapper | ||||
| @@ -48,5 +47,24 @@ class FFTwrapper | |||||
| fftw_plan planfftw, planfftw_inv; | fftw_plan planfftw, planfftw_inv; | ||||
| }; | }; | ||||
| /* | |||||
| * The "std::polar" template has no clear definition for the range of | |||||
| * the input parameters, and some C++ standard library implementations | |||||
| * don't accept negative amplitude among others. Define our own | |||||
| * FFTpolar template, which works like we expect it to. | |||||
| */ | |||||
| template<class _Tp> | |||||
| std::complex<_Tp> | |||||
| FFTpolar(const _Tp& __rho, const _Tp& __theta = _Tp(0)) | |||||
| { | |||||
| _Tp __x = __rho * cos(__theta); | |||||
| if (isnan(__x)) | |||||
| __x = 0; | |||||
| _Tp __y = __rho * sin(__theta); | |||||
| if (isnan(__y)) | |||||
| __y = 0; | |||||
| return std::complex<_Tp>(__x, __y); | |||||
| } | |||||
| void FFT_cleanup(); | void FFT_cleanup(); | ||||
| #endif | #endif | ||||
| @@ -20,14 +20,16 @@ | |||||
| */ | */ | ||||
| #include <math.h> | |||||
| #include <stdio.h> | |||||
| #include <cmath> | |||||
| #include <cstdio> | |||||
| #include <cassert> | |||||
| #include "Filter.h" | #include "Filter.h" | ||||
| #include "AnalogFilter.h" | #include "AnalogFilter.h" | ||||
| #include "FormantFilter.h" | #include "FormantFilter.h" | ||||
| #include "SVFilter.h" | #include "SVFilter.h" | ||||
| #include "../Params/FilterParams.h" | #include "../Params/FilterParams.h" | ||||
| #include "../Misc/Allocator.h" | |||||
| Filter::Filter(unsigned int srate, int bufsize) | Filter::Filter(unsigned int srate, int bufsize) | ||||
| : outgain(1.0f), | : outgain(1.0f), | ||||
| @@ -37,12 +39,11 @@ Filter::Filter(unsigned int srate, int bufsize) | |||||
| alias(); | alias(); | ||||
| } | } | ||||
| Filter *Filter::generate(FilterParams *pars, unsigned int srate, int bufsize) | |||||
| Filter *Filter::generate(Allocator &memory, FilterParams *pars, | |||||
| unsigned int srate, int bufsize) | |||||
| { | { | ||||
| if (srate == 0) | |||||
| srate = synth->samplerate; | |||||
| if (bufsize == 0) | |||||
| bufsize = synth->buffersize; | |||||
| assert(srate != 0); | |||||
| assert(bufsize != 0); | |||||
| unsigned char Ftype = pars->Ptype; | unsigned char Ftype = pars->Ptype; | ||||
| unsigned char Fstages = pars->Pstages; | unsigned char Fstages = pars->Pstages; | ||||
| @@ -50,16 +51,16 @@ Filter *Filter::generate(FilterParams *pars, unsigned int srate, int bufsize) | |||||
| Filter *filter; | Filter *filter; | ||||
| switch(pars->Pcategory) { | switch(pars->Pcategory) { | ||||
| case 1: | case 1: | ||||
| filter = new FormantFilter(pars, srate, bufsize); | |||||
| filter = memory.alloc<FormantFilter>(pars, &memory, srate, bufsize); | |||||
| break; | break; | ||||
| case 2: | case 2: | ||||
| filter = new SVFilter(Ftype, 1000.0f, pars->getq(), Fstages, srate, bufsize); | |||||
| filter = memory.alloc<SVFilter>(Ftype, 1000.0f, pars->getq(), Fstages, srate, bufsize); | |||||
| filter->outgain = dB2rap(pars->getgain()); | filter->outgain = dB2rap(pars->getgain()); | ||||
| if(filter->outgain > 1.0f) | if(filter->outgain > 1.0f) | ||||
| filter->outgain = sqrt(filter->outgain); | filter->outgain = sqrt(filter->outgain); | ||||
| break; | break; | ||||
| default: | default: | ||||
| filter = new AnalogFilter(Ftype, 1000.0f, pars->getq(), Fstages, srate, bufsize); | |||||
| filter = memory.alloc<AnalogFilter>(Ftype, 1000.0f, pars->getq(), Fstages, srate, bufsize); | |||||
| if((Ftype >= 6) && (Ftype <= 8)) | if((Ftype >= 6) && (Ftype <= 8)) | ||||
| filter->setgain(pars->getgain()); | filter->setgain(pars->getgain()); | ||||
| else | else | ||||
| @@ -29,7 +29,8 @@ class Filter | |||||
| { | { | ||||
| public: | public: | ||||
| static float getrealfreq(float freqpitch); | static float getrealfreq(float freqpitch); | ||||
| static Filter *generate(class FilterParams * pars, unsigned int srate = 0, int bufsize = 0); | |||||
| static Filter *generate(class Allocator &memory, class FilterParams *pars, | |||||
| unsigned int srate, int bufsize); | |||||
| Filter(unsigned int srate, int bufsize); | Filter(unsigned int srate, int bufsize); | ||||
| virtual ~Filter() {} | virtual ~Filter() {} | ||||
| @@ -23,16 +23,17 @@ | |||||
| #include <cmath> | #include <cmath> | ||||
| #include <cstdio> | #include <cstdio> | ||||
| #include "../Misc/Util.h" | #include "../Misc/Util.h" | ||||
| #include "../Misc/Allocator.h" | |||||
| #include "FormantFilter.h" | #include "FormantFilter.h" | ||||
| #include "AnalogFilter.h" | #include "AnalogFilter.h" | ||||
| #include "../Params/FilterParams.h" | #include "../Params/FilterParams.h" | ||||
| FormantFilter::FormantFilter(FilterParams *pars, unsigned int srate, int bufsize) | |||||
| : Filter(srate, bufsize) | |||||
| FormantFilter::FormantFilter(FilterParams *pars, Allocator *alloc, unsigned int srate, int bufsize) | |||||
| : Filter(srate, bufsize), memory(*alloc) | |||||
| { | { | ||||
| numformants = pars->Pnumformants; | numformants = pars->Pnumformants; | ||||
| for(int i = 0; i < numformants; ++i) | for(int i = 0; i < numformants; ++i) | ||||
| formant[i] = new AnalogFilter(4 /*BPF*/, 1000.0f, 10.0f, pars->Pstages, srate, bufsize); | |||||
| formant[i] = memory.alloc<AnalogFilter>(4 /*BPF*/, 1000.0f, 10.0f, pars->Pstages, srate, bufsize); | |||||
| cleanup(); | cleanup(); | ||||
| for(int j = 0; j < FF_MAX_VOWELS; ++j) | for(int j = 0; j < FF_MAX_VOWELS; ++j) | ||||
| @@ -78,7 +79,7 @@ FormantFilter::FormantFilter(FilterParams *pars, unsigned int srate, int bufsize | |||||
| FormantFilter::~FormantFilter() | FormantFilter::~FormantFilter() | ||||
| { | { | ||||
| for(int i = 0; i < numformants; ++i) | for(int i = 0; i < numformants; ++i) | ||||
| delete (formant[i]); | |||||
| memory.dealloc(formant[i]); | |||||
| } | } | ||||
| void FormantFilter::cleanup() | void FormantFilter::cleanup() | ||||
| @@ -27,10 +27,11 @@ | |||||
| #include "Filter.h" | #include "Filter.h" | ||||
| class Allocator; | |||||
| class FormantFilter:public Filter | class FormantFilter:public Filter | ||||
| { | { | ||||
| public: | public: | ||||
| FormantFilter(class FilterParams *pars, unsigned int srate, int bufsize); | |||||
| FormantFilter(class FilterParams *pars, Allocator *alloc, unsigned int srate, int bufsize); | |||||
| ~FormantFilter(); | ~FormantFilter(); | ||||
| void filterout(float *smp); | void filterout(float *smp); | ||||
| void setfreq(float frequency); | void setfreq(float frequency); | ||||
| @@ -61,6 +62,7 @@ class FormantFilter:public Filter | |||||
| float oldinput, slowinput; | float oldinput, slowinput; | ||||
| float Qfactor, formantslowness, oldQfactor; | float Qfactor, formantslowness, oldQfactor; | ||||
| float vowelclearness, sequencestretch; | float vowelclearness, sequencestretch; | ||||
| Allocator &memory; | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -24,9 +24,7 @@ | |||||
| #include <cstdio> | #include <cstdio> | ||||
| #include <cstring> | #include <cstring> | ||||
| #include <cassert> | #include <cassert> | ||||
| #ifndef CARLA_OS_WIN | |||||
| #include <err.h> | #include <err.h> | ||||
| #endif | |||||
| #include "../Misc/Util.h" | #include "../Misc/Util.h" | ||||
| #include "SVFilter.h" | #include "SVFilter.h" | ||||
| @@ -21,13 +21,12 @@ | |||||
| #include <cmath> | #include <cmath> | ||||
| #include <cstring> | #include <cstring> | ||||
| #ifndef CARLA_OS_WIN | |||||
| #include <err.h> | #include <err.h> | ||||
| #endif | |||||
| #include "../Misc/Allocator.h" | |||||
| #include "Unison.h" | #include "Unison.h" | ||||
| Unison::Unison(int update_period_samples_, float max_delay_sec_, float srate_f) | |||||
| Unison::Unison(Allocator *alloc_, int update_period_samples_, float max_delay_sec_, float srate_f) | |||||
| :unison_size(0), | :unison_size(0), | ||||
| base_freq(1.0f), | base_freq(1.0f), | ||||
| uv(NULL), | uv(NULL), | ||||
| @@ -39,18 +38,19 @@ Unison::Unison(int update_period_samples_, float max_delay_sec_, float srate_f) | |||||
| delay_buffer(NULL), | delay_buffer(NULL), | ||||
| unison_amplitude_samples(0.0f), | unison_amplitude_samples(0.0f), | ||||
| unison_bandwidth_cents(10.0f), | unison_bandwidth_cents(10.0f), | ||||
| samplerate_f(srate_f) | |||||
| samplerate_f(srate_f), | |||||
| alloc(*alloc_) | |||||
| { | { | ||||
| if(max_delay < 10) | if(max_delay < 10) | ||||
| max_delay = 10; | max_delay = 10; | ||||
| delay_buffer = new float[max_delay]; | |||||
| delay_buffer = alloc.valloc<float>(max_delay); | |||||
| memset(delay_buffer, 0, max_delay * sizeof(float)); | memset(delay_buffer, 0, max_delay * sizeof(float)); | ||||
| setSize(1); | setSize(1); | ||||
| } | } | ||||
| Unison::~Unison() { | Unison::~Unison() { | ||||
| delete [] delay_buffer; | |||||
| delete [] uv; | |||||
| alloc.devalloc(delay_buffer); | |||||
| alloc.devalloc(uv); | |||||
| } | } | ||||
| void Unison::setSize(int new_size) | void Unison::setSize(int new_size) | ||||
| @@ -58,9 +58,8 @@ void Unison::setSize(int new_size) | |||||
| if(new_size < 1) | if(new_size < 1) | ||||
| new_size = 1; | new_size = 1; | ||||
| unison_size = new_size; | unison_size = new_size; | ||||
| if(uv) | |||||
| delete [] uv; | |||||
| uv = new UnisonVoice[unison_size]; | |||||
| alloc.devalloc(uv); | |||||
| uv = alloc.valloc<UnisonVoice>(unison_size); | |||||
| first_time = true; | first_time = true; | ||||
| updateParameters(); | updateParameters(); | ||||
| } | } | ||||
| @@ -26,11 +26,12 @@ | |||||
| //how much the unison frequencies varies (always >= 1.0) | //how much the unison frequencies varies (always >= 1.0) | ||||
| #define UNISON_FREQ_SPAN 2.0f | #define UNISON_FREQ_SPAN 2.0f | ||||
| class Allocator; | |||||
| class Unison | class Unison | ||||
| { | { | ||||
| public: | public: | ||||
| Unison(int update_period_samples_, float max_delay_sec_, float srate_f); | |||||
| Unison(Allocator *alloc_, int update_period_samples_, float max_delay_sec_, float srate_f); | |||||
| ~Unison(); | ~Unison(); | ||||
| void setSize(int new_size); | void setSize(int new_size); | ||||
| @@ -72,5 +73,6 @@ class Unison | |||||
| // current setup | // current setup | ||||
| float samplerate_f; | float samplerate_f; | ||||
| Allocator &alloc; | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -21,11 +21,14 @@ | |||||
| */ | */ | ||||
| #include <cmath> | #include <cmath> | ||||
| #include "../Misc/Allocator.h" | |||||
| #include "Alienwah.h" | #include "Alienwah.h" | ||||
| Alienwah::Alienwah(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize) | |||||
| :Effect(insertion_, efxoutl_, efxoutr_, NULL, 0, srate, bufsize), | |||||
| lfo(srate, bufsize), | |||||
| using std::complex; | |||||
| Alienwah::Alienwah(EffectParams pars) | |||||
| :Effect(pars), | |||||
| lfo(pars.srate, pars.bufsize), | |||||
| oldl(NULL), | oldl(NULL), | ||||
| oldr(NULL) | oldr(NULL) | ||||
| { | { | ||||
| @@ -37,10 +40,8 @@ Alienwah::Alienwah(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned i | |||||
| Alienwah::~Alienwah() | Alienwah::~Alienwah() | ||||
| { | { | ||||
| if(oldl != NULL) | |||||
| delete [] oldl; | |||||
| if(oldr != NULL) | |||||
| delete [] oldr; | |||||
| memory.devalloc(oldl); | |||||
| memory.devalloc(oldr); | |||||
| } | } | ||||
| @@ -139,13 +140,11 @@ void Alienwah::setphase(unsigned char _Pphase) | |||||
| void Alienwah::setdelay(unsigned char _Pdelay) | void Alienwah::setdelay(unsigned char _Pdelay) | ||||
| { | { | ||||
| if(oldl != NULL) | |||||
| delete [] oldl; | |||||
| if(oldr != NULL) | |||||
| delete [] oldr; | |||||
| memory.devalloc(oldl); | |||||
| memory.devalloc(oldr); | |||||
| Pdelay = (_Pdelay >= MAX_ALIENWAH_DELAY) ? MAX_ALIENWAH_DELAY : _Pdelay; | Pdelay = (_Pdelay >= MAX_ALIENWAH_DELAY) ? MAX_ALIENWAH_DELAY : _Pdelay; | ||||
| oldl = new complex<float>[Pdelay]; | |||||
| oldr = new complex<float>[Pdelay]; | |||||
| oldl = memory.valloc<complex<float>>(Pdelay); | |||||
| oldr = memory.valloc<complex<float>>(Pdelay); | |||||
| cleanup(); | cleanup(); | ||||
| } | } | ||||
| @@ -23,11 +23,9 @@ | |||||
| #ifndef ALIENWAH_H | #ifndef ALIENWAH_H | ||||
| #define ALIENWAH_H | #define ALIENWAH_H | ||||
| #include <complex> | |||||
| #include "Effect.h" | #include "Effect.h" | ||||
| #include "EffectLFO.h" | #include "EffectLFO.h" | ||||
| using namespace std; | |||||
| #include <complex> | |||||
| #define MAX_ALIENWAH_DELAY 100 | #define MAX_ALIENWAH_DELAY 100 | ||||
| @@ -35,17 +33,7 @@ using namespace std; | |||||
| class Alienwah:public Effect | class Alienwah:public Effect | ||||
| { | { | ||||
| public: | public: | ||||
| /** | |||||
| * Constructor | |||||
| * @param insertion_ true for insertion Effect | |||||
| * @param efxoutl_ Pointer to Alienwah's left channel output buffer | |||||
| * @param efxoutr_ Pointer to Alienwah's left channel output buffer | |||||
| * @return Initialized Alienwah | |||||
| */ | |||||
| Alienwah(bool insertion_, | |||||
| float *const efxoutl_, | |||||
| float *const efxoutr_, | |||||
| unsigned int srate, int bufsize); | |||||
| Alienwah(EffectParams pars); | |||||
| ~Alienwah(); | ~Alienwah(); | ||||
| void out(const Stereo<float *> &smp); | void out(const Stereo<float *> &smp); | ||||
| @@ -73,8 +61,8 @@ class Alienwah:public Effect | |||||
| //Internal Values | //Internal Values | ||||
| float fb, depth, phase; | float fb, depth, phase; | ||||
| complex<float> *oldl, *oldr; | |||||
| complex<float> oldclfol, oldclfor; | |||||
| std::complex<float> *oldl, *oldr; | |||||
| std::complex<float> oldclfol, oldclfor; | |||||
| int oldk; | int oldk; | ||||
| }; | }; | ||||
| @@ -21,16 +21,17 @@ | |||||
| */ | */ | ||||
| #include <cmath> | #include <cmath> | ||||
| #include "../Misc/Allocator.h" | |||||
| #include "Chorus.h" | #include "Chorus.h" | ||||
| #include <iostream> | #include <iostream> | ||||
| using namespace std; | using namespace std; | ||||
| Chorus::Chorus(bool insertion_, float *const efxoutl_, float *efxoutr_, unsigned int srate, int bufsize) | |||||
| :Effect(insertion_, efxoutl_, efxoutr_, NULL, 0, srate, bufsize), | |||||
| lfo(srate, bufsize), | |||||
| Chorus::Chorus(EffectParams pars) | |||||
| :Effect(pars), | |||||
| lfo(pars.srate, pars.bufsize), | |||||
| maxdelay((int)(MAX_CHORUS_DELAY / 1000.0f * samplerate_f)), | maxdelay((int)(MAX_CHORUS_DELAY / 1000.0f * samplerate_f)), | ||||
| delaySample(new float[maxdelay], new float[maxdelay]) | |||||
| delaySample(memory.valloc<float>(maxdelay), memory.valloc<float>(maxdelay)) | |||||
| { | { | ||||
| dlk = 0; | dlk = 0; | ||||
| drk = 0; | drk = 0; | ||||
| @@ -44,8 +45,8 @@ Chorus::Chorus(bool insertion_, float *const efxoutl_, float *efxoutr_, unsigned | |||||
| Chorus::~Chorus() | Chorus::~Chorus() | ||||
| { | { | ||||
| delete [] delaySample.l; | |||||
| delete [] delaySample.r; | |||||
| memory.devalloc(delaySample.l); | |||||
| memory.devalloc(delaySample.r); | |||||
| } | } | ||||
| //get the delay value in samples; xlfo is the current lfo value | //get the delay value in samples; xlfo is the current lfo value | ||||
| @@ -32,7 +32,7 @@ | |||||
| class Chorus:public Effect | class Chorus:public Effect | ||||
| { | { | ||||
| public: | public: | ||||
| Chorus(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||||
| Chorus(EffectParams pars); | |||||
| /**Destructor*/ | /**Destructor*/ | ||||
| ~Chorus(); | ~Chorus(); | ||||
| void out(const Stereo<float *> &input); | void out(const Stereo<float *> &input); | ||||
| @@ -23,10 +23,11 @@ | |||||
| #include "Distorsion.h" | #include "Distorsion.h" | ||||
| #include "../DSP/AnalogFilter.h" | #include "../DSP/AnalogFilter.h" | ||||
| #include "../Misc/WaveShapeSmps.h" | #include "../Misc/WaveShapeSmps.h" | ||||
| #include "../Misc/Allocator.h" | |||||
| #include <cmath> | #include <cmath> | ||||
| Distorsion::Distorsion(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize) | |||||
| :Effect(insertion_, efxoutl_, efxoutr_, NULL, 0, srate, bufsize), | |||||
| Distorsion::Distorsion(EffectParams pars) | |||||
| :Effect(pars), | |||||
| Pvolume(50), | Pvolume(50), | ||||
| Pdrive(90), | Pdrive(90), | ||||
| Plevel(64), | Plevel(64), | ||||
| @@ -37,20 +38,20 @@ Distorsion::Distorsion(bool insertion_, float *efxoutl_, float *efxoutr_, unsign | |||||
| Pstereo(0), | Pstereo(0), | ||||
| Pprefiltering(0) | Pprefiltering(0) | ||||
| { | { | ||||
| lpfl = new AnalogFilter(2, 22000, 1, 0, srate, bufsize); | |||||
| lpfr = new AnalogFilter(2, 22000, 1, 0, srate, bufsize); | |||||
| hpfl = new AnalogFilter(3, 20, 1, 0, srate, bufsize); | |||||
| hpfr = new AnalogFilter(3, 20, 1, 0, srate, bufsize); | |||||
| lpfl = memory.alloc<AnalogFilter>(2, 22000, 1, 0, pars.srate, pars.bufsize); | |||||
| lpfr = memory.alloc<AnalogFilter>(2, 22000, 1, 0, pars.srate, pars.bufsize); | |||||
| hpfl = memory.alloc<AnalogFilter>(3, 20, 1, 0, pars.srate, pars.bufsize); | |||||
| hpfr = memory.alloc<AnalogFilter>(3, 20, 1, 0, pars.srate, pars.bufsize); | |||||
| setpreset(Ppreset); | setpreset(Ppreset); | ||||
| cleanup(); | cleanup(); | ||||
| } | } | ||||
| Distorsion::~Distorsion() | Distorsion::~Distorsion() | ||||
| { | { | ||||
| delete lpfl; | |||||
| delete lpfr; | |||||
| delete hpfl; | |||||
| delete hpfr; | |||||
| memory.dealloc(lpfl); | |||||
| memory.dealloc(lpfr); | |||||
| memory.dealloc(hpfl); | |||||
| memory.dealloc(hpfr); | |||||
| } | } | ||||
| //Cleanup the effect | //Cleanup the effect | ||||
| @@ -137,7 +138,7 @@ void Distorsion::setvolume(unsigned char _Pvolume) | |||||
| void Distorsion::setlpf(unsigned char _Plpf) | void Distorsion::setlpf(unsigned char _Plpf) | ||||
| { | { | ||||
| Plpf = _Plpf; | Plpf = _Plpf; | ||||
| float fr = expf(powf(Plpf / 127.0f, 0.5f) * logf(25000.0f)) + 40.0f; | |||||
| float fr = expf(sqrtf(Plpf / 127.0f) * logf(25000.0f)) + 40.0f; | |||||
| lpfl->setfreq(fr); | lpfl->setfreq(fr); | ||||
| lpfr->setfreq(fr); | lpfr->setfreq(fr); | ||||
| } | } | ||||
| @@ -145,7 +146,7 @@ void Distorsion::setlpf(unsigned char _Plpf) | |||||
| void Distorsion::sethpf(unsigned char _Phpf) | void Distorsion::sethpf(unsigned char _Phpf) | ||||
| { | { | ||||
| Phpf = _Phpf; | Phpf = _Phpf; | ||||
| float fr = expf(powf(Phpf / 127.0f, 0.5f) * logf(25000.0f)) + 20.0f; | |||||
| float fr = expf(sqrtf(Phpf / 127.0f) * logf(25000.0f)) + 20.0f; | |||||
| hpfl->setfreq(fr); | hpfl->setfreq(fr); | ||||
| hpfr->setfreq(fr); | hpfr->setfreq(fr); | ||||
| } | } | ||||
| @@ -29,7 +29,7 @@ | |||||
| class Distorsion:public Effect | class Distorsion:public Effect | ||||
| { | { | ||||
| public: | public: | ||||
| Distorsion(bool insertion, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||||
| Distorsion(EffectParams pars); | |||||
| ~Distorsion(); | ~Distorsion(); | ||||
| void out(const Stereo<float *> &smp); | void out(const Stereo<float *> &smp); | ||||
| void setpreset(unsigned char npreset); | void setpreset(unsigned char npreset); | ||||
| @@ -23,10 +23,11 @@ | |||||
| #include <cmath> | #include <cmath> | ||||
| #include "DynamicFilter.h" | #include "DynamicFilter.h" | ||||
| #include "../DSP/Filter.h" | #include "../DSP/Filter.h" | ||||
| #include "../Misc/Allocator.h" | |||||
| DynamicFilter::DynamicFilter(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize) | |||||
| :Effect(insertion_, efxoutl_, efxoutr_, new FilterParams(0, 64, 64), 0, srate, bufsize), | |||||
| lfo(srate, bufsize), | |||||
| DynamicFilter::DynamicFilter(EffectParams pars) | |||||
| :Effect(pars), | |||||
| lfo(pars.srate, pars.bufsize), | |||||
| Pvolume(110), | Pvolume(110), | ||||
| Pdepth(0), | Pdepth(0), | ||||
| Pampsns(90), | Pampsns(90), | ||||
| @@ -35,15 +36,16 @@ DynamicFilter::DynamicFilter(bool insertion_, float *efxoutl_, float *efxoutr_, | |||||
| filterl(NULL), | filterl(NULL), | ||||
| filterr(NULL) | filterr(NULL) | ||||
| { | { | ||||
| filterpars = memory.alloc<FilterParams>(0,0,0); | |||||
| setpreset(Ppreset); | setpreset(Ppreset); | ||||
| cleanup(); | cleanup(); | ||||
| } | } | ||||
| DynamicFilter::~DynamicFilter() | DynamicFilter::~DynamicFilter() | ||||
| { | { | ||||
| delete filterpars; | |||||
| delete filterl; | |||||
| delete filterr; | |||||
| memory.dealloc(filterpars); | |||||
| memory.dealloc(filterl); | |||||
| memory.dealloc(filterr); | |||||
| } | } | ||||
| @@ -129,10 +131,10 @@ void DynamicFilter::setampsns(unsigned char _Pampsns) | |||||
| void DynamicFilter::reinitfilter(void) | void DynamicFilter::reinitfilter(void) | ||||
| { | { | ||||
| delete filterl; | |||||
| delete filterr; | |||||
| filterl = Filter::generate(filterpars, samplerate, buffersize); | |||||
| filterr = Filter::generate(filterpars, samplerate, buffersize); | |||||
| memory.dealloc(filterl); | |||||
| memory.dealloc(filterr); | |||||
| filterl = Filter::generate(memory, filterpars, samplerate, buffersize); | |||||
| filterr = Filter::generate(memory, filterpars, samplerate, buffersize); | |||||
| } | } | ||||
| void DynamicFilter::setpreset(unsigned char npreset) | void DynamicFilter::setpreset(unsigned char npreset) | ||||
| @@ -30,7 +30,7 @@ | |||||
| class DynamicFilter:public Effect | class DynamicFilter:public Effect | ||||
| { | { | ||||
| public: | public: | ||||
| DynamicFilter(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||||
| DynamicFilter(EffectParams pars); | |||||
| ~DynamicFilter(); | ~DynamicFilter(); | ||||
| void out(const Stereo<float *> &smp); | void out(const Stereo<float *> &smp); | ||||
| @@ -23,9 +23,10 @@ | |||||
| #include <cmath> | #include <cmath> | ||||
| #include "EQ.h" | #include "EQ.h" | ||||
| #include "../DSP/AnalogFilter.h" | #include "../DSP/AnalogFilter.h" | ||||
| #include "../Misc/Allocator.h" | |||||
| EQ::EQ(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize) | |||||
| :Effect(insertion_, efxoutl_, efxoutr_, NULL, 0, srate, bufsize) | |||||
| EQ::EQ(EffectParams pars) | |||||
| :Effect(pars) | |||||
| { | { | ||||
| for(int i = 0; i < MAX_EQ_BANDS; ++i) { | for(int i = 0; i < MAX_EQ_BANDS; ++i) { | ||||
| filter[i].Ptype = 0; | filter[i].Ptype = 0; | ||||
| @@ -33,8 +34,8 @@ EQ::EQ(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, in | |||||
| filter[i].Pgain = 64; | filter[i].Pgain = 64; | ||||
| filter[i].Pq = 64; | filter[i].Pq = 64; | ||||
| filter[i].Pstages = 0; | filter[i].Pstages = 0; | ||||
| filter[i].l = new AnalogFilter(6, 1000.0f, 1.0f, 0, srate, bufsize); | |||||
| filter[i].r = new AnalogFilter(6, 1000.0f, 1.0f, 0, srate, bufsize); | |||||
| filter[i].l = memory.alloc<AnalogFilter>(6, 1000.0f, 1.0f, 0, pars.srate, pars.bufsize); | |||||
| filter[i].r = memory.alloc<AnalogFilter>(6, 1000.0f, 1.0f, 0, pars.srate, pars.bufsize); | |||||
| } | } | ||||
| //default values | //default values | ||||
| Pvolume = 50; | Pvolume = 50; | ||||
| @@ -43,6 +44,13 @@ EQ::EQ(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, in | |||||
| cleanup(); | cleanup(); | ||||
| } | } | ||||
| EQ::~EQ() | |||||
| { | |||||
| for(int i = 0; i < MAX_EQ_BANDS; ++i) { | |||||
| memory.dealloc(filter[i].l); | |||||
| memory.dealloc(filter[i].r); | |||||
| } | |||||
| } | |||||
| // Cleanup the effect | // Cleanup the effect | ||||
| void EQ::cleanup(void) | void EQ::cleanup(void) | ||||
| @@ -196,3 +204,42 @@ float EQ::getfreqresponse(float freq) | |||||
| } | } | ||||
| return rap2dB(resp * outvolume); | return rap2dB(resp * outvolume); | ||||
| } | } | ||||
| //full taps & three filter taps | |||||
| static void convolve(float *a, const float *filter) | |||||
| { | |||||
| float tmp[MAX_EQ_BANDS*2+1]; | |||||
| for(int i=0; i<MAX_EQ_BANDS*2+1; ++i) | |||||
| tmp[i] = a[i]; | |||||
| //Base Case | |||||
| a[0] = tmp[0]*filter[0]; | |||||
| a[1] = tmp[0]*filter[1] + tmp[1]*filter[0]; | |||||
| for(int i=2; i<MAX_EQ_BANDS+1; ++i) { | |||||
| a[i] = filter[0]*tmp[i] + | |||||
| filter[1]*tmp[i-1] + | |||||
| filter[2]*tmp[i-2]; | |||||
| } | |||||
| } | |||||
| //Not exactly the most efficient manner to derive the total taps, but it should | |||||
| //be fast enough in practice | |||||
| void EQ::getFilter(float *a, float *b) const | |||||
| { | |||||
| a[0] = 1; | |||||
| b[0] = 1; | |||||
| for(int i = 0; i < MAX_EQ_BANDS; ++i) { | |||||
| auto &F = filter[i]; | |||||
| if(F.Ptype == 0) | |||||
| continue; | |||||
| float Fb[3] = {F.l->coeff.c[0], F.l->coeff.c[1], F.l->coeff.c[2]}; | |||||
| float Fa[3] = {1.0f, -F.l->coeff.d[1], -F.l->coeff.d[2]}; | |||||
| for(int j=0; j<F.Pstages+1; ++j) { | |||||
| convolve(b, Fb); | |||||
| convolve(a, Fa); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -29,8 +29,8 @@ | |||||
| class EQ:public Effect | class EQ:public Effect | ||||
| { | { | ||||
| public: | public: | ||||
| EQ(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||||
| ~EQ() {} | |||||
| EQ(EffectParams pars); | |||||
| ~EQ(); | |||||
| void out(const Stereo<float *> &smp); | void out(const Stereo<float *> &smp); | ||||
| void setpreset(unsigned char npreset); | void setpreset(unsigned char npreset); | ||||
| void changepar(int npar, unsigned char value); | void changepar(int npar, unsigned char value); | ||||
| @@ -38,6 +38,8 @@ class EQ:public Effect | |||||
| void cleanup(void); | void cleanup(void); | ||||
| float getfreqresponse(float freq); | float getfreqresponse(float freq); | ||||
| void getFilter(float *a/*[MAX_EQ_BANDS*MAX_FILTER_STAGES*2+1]*/, | |||||
| float *b/*[MAX_EQ_BANDS*MAX_FILTER_STAGES*2+1]*/) const; | |||||
| private: | private: | ||||
| //Parameters | //Parameters | ||||
| unsigned char Pvolume; | unsigned char Pvolume; | ||||
| @@ -48,7 +50,13 @@ class EQ:public Effect | |||||
| //parameters | //parameters | ||||
| unsigned char Ptype, Pfreq, Pgain, Pq, Pstages; | unsigned char Ptype, Pfreq, Pgain, Pq, Pstages; | ||||
| //internal values | //internal values | ||||
| class AnalogFilter * l, *r; | |||||
| /* TODO | |||||
| * The analog filters here really ought to be dumbed down some as | |||||
| * you are just looking to do a batch convolution in the end | |||||
| * Perhaps some static functions to do the filter design? | |||||
| */ | |||||
| class AnalogFilter *l, *r; | |||||
| } filter[MAX_EQ_BANDS]; | } filter[MAX_EQ_BANDS]; | ||||
| }; | }; | ||||
| @@ -23,12 +23,13 @@ | |||||
| */ | */ | ||||
| #include <cmath> | #include <cmath> | ||||
| #include "../Misc/Allocator.h" | |||||
| #include "Echo.h" | #include "Echo.h" | ||||
| #define MAX_DELAY 2 | #define MAX_DELAY 2 | ||||
| Echo::Echo(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize) | |||||
| :Effect(insertion_, efxoutl_, efxoutr_, NULL, 0, srate, bufsize), | |||||
| Echo::Echo(EffectParams pars) | |||||
| :Effect(pars), | |||||
| Pvolume(50), | Pvolume(50), | ||||
| Pdelay(60), | Pdelay(60), | ||||
| Plrdelay(100), | Plrdelay(100), | ||||
| @@ -37,8 +38,8 @@ Echo::Echo(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate | |||||
| delayTime(1), | delayTime(1), | ||||
| lrdelay(0), | lrdelay(0), | ||||
| avgDelay(0), | avgDelay(0), | ||||
| delay(new float[(int)(MAX_DELAY * srate)], | |||||
| new float[(int)(MAX_DELAY * srate)]), | |||||
| delay(memory.valloc<float>(MAX_DELAY * pars.srate), | |||||
| memory.valloc<float>(MAX_DELAY * pars.srate)), | |||||
| old(0.0f), | old(0.0f), | ||||
| pos(0), | pos(0), | ||||
| delta(1), | delta(1), | ||||
| @@ -50,8 +51,8 @@ Echo::Echo(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate | |||||
| Echo::~Echo() | Echo::~Echo() | ||||
| { | { | ||||
| delete[] delay.l; | |||||
| delete[] delay.r; | |||||
| memory.devalloc(delay.l); | |||||
| memory.devalloc(delay.r); | |||||
| } | } | ||||
| //Cleanup the effect | //Cleanup the effect | ||||
| @@ -79,6 +80,7 @@ void Echo::initdelays(void) | |||||
| ndelta.l = max(1, (int) (dl * samplerate)); | ndelta.l = max(1, (int) (dl * samplerate)); | ||||
| ndelta.r = max(1, (int) (dr * samplerate)); | ndelta.r = max(1, (int) (dr * samplerate)); | ||||
| delta = ndelta; | |||||
| } | } | ||||
| //Effect output | //Effect output | ||||
| @@ -30,7 +30,7 @@ | |||||
| class Echo:public Effect | class Echo:public Effect | ||||
| { | { | ||||
| public: | public: | ||||
| Echo(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||||
| Echo(EffectParams pars); | |||||
| ~Echo(); | ~Echo(); | ||||
| void out(const Stereo<float *> &input); | void out(const Stereo<float *> &input); | ||||
| @@ -25,16 +25,20 @@ | |||||
| #include "../Params/FilterParams.h" | #include "../Params/FilterParams.h" | ||||
| #include <cmath> | #include <cmath> | ||||
| Effect::Effect(bool insertion_, float *efxoutl_, float *efxoutr_, | |||||
| FilterParams *filterpars_, unsigned char Ppreset_, | |||||
| unsigned int srate, int bufsize) | |||||
| :Ppreset(Ppreset_), | |||||
| efxoutl(efxoutl_), | |||||
| efxoutr(efxoutr_), | |||||
| filterpars(filterpars_), | |||||
| insertion(insertion_), | |||||
| samplerate(srate), | |||||
| buffersize(bufsize) | |||||
| EffectParams::EffectParams(Allocator &alloc_, bool insertion_, float *efxoutl_, float *efxoutr_, | |||||
| unsigned char Ppreset_, unsigned int srate_, int bufsize_, FilterParams *filterpars_) | |||||
| :alloc(alloc_), insertion(insertion_), efxoutl(efxoutl_), efxoutr(efxoutr_), | |||||
| Ppreset(Ppreset_), srate(srate_), bufsize(bufsize_), filterpars(filterpars_) | |||||
| {} | |||||
| Effect::Effect(EffectParams pars) | |||||
| :Ppreset(pars.Ppreset), | |||||
| efxoutl(pars.efxoutl), | |||||
| efxoutr(pars.efxoutr), | |||||
| filterpars(pars.filterpars), | |||||
| insertion(pars.insertion), | |||||
| memory(pars.alloc), | |||||
| samplerate(pars.srate), | |||||
| buffersize(pars.bufsize) | |||||
| { | { | ||||
| alias(); | alias(); | ||||
| } | } | ||||
| @@ -29,22 +29,38 @@ | |||||
| #include "../Misc/Stereo.h" | #include "../Misc/Stereo.h" | ||||
| class FilterParams; | class FilterParams; | ||||
| class Allocator; | |||||
| struct EffectParams | |||||
| { | |||||
| /** | |||||
| * Effect Parameter Constructor | |||||
| * @param alloc Realtime Memory Allocator | |||||
| * @param insertion_ 1 when it is an insertion Effect | |||||
| * @param efxoutl_ Effect output buffer Left channel | |||||
| * @param efxoutr_ Effect output buffer Right channel | |||||
| * @param filterpars_ pointer to FilterParams array | |||||
| * @param Ppreset_ chosen preset | |||||
| * @return Initialized Effect Parameter object*/ | |||||
| EffectParams(Allocator &alloc_, bool insertion_, float *efxoutl_, float *efxoutr_, | |||||
| unsigned char Ppreset_, unsigned int srate, int bufsize, FilterParams *filterpars_=0); | |||||
| Allocator &alloc; | |||||
| bool insertion; | |||||
| float *efxoutl; | |||||
| float *efxoutr; | |||||
| unsigned char Ppreset; | |||||
| unsigned int srate; | |||||
| int bufsize; | |||||
| FilterParams *filterpars; | |||||
| }; | |||||
| /**this class is inherited by the all effects(Reverb, Echo, ..)*/ | /**this class is inherited by the all effects(Reverb, Echo, ..)*/ | ||||
| class Effect | class Effect | ||||
| { | { | ||||
| public: | public: | ||||
| /** | |||||
| * Effect Constructor | |||||
| * @param insertion_ 1 when it is an insertion Effect | |||||
| * @param efxoutl_ Effect output buffer Left channel | |||||
| * @param efxoutr_ Effect output buffer Right channel | |||||
| * @param filterpars_ pointer to FilterParams array | |||||
| * @param Ppreset_ chosen preset | |||||
| * @return Initialized Effect object*/ | |||||
| Effect(bool insertion_, float *efxoutl_, float *efxoutr_, | |||||
| FilterParams *filterpars_, unsigned char Ppreset_, | |||||
| unsigned int srate, int bufsize); | |||||
| Effect(EffectParams pars); | |||||
| virtual ~Effect() {} | virtual ~Effect() {} | ||||
| /** | /** | ||||
| * Choose a preset | * Choose a preset | ||||
| @@ -102,6 +118,9 @@ class Effect | |||||
| char Plrcross; // L/R mix | char Plrcross; // L/R mix | ||||
| float lrcross; | float lrcross; | ||||
| //Allocator | |||||
| Allocator &memory; | |||||
| // current setup | // current setup | ||||
| unsigned int samplerate; | unsigned int samplerate; | ||||
| int buffersize; | int buffersize; | ||||
| @@ -20,6 +20,10 @@ | |||||
| */ | */ | ||||
| #include <rtosc/ports.h> | |||||
| #include <rtosc/port-sugar.h> | |||||
| #include "EffectMgr.h" | #include "EffectMgr.h" | ||||
| #include "Effect.h" | #include "Effect.h" | ||||
| #include "Reverb.h" | #include "Reverb.h" | ||||
| @@ -29,31 +33,103 @@ | |||||
| #include "EQ.h" | #include "EQ.h" | ||||
| #include "DynamicFilter.h" | #include "DynamicFilter.h" | ||||
| #include "../Misc/XMLwrapper.h" | #include "../Misc/XMLwrapper.h" | ||||
| #include "../Misc/Util.h" | |||||
| #include "../Params/FilterParams.h" | #include "../Params/FilterParams.h" | ||||
| #include <iostream> | |||||
| using namespace std; | |||||
| EffectMgr::EffectMgr(const bool insertion_, pthread_mutex_t *mutex_) | |||||
| #include "../Misc/Allocator.h" | |||||
| #define rObject EffectMgr | |||||
| static const rtosc::Ports local_ports = { | |||||
| rSelf(EffectMgr), | |||||
| rPaste, | |||||
| rRecurp(filterpars, "Filter Parameter for Dynamic Filter"), | |||||
| {"parameter#64::i", rProp(alias) rDoc("Parameter Accessor"), NULL, | |||||
| [](const char *msg, rtosc::RtData &d) | |||||
| { | |||||
| EffectMgr *eff = (EffectMgr*)d.obj; | |||||
| const char *mm = msg; | |||||
| while(!isdigit(*mm))++mm; | |||||
| if(!rtosc_narguments(msg)) | |||||
| d.reply(d.loc, "i", eff->geteffectparrt(atoi(mm))); | |||||
| else | |||||
| eff->seteffectparrt(atoi(mm), rtosc_argument(msg, 0).i); | |||||
| }}, | |||||
| {"preset::i", rProp(alias) rDoc("Effect Preset Selector"), NULL, | |||||
| [](const char *msg, rtosc::RtData &d) | |||||
| { | |||||
| EffectMgr *eff = (EffectMgr*)d.obj; | |||||
| if(!rtosc_narguments(msg)) | |||||
| d.reply(d.loc, "i", eff->getpreset()); | |||||
| else | |||||
| eff->changepresetrt(rtosc_argument(msg, 0).i); | |||||
| }}, | |||||
| {"eq-coeffs:", rProp(internal) rDoc("Get equalizer Coefficients"), NULL, | |||||
| [](const char *, rtosc::RtData &d) | |||||
| { | |||||
| EffectMgr *eff = (EffectMgr*)d.obj; | |||||
| if(eff->nefx != 7) | |||||
| return; | |||||
| EQ *eq = (EQ*)eff->efx; | |||||
| float a[MAX_EQ_BANDS*MAX_FILTER_STAGES*2+1]; | |||||
| float b[MAX_EQ_BANDS*MAX_FILTER_STAGES*2+1]; | |||||
| memset(a, 0, sizeof(a)); | |||||
| memset(b, 0, sizeof(b)); | |||||
| eq->getFilter(a,b); | |||||
| d.reply(d.loc, "bb", sizeof(a), a, sizeof(b), b); | |||||
| }}, | |||||
| {"efftype::i", rDoc("Get Effect Type"), NULL, [](const char *m, rtosc::RtData &d) | |||||
| { | |||||
| EffectMgr *eff = (EffectMgr*)d.obj; | |||||
| if(rtosc_narguments(m)) | |||||
| eff->changeeffectrt(rtosc_argument(m,0).i); | |||||
| else | |||||
| d.reply(d.loc, "i", eff->nefx); | |||||
| }}, | |||||
| {"efftype:b", rProp(internal) rDoc("Pointer swap EffectMgr"), NULL, | |||||
| [](const char *msg, rtosc::RtData &d) | |||||
| { | |||||
| printf("OBSOLETE METHOD CALLED\n"); | |||||
| EffectMgr *eff = (EffectMgr*)d.obj; | |||||
| EffectMgr *eff_ = *(EffectMgr**)rtosc_argument(msg,0).b.data; | |||||
| //Lets trade data | |||||
| std::swap(eff->nefx,eff_->nefx); | |||||
| std::swap(eff->efx,eff_->efx); | |||||
| std::swap(eff->filterpars,eff_->filterpars); | |||||
| std::swap(eff->efxoutl, eff_->efxoutl); | |||||
| std::swap(eff->efxoutr, eff_->efxoutr); | |||||
| //Return the old data for distruction | |||||
| d.reply("/free", "sb", "EffectMgr", sizeof(EffectMgr*), &eff_); | |||||
| }}, | |||||
| }; | |||||
| const rtosc::Ports &EffectMgr::ports = local_ports; | |||||
| EffectMgr::EffectMgr(Allocator &alloc, const SYNTH_T &synth_, const bool insertion_) | |||||
| :insertion(insertion_), | :insertion(insertion_), | ||||
| efxoutl(new float[synth->buffersize]), | |||||
| efxoutr(new float[synth->buffersize]), | |||||
| efxoutl(new float[synth_.buffersize]), | |||||
| efxoutr(new float[synth_.buffersize]), | |||||
| filterpars(NULL), | filterpars(NULL), | ||||
| nefx(0), | nefx(0), | ||||
| efx(NULL), | efx(NULL), | ||||
| mutex(mutex_), | |||||
| dryonly(false) | |||||
| dryonly(false), | |||||
| memory(alloc), | |||||
| synth(synth_) | |||||
| { | { | ||||
| setpresettype("Peffect"); | setpresettype("Peffect"); | ||||
| memset(efxoutl, 0, synth->bufferbytes); | |||||
| memset(efxoutr, 0, synth->bufferbytes); | |||||
| memset(efxoutl, 0, synth.bufferbytes); | |||||
| memset(efxoutr, 0, synth.bufferbytes); | |||||
| memset(settings, 0, sizeof(settings)); | |||||
| defaults(); | defaults(); | ||||
| } | } | ||||
| EffectMgr::~EffectMgr() | EffectMgr::~EffectMgr() | ||||
| { | { | ||||
| delete efx; | |||||
| memory.dealloc(efx); | |||||
| delete [] efxoutl; | delete [] efxoutl; | ||||
| delete [] efxoutr; | delete [] efxoutr; | ||||
| } | } | ||||
| @@ -65,39 +141,41 @@ void EffectMgr::defaults(void) | |||||
| } | } | ||||
| //Change the effect | //Change the effect | ||||
| void EffectMgr::changeeffect(int _nefx) | |||||
| void EffectMgr::changeeffectrt(int _nefx) | |||||
| { | { | ||||
| cleanup(); | cleanup(); | ||||
| if(nefx == _nefx) | |||||
| if(nefx == _nefx && efx != NULL) | |||||
| return; | return; | ||||
| nefx = _nefx; | nefx = _nefx; | ||||
| memset(efxoutl, 0, synth->bufferbytes); | |||||
| memset(efxoutr, 0, synth->bufferbytes); | |||||
| delete efx; | |||||
| memset(efxoutl, 0, synth.bufferbytes); | |||||
| memset(efxoutr, 0, synth.bufferbytes); | |||||
| memory.dealloc(efx); | |||||
| EffectParams pars(memory, insertion, efxoutl, efxoutr, 0, | |||||
| synth.samplerate, synth.buffersize); | |||||
| switch(nefx) { | switch(nefx) { | ||||
| case 1: | case 1: | ||||
| efx = new Reverb(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||||
| efx = memory.alloc<Reverb>(pars); | |||||
| break; | break; | ||||
| case 2: | case 2: | ||||
| efx = new Echo(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||||
| efx = memory.alloc<Echo>(pars); | |||||
| break; | break; | ||||
| case 3: | case 3: | ||||
| efx = new Chorus(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||||
| efx = memory.alloc<Chorus>(pars); | |||||
| break; | break; | ||||
| case 4: | case 4: | ||||
| efx = new Phaser(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||||
| efx = memory.alloc<Phaser>(pars); | |||||
| break; | break; | ||||
| case 5: | case 5: | ||||
| efx = new Alienwah(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||||
| efx = memory.alloc<Alienwah>(pars); | |||||
| break; | break; | ||||
| case 6: | case 6: | ||||
| efx = new Distorsion(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||||
| efx = memory.alloc<Distorsion>(pars); | |||||
| break; | break; | ||||
| case 7: | case 7: | ||||
| efx = new EQ(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||||
| efx = memory.alloc<EQ>(pars); | |||||
| break; | break; | ||||
| case 8: | case 8: | ||||
| efx = new DynamicFilter(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||||
| efx = memory.alloc<DynamicFilter>(pars); | |||||
| break; | break; | ||||
| //put more effect here | //put more effect here | ||||
| default: | default: | ||||
| @@ -109,12 +187,35 @@ void EffectMgr::changeeffect(int _nefx) | |||||
| filterpars = efx->filterpars; | filterpars = efx->filterpars; | ||||
| } | } | ||||
| void EffectMgr::changeeffect(int _nefx) | |||||
| { | |||||
| nefx = _nefx; | |||||
| //preset = 0; | |||||
| //memset(settings, 0, sizeof(settings)); | |||||
| } | |||||
| //Obtain the effect number | //Obtain the effect number | ||||
| int EffectMgr::geteffect(void) | int EffectMgr::geteffect(void) | ||||
| { | { | ||||
| return nefx; | return nefx; | ||||
| } | } | ||||
| // Initialize An Effect in RT context | |||||
| void EffectMgr::init(void) | |||||
| { | |||||
| changeeffectrt(nefx); | |||||
| changepresetrt(preset); | |||||
| for(int i=0; i<128; ++i) | |||||
| seteffectparrt(i, settings[i]); | |||||
| } | |||||
| //Strip effect manager of it's realtime memory | |||||
| void EffectMgr::kill(void) | |||||
| { | |||||
| //printf("Killing Effect(%d)\n", nefx); | |||||
| memory.dealloc(efx); | |||||
| } | |||||
| // Cleanup the current effect | // Cleanup the current effect | ||||
| void EffectMgr::cleanup(void) | void EffectMgr::cleanup(void) | ||||
| { | { | ||||
| @@ -133,39 +234,47 @@ unsigned char EffectMgr::getpreset(void) | |||||
| } | } | ||||
| // Change the preset of the current effect | // Change the preset of the current effect | ||||
| void EffectMgr::changepreset_nolock(unsigned char npreset) | |||||
| void EffectMgr::changepreset(unsigned char npreset) | |||||
| { | { | ||||
| if(efx) | |||||
| efx->setpreset(npreset); | |||||
| preset = npreset; | |||||
| } | } | ||||
| //Change the preset of the current effect(with thread locking) | |||||
| void EffectMgr::changepreset(unsigned char npreset) | |||||
| // Change the preset of the current effect | |||||
| void EffectMgr::changepresetrt(unsigned char npreset) | |||||
| { | { | ||||
| pthread_mutex_lock(mutex); | |||||
| changepreset_nolock(npreset); | |||||
| pthread_mutex_unlock(mutex); | |||||
| preset = npreset; | |||||
| if(efx) | |||||
| efx->setpreset(npreset); | |||||
| } | } | ||||
| //Change a parameter of the current effect | //Change a parameter of the current effect | ||||
| void EffectMgr::seteffectpar_nolock(int npar, unsigned char value) | |||||
| void EffectMgr::seteffectparrt(int npar, unsigned char value) | |||||
| { | { | ||||
| if(npar<128) | |||||
| settings[npar] = value; | |||||
| if(!efx) | if(!efx) | ||||
| return; | return; | ||||
| efx->changepar(npar, value); | efx->changepar(npar, value); | ||||
| } | } | ||||
| // Change a parameter of the current effect (with thread locking) | |||||
| //Change a parameter of the current effect | |||||
| void EffectMgr::seteffectpar(int npar, unsigned char value) | void EffectMgr::seteffectpar(int npar, unsigned char value) | ||||
| { | { | ||||
| pthread_mutex_lock(mutex); | |||||
| seteffectpar_nolock(npar, value); | |||||
| pthread_mutex_unlock(mutex); | |||||
| settings[npar] = value; | |||||
| } | } | ||||
| //Get a parameter of the current effect | //Get a parameter of the current effect | ||||
| unsigned char EffectMgr::geteffectpar(int npar) | unsigned char EffectMgr::geteffectpar(int npar) | ||||
| { | |||||
| if(npar<128) | |||||
| return settings[npar]; | |||||
| if(!efx) | |||||
| return 0; | |||||
| return efx->getpar(npar); | |||||
| } | |||||
| unsigned char EffectMgr::geteffectparrt(int npar) | |||||
| { | { | ||||
| if(!efx) | if(!efx) | ||||
| return 0; | return 0; | ||||
| @@ -177,7 +286,7 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||||
| { | { | ||||
| if(!efx) { | if(!efx) { | ||||
| if(!insertion) | if(!insertion) | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| smpsl[i] = 0.0f; | smpsl[i] = 0.0f; | ||||
| smpsr[i] = 0.0f; | smpsr[i] = 0.0f; | ||||
| efxoutl[i] = 0.0f; | efxoutl[i] = 0.0f; | ||||
| @@ -185,7 +294,7 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||||
| } | } | ||||
| return; | return; | ||||
| } | } | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| smpsl[i] += denormalkillbuf[i]; | smpsl[i] += denormalkillbuf[i]; | ||||
| smpsr[i] += denormalkillbuf[i]; | smpsr[i] += denormalkillbuf[i]; | ||||
| efxoutl[i] = 0.0f; | efxoutl[i] = 0.0f; | ||||
| @@ -196,8 +305,8 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||||
| float volume = efx->volume; | float volume = efx->volume; | ||||
| if(nefx == 7) { //this is need only for the EQ effect | if(nefx == 7) { //this is need only for the EQ effect | ||||
| memcpy(smpsl, efxoutl, synth->bufferbytes); | |||||
| memcpy(smpsr, efxoutr, synth->bufferbytes); | |||||
| memcpy(smpsl, efxoutl, synth.bufferbytes); | |||||
| memcpy(smpsr, efxoutr, synth.bufferbytes); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -216,20 +325,20 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||||
| v2 *= v2; //for Reverb and Echo, the wet function is not liniar | v2 *= v2; //for Reverb and Echo, the wet function is not liniar | ||||
| if(dryonly) //this is used for instrument effect only | if(dryonly) //this is used for instrument effect only | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| smpsl[i] *= v1; | smpsl[i] *= v1; | ||||
| smpsr[i] *= v1; | smpsr[i] *= v1; | ||||
| efxoutl[i] *= v2; | efxoutl[i] *= v2; | ||||
| efxoutr[i] *= v2; | efxoutr[i] *= v2; | ||||
| } | } | ||||
| else // normal instrument/insertion effect | else // normal instrument/insertion effect | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| smpsl[i] = smpsl[i] * v1 + efxoutl[i] * v2; | smpsl[i] = smpsl[i] * v1 + efxoutl[i] * v2; | ||||
| smpsr[i] = smpsr[i] * v1 + efxoutr[i] * v2; | smpsr[i] = smpsr[i] * v1 + efxoutr[i] * v2; | ||||
| } | } | ||||
| } | } | ||||
| else // System effect | else // System effect | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| efxoutl[i] *= 2.0f * volume; | efxoutl[i] *= 2.0f * volume; | ||||
| efxoutr[i] *= 2.0f * volume; | efxoutr[i] *= 2.0f * volume; | ||||
| smpsl[i] = efxoutl[i]; | smpsl[i] = efxoutl[i]; | ||||
| @@ -241,7 +350,7 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||||
| // Get the effect volume for the system effect | // Get the effect volume for the system effect | ||||
| float EffectMgr::sysefxgetvolume(void) | float EffectMgr::sysefxgetvolume(void) | ||||
| { | { | ||||
| return (!efx) ? 1.0f : efx->outvolume; | |||||
| return efx ? efx->outvolume : 1.0f; | |||||
| } | } | ||||
| @@ -257,13 +366,22 @@ void EffectMgr::setdryonly(bool value) | |||||
| dryonly = value; | dryonly = value; | ||||
| } | } | ||||
| void EffectMgr::paste(EffectMgr &e) | |||||
| { | |||||
| changeeffectrt(e.nefx); | |||||
| changepresetrt(e.preset); | |||||
| for(int i=0;i<128;++i){ | |||||
| seteffectparrt(e.settings[i], i); | |||||
| } | |||||
| } | |||||
| void EffectMgr::add2XML(XMLwrapper *xml) | void EffectMgr::add2XML(XMLwrapper *xml) | ||||
| { | { | ||||
| xml->addpar("type", geteffect()); | xml->addpar("type", geteffect()); | ||||
| if(!efx || !geteffect()) | |||||
| if(!geteffect()) | |||||
| return; | return; | ||||
| xml->addpar("preset", efx->Ppreset); | |||||
| xml->addpar("preset", preset); | |||||
| xml->beginbranch("EFFECT_PARAMETERS"); | xml->beginbranch("EFFECT_PARAMETERS"); | ||||
| for(int n = 0; n < 128; ++n) { | for(int n = 0; n < 128; ++n) { | ||||
| @@ -286,18 +404,18 @@ void EffectMgr::getfromXML(XMLwrapper *xml) | |||||
| { | { | ||||
| changeeffect(xml->getpar127("type", geteffect())); | changeeffect(xml->getpar127("type", geteffect())); | ||||
| if(!efx || !geteffect()) | |||||
| if(!geteffect()) | |||||
| return; | return; | ||||
| efx->Ppreset = xml->getpar127("preset", efx->Ppreset); | |||||
| preset = xml->getpar127("preset", preset); | |||||
| if(xml->enterbranch("EFFECT_PARAMETERS")) { | if(xml->enterbranch("EFFECT_PARAMETERS")) { | ||||
| for(int n = 0; n < 128; ++n) { | for(int n = 0; n < 128; ++n) { | ||||
| seteffectpar_nolock(n, 0); //erase effect parameter | |||||
| seteffectpar(n, 0); //erase effect parameter | |||||
| if(xml->enterbranch("par_no", n) == 0) | if(xml->enterbranch("par_no", n) == 0) | ||||
| continue; | continue; | ||||
| int par = geteffectpar(n); | int par = geteffectpar(n); | ||||
| seteffectpar_nolock(n, xml->getpar127("par", par)); | |||||
| seteffectpar(n, xml->getpar127("par", par)); | |||||
| xml->exitbranch(); | xml->exitbranch(); | ||||
| } | } | ||||
| if(filterpars) | if(filterpars) | ||||
| @@ -31,11 +31,11 @@ | |||||
| class Effect; | class Effect; | ||||
| class FilterParams; | class FilterParams; | ||||
| class XMLwrapper; | class XMLwrapper; | ||||
| class Allocator; | |||||
| #include "Distorsion.h" | #include "Distorsion.h" | ||||
| #include "EQ.h" | #include "EQ.h" | ||||
| #include "DynamicFilter.h" | #include "DynamicFilter.h" | ||||
| #include "../Misc/XMLwrapper.h" | |||||
| #include "../Params/FilterParams.h" | #include "../Params/FilterParams.h" | ||||
| #include "../Params/Presets.h" | #include "../Params/Presets.h" | ||||
| @@ -43,30 +43,35 @@ class XMLwrapper; | |||||
| class EffectMgr:public Presets | class EffectMgr:public Presets | ||||
| { | { | ||||
| public: | public: | ||||
| EffectMgr(const bool insertion_, pthread_mutex_t *mutex_); | |||||
| EffectMgr(Allocator &alloc, const SYNTH_T &synth, const bool insertion_); | |||||
| ~EffectMgr(); | ~EffectMgr(); | ||||
| void paste(EffectMgr &e); | |||||
| void add2XML(XMLwrapper *xml); | void add2XML(XMLwrapper *xml); | ||||
| void defaults(void); | |||||
| void defaults(void) REALTIME; | |||||
| void getfromXML(XMLwrapper *xml); | void getfromXML(XMLwrapper *xml); | ||||
| void out(float *smpsl, float *smpsr); | |||||
| void out(float *smpsl, float *smpsr) REALTIME; | |||||
| void setdryonly(bool value); | void setdryonly(bool value); | ||||
| /**get the output(to speakers) volume of the systemeffect*/ | /**get the output(to speakers) volume of the systemeffect*/ | ||||
| float sysefxgetvolume(void); | float sysefxgetvolume(void); | ||||
| void cleanup(void); | |||||
| void init(void) REALTIME; | |||||
| void kill(void) REALTIME; | |||||
| void cleanup(void) REALTIME; | |||||
| void changeeffect(int nefx_); | |||||
| void changeeffectrt(int nefx_) REALTIME; | |||||
| void changeeffect(int nefx_) NONREALTIME; | |||||
| int geteffect(void); | int geteffect(void); | ||||
| void changepreset(unsigned char npreset); | |||||
| void changepreset_nolock(unsigned char npreset); | |||||
| void changepreset(unsigned char npreset) NONREALTIME; | |||||
| void changepresetrt(unsigned char npreset) REALTIME; | |||||
| unsigned char getpreset(void); | unsigned char getpreset(void); | ||||
| void seteffectpar(int npar, unsigned char value); | |||||
| void seteffectpar_nolock(int npar, unsigned char value); | |||||
| void seteffectpar(int npar, unsigned char value) NONREALTIME; | |||||
| void seteffectparrt(int npar, unsigned char value) REALTIME; | |||||
| unsigned char geteffectpar(int npar); | unsigned char geteffectpar(int npar); | ||||
| unsigned char geteffectparrt(int npar) REALTIME; | |||||
| const bool insertion; | const bool insertion; | ||||
| float *efxoutl, *efxoutr; | float *efxoutl, *efxoutr; | ||||
| @@ -76,11 +81,19 @@ class EffectMgr:public Presets | |||||
| FilterParams *filterpars; | FilterParams *filterpars; | ||||
| private: | |||||
| static const rtosc::Ports &ports; | |||||
| int nefx; | int nefx; | ||||
| Effect *efx; | Effect *efx; | ||||
| pthread_mutex_t *mutex; | |||||
| private: | |||||
| //Parameters Prior to initialization | |||||
| char effect_id; | |||||
| char preset; | |||||
| char settings[128]; | |||||
| bool dryonly; | bool dryonly; | ||||
| Allocator &memory; | |||||
| const SYNTH_T &synth; | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -31,6 +31,7 @@ | |||||
| #include <cmath> | #include <cmath> | ||||
| #include <algorithm> | #include <algorithm> | ||||
| #include "../Misc/Allocator.h" | |||||
| #include "Phaser.h" | #include "Phaser.h" | ||||
| using namespace std; | using namespace std; | ||||
| @@ -39,8 +40,8 @@ using namespace std; | |||||
| #define ONE_ 0.99999f // To prevent LFO ever reaching 1.0f for filter stability purposes | #define ONE_ 0.99999f // To prevent LFO ever reaching 1.0f for filter stability purposes | ||||
| #define ZERO_ 0.00001f // Same idea as above. | #define ZERO_ 0.00001f // Same idea as above. | ||||
| Phaser::Phaser(const int &insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize) | |||||
| :Effect(insertion_, efxoutl_, efxoutr_, NULL, 0, srate, bufsize), lfo(srate, bufsize), old(NULL), xn1(NULL), | |||||
| Phaser::Phaser(EffectParams pars) | |||||
| :Effect(pars), lfo(pars.srate, pars.bufsize), old(NULL), xn1(NULL), | |||||
| yn1(NULL), diff(0.0f), oldgain(0.0f), fb(0.0f) | yn1(NULL), diff(0.0f), oldgain(0.0f), fb(0.0f) | ||||
| { | { | ||||
| analog_setup(); | analog_setup(); | ||||
| @@ -78,18 +79,12 @@ void Phaser::analog_setup() | |||||
| Phaser::~Phaser() | Phaser::~Phaser() | ||||
| { | { | ||||
| if(old.l) | |||||
| delete[] old.l; | |||||
| if(xn1.l) | |||||
| delete[] xn1.l; | |||||
| if(yn1.l) | |||||
| delete[] yn1.l; | |||||
| if(old.r) | |||||
| delete[] old.r; | |||||
| if(xn1.r) | |||||
| delete[] xn1.r; | |||||
| if(yn1.r) | |||||
| delete[] yn1.r; | |||||
| memory.devalloc(old.l); | |||||
| memory.devalloc(old.r); | |||||
| memory.devalloc(xn1.l); | |||||
| memory.devalloc(xn1.r); | |||||
| memory.devalloc(yn1.l); | |||||
| memory.devalloc(yn1.r); | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -303,30 +298,23 @@ void Phaser::setoffset(unsigned char Poffset) | |||||
| void Phaser::setstages(unsigned char Pstages) | void Phaser::setstages(unsigned char Pstages) | ||||
| { | { | ||||
| if(old.l) | |||||
| delete[] old.l; | |||||
| if(xn1.l) | |||||
| delete[] xn1.l; | |||||
| if(yn1.l) | |||||
| delete[] yn1.l; | |||||
| if(old.r) | |||||
| delete[] old.r; | |||||
| if(xn1.r) | |||||
| delete[] xn1.r; | |||||
| if(yn1.r) | |||||
| delete[] yn1.r; | |||||
| memory.devalloc(old.l); | |||||
| memory.devalloc(old.r); | |||||
| memory.devalloc(xn1.l); | |||||
| memory.devalloc(xn1.r); | |||||
| memory.devalloc(yn1.l); | |||||
| memory.devalloc(yn1.r); | |||||
| this->Pstages = min(MAX_PHASER_STAGES, (int)Pstages); | this->Pstages = min(MAX_PHASER_STAGES, (int)Pstages); | ||||
| old = Stereo<float *>(new float[Pstages * 2], | |||||
| new float[Pstages * 2]); | |||||
| old = Stereo<float *>(memory.valloc<float>(Pstages * 2), | |||||
| memory.valloc<float>(Pstages * 2)); | |||||
| xn1 = Stereo<float *>(new float[Pstages], | |||||
| new float[Pstages]); | |||||
| xn1 = Stereo<float *>(memory.valloc<float>(Pstages), | |||||
| memory.valloc<float>(Pstages)); | |||||
| yn1 = Stereo<float *>(new float[Pstages], | |||||
| new float[Pstages]); | |||||
| yn1 = Stereo<float *>(memory.valloc<float>(Pstages), | |||||
| memory.valloc<float>(Pstages)); | |||||
| cleanup(); | cleanup(); | ||||
| } | } | ||||
| @@ -35,7 +35,7 @@ | |||||
| class Phaser:public Effect | class Phaser:public Effect | ||||
| { | { | ||||
| public: | public: | ||||
| Phaser(const int &insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||||
| Phaser(EffectParams pars); | |||||
| ~Phaser(); | ~Phaser(); | ||||
| void out(const Stereo<float *> &input); | void out(const Stereo<float *> &input); | ||||
| void setpreset(unsigned char npreset); | void setpreset(unsigned char npreset); | ||||
| @@ -22,12 +22,13 @@ | |||||
| #include "Reverb.h" | #include "Reverb.h" | ||||
| #include "../Misc/Util.h" | #include "../Misc/Util.h" | ||||
| #include "../Misc/Allocator.h" | |||||
| #include "../DSP/AnalogFilter.h" | #include "../DSP/AnalogFilter.h" | ||||
| #include "../DSP/Unison.h" | #include "../DSP/Unison.h" | ||||
| #include <cmath> | #include <cmath> | ||||
| Reverb::Reverb(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize) | |||||
| :Effect(insertion_, efxoutl_, efxoutr_, NULL, 0, srate, bufsize), | |||||
| Reverb::Reverb(EffectParams pars) | |||||
| :Effect(pars), | |||||
| // defaults | // defaults | ||||
| Pvolume(48), | Pvolume(48), | ||||
| Ptime(64), | Ptime(64), | ||||
| @@ -39,6 +40,7 @@ Reverb::Reverb(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int s | |||||
| Ptype(1), | Ptype(1), | ||||
| Proomsize(64), | Proomsize(64), | ||||
| Pbandwidth(30), | Pbandwidth(30), | ||||
| idelaylen(0), | |||||
| roomsize(1.0f), | roomsize(1.0f), | ||||
| rs(1.0f), | rs(1.0f), | ||||
| bandwidth(NULL), | bandwidth(NULL), | ||||
| @@ -66,35 +68,33 @@ Reverb::Reverb(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int s | |||||
| Reverb::~Reverb() | Reverb::~Reverb() | ||||
| { | { | ||||
| delete [] idelay; | |||||
| delete hpf; | |||||
| delete lpf; | |||||
| memory.devalloc(idelay); | |||||
| memory.dealloc(hpf); | |||||
| memory.dealloc(lpf); | |||||
| for(int i = 0; i < REV_APS * 2; ++i) | for(int i = 0; i < REV_APS * 2; ++i) | ||||
| delete [] ap[i]; | |||||
| memory.devalloc(ap[i]); | |||||
| for(int i = 0; i < REV_COMBS * 2; ++i) | for(int i = 0; i < REV_COMBS * 2; ++i) | ||||
| delete [] comb[i]; | |||||
| memory.devalloc(comb[i]); | |||||
| if(bandwidth) | |||||
| delete bandwidth; | |||||
| memory.dealloc(bandwidth); | |||||
| } | } | ||||
| //Cleanup the effect | //Cleanup the effect | ||||
| void Reverb::cleanup(void) | void Reverb::cleanup(void) | ||||
| { | { | ||||
| int i, j; | |||||
| for(i = 0; i < REV_COMBS * 2; ++i) { | |||||
| for(int i = 0; i < REV_COMBS * 2; ++i) { | |||||
| lpcomb[i] = 0.0f; | lpcomb[i] = 0.0f; | ||||
| for(j = 0; j < comblen[i]; ++j) | |||||
| for(int j = 0; j < comblen[i]; ++j) | |||||
| comb[i][j] = 0.0f; | comb[i][j] = 0.0f; | ||||
| } | } | ||||
| for(i = 0; i < REV_APS * 2; ++i) | |||||
| for(j = 0; j < aplen[i]; ++j) | |||||
| for(int i = 0; i < REV_APS * 2; ++i) | |||||
| for(int j = 0; j < aplen[i]; ++j) | |||||
| ap[i][j] = 0.0f; | ap[i][j] = 0.0f; | ||||
| if(idelay) | if(idelay) | ||||
| for(i = 0; i < idelaylen; ++i) | |||||
| for(int i = 0; i < idelaylen; ++i) | |||||
| idelay[i] = 0.0f; | idelay[i] = 0.0f; | ||||
| if(hpf) | if(hpf) | ||||
| hpf->cleanup(); | hpf->cleanup(); | ||||
| @@ -231,15 +231,16 @@ void Reverb::setidelay(unsigned char _Pidelay) | |||||
| { | { | ||||
| Pidelay = _Pidelay; | Pidelay = _Pidelay; | ||||
| float delay = powf(50.0f * Pidelay / 127.0f, 2.0f) - 1.0f; | float delay = powf(50.0f * Pidelay / 127.0f, 2.0f) - 1.0f; | ||||
| int newDelayLen = (int) (samplerate_f * delay / 1000); | |||||
| if(newDelayLen == idelaylen) | |||||
| return; | |||||
| if(idelay) | |||||
| delete [] idelay; | |||||
| idelay = NULL; | |||||
| memory.devalloc(idelay); | |||||
| idelaylen = (int) (samplerate_f * delay / 1000); | |||||
| idelaylen = newDelayLen; | |||||
| if(idelaylen > 1) { | if(idelaylen > 1) { | ||||
| idelayk = 0; | idelayk = 0; | ||||
| idelay = new float[idelaylen]; | |||||
| idelay = memory.valloc<float>(idelaylen); | |||||
| memset(idelay, 0, idelaylen * sizeof(float)); | memset(idelay, 0, idelaylen * sizeof(float)); | ||||
| } | } | ||||
| } | } | ||||
| @@ -254,14 +255,11 @@ void Reverb::sethpf(unsigned char _Phpf) | |||||
| { | { | ||||
| Phpf = _Phpf; | Phpf = _Phpf; | ||||
| if(Phpf == 0) { //No HighPass | if(Phpf == 0) { //No HighPass | ||||
| if(hpf) | |||||
| delete hpf; | |||||
| hpf = NULL; | |||||
| } | |||||
| else { | |||||
| float fr = expf(powf(Phpf / 127.0f, 0.5f) * logf(10000.0f)) + 20.0f; | |||||
| memory.dealloc(hpf); | |||||
| } else { | |||||
| float fr = expf(sqrtf(Phpf / 127.0f) * logf(10000.0f)) + 20.0f; | |||||
| if(hpf == NULL) | if(hpf == NULL) | ||||
| hpf = new AnalogFilter(3, fr, 1, 0, samplerate, buffersize); | |||||
| hpf = memory.alloc<AnalogFilter>(3, fr, 1, 0, samplerate, buffersize); | |||||
| else | else | ||||
| hpf->setfreq(fr); | hpf->setfreq(fr); | ||||
| } | } | ||||
| @@ -271,14 +269,11 @@ void Reverb::setlpf(unsigned char _Plpf) | |||||
| { | { | ||||
| Plpf = _Plpf; | Plpf = _Plpf; | ||||
| if(Plpf == 127) { //No LowPass | if(Plpf == 127) { //No LowPass | ||||
| if(lpf) | |||||
| delete lpf; | |||||
| lpf = NULL; | |||||
| } | |||||
| else { | |||||
| float fr = expf(powf(Plpf / 127.0f, 0.5f) * logf(25000.0f)) + 40.0f; | |||||
| memory.dealloc(lpf); | |||||
| } else { | |||||
| float fr = expf(sqrtf(Plpf / 127.0f) * logf(25000.0f)) + 40.0f; | |||||
| if(!lpf) | if(!lpf) | ||||
| lpf = new AnalogFilter(2, fr, 1, 0, samplerate, buffersize); | |||||
| lpf = memory.alloc<AnalogFilter>(2, fr, 1, 0, samplerate, buffersize); | |||||
| else | else | ||||
| lpf->setfreq(fr); | lpf->setfreq(fr); | ||||
| } | } | ||||
| @@ -323,12 +318,13 @@ void Reverb::settype(unsigned char _Ptype) | |||||
| tmp *= samplerate_adjust; //adjust the combs according to the samplerate | tmp *= samplerate_adjust; //adjust the combs according to the samplerate | ||||
| if(tmp < 10.0f) | if(tmp < 10.0f) | ||||
| tmp = 10.0f; | tmp = 10.0f; | ||||
| comblen[i] = (int) tmp; | |||||
| combk[i] = 0; | combk[i] = 0; | ||||
| lpcomb[i] = 0; | lpcomb[i] = 0; | ||||
| if(comb[i]) | |||||
| delete [] comb[i]; | |||||
| comb[i] = new float[comblen[i]]; | |||||
| if(comblen[i] != (int)tmp || comb[i] == NULL) { | |||||
| comblen[i] = (int) tmp; | |||||
| memory.devalloc(comb[i]); | |||||
| comb[i] = memory.valloc<float>(comblen[i]); | |||||
| } | |||||
| } | } | ||||
| for(int i = 0; i < REV_APS * 2; ++i) { | for(int i = 0; i < REV_APS * 2; ++i) { | ||||
| @@ -342,20 +338,20 @@ void Reverb::settype(unsigned char _Ptype) | |||||
| tmp *= samplerate_adjust; //adjust the combs according to the samplerate | tmp *= samplerate_adjust; //adjust the combs according to the samplerate | ||||
| if(tmp < 10) | if(tmp < 10) | ||||
| tmp = 10; | tmp = 10; | ||||
| aplen[i] = (int) tmp; | |||||
| apk[i] = 0; | apk[i] = 0; | ||||
| if(ap[i]) | |||||
| delete [] ap[i]; | |||||
| ap[i] = new float[aplen[i]]; | |||||
| if(aplen[i] != (int)tmp || ap[i] == NULL) { | |||||
| aplen[i] = (int) tmp; | |||||
| memory.devalloc(ap[i]); | |||||
| ap[i] = memory.valloc<float>(aplen[i]); | |||||
| } | |||||
| } | } | ||||
| delete bandwidth; | |||||
| bandwidth = NULL; | |||||
| memory.dealloc(bandwidth); | |||||
| if(Ptype == 2) { //bandwidth | if(Ptype == 2) { //bandwidth | ||||
| //TODO the size of the unison buffer may be too small, though this has | //TODO the size of the unison buffer may be too small, though this has | ||||
| //not been verified yet. | //not been verified yet. | ||||
| //As this cannot be resized in a RT context, a good upper bound should | //As this cannot be resized in a RT context, a good upper bound should | ||||
| //be found | //be found | ||||
| bandwidth = new Unison(buffersize / 4 + 1, 2.0f, samplerate_f); | |||||
| bandwidth = memory.alloc<Unison>(&memory, buffersize / 4 + 1, 2.0f, samplerate_f); | |||||
| bandwidth->setSize(50); | bandwidth->setSize(50); | ||||
| bandwidth->setBaseFrequency(1.0f); | bandwidth->setBaseFrequency(1.0f); | ||||
| } | } | ||||
| @@ -32,7 +32,7 @@ | |||||
| class Reverb:public Effect | class Reverb:public Effect | ||||
| { | { | ||||
| public: | public: | ||||
| Reverb(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||||
| Reverb(EffectParams pars); | |||||
| ~Reverb(); | ~Reverb(); | ||||
| void out(const Stereo<float *> &smp); | void out(const Stereo<float *> &smp); | ||||
| void cleanup(void); | void cleanup(void); | ||||
| @@ -0,0 +1,192 @@ | |||||
| #include <cstddef> | |||||
| #include <cstdlib> | |||||
| #include <cassert> | |||||
| #include <utility> | |||||
| #include <cstdio> | |||||
| #include "tlsf/tlsf.h" | |||||
| #include "Allocator.h" | |||||
| //Used for dummy allocations | |||||
| Allocator DummyAlloc; | |||||
| //recursive type class to avoid void *v = *(void**)v style casting | |||||
| struct next_t | |||||
| { | |||||
| next_t *next; | |||||
| size_t pool_size; | |||||
| }; | |||||
| void *data(next_t *n) | |||||
| { | |||||
| return n+sizeof(next_t); | |||||
| } | |||||
| struct AllocatorImpl | |||||
| { | |||||
| void *tlsf = 0; | |||||
| //singly linked list of memory pools | |||||
| //XXX this may violate alignment on some platforms if malloc doesn't return | |||||
| //nice values | |||||
| next_t *pools = 0; | |||||
| unsigned long long totalAlloced = 0; | |||||
| }; | |||||
| Allocator::Allocator(void) | |||||
| { | |||||
| impl = new AllocatorImpl; | |||||
| size_t default_size = 10*1024*1024; | |||||
| impl->pools = (next_t*)malloc(default_size); | |||||
| impl->pools->next = 0x0; | |||||
| impl->pools->pool_size = default_size; | |||||
| size_t off = tlsf_size() + tlsf_pool_overhead() + sizeof(next_t); | |||||
| //printf("Generated Memory Pool with '%p'\n", impl->pools); | |||||
| impl->tlsf = tlsf_create_with_pool(((char*)impl->pools)+off, default_size-2*off); | |||||
| //printf("Allocator(%p)\n", impl); | |||||
| } | |||||
| Allocator::~Allocator(void) | |||||
| { | |||||
| next_t *n = impl->pools; | |||||
| while(n) { | |||||
| next_t *nn = n->next; | |||||
| free(n); | |||||
| n = nn; | |||||
| } | |||||
| delete impl; | |||||
| } | |||||
| void *Allocator::alloc_mem(size_t mem_size) | |||||
| { | |||||
| impl->totalAlloced += mem_size; | |||||
| void *mem = tlsf_malloc(impl->tlsf, mem_size); | |||||
| //printf("Allocator.malloc(%p, %d) = %p\n", impl, mem_size, mem); | |||||
| //void *mem = malloc(mem_size); | |||||
| //printf("Allocator result = %p\n", mem); | |||||
| return mem; | |||||
| } | |||||
| void Allocator::dealloc_mem(void *memory) | |||||
| { | |||||
| //printf("dealloc_mem(%d)\n", tlsf_block_size(memory)); | |||||
| tlsf_free(impl->tlsf, memory); | |||||
| //free(memory); | |||||
| } | |||||
| bool Allocator::lowMemory(unsigned n, size_t chunk_size) | |||||
| { | |||||
| //This should stay on the stack | |||||
| void *buf[n]; | |||||
| for(unsigned i=0; i<n; ++i) | |||||
| buf[i] = tlsf_malloc(impl->tlsf, chunk_size); | |||||
| bool outOfMem = false; | |||||
| for(unsigned i=0; i<n; ++i) | |||||
| outOfMem |= (buf[i] == nullptr); | |||||
| for(unsigned i=0; i<n; ++i) | |||||
| if(buf[i]) | |||||
| tlsf_free(impl->tlsf, buf[i]); | |||||
| return outOfMem; | |||||
| } | |||||
| void Allocator::addMemory(void *v, size_t mem_size) | |||||
| { | |||||
| next_t *n = impl->pools; | |||||
| while(n->next) n = n->next; | |||||
| n->next = (next_t*)v; | |||||
| n->next->next = 0x0; | |||||
| n->next->pool_size = mem_size; | |||||
| //printf("Inserting '%p'\n", v); | |||||
| off_t off = sizeof(next_t) + tlsf_pool_overhead(); | |||||
| void *result = | |||||
| tlsf_add_pool(impl->tlsf, ((char*)n->next)+off, | |||||
| //0x0eadbeef); | |||||
| mem_size-off-sizeof(size_t)); | |||||
| if(!result) | |||||
| printf("FAILED TO INSERT MEMORY POOL\n"); | |||||
| };//{(void)mem_size;}; | |||||
| #ifndef INCLUDED_tlsfbits | |||||
| //From tlsf internals | |||||
| typedef struct block_header_t | |||||
| { | |||||
| /* Points to the previous physical block. */ | |||||
| struct block_header_t* prev_phys_block; | |||||
| /* The size of this block, excluding the block header. */ | |||||
| size_t size; | |||||
| /* Next and previous free blocks. */ | |||||
| struct block_header_t* next_free; | |||||
| struct block_header_t* prev_free; | |||||
| } block_header_t; | |||||
| static const size_t block_header_free_bit = 1 << 0; | |||||
| #endif | |||||
| bool Allocator::memFree(void *pool) | |||||
| { | |||||
| size_t bh_shift = sizeof(next_t)+sizeof(size_t); | |||||
| //Assume that memory is free to start with | |||||
| bool isFree = true; | |||||
| //Get the block header from the pool | |||||
| block_header_t &bh = *(block_header_t*)((char*)pool+bh_shift); | |||||
| //The first block must be free | |||||
| if((bh.size&block_header_free_bit) == 0) | |||||
| isFree = false; | |||||
| block_header_t &bhn = *(block_header_t*) | |||||
| (((char*)&bh)+((bh.size&~0x3)+bh_shift-2*sizeof(size_t))); | |||||
| //The next block must be 'non-free' and zero length | |||||
| if((bhn.size&block_header_free_bit) != 0) | |||||
| isFree = false; | |||||
| if((bhn.size&~0x3) != 0) | |||||
| isFree = false; | |||||
| return isFree; | |||||
| } | |||||
| int Allocator::memPools() | |||||
| { | |||||
| int i = 1; | |||||
| next_t *n = impl->pools; | |||||
| while(n->next) { | |||||
| i++; | |||||
| n = n->next; | |||||
| } | |||||
| return i; | |||||
| } | |||||
| int Allocator::freePools() | |||||
| { | |||||
| int i = 0; | |||||
| next_t *n = impl->pools->next; | |||||
| while(n) { | |||||
| if(memFree(n)) | |||||
| i++; | |||||
| n = n->next; | |||||
| } | |||||
| return i; | |||||
| } | |||||
| unsigned long long Allocator::totalAlloced() | |||||
| { | |||||
| return impl->totalAlloced; | |||||
| } | |||||
| /* | |||||
| * Notes on tlsf internals | |||||
| * - TLSF consists of blocks linked by block headers and these form a doubly | |||||
| * linked list of free segments | |||||
| * - Original memory is [control_t pool] | |||||
| * base sentinal | |||||
| * Pools are [block_t block_t blocks ...] | |||||
| * Blocks are [memory block_t](??) | |||||
| * - These are stored in the control_t structure in an order dependent on the | |||||
| * size that they are | |||||
| * it's a bit unclear how collisions are handled here, but the basic premise | |||||
| * makes sense | |||||
| * - Additional structure is added before the start of each pool to define the | |||||
| * pool size and the next pool in the list as this information is not | |||||
| * accessible in O(good) time | |||||
| */ | |||||
| @@ -0,0 +1,113 @@ | |||||
| #ifndef ALLOCATOR_H | |||||
| #define ALLOCATOR_H | |||||
| #include <cstdlib> | |||||
| #include <utility> | |||||
| class Allocator | |||||
| { | |||||
| public: | |||||
| Allocator(void); | |||||
| Allocator(const Allocator&) = delete; | |||||
| ~Allocator(void); | |||||
| void *alloc_mem(size_t mem_size); | |||||
| void dealloc_mem(void *memory); | |||||
| template <typename T, typename... Ts> | |||||
| T *alloc(Ts&&... ts) | |||||
| { | |||||
| void *data = alloc_mem(sizeof(T)); | |||||
| if(!data) | |||||
| return nullptr; | |||||
| return new (data) T(std::forward<Ts>(ts)...); | |||||
| } | |||||
| template <typename T, typename... Ts> | |||||
| T *valloc(size_t len, Ts&&... ts) | |||||
| { | |||||
| T *data = (T*)alloc_mem(len*sizeof(T)); | |||||
| if(!data) | |||||
| return nullptr; | |||||
| for(unsigned i=0; i<len; ++i) | |||||
| new ((void*)&data[i]) T(std::forward<Ts>(ts)...); | |||||
| return data; | |||||
| } | |||||
| template <typename T> | |||||
| void dealloc(T*&t) | |||||
| { | |||||
| if(t) { | |||||
| t->~T(); | |||||
| dealloc_mem((void*)t); | |||||
| t = nullptr; | |||||
| } | |||||
| } | |||||
| //Destructor Free Version | |||||
| template <typename T> | |||||
| void devalloc(T*&t) | |||||
| { | |||||
| if(t) { | |||||
| dealloc_mem(t); | |||||
| t = nullptr; | |||||
| } | |||||
| } | |||||
| template <typename T> | |||||
| void devalloc(size_t elms, T*&t) | |||||
| { | |||||
| if(t) { | |||||
| for(size_t i=0; i<elms; ++i) | |||||
| (t+i)->~T(); | |||||
| dealloc_mem(t); | |||||
| t = nullptr; | |||||
| } | |||||
| } | |||||
| void addMemory(void *, size_t mem_size); | |||||
| //Return true if the current pool cannot allocate n chunks of chunk_size | |||||
| bool lowMemory(unsigned n, size_t chunk_size); | |||||
| bool memFree(void *pool); | |||||
| //returns number of pools | |||||
| int memPools(); | |||||
| int freePools(); | |||||
| unsigned long long totalAlloced(); | |||||
| struct AllocatorImpl *impl; | |||||
| }; | |||||
| extern Allocator DummyAlloc; | |||||
| /** | |||||
| * General notes on Memory Allocation Within ZynAddSubFX | |||||
| * ----------------------------------------------------- | |||||
| * | |||||
| * - Parameter Objects Are never allocated within the realtime thread | |||||
| * - Effects, notes and note subcomponents must be allocated with an allocator | |||||
| * - 5M Chunks are used to give the allocator the memory it wants | |||||
| * - If there are 3 chunks that are unused then 1 will be deallocated | |||||
| * - The system will request more allocated space if 5x 1MB chunks cannot be | |||||
| * allocated at any given time (this is likely huge overkill, but if this is | |||||
| * satisfied, then a lot of note spamming would be needed to run out of | |||||
| * space) | |||||
| * | |||||
| * - Things will get a bit weird around the effects due to how pointer swaps | |||||
| * occur | |||||
| * * When a new Part instance is provided it may or may not come with some | |||||
| * instrument effects | |||||
| * * Merging blocks is an option, but one that is not going to likely be | |||||
| * implmented too soon, thus all effects need to be reallocated when the | |||||
| * pointer swap occurs | |||||
| * * The old effect is extracted from the manager | |||||
| * * A new one is constructed with a deep copy | |||||
| * * The old one is returned to middleware for deallocation | |||||
| */ | |||||
| #endif | |||||
| @@ -23,13 +23,12 @@ | |||||
| */ | */ | ||||
| #include "Bank.h" | #include "Bank.h" | ||||
| #include <string.h> | |||||
| #include <stdio.h> | |||||
| #include <stdlib.h> | |||||
| #include <cstring> | |||||
| #include <cstdio> | |||||
| #include <cstdlib> | |||||
| #include <dirent.h> | #include <dirent.h> | ||||
| #include <sys/stat.h> | #include <sys/stat.h> | ||||
| #include <algorithm> | #include <algorithm> | ||||
| #include <iostream> | |||||
| #include <sys/types.h> | #include <sys/types.h> | ||||
| #include <fcntl.h> | #include <fcntl.h> | ||||
| @@ -48,11 +47,19 @@ | |||||
| using namespace std; | using namespace std; | ||||
| Bank::Bank() | Bank::Bank() | ||||
| :defaultinsname(" ") | |||||
| :bankpos(0), defaultinsname(" ") | |||||
| { | { | ||||
| clearbank(); | clearbank(); | ||||
| bankfiletitle = dirname; | bankfiletitle = dirname; | ||||
| rescanforbanks(); | |||||
| loadbank(config.cfg.currentBankDir); | loadbank(config.cfg.currentBankDir); | ||||
| for(unsigned i=0; i<banks.size(); ++i) { | |||||
| if(banks[i].dir == config.cfg.currentBankDir) { | |||||
| bankpos = i; | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| Bank::~Bank() | Bank::~Bank() | ||||
| @@ -84,10 +91,10 @@ string Bank::getnamenumbered(unsigned int ninstrument) | |||||
| /* | /* | ||||
| * Changes the name of an instrument (and the filename) | * Changes the name of an instrument (and the filename) | ||||
| */ | */ | ||||
| void Bank::setname(unsigned int ninstrument, const string &newname, int newslot) | |||||
| int Bank::setname(unsigned int ninstrument, const string &newname, int newslot) | |||||
| { | { | ||||
| if(emptyslot(ninstrument)) | if(emptyslot(ninstrument)) | ||||
| return; | |||||
| return 0; | |||||
| string newfilename; | string newfilename; | ||||
| char tmpfilename[100 + 1]; | char tmpfilename[100 + 1]; | ||||
| @@ -103,12 +110,15 @@ void Bank::setname(unsigned int ninstrument, const string &newname, int newslot) | |||||
| if(tmpfilename[i] == ' ') | if(tmpfilename[i] == ' ') | ||||
| tmpfilename[i] = '0'; | tmpfilename[i] = '0'; | ||||
| newfilename = dirname + '/' + legalizeFilename(tmpfilename) + ".xiz"; | |||||
| newfilename = dirname + legalizeFilename(tmpfilename) + ".xiz"; | |||||
| rename(ins[ninstrument].filename.c_str(), newfilename.c_str()); | |||||
| int err = rename(ins[ninstrument].filename.c_str(), newfilename.c_str()); | |||||
| if(err) | |||||
| return err; | |||||
| ins[ninstrument].filename = newfilename; | ins[ninstrument].filename = newfilename; | ||||
| ins[ninstrument].name = newname; | ins[ninstrument].name = newname; | ||||
| return err; | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -121,30 +131,37 @@ bool Bank::emptyslot(unsigned int ninstrument) | |||||
| if(ins[ninstrument].filename.empty()) | if(ins[ninstrument].filename.empty()) | ||||
| return true; | return true; | ||||
| if(ins[ninstrument].used) | |||||
| return false; | |||||
| else | |||||
| return true; | |||||
| return false; | |||||
| } | } | ||||
| /* | /* | ||||
| * Removes the instrument from the bank | * Removes the instrument from the bank | ||||
| */ | */ | ||||
| void Bank::clearslot(unsigned int ninstrument) | |||||
| int Bank::clearslot(unsigned int ninstrument) | |||||
| { | { | ||||
| if(emptyslot(ninstrument)) | if(emptyslot(ninstrument)) | ||||
| return; | |||||
| return 0; | |||||
| remove(ins[ninstrument].filename.c_str()); | |||||
| deletefrombank(ninstrument); | |||||
| //no error when no file | |||||
| FILE *f = fopen(ins[ninstrument].filename.c_str(), "r"); | |||||
| if(!f) | |||||
| return 0; | |||||
| fclose(f); | |||||
| int err = remove(ins[ninstrument].filename.c_str()); | |||||
| if(!err) | |||||
| deletefrombank(ninstrument); | |||||
| return err; | |||||
| } | } | ||||
| /* | /* | ||||
| * Save the instrument to a slot | * Save the instrument to a slot | ||||
| */ | */ | ||||
| void Bank::savetoslot(unsigned int ninstrument, Part *part) | |||||
| int Bank::savetoslot(unsigned int ninstrument, Part *part) | |||||
| { | { | ||||
| clearslot(ninstrument); | |||||
| int err = clearslot(ninstrument); | |||||
| if(err) | |||||
| return err; | |||||
| const int maxfilename = 200; | const int maxfilename = 200; | ||||
| char tmpfilename[maxfilename + 20]; | char tmpfilename[maxfilename + 20]; | ||||
| @@ -163,23 +180,35 @@ void Bank::savetoslot(unsigned int ninstrument, Part *part) | |||||
| string filename = dirname + '/' + legalizeFilename(tmpfilename) + ".xiz"; | string filename = dirname + '/' + legalizeFilename(tmpfilename) + ".xiz"; | ||||
| remove(filename.c_str()); | |||||
| part->saveXML(filename.c_str()); | |||||
| FILE *f = fopen(filename.c_str(), "r"); | |||||
| if(f) { | |||||
| fclose(f); | |||||
| err = remove(filename.c_str()); | |||||
| if(err) | |||||
| return err; | |||||
| } | |||||
| err = part->saveXML(filename.c_str()); | |||||
| if(err) | |||||
| return err; | |||||
| addtobank(ninstrument, legalizeFilename(tmpfilename) + ".xiz", (char *) part->Pname); | addtobank(ninstrument, legalizeFilename(tmpfilename) + ".xiz", (char *) part->Pname); | ||||
| return 0; | |||||
| } | } | ||||
| /* | /* | ||||
| * Loads the instrument from the bank | * Loads the instrument from the bank | ||||
| */ | */ | ||||
| void Bank::loadfromslot(unsigned int ninstrument, Part *part) | |||||
| int Bank::loadfromslot(unsigned int ninstrument, Part *part) | |||||
| { | { | ||||
| if(emptyslot(ninstrument)) | if(emptyslot(ninstrument)) | ||||
| return; | |||||
| return 0; | |||||
| part->AllNotesOff(); | part->AllNotesOff(); | ||||
| part->defaultsinstrument(); | part->defaultsinstrument(); | ||||
| part->loadXMLinstrument(ins[ninstrument].filename.c_str()); | part->loadXMLinstrument(ins[ninstrument].filename.c_str()); | ||||
| return 0; | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -251,7 +280,6 @@ int Bank::loadbank(string bankdirname) | |||||
| */ | */ | ||||
| int Bank::newbank(string newbankdirname) | int Bank::newbank(string newbankdirname) | ||||
| { | { | ||||
| #ifndef CARLA_OS_WIN | |||||
| string bankdir; | string bankdir; | ||||
| bankdir = config.cfg.bankRootDirList[0]; | bankdir = config.cfg.bankRootDirList[0]; | ||||
| @@ -269,9 +297,6 @@ int Bank::newbank(string newbankdirname) | |||||
| fclose(tmpfile); | fclose(tmpfile); | ||||
| return loadbank(bankdir); | return loadbank(bankdir); | ||||
| #else | |||||
| return -1; | |||||
| #endif | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -279,23 +304,27 @@ int Bank::newbank(string newbankdirname) | |||||
| */ | */ | ||||
| int Bank::locked() | int Bank::locked() | ||||
| { | { | ||||
| //XXX Fixme | |||||
| return dirname.empty(); | return dirname.empty(); | ||||
| } | } | ||||
| /* | /* | ||||
| * Swaps a slot with another | * Swaps a slot with another | ||||
| */ | */ | ||||
| void Bank::swapslot(unsigned int n1, unsigned int n2) | |||||
| int Bank::swapslot(unsigned int n1, unsigned int n2) | |||||
| { | { | ||||
| int err = 0; | |||||
| if((n1 == n2) || (locked())) | if((n1 == n2) || (locked())) | ||||
| return; | |||||
| return 0; | |||||
| if(emptyslot(n1) && (emptyslot(n2))) | if(emptyslot(n1) && (emptyslot(n2))) | ||||
| return; | |||||
| return 0; | |||||
| if(emptyslot(n1)) //change n1 to n2 in order to make | if(emptyslot(n1)) //change n1 to n2 in order to make | ||||
| swap(n1, n2); | swap(n1, n2); | ||||
| if(emptyslot(n2)) { //this is just a movement from slot1 to slot2 | if(emptyslot(n2)) { //this is just a movement from slot1 to slot2 | ||||
| setname(n1, getname(n1), n2); | |||||
| err |= setname(n1, getname(n1), n2); | |||||
| if(err) | |||||
| return err; | |||||
| ins[n2] = ins[n1]; | ins[n2] = ins[n1]; | ||||
| ins[n1] = ins_t(); | ins[n1] = ins_t(); | ||||
| } | } | ||||
| @@ -303,10 +332,13 @@ void Bank::swapslot(unsigned int n1, unsigned int n2) | |||||
| if(ins[n1].name == ins[n2].name) //change the name of the second instrument if the name are equal | if(ins[n1].name == ins[n2].name) //change the name of the second instrument if the name are equal | ||||
| ins[n2].name += "2"; | ins[n2].name += "2"; | ||||
| setname(n1, getname(n1), n2); | |||||
| setname(n2, getname(n2), n1); | |||||
| err |= setname(n1, getname(n1), n2); | |||||
| err |= setname(n2, getname(n2), n1); | |||||
| if(err) | |||||
| return err; | |||||
| swap(ins[n2], ins[n1]); | swap(ins[n2], ins[n1]); | ||||
| } | } | ||||
| return err; | |||||
| } | } | ||||
| @@ -413,7 +445,7 @@ void Bank::clearbank() | |||||
| int Bank::addtobank(int pos, string filename, string name) | int Bank::addtobank(int pos, string filename, string name) | ||||
| { | { | ||||
| if((pos >= 0) && (pos < BANK_SIZE)) { | if((pos >= 0) && (pos < BANK_SIZE)) { | ||||
| if(ins[pos].used) | |||||
| if(!ins[pos].filename.empty()) | |||||
| pos = -1; //force it to find a new free position | pos = -1; //force it to find a new free position | ||||
| } | } | ||||
| else | else | ||||
| @@ -423,7 +455,7 @@ int Bank::addtobank(int pos, string filename, string name) | |||||
| if(pos < 0) //find a free position | if(pos < 0) //find a free position | ||||
| for(int i = BANK_SIZE - 1; i >= 0; i--) | for(int i = BANK_SIZE - 1; i >= 0; i--) | ||||
| if(!ins[i].used) { | |||||
| if(ins[i].filename.empty()) { | |||||
| pos = i; | pos = i; | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -433,32 +465,11 @@ int Bank::addtobank(int pos, string filename, string name) | |||||
| deletefrombank(pos); | deletefrombank(pos); | ||||
| ins[pos].used = true; | |||||
| ins[pos].name = name; | ins[pos].name = name; | ||||
| ins[pos].filename = dirname + '/' + filename; | |||||
| //see if PADsynth is used | |||||
| if(config.cfg.CheckPADsynth) { | |||||
| XMLwrapper xml; | |||||
| xml.loadXMLfile(ins[pos].filename); | |||||
| ins[pos].info.PADsynth_used = xml.hasPadSynth(); | |||||
| } | |||||
| else | |||||
| ins[pos].info.PADsynth_used = false; | |||||
| ins[pos].filename = dirname + filename; | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| bool Bank::isPADsynth_used(unsigned int ninstrument) | |||||
| { | |||||
| if(config.cfg.CheckPADsynth == 0) | |||||
| return 0; | |||||
| else | |||||
| return ins[ninstrument].info.PADsynth_used; | |||||
| } | |||||
| void Bank::deletefrombank(int pos) | void Bank::deletefrombank(int pos) | ||||
| { | { | ||||
| if((pos < 0) || (pos >= BANK_SIZE)) | if((pos < 0) || (pos >= BANK_SIZE)) | ||||
| @@ -467,7 +478,5 @@ void Bank::deletefrombank(int pos) | |||||
| } | } | ||||
| Bank::ins_t::ins_t() | Bank::ins_t::ins_t() | ||||
| :used(false), name(""), filename("") | |||||
| { | |||||
| info.PADsynth_used = false; | |||||
| } | |||||
| :name(""), filename("") | |||||
| {} | |||||
| @@ -25,6 +25,7 @@ | |||||
| #include <string> | #include <string> | ||||
| #include <vector> | #include <vector> | ||||
| #include "../globals.h" | |||||
| //entries in a bank | //entries in a bank | ||||
| #define BANK_SIZE 160 | #define BANK_SIZE 160 | ||||
| @@ -38,26 +39,26 @@ class Bank | |||||
| ~Bank(); | ~Bank(); | ||||
| std::string getname(unsigned int ninstrument); | std::string getname(unsigned int ninstrument); | ||||
| std::string getnamenumbered(unsigned int ninstrument); | std::string getnamenumbered(unsigned int ninstrument); | ||||
| void setname(unsigned int ninstrument, | |||||
| //if newslot==-1 then this is ignored, else it will be put on that slot | |||||
| int setname(unsigned int ninstrument, | |||||
| const std::string &newname, | const std::string &newname, | ||||
| int newslot); //if newslot==-1 then this is ignored, else it will be put on that slot | |||||
| bool isPADsynth_used(unsigned int ninstrument); | |||||
| int newslot); | |||||
| /**returns true when slot is empty*/ | /**returns true when slot is empty*/ | ||||
| bool emptyslot(unsigned int ninstrument); | bool emptyslot(unsigned int ninstrument); | ||||
| /**Empties out the selected slot*/ | /**Empties out the selected slot*/ | ||||
| void clearslot(unsigned int ninstrument); | |||||
| int clearslot(unsigned int ninstrument); | |||||
| /**Saves the given Part to slot*/ | /**Saves the given Part to slot*/ | ||||
| void savetoslot(unsigned int ninstrument, class Part * part); | |||||
| int savetoslot(unsigned int ninstrument, class Part * part); | |||||
| /**Loads the given slot into a Part*/ | /**Loads the given slot into a Part*/ | ||||
| void loadfromslot(unsigned int ninstrument, class Part * part); | |||||
| int loadfromslot(unsigned int ninstrument, class Part * part); | |||||
| /**Swaps Slots*/ | /**Swaps Slots*/ | ||||
| void swapslot(unsigned int n1, unsigned int n2); | |||||
| int swapslot(unsigned int n1, unsigned int n2); | |||||
| int loadbank(std::string bankdirname); | |||||
| int newbank(std::string newbankdirname); | |||||
| int loadbank(std::string bankdirname) NONREALTIME; | |||||
| int newbank(std::string newbankdirname) NONREALTIME; | |||||
| std::string bankfiletitle; //this is shown on the UI of the bank (the title of the window) | std::string bankfiletitle; //this is shown on the UI of the bank (the title of the window) | ||||
| int locked(); | int locked(); | ||||
| @@ -71,6 +72,14 @@ class Bank | |||||
| }; | }; | ||||
| std::vector<bankstruct> banks; | std::vector<bankstruct> banks; | ||||
| int bankpos; | |||||
| struct ins_t { | |||||
| ins_t(void); | |||||
| std::string name; | |||||
| //All valid instruments must have a non-empty filename | |||||
| std::string filename; | |||||
| } ins[BANK_SIZE]; | |||||
| private: | private: | ||||
| @@ -84,17 +93,6 @@ class Bank | |||||
| void clearbank(); | void clearbank(); | ||||
| std::string defaultinsname; | std::string defaultinsname; | ||||
| struct ins_t { | |||||
| ins_t(); | |||||
| bool used; | |||||
| std::string name; | |||||
| std::string filename; | |||||
| struct { | |||||
| bool PADsynth_used; | |||||
| } info; | |||||
| } ins[BANK_SIZE]; | |||||
| std::string dirname; | std::string dirname; | ||||
| void scanrootdir(std::string rootdir); //scans a root dir for banks | void scanrootdir(std::string rootdir); //scans a root dir for banks | ||||
| @@ -3,7 +3,6 @@ include_directories(${MXML_INCLUDE_DIR}) | |||||
| set(zynaddsubfx_misc_SRCS | set(zynaddsubfx_misc_SRCS | ||||
| Misc/Bank.cpp | Misc/Bank.cpp | ||||
| Misc/Config.cpp | Misc/Config.cpp | ||||
| Misc/Dump.cpp | |||||
| Misc/Master.cpp | Misc/Master.cpp | ||||
| Misc/Microtonal.cpp | Misc/Microtonal.cpp | ||||
| Misc/Part.cpp | Misc/Part.cpp | ||||
| @@ -12,6 +11,9 @@ set(zynaddsubfx_misc_SRCS | |||||
| Misc/Recorder.cpp | Misc/Recorder.cpp | ||||
| Misc/WavFile.cpp | Misc/WavFile.cpp | ||||
| Misc/WaveShapeSmps.cpp | Misc/WaveShapeSmps.cpp | ||||
| Misc/MiddleWare.cpp | |||||
| Misc/PresetExtractor.cpp | |||||
| Misc/Allocator.cpp | |||||
| ) | ) | ||||
| @@ -19,19 +19,110 @@ | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||||
| */ | */ | ||||
| #include <stdio.h> | |||||
| #include <math.h> | |||||
| #include <stdlib.h> | |||||
| #include <string.h> | |||||
| #include <cstdio> | |||||
| #include <cmath> | |||||
| #include <cstdlib> | |||||
| #include <cstring> | |||||
| #include <rtosc/ports.h> | |||||
| #include <rtosc/port-sugar.h> | |||||
| #include "Config.h" | #include "Config.h" | ||||
| #include "XMLwrapper.h" | #include "XMLwrapper.h" | ||||
| using namespace std; | |||||
| #define rStdString(name, len, ...) \ | |||||
| {STRINGIFY(name) "::s", rMap(length, len) DOC(__VA_ARGS__), NULL, rStringCb(name,len)} | |||||
| #define rStdStringCb(name, length) rBOIL_BEGIN \ | |||||
| if(!strcmp("", args)) {\ | |||||
| data.reply(loc, "s", obj->name); \ | |||||
| } else { \ | |||||
| strncpy(obj->name, rtosc_argument(msg, 0).s, length); \ | |||||
| data.broadcast(loc, "s", obj->name);\ | |||||
| rChangeCb \ | |||||
| } rBOIL_END | |||||
| #if 1 | |||||
| #define rObject Config | |||||
| static rtosc::Ports ports = { | |||||
| //rString(cfg.LinuxOSSWaveOutDev), | |||||
| //rString(cfg.LinuxOSSSeqInDev), | |||||
| rParamI(cfg.SampleRate, "samples of audio per second"), | |||||
| rParamI(cfg.SoundBufferSize, "Size of processed audio buffer"), | |||||
| rParamI(cfg.OscilSize, "Size Of Oscillator Wavetable"), | |||||
| rToggle(cfg.SwapStereo, "Swap Left And Right Channels"), | |||||
| rToggle(cfg.BankUIAutoClose, "Automatic Closing of BackUI After Patch Selection"), | |||||
| rParamI(cfg.GzipCompression, "Level of Gzip Compression For Save Files"), | |||||
| rParamI(cfg.Interpolation, "Level of Interpolation, Linear/Cubic"), | |||||
| {"cfg.presetsDirList", rProp(parameter) rDoc("list of preset search directories"), 0, | |||||
| [](const char *msg, rtosc::RtData &d) | |||||
| { | |||||
| Config &c = *(Config*)d.obj; | |||||
| if(rtosc_narguments(msg) != 0) | |||||
| return; | |||||
| char types[MAX_BANK_ROOT_DIRS+1]; | |||||
| rtosc_arg_t args[MAX_BANK_ROOT_DIRS]; | |||||
| size_t pos = 0; | |||||
| //zero out data | |||||
| memset(types, 0, sizeof(types)); | |||||
| memset(args, 0, sizeof(args)); | |||||
| for(int i=0; i<MAX_BANK_ROOT_DIRS; ++i) { | |||||
| if(!c.cfg.presetsDirList[i].empty()) { | |||||
| types[pos] = 's'; | |||||
| args[pos].s = c.cfg.presetsDirList[i].c_str(); | |||||
| pos++; | |||||
| } | |||||
| } | |||||
| char buffer[1024*5]; | |||||
| rtosc_amessage(buffer, sizeof(buffer), d.loc, types, args); | |||||
| d.reply(buffer); | |||||
| }}, | |||||
| {"cfg.bankRootDirList", rProp(parameter) rDoc("list of bank search directories"), 0, | |||||
| [](const char *msg, rtosc::RtData &d) | |||||
| { | |||||
| Config &c = *(Config*)d.obj; | |||||
| if(rtosc_narguments(msg) != 0) | |||||
| return; | |||||
| char types[MAX_BANK_ROOT_DIRS+1]; | |||||
| rtosc_arg_t args[MAX_BANK_ROOT_DIRS]; | |||||
| size_t pos = 0; | |||||
| //zero out data | |||||
| memset(types, 0, sizeof(types)); | |||||
| memset(args, 0, sizeof(args)); | |||||
| for(int i=0; i<MAX_BANK_ROOT_DIRS; ++i) { | |||||
| if(!c.cfg.bankRootDirList[i].empty()) { | |||||
| types[pos] = 's'; | |||||
| args[pos].s = c.cfg.bankRootDirList[i].c_str(); | |||||
| pos++; | |||||
| } | |||||
| } | |||||
| char buffer[1024*5]; | |||||
| rtosc_amessage(buffer, sizeof(buffer), d.loc, types, args); | |||||
| d.reply(buffer); | |||||
| }}, | |||||
| //rArrayS(cfg.bankRootDirList,MAX_BANK_ROOT_DIRS), | |||||
| //rString(cfg.currentBankDir), | |||||
| //rArrayS(cfg.presetsDirList,MAX_BANK_ROOT_DIRS), | |||||
| rToggle(cfg.CheckPADsynth, "Old Check For PADsynth functionality within a patch"), | |||||
| rToggle(cfg.IgnoreProgramChange, "Ignore MIDI Program Change Events"), | |||||
| rParamI(cfg.UserInterfaceMode, "Beginner/Advanced Mode Select"), | |||||
| rParamI(cfg.VirKeybLayout, "Keyboard Layout For Virtual Piano Keyboard"), | |||||
| //rParamS(cfg.LinuxALSAaudioDev), | |||||
| //rParamS(cfg.nameTag) | |||||
| }; | |||||
| rtosc::Ports &Config::ports = ::ports; | |||||
| #endif | |||||
| Config::Config() | Config::Config() | ||||
| {} | {} | ||||
| void Config::init() | void Config::init() | ||||
| { | { | ||||
| maxstringsize = MAX_STRING_SIZE; //for ui | maxstringsize = MAX_STRING_SIZE; //for ui | ||||
| @@ -46,14 +137,10 @@ void Config::init() | |||||
| cfg.LinuxOSSSeqInDev = new char[MAX_STRING_SIZE]; | cfg.LinuxOSSSeqInDev = new char[MAX_STRING_SIZE]; | ||||
| snprintf(cfg.LinuxOSSSeqInDev, MAX_STRING_SIZE, "/dev/sequencer"); | snprintf(cfg.LinuxOSSSeqInDev, MAX_STRING_SIZE, "/dev/sequencer"); | ||||
| cfg.DumpFile = "zynaddsubfx_dump.txt"; | |||||
| cfg.WindowsWaveOutId = 0; | cfg.WindowsWaveOutId = 0; | ||||
| cfg.WindowsMidiInId = 0; | cfg.WindowsMidiInId = 0; | ||||
| cfg.BankUIAutoClose = 0; | cfg.BankUIAutoClose = 0; | ||||
| cfg.DumpNotesToFile = 0; | |||||
| cfg.DumpAppend = 1; | |||||
| cfg.GzipCompression = 3; | cfg.GzipCompression = 3; | ||||
| @@ -168,16 +255,6 @@ void Config::readConfig(const char *filename) | |||||
| 0, | 0, | ||||
| 1); | 1); | ||||
| cfg.DumpNotesToFile = xmlcfg.getpar("dump_notes_to_file", | |||||
| cfg.DumpNotesToFile, | |||||
| 0, | |||||
| 1); | |||||
| cfg.DumpAppend = xmlcfg.getpar("dump_append", | |||||
| cfg.DumpAppend, | |||||
| 0, | |||||
| 1); | |||||
| cfg.DumpFile = xmlcfg.getparstr("dump_file", ""); | |||||
| cfg.GzipCompression = xmlcfg.getpar("gzip_compression", | cfg.GzipCompression = xmlcfg.getpar("gzip_compression", | ||||
| cfg.GzipCompression, | cfg.GzipCompression, | ||||
| 0, | 0, | ||||
| @@ -259,10 +336,6 @@ void Config::saveConfig(const char *filename) | |||||
| xmlcfg->addpar("swap_stereo", cfg.SwapStereo); | xmlcfg->addpar("swap_stereo", cfg.SwapStereo); | ||||
| xmlcfg->addpar("bank_window_auto_close", cfg.BankUIAutoClose); | xmlcfg->addpar("bank_window_auto_close", cfg.BankUIAutoClose); | ||||
| xmlcfg->addpar("dump_notes_to_file", cfg.DumpNotesToFile); | |||||
| xmlcfg->addpar("dump_append", cfg.DumpAppend); | |||||
| xmlcfg->addparstr("dump_file", cfg.DumpFile); | |||||
| xmlcfg->addpar("gzip_compression", cfg.GzipCompression); | xmlcfg->addpar("gzip_compression", cfg.GzipCompression); | ||||
| xmlcfg->addpar("check_pad_synth", cfg.CheckPADsynth); | xmlcfg->addpar("check_pad_synth", cfg.CheckPADsynth); | ||||
| @@ -40,10 +40,8 @@ class Config | |||||
| int SampleRate, SoundBufferSize, OscilSize, SwapStereo; | int SampleRate, SoundBufferSize, OscilSize, SwapStereo; | ||||
| int WindowsWaveOutId, WindowsMidiInId; | int WindowsWaveOutId, WindowsMidiInId; | ||||
| int BankUIAutoClose; | int BankUIAutoClose; | ||||
| int DumpNotesToFile, DumpAppend; | |||||
| int GzipCompression; | int GzipCompression; | ||||
| int Interpolation; | int Interpolation; | ||||
| std::string DumpFile; | |||||
| std::string bankRootDirList[MAX_BANK_ROOT_DIRS], currentBankDir; | std::string bankRootDirList[MAX_BANK_ROOT_DIRS], currentBankDir; | ||||
| std::string presetsDirList[MAX_BANK_ROOT_DIRS]; | std::string presetsDirList[MAX_BANK_ROOT_DIRS]; | ||||
| int CheckPADsynth; | int CheckPADsynth; | ||||
| @@ -66,6 +64,7 @@ class Config | |||||
| void init(); | void init(); | ||||
| void save(); | void save(); | ||||
| static rtosc::Ports &ports; | |||||
| private: | private: | ||||
| void readConfig(const char *filename); | void readConfig(const char *filename); | ||||
| void saveConfig(const char *filename); | void saveConfig(const char *filename); | ||||
| @@ -1,121 +0,0 @@ | |||||
| /* | |||||
| ZynAddSubFX - a software synthesizer | |||||
| Dump.cpp - It dumps the notes to a text file | |||||
| Copyright (C) 2002-2005 Nasca Octavian Paul | |||||
| Author: Nasca Octavian Paul | |||||
| This program is free software; you can redistribute it and/or modify | |||||
| it under the terms of version 2 of the GNU General Public License | |||||
| as published by the Free Software Foundation. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License (version 2 or later) for more details. | |||||
| You should have received a copy of the GNU General Public License (version 2) | |||||
| along with this program; if not, write to the Free Software Foundation, | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||||
| */ | |||||
| #include <stdlib.h> | |||||
| #include <time.h> | |||||
| #include "Util.h" | |||||
| #include "Dump.h" | |||||
| Dump dump; | |||||
| Dump::Dump() | |||||
| { | |||||
| file = NULL; | |||||
| tick = 0; | |||||
| k = 0; | |||||
| keyspressed = 0; | |||||
| } | |||||
| Dump::~Dump() | |||||
| { | |||||
| if(file != NULL) { | |||||
| int duration = tick * synth->buffersize_f / synth->samplerate_f; | |||||
| fprintf( | |||||
| file, | |||||
| "\n# statistics: duration = %d seconds; keyspressed = %d\n\n\n\n", | |||||
| duration, | |||||
| keyspressed); | |||||
| fclose(file); | |||||
| } | |||||
| } | |||||
| void Dump::startnow() | |||||
| { | |||||
| if(file != NULL) | |||||
| return; //the file is already open | |||||
| if(config.cfg.DumpNotesToFile != 0) { | |||||
| if(config.cfg.DumpAppend != 0) | |||||
| file = fopen(config.cfg.DumpFile.c_str(), "a"); | |||||
| else | |||||
| file = fopen(config.cfg.DumpFile.c_str(), "w"); | |||||
| if(file == NULL) | |||||
| return; | |||||
| if(config.cfg.DumpAppend != 0) | |||||
| fprintf(file, "%s", "#************************************\n"); | |||||
| time_t tm = time(NULL); | |||||
| fprintf(file, "#date/time = %s\n", ctime(&tm)); | |||||
| fprintf(file, "#1 tick = %g milliseconds\n", | |||||
| synth->buffersize_f * 1000.0f / synth->samplerate_f); | |||||
| fprintf(file, "SAMPLERATE = %d\n", synth->samplerate); | |||||
| fprintf(file, "TICKSIZE = %d #samples\n", synth->buffersize); | |||||
| fprintf(file, "\n\nSTART\n"); | |||||
| } | |||||
| } | |||||
| void Dump::inctick() | |||||
| { | |||||
| tick++; | |||||
| } | |||||
| void Dump::dumpnote(char chan, char note, char vel) | |||||
| { | |||||
| if(file == NULL) | |||||
| return; | |||||
| if(note == 0) | |||||
| return; | |||||
| if(vel == 0) | |||||
| fprintf(file, "n %d -> %d %d \n", tick, chan, note); //note off | |||||
| else | |||||
| fprintf(file, "N %d -> %d %d %d \n", tick, chan, note, vel); //note on | |||||
| if(vel != 0) | |||||
| keyspressed++; | |||||
| #ifndef JACKAUDIOOUT | |||||
| if(k++ > 25) { | |||||
| fflush(file); | |||||
| k = 0; | |||||
| } | |||||
| #endif | |||||
| } | |||||
| void Dump::dumpcontroller(char chan, unsigned int type, int par) | |||||
| { | |||||
| if(file == NULL) | |||||
| return; | |||||
| switch(type) { | |||||
| case C_pitchwheel: | |||||
| fprintf(file, "P %d -> %d %d\n", tick, chan, par); | |||||
| break; | |||||
| default: | |||||
| fprintf(file, "C %d -> %d %d %d\n", tick, chan, type, par); | |||||
| break; | |||||
| } | |||||
| #ifndef JACKAUDIOOUT | |||||
| if(k++ > 25) { | |||||
| fflush(file); | |||||
| k = 0; | |||||
| } | |||||
| #endif | |||||
| } | |||||
| @@ -1,63 +0,0 @@ | |||||
| /* | |||||
| ZynAddSubFX - a software synthesizer | |||||
| Dump.h - It dumps the notes to a text file | |||||
| Copyright (C) 2002-2005 Nasca Octavian Paul | |||||
| Author: Nasca Octavian Paul | |||||
| This program is free software; you can redistribute it and/or modify | |||||
| it under the terms of version 2 of the GNU General Public License | |||||
| as published by the Free Software Foundation. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License (version 2 or later) for more details. | |||||
| You should have received a copy of the GNU General Public License (version 2) | |||||
| along with this program; if not, write to the Free Software Foundation, | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||||
| */ | |||||
| #ifndef DUMP_H | |||||
| #define DUMP_H | |||||
| #include <stdio.h> | |||||
| /**Object used to dump the notes into a text file | |||||
| * \todo see if this object should have knowledge about the file | |||||
| * that it will write to | |||||
| * \todo upgrade from stdio to iostream*/ | |||||
| class Dump | |||||
| { | |||||
| public: | |||||
| /**Constructor*/ | |||||
| Dump(); | |||||
| /**Destructor | |||||
| * Closes the dumpfile*/ | |||||
| ~Dump(); | |||||
| /**Open dumpfile and prepare it for dumps | |||||
| * \todo see if this fits better in the constructor*/ | |||||
| void startnow(); | |||||
| /**Tick the timestamp*/ | |||||
| void inctick(); | |||||
| /**Dump Note to dumpfile | |||||
| * @param chan The channel of the note | |||||
| * @param note The note | |||||
| * @param vel The velocity of the note*/ | |||||
| void dumpnote(char chan, char note, char vel); | |||||
| /** Dump the Controller | |||||
| * @param chan The channel of the Controller | |||||
| * @param type The type | |||||
| * @param par The value of the controller | |||||
| * \todo figure out what type is exactly meaning*/ | |||||
| void dumpcontroller(char chan, unsigned int type, int par); | |||||
| private: | |||||
| FILE *file; | |||||
| int tick; | |||||
| int k; //This appears to be a constant used to flush the file | |||||
| //periodically when JACK is used | |||||
| int keyspressed; | |||||
| }; | |||||
| #endif | |||||
| @@ -28,36 +28,276 @@ | |||||
| #include "../Params/LFOParams.h" | #include "../Params/LFOParams.h" | ||||
| #include "../Effects/EffectMgr.h" | #include "../Effects/EffectMgr.h" | ||||
| #include "../DSP/FFTwrapper.h" | #include "../DSP/FFTwrapper.h" | ||||
| #include "../Misc/Allocator.h" | |||||
| #include "../Nio/Nio.h" | |||||
| #include "PresetExtractor.h" | |||||
| #include <rtosc/ports.h> | |||||
| #include <rtosc/port-sugar.h> | |||||
| #include <rtosc/thread-link.h> | |||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #include <sys/stat.h> | #include <sys/stat.h> | ||||
| #include <sys/types.h> | #include <sys/types.h> | ||||
| #include <iostream> | #include <iostream> | ||||
| #include <algorithm> | #include <algorithm> | ||||
| #include <cmath> | #include <cmath> | ||||
| #include <atomic> | |||||
| #include <unistd.h> | #include <unistd.h> | ||||
| using namespace std; | using namespace std; | ||||
| using namespace rtosc; | |||||
| #define rObject Master | |||||
| static const Ports sysefxPort = | |||||
| { | |||||
| {"part#" STRINGIFY(NUM_MIDI_PARTS) "::i", 0, 0, [](const char *m, RtData&d) | |||||
| { | |||||
| //ok, this is going to be an ugly workaround | |||||
| //we know that if we are here the message previously MUST have | |||||
| //matched Psysefxvol#/ | |||||
| //and the number is one or two digits at most | |||||
| const char *index_1 = m; | |||||
| index_1 -=2; | |||||
| assert(isdigit(*index_1)); | |||||
| if(isdigit(index_1[-1])) | |||||
| index_1--; | |||||
| int ind1 = atoi(index_1); | |||||
| //Now get the second index like normal | |||||
| while(!isdigit(*m)) m++; | |||||
| int ind2 = atoi(m); | |||||
| Master &mast = *(Master*)d.obj; | |||||
| if(rtosc_narguments(m)) | |||||
| mast.setPsysefxvol(ind2, ind1, rtosc_argument(m,0).i); | |||||
| else | |||||
| d.reply(d.loc, "i", mast.Psysefxvol[ind2][ind1]); | |||||
| }} | |||||
| }; | |||||
| static const Ports sysefsendto = | |||||
| { | |||||
| {"to#" STRINGIFY(NUM_SYS_EFX) "::i", 0, 0, [](const char *m, RtData&d) | |||||
| { | |||||
| //same ugly workaround as before | |||||
| const char *index_1 = m; | |||||
| index_1 -=2; | |||||
| assert(isdigit(*index_1)); | |||||
| if(isdigit(index_1[-1])) | |||||
| index_1--; | |||||
| int ind1 = atoi(index_1); | |||||
| //Now get the second index like normal | |||||
| while(!isdigit(*m)) m++; | |||||
| int ind2 = atoi(m); | |||||
| Master &master = *(Master*)d.obj; | |||||
| if(rtosc_narguments(m)) | |||||
| master.setPsysefxsend(ind1, ind2, rtosc_argument(m,0).i); | |||||
| else | |||||
| d.reply(d.loc, "i", master.Psysefxsend[ind1][ind2]); | |||||
| }} | |||||
| }; | |||||
| static const Ports master_ports = { | |||||
| rRecursp(part, 16, "Part"),//NUM_MIDI_PARTS | |||||
| rRecursp(sysefx, 4, "System Effect"),//NUM_SYS_EFX | |||||
| rRecursp(insefx, 8, "Insertion Effect"),//NUM_INS_EFX | |||||
| rRecur(microtonal, "Micrtonal Mapping Functionality"), | |||||
| rRecur(ctl, "Controller"), | |||||
| rParamZyn(Pkeyshift, "Global Key Shift"), | |||||
| rArrayI(Pinsparts, NUM_INS_EFX, "Part to insert part onto"), | |||||
| {"echo", rDoc("Hidden port to echo messages"), 0, [](const char *m, RtData&d) { | |||||
| d.reply(m-1);}}, | |||||
| {"get-vu", rDoc("Grab VU Data"), 0, [](const char *, RtData &d) { | |||||
| Master *m = (Master*)d.obj; | |||||
| d.reply("/vu-meter", "bb", sizeof(m->vu), &m->vu, sizeof(float)*NUM_MIDI_PARTS, m->vuoutpeakpart);}}, | |||||
| {"reset-vu", rDoc("Grab VU Data"), 0, [](const char *, RtData &d) { | |||||
| Master *m = (Master*)d.obj; | |||||
| m->vuresetpeaks();}}, | |||||
| {"load-part:ib", rProp(internal) rDoc("Load Part From Middleware"), 0, [](const char *msg, RtData &d) { | |||||
| Master *m = (Master*)d.obj; | |||||
| Part *p = *(Part**)rtosc_argument(msg, 1).b.data; | |||||
| int i = rtosc_argument(msg, 0).i; | |||||
| m->part[i]->cloneTraits(*p); | |||||
| m->part[i]->kill_rt(); | |||||
| d.reply("/free", "sb", "Part", sizeof(void*), &m->part[i]); | |||||
| m->part[i] = p; | |||||
| p->initialize_rt(); | |||||
| //printf("part %d is now pointer %p\n", i, p); | |||||
| }}, | |||||
| {"Pvolume::i", rDoc("Master Volume"), 0, | |||||
| [](const char *m, rtosc::RtData &d) { | |||||
| if(rtosc_narguments(m)==0) { | |||||
| d.reply(d.loc, "i", ((Master*)d.obj)->Pvolume); | |||||
| } else if(rtosc_narguments(m)==1 && rtosc_type(m,0)=='i') { | |||||
| ((Master*)d.obj)->setPvolume(limit<char>(rtosc_argument(m,0).i,0,127)); | |||||
| d.broadcast(d.loc, "i", ((Master*)d.obj)->Pvolume);}}}, | |||||
| {"volume::i", rDoc("Master Volume"), 0, | |||||
| [](const char *m, rtosc::RtData &d) { | |||||
| if(rtosc_narguments(m)==0) { | |||||
| d.reply(d.loc, "i", ((Master*)d.obj)->Pvolume); | |||||
| } else if(rtosc_narguments(m)==1 && rtosc_type(m,0)=='i') { | |||||
| //printf("looking at value %d\n", rtosc_argument(m,0).i); | |||||
| //printf("limited value is %d\n", limit<char>( | |||||
| // rtosc_argument(m,0).i, 0,127)); | |||||
| ((Master*)d.obj)->setPvolume(limit<char>(rtosc_argument(m,0).i,0,127)); | |||||
| //printf("sets volume to value %d\n", ((Master*)d.obj)->Pvolume); | |||||
| d.broadcast(d.loc, "i", ((Master*)d.obj)->Pvolume);}}}, | |||||
| {"Psysefxvol#" STRINGIFY(NUM_SYS_EFX) "/::i", 0, &sysefxPort, | |||||
| [](const char *msg, rtosc::RtData &d) { | |||||
| SNIP; | |||||
| sysefxPort.dispatch(msg, d); | |||||
| }}, | |||||
| {"sysefxfrom#" STRINGIFY(NUM_SYS_EFX) "/", rDoc("Routing Between System Effects"), &sysefsendto, | |||||
| [](const char *msg, RtData&d) { | |||||
| SNIP; | |||||
| sysefsendto.dispatch(msg, d); | |||||
| }}, | |||||
| {"noteOn:iii", rDoc("Noteon Event"), 0, | |||||
| [](const char *m,RtData &d){ | |||||
| Master *M = (Master*)d.obj; | |||||
| M->noteOn(rtosc_argument(m,0).i,rtosc_argument(m,1).i,rtosc_argument(m,2).i);}}, | |||||
| {"noteOff:ii", rDoc("Noteoff Event"), 0, | |||||
| [](const char *m,RtData &d){ | |||||
| Master *M = (Master*)d.obj; | |||||
| M->noteOff(rtosc_argument(m,0).i,rtosc_argument(m,1).i);}}, | |||||
| {"setController:iii", rDoc("MIDI CC Event"), 0, | |||||
| [](const char *m,RtData &d){ | |||||
| Master *M = (Master*)d.obj; | |||||
| M->setController(rtosc_argument(m,0).i,rtosc_argument(m,1).i,rtosc_argument(m,2).i);}}, | |||||
| {"Panic:", rDoc("Stop All Sound"), 0, | |||||
| [](const char *, RtData &d) { | |||||
| Master &M = *(Master*)d.obj; | |||||
| M.ShutUp(); | |||||
| }}, | |||||
| {"freeze_state:", rDoc("Internal Read-only Mode"), 0, | |||||
| [](const char *,RtData &d) { | |||||
| Master *M = (Master*)d.obj; | |||||
| std::atomic_thread_fence(std::memory_order_release); | |||||
| M->frozenState = true; | |||||
| d.reply("/state_frozen", "");}}, | |||||
| {"thaw_state:", rDoc("Internal Read-only Mode"), 0, | |||||
| [](const char *,RtData &d) { | |||||
| Master *M = (Master*)d.obj; | |||||
| M->frozenState = false;}}, | |||||
| {"register:iis", rDoc("MIDI Mapping Registration"), 0, | |||||
| [](const char *m,RtData &d){ | |||||
| Master *M = (Master*)d.obj; | |||||
| M->midi.addElm(rtosc_argument(m,0).i, rtosc_argument(m,1).i,rtosc_argument(m,2).s);}}, | |||||
| {"learn:s", rDoc("Begin Learning for specified address"), 0, | |||||
| [](const char *m, RtData &d){ | |||||
| Master *M = (Master*)d.obj; | |||||
| printf("learning '%s'\n", rtosc_argument(m,0).s); | |||||
| M->midi.learn(rtosc_argument(m,0).s);}}, | |||||
| {"unlearn:s", rDoc("Remove Learning for specified address"), 0, | |||||
| [](const char *m, RtData &d){ | |||||
| Master *M = (Master*)d.obj; | |||||
| M->midi.clear_entry(rtosc_argument(m,0).s);}}, | |||||
| {"close-ui", rDoc("Request to close any connection named \"GUI\""), 0, [](const char *, RtData &d) { | |||||
| d.reply("/close-ui", "");}}, | |||||
| {"add-rt-memory:bi", rProp(internal) rDoc("Add Additional Memory To RT MemPool"), 0, | |||||
| [](const char *msg, RtData &d) | |||||
| { | |||||
| Master &m = *(Master*)d.obj; | |||||
| char *mem = *(char**)rtosc_argument(msg, 0).b.data; | |||||
| int i = rtosc_argument(msg, 1).i; | |||||
| m.memory->addMemory(mem, i); | |||||
| m.pendingMemory = false; | |||||
| }}, | |||||
| {"samplerate:", rMap(unit, Hz) rDoc("Synthesizer Global Sample Rate"), 0, [](const char *, RtData &d) { | |||||
| Master &m = *(Master*)d.obj; | |||||
| d.reply("/samplerate", "f", m.synth.samplerate_f); | |||||
| }}, | |||||
| {"oscilsize:", rDoc("Synthesizer Global Oscillator Size"), 0, [](const char *, RtData &d) { | |||||
| Master &m = *(Master*)d.obj; | |||||
| d.reply("/oscilsize", "f", m.synth.oscilsize_f); | |||||
| d.reply("/oscilsize", "i", m.synth.oscilsize); | |||||
| }}, | |||||
| {"undo_pause",0,0,[](const char *, rtosc::RtData &d) | |||||
| {d.reply("/undo_pause", "");}}, | |||||
| {"undo_resume",0,0,[](const char *, rtosc::RtData &d) | |||||
| {d.reply("/undo_resume", "");}}, | |||||
| {"config/", 0, &Config::ports, [](const char *, rtosc::RtData &){}}, | |||||
| {"presets/", 0, &preset_ports, rBOIL_BEGIN | |||||
| SNIP | |||||
| preset_ports.dispatch(msg, data); | |||||
| rBOIL_END}, | |||||
| }; | |||||
| const Ports &Master::ports = master_ports; | |||||
| //XXX HACKS | |||||
| Master *the_master; | |||||
| rtosc::ThreadLink *the_bToU; | |||||
| class DataObj:public rtosc::RtData | |||||
| { | |||||
| public: | |||||
| DataObj(char *loc_, size_t loc_size_, void *obj_, rtosc::ThreadLink *bToU_) | |||||
| { | |||||
| memset(loc_, 0, loc_size_); | |||||
| loc = loc_; | |||||
| loc_size = loc_size_; | |||||
| obj = obj_; | |||||
| bToU = bToU_; | |||||
| } | |||||
| virtual void reply(const char *path, const char *args, ...) override | |||||
| { | |||||
| va_list va; | |||||
| va_start(va,args); | |||||
| char *buffer = bToU->buffer(); | |||||
| rtosc_vmessage(buffer,bToU->buffer_size(),path,args,va); | |||||
| reply(buffer); | |||||
| va_end(va); | |||||
| } | |||||
| virtual void reply(const char *msg) override | |||||
| { | |||||
| if(rtosc_message_length(msg, -1) == 0) | |||||
| fprintf(stderr, "Warning: Invalid Rtosc message '%s'\n", msg); | |||||
| bToU->raw_write(msg); | |||||
| } | |||||
| virtual void broadcast(const char *path, const char *args, ...) override{ | |||||
| va_list va; | |||||
| va_start(va,args); | |||||
| reply("/broadcast", ""); | |||||
| char *buffer = bToU->buffer(); | |||||
| rtosc_vmessage(buffer,bToU->buffer_size(),path,args,va); | |||||
| reply(buffer); | |||||
| va_end(va); | |||||
| } | |||||
| virtual void broadcast(const char *msg) override | |||||
| { | |||||
| reply("/broadcast"); | |||||
| reply(msg); | |||||
| }; | |||||
| private: | |||||
| rtosc::ThreadLink *bToU; | |||||
| }; | |||||
| vuData::vuData(void) | vuData::vuData(void) | ||||
| :outpeakl(0.0f), outpeakr(0.0f), maxoutpeakl(0.0f), maxoutpeakr(0.0f), | :outpeakl(0.0f), outpeakr(0.0f), maxoutpeakl(0.0f), maxoutpeakr(0.0f), | ||||
| rmspeakl(0.0f), rmspeakr(0.0f), clipped(0) | rmspeakl(0.0f), rmspeakr(0.0f), clipped(0) | ||||
| {} | {} | ||||
| static Master* masterInstance = NULL; | |||||
| Master::Master() | |||||
| Master::Master(const SYNTH_T &synth_) | |||||
| :HDDRecorder(synth_), ctl(synth_), midi(Master::ports), frozenState(false), pendingMemory(false), synth(synth_) | |||||
| { | { | ||||
| bToU = NULL; | |||||
| uToB = NULL; | |||||
| memory = new Allocator(); | |||||
| the_master = this; | |||||
| swaplr = 0; | swaplr = 0; | ||||
| off = 0; | off = 0; | ||||
| smps = 0; | smps = 0; | ||||
| bufl = new float[synth->buffersize]; | |||||
| bufr = new float[synth->buffersize]; | |||||
| bufl = new float[synth.buffersize]; | |||||
| bufr = new float[synth.buffersize]; | |||||
| pthread_mutex_init(&mutex, NULL); | |||||
| pthread_mutex_init(&vumutex, NULL); | |||||
| fft = new FFTwrapper(synth->oscilsize); | |||||
| fft = new FFTwrapper(synth.oscilsize); | |||||
| shutup = 0; | shutup = 0; | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | ||||
| @@ -66,18 +306,43 @@ Master::Master() | |||||
| } | } | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | ||||
| part[npart] = new Part(µtonal, fft, &mutex); | |||||
| part[npart] = new Part(*memory, synth, µtonal, fft); | |||||
| //Insertion Effects init | //Insertion Effects init | ||||
| for(int nefx = 0; nefx < NUM_INS_EFX; ++nefx) | for(int nefx = 0; nefx < NUM_INS_EFX; ++nefx) | ||||
| insefx[nefx] = new EffectMgr(1, &mutex); | |||||
| insefx[nefx] = new EffectMgr(*memory, synth, 1); | |||||
| //System Effects init | //System Effects init | ||||
| for(int nefx = 0; nefx < NUM_SYS_EFX; ++nefx) | for(int nefx = 0; nefx < NUM_SYS_EFX; ++nefx) | ||||
| sysefx[nefx] = new EffectMgr(0, &mutex); | |||||
| sysefx[nefx] = new EffectMgr(*memory, synth, 0); | |||||
| defaults(); | defaults(); | ||||
| midi.event_cb = [](const char *m) | |||||
| { | |||||
| char loc_buf[1024]; | |||||
| DataObj d{loc_buf, 1024, the_master, the_bToU}; | |||||
| memset(loc_buf, 0, sizeof(loc_buf)); | |||||
| //printf("sending an event to the owner of '%s'\n", m); | |||||
| Master::ports.dispatch(m+1, d); | |||||
| }; | |||||
| midi.error_cb = [](const char *a, const char *b) | |||||
| { | |||||
| fprintf(stderr, "MIDI- got an error '%s' -- '%s'\n",a,b); | |||||
| }; | |||||
| } | |||||
| void Master::applyOscEvent(const char *msg) | |||||
| { | |||||
| char loc_buf[1024]; | |||||
| DataObj d{loc_buf, 1024, this, bToU}; | |||||
| memset(loc_buf, 0, sizeof(loc_buf)); | |||||
| d.matches = 0; | |||||
| ports.dispatch(msg+1, d); | |||||
| if(d.matches == 0) | |||||
| fprintf(stderr, "Unknown path '%s'\n", msg); | |||||
| } | } | ||||
| void Master::defaults() | void Master::defaults() | ||||
| @@ -112,36 +377,6 @@ void Master::defaults() | |||||
| ShutUp(); | ShutUp(); | ||||
| } | } | ||||
| bool Master::mutexLock(lockset request) | |||||
| { | |||||
| switch(request) { | |||||
| case MUTEX_TRYLOCK: | |||||
| return !pthread_mutex_trylock(&mutex); | |||||
| case MUTEX_LOCK: | |||||
| return !pthread_mutex_lock(&mutex); | |||||
| case MUTEX_UNLOCK: | |||||
| return !pthread_mutex_unlock(&mutex); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| Master &Master::getInstance() | |||||
| { | |||||
| if (!masterInstance) | |||||
| masterInstance = new Master; | |||||
| return *masterInstance; | |||||
| } | |||||
| void Master::deleteInstance() | |||||
| { | |||||
| if (masterInstance) | |||||
| { | |||||
| delete masterInstance; | |||||
| masterInstance = NULL; | |||||
| } | |||||
| } | |||||
| /* | /* | ||||
| * Note On Messages (velocity=0 for NoteOff) | * Note On Messages (velocity=0 for NoteOff) | ||||
| */ | */ | ||||
| @@ -191,38 +426,36 @@ void Master::polyphonicAftertouch(char chan, char note, char velocity) | |||||
| */ | */ | ||||
| void Master::setController(char chan, int type, int par) | void Master::setController(char chan, int type, int par) | ||||
| { | { | ||||
| if(frozenState) | |||||
| return; | |||||
| midi.process(chan,type,par); | |||||
| if((type == C_dataentryhi) || (type == C_dataentrylo) | if((type == C_dataentryhi) || (type == C_dataentrylo) | ||||
| || (type == C_nrpnhi) || (type == C_nrpnlo)) { //Process RPN and NRPN by the Master (ignore the chan) | || (type == C_nrpnhi) || (type == C_nrpnlo)) { //Process RPN and NRPN by the Master (ignore the chan) | ||||
| ctl.setparameternumber(type, par); | ctl.setparameternumber(type, par); | ||||
| int parhi = -1, parlo = -1, valhi = -1, vallo = -1; | int parhi = -1, parlo = -1, valhi = -1, vallo = -1; | ||||
| if(ctl.getnrpn(&parhi, &parlo, &valhi, &vallo) == 0) //this is NRPN | if(ctl.getnrpn(&parhi, &parlo, &valhi, &vallo) == 0) //this is NRPN | ||||
| //fprintf(stderr,"rcv. NRPN: %d %d %d %d\n",parhi,parlo,valhi,vallo); | |||||
| switch(parhi) { | switch(parhi) { | ||||
| case 0x04: //System Effects | case 0x04: //System Effects | ||||
| if(parlo < NUM_SYS_EFX) | if(parlo < NUM_SYS_EFX) | ||||
| sysefx[parlo]->seteffectpar_nolock(valhi, vallo); | |||||
| ; | |||||
| sysefx[parlo]->seteffectparrt(valhi, vallo); | |||||
| break; | break; | ||||
| case 0x08: //Insertion Effects | case 0x08: //Insertion Effects | ||||
| if(parlo < NUM_INS_EFX) | if(parlo < NUM_INS_EFX) | ||||
| insefx[parlo]->seteffectpar_nolock(valhi, vallo); | |||||
| ; | |||||
| insefx[parlo]->seteffectparrt(valhi, vallo); | |||||
| break; | break; | ||||
| } | } | ||||
| ; | |||||
| } | } | ||||
| else | else | ||||
| if(type == C_bankselectmsb) { // Change current bank | if(type == C_bankselectmsb) { // Change current bank | ||||
| if(((unsigned int)par < bank.banks.size()) | |||||
| && (bank.banks[par].dir != bank.bankfiletitle)) | |||||
| bank.loadbank(bank.banks[par].dir); | |||||
| //if(((unsigned int)par < bank.banks.size()) | |||||
| // && (bank.banks[par].dir != bank.bankfiletitle)) | |||||
| // bank.loadbank(bank.banks[par].dir); | |||||
| } | } | ||||
| else { //other controllers | else { //other controllers | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) //Send the controller to all part assigned to the channel | for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) //Send the controller to all part assigned to the channel | ||||
| if((chan == part[npart]->Prcvchn) && (part[npart]->Penabled != 0)) | if((chan == part[npart]->Prcvchn) && (part[npart]->Penabled != 0)) | ||||
| part[npart]->SetController(type, par); | part[npart]->SetController(type, par); | ||||
| ; | |||||
| if(type == C_allsoundsoff) { //cleanup insertion/system FX | if(type == C_allsoundsoff) { //cleanup insertion/system FX | ||||
| for(int nefx = 0; nefx < NUM_SYS_EFX; ++nefx) | for(int nefx = 0; nefx < NUM_SYS_EFX; ++nefx) | ||||
| @@ -233,30 +466,12 @@ void Master::setController(char chan, int type, int par) | |||||
| } | } | ||||
| } | } | ||||
| void Master::setProgram(char chan, unsigned int pgm) | |||||
| { | |||||
| if(config.cfg.IgnoreProgramChange) | |||||
| return; | |||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | |||||
| if(chan == part[npart]->Prcvchn) { | |||||
| bank.loadfromslot(pgm, part[npart]); | |||||
| //Hack to get pad note parameters to update | |||||
| //this is not real time safe and makes assumptions about the calling | |||||
| //convention of this function... | |||||
| pthread_mutex_unlock(&mutex); | |||||
| part[npart]->applyparameters(); | |||||
| pthread_mutex_lock(&mutex); | |||||
| } | |||||
| } | |||||
| void Master::vuUpdate(const float *outl, const float *outr) | void Master::vuUpdate(const float *outl, const float *outr) | ||||
| { | { | ||||
| //Peak computation (for vumeters) | //Peak computation (for vumeters) | ||||
| vu.outpeakl = 1e-12; | vu.outpeakl = 1e-12; | ||||
| vu.outpeakr = 1e-12; | vu.outpeakr = 1e-12; | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| if(fabs(outl[i]) > vu.outpeakl) | if(fabs(outl[i]) > vu.outpeakl) | ||||
| vu.outpeakl = fabs(outl[i]); | vu.outpeakl = fabs(outl[i]); | ||||
| if(fabs(outr[i]) > vu.outpeakr) | if(fabs(outr[i]) > vu.outpeakr) | ||||
| @@ -272,12 +487,12 @@ void Master::vuUpdate(const float *outl, const float *outr) | |||||
| //RMS Peak computation (for vumeters) | //RMS Peak computation (for vumeters) | ||||
| vu.rmspeakl = 1e-12; | vu.rmspeakl = 1e-12; | ||||
| vu.rmspeakr = 1e-12; | vu.rmspeakr = 1e-12; | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| vu.rmspeakl += outl[i] * outl[i]; | vu.rmspeakl += outl[i] * outl[i]; | ||||
| vu.rmspeakr += outr[i] * outr[i]; | vu.rmspeakr += outr[i] * outr[i]; | ||||
| } | } | ||||
| vu.rmspeakl = sqrt(vu.rmspeakl / synth->buffersize_f); | |||||
| vu.rmspeakr = sqrt(vu.rmspeakr / synth->buffersize_f); | |||||
| vu.rmspeakl = sqrt(vu.rmspeakl / synth.buffersize_f); | |||||
| vu.rmspeakr = sqrt(vu.rmspeakr / synth.buffersize_f); | |||||
| //Part Peak computation (for Part vumeters or fake part vumeters) | //Part Peak computation (for Part vumeters or fake part vumeters) | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | ||||
| @@ -285,7 +500,7 @@ void Master::vuUpdate(const float *outl, const float *outr) | |||||
| if(part[npart]->Penabled != 0) { | if(part[npart]->Penabled != 0) { | ||||
| float *outl = part[npart]->partoutl, | float *outl = part[npart]->partoutl, | ||||
| *outr = part[npart]->partoutr; | *outr = part[npart]->partoutr; | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| float tmp = fabs(outl[i] + outr[i]); | float tmp = fabs(outl[i] + outr[i]); | ||||
| if(tmp > vuoutpeakpart[npart]) | if(tmp > vuoutpeakpart[npart]) | ||||
| vuoutpeakpart[npart] = tmp; | vuoutpeakpart[npart] = tmp; | ||||
| @@ -312,7 +527,6 @@ void Master::partonoff(int npart, int what) | |||||
| for(int nefx = 0; nefx < NUM_INS_EFX; ++nefx) { | for(int nefx = 0; nefx < NUM_INS_EFX; ++nefx) { | ||||
| if(Pinsparts[nefx] == npart) | if(Pinsparts[nefx] == npart) | ||||
| insefx[nefx]->cleanup(); | insefx[nefx]->cleanup(); | ||||
| ; | |||||
| } | } | ||||
| } | } | ||||
| else { //enabled | else { //enabled | ||||
| @@ -321,26 +535,135 @@ void Master::partonoff(int npart, int what) | |||||
| } | } | ||||
| } | } | ||||
| #if 0 | |||||
| template <class T> | |||||
| struct def_skip | |||||
| { | |||||
| static void skip(const char*& argptr) { argptr += sizeof(T); } | |||||
| }; | |||||
| template <class T> | |||||
| struct str_skip | |||||
| { | |||||
| static void skip(const char*& argptr) { while(argptr++); /*TODO: 4 padding */ } | |||||
| }; | |||||
| template<class T, class Display = T, template<class TMP> class SkipsizeFunc = def_skip> | |||||
| void _dump_prim_arg(const char*& argptr, std::ostream& os) | |||||
| { | |||||
| os << ' ' << (Display)*(const T*)argptr; | |||||
| SkipsizeFunc<T>::skip(argptr); | |||||
| } | |||||
| void dump_msg(const char* ptr, std::ostream& os = std::cerr) | |||||
| { | |||||
| assert(*ptr == '/'); | |||||
| os << ptr; | |||||
| while(*++ptr) ; // skip address | |||||
| while(!*++ptr) ; // skip 0s | |||||
| assert(*ptr == ','); | |||||
| os << ' ' << (ptr + 1); | |||||
| const char* argptr = ptr; | |||||
| while(*++argptr) ; // skip type string | |||||
| while(!*++argptr) ; // skip 0s | |||||
| char c; | |||||
| while((c = *++ptr)) | |||||
| { | |||||
| switch(c) | |||||
| { | |||||
| case 'i': | |||||
| _dump_prim_arg<int32_t>(argptr, os); break; | |||||
| case 'c': | |||||
| _dump_prim_arg<int32_t, char>(argptr, os); break; | |||||
| // case 's': | |||||
| // _dump_prim_arg<char, const char*>(argptr, os); break; | |||||
| default: | |||||
| exit(1); | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif | |||||
| int msg_id=0; | |||||
| /* | /* | ||||
| * Master audio out (the final sound) | * Master audio out (the final sound) | ||||
| */ | */ | ||||
| void Master::AudioOut(float *outl, float *outr) | void Master::AudioOut(float *outl, float *outr) | ||||
| { | { | ||||
| //Danger Limits | |||||
| if(memory->lowMemory(2,1024*1024)) | |||||
| printf("QUITE LOW MEMORY IN THE RT POOL BE PREPARED FOR WEIRD BEHAVIOR!!\n"); | |||||
| //Normal Limits | |||||
| if(!pendingMemory && memory->lowMemory(4,1024*1024)) { | |||||
| printf("Requesting more memory\n"); | |||||
| bToU->write("/request-memory", ""); | |||||
| pendingMemory = true; | |||||
| } | |||||
| //Handle user events TODO move me to a proper location | |||||
| char loc_buf[1024]; | |||||
| DataObj d{loc_buf, 1024, this, bToU}; | |||||
| memset(loc_buf, 0, sizeof(loc_buf)); | |||||
| int events = 0; | |||||
| while(uToB && uToB->hasNext() && events < 10) { | |||||
| const char *msg = uToB->read(); | |||||
| #ifndef PLUGINVERSION | |||||
| if(!strcmp(msg, "/load-master")) { | |||||
| Master *this_master = this; | |||||
| Master *new_master = *(Master**)rtosc_argument(msg, 0).b.data; | |||||
| new_master->AudioOut(outl, outr); | |||||
| Nio::masterSwap(new_master); | |||||
| bToU->write("/free", "sb", "Master", sizeof(Master*), &this_master); | |||||
| return; | |||||
| } | |||||
| #endif | |||||
| //XXX yes, this is not realtime safe, but it is useful... | |||||
| if(strcmp(msg, "/get-vu") && false) { | |||||
| fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 5 + 30, 0 + 40); | |||||
| fprintf(stdout, "backend[%d]: '%s'<%s>\n", msg_id++, msg, | |||||
| rtosc_argument_string(msg)); | |||||
| fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); | |||||
| } | |||||
| d.matches = 0; | |||||
| //fprintf(stdout, "address '%s'\n", uToB->peak()); | |||||
| ports.dispatch(msg+1, d); | |||||
| events++; | |||||
| if(!d.matches) {// && !ports.apropos(msg)) { | |||||
| fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40); | |||||
| fprintf(stderr, "Unknown address<BACKEND> '%s:%s'\n", uToB->peak(), rtosc_argument_string(uToB->peak())); | |||||
| #if 0 | |||||
| if(strstr(msg, "PFMVelocity")) | |||||
| dump_msg(msg); | |||||
| if(ports.apropos(msg)) | |||||
| fprintf(stderr, " -> best match: '%s'\n", ports.apropos(msg)->name); | |||||
| if(ports.apropos(msg+1)) | |||||
| fprintf(stderr, " -> best match: '%s'\n", ports.apropos(msg+1)->name); | |||||
| #endif | |||||
| fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); | |||||
| } | |||||
| } | |||||
| if(events>1 && false) | |||||
| fprintf(stderr, "backend: %d events per cycle\n",events); | |||||
| //Swaps the Left channel with Right Channel | //Swaps the Left channel with Right Channel | ||||
| if(swaplr) | if(swaplr) | ||||
| swap(outl, outr); | swap(outl, outr); | ||||
| //clean up the output samples (should not be needed?) | //clean up the output samples (should not be needed?) | ||||
| memset(outl, 0, synth->bufferbytes); | |||||
| memset(outr, 0, synth->bufferbytes); | |||||
| memset(outl, 0, synth.bufferbytes); | |||||
| memset(outr, 0, synth.bufferbytes); | |||||
| //Compute part samples and store them part[npart]->partoutl,partoutr | //Compute part samples and store them part[npart]->partoutl,partoutr | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | |||||
| if(part[npart]->Penabled != 0 && !pthread_mutex_trylock(&part[npart]->load_mutex)) { | |||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | |||||
| if(part[npart]->Penabled) | |||||
| part[npart]->ComputePartSmps(); | part[npart]->ComputePartSmps(); | ||||
| pthread_mutex_unlock(&part[npart]->load_mutex); | |||||
| } | |||||
| } | |||||
| //Insertion effects | //Insertion effects | ||||
| for(int nefx = 0; nefx < NUM_INS_EFX; ++nefx) | for(int nefx = 0; nefx < NUM_INS_EFX; ++nefx) | ||||
| @@ -354,7 +677,7 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| //Apply the part volumes and pannings (after insertion effects) | //Apply the part volumes and pannings (after insertion effects) | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | ||||
| if(part[npart]->Penabled == 0) | |||||
| if(!part[npart]->Penabled) | |||||
| continue; | continue; | ||||
| Stereo<float> newvol(part[npart]->volume), | Stereo<float> newvol(part[npart]->volume), | ||||
| @@ -366,26 +689,29 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| newvol.l *= pan * 2.0f; | newvol.l *= pan * 2.0f; | ||||
| else | else | ||||
| newvol.r *= (1.0f - pan) * 2.0f; | newvol.r *= (1.0f - pan) * 2.0f; | ||||
| //if(npart==0) | |||||
| //printf("[%d]vol = %f->%f\n", npart, oldvol.l, newvol.l); | |||||
| //the volume or the panning has changed and needs interpolation | //the volume or the panning has changed and needs interpolation | ||||
| if(ABOVE_AMPLITUDE_THRESHOLD(oldvol.l, newvol.l) | if(ABOVE_AMPLITUDE_THRESHOLD(oldvol.l, newvol.l) | ||||
| || ABOVE_AMPLITUDE_THRESHOLD(oldvol.r, newvol.r)) { | || ABOVE_AMPLITUDE_THRESHOLD(oldvol.r, newvol.r)) { | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| Stereo<float> vol(INTERPOLATE_AMPLITUDE(oldvol.l, newvol.l, | Stereo<float> vol(INTERPOLATE_AMPLITUDE(oldvol.l, newvol.l, | ||||
| i, synth->buffersize), | |||||
| i, synth.buffersize), | |||||
| INTERPOLATE_AMPLITUDE(oldvol.r, newvol.r, | INTERPOLATE_AMPLITUDE(oldvol.r, newvol.r, | ||||
| i, synth->buffersize)); | |||||
| i, synth.buffersize)); | |||||
| part[npart]->partoutl[i] *= vol.l; | part[npart]->partoutl[i] *= vol.l; | ||||
| part[npart]->partoutr[i] *= vol.r; | part[npart]->partoutr[i] *= vol.r; | ||||
| } | } | ||||
| part[npart]->oldvolumel = newvol.l; | part[npart]->oldvolumel = newvol.l; | ||||
| part[npart]->oldvolumer = newvol.r; | part[npart]->oldvolumer = newvol.r; | ||||
| } | } | ||||
| else | |||||
| for(int i = 0; i < synth->buffersize; ++i) { //the volume did not changed | |||||
| else { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { //the volume did not changed | |||||
| part[npart]->partoutl[i] *= newvol.l; | part[npart]->partoutl[i] *= newvol.l; | ||||
| part[npart]->partoutr[i] *= newvol.r; | part[npart]->partoutr[i] *= newvol.r; | ||||
| } | } | ||||
| } | |||||
| } | } | ||||
| @@ -394,11 +720,11 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| if(sysefx[nefx]->geteffect() == 0) | if(sysefx[nefx]->geteffect() == 0) | ||||
| continue; //the effect is disabled | continue; //the effect is disabled | ||||
| float tmpmixl[synth->buffersize]; | |||||
| float tmpmixr[synth->buffersize]; | |||||
| float tmpmixl[synth.buffersize]; | |||||
| float tmpmixr[synth.buffersize]; | |||||
| //Clean up the samples used by the system effects | //Clean up the samples used by the system effects | ||||
| memset(tmpmixl, 0, synth->bufferbytes); | |||||
| memset(tmpmixr, 0, synth->bufferbytes); | |||||
| memset(tmpmixl, 0, synth.bufferbytes); | |||||
| memset(tmpmixr, 0, synth.bufferbytes); | |||||
| //Mix the channels according to the part settings about System Effect | //Mix the channels according to the part settings about System Effect | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | ||||
| @@ -412,7 +738,7 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| //the output volume of each part to system effect | //the output volume of each part to system effect | ||||
| const float vol = sysefxvol[nefx][npart]; | const float vol = sysefxvol[nefx][npart]; | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| tmpmixl[i] += part[npart]->partoutl[i] * vol; | tmpmixl[i] += part[npart]->partoutl[i] * vol; | ||||
| tmpmixr[i] += part[npart]->partoutr[i] * vol; | tmpmixr[i] += part[npart]->partoutr[i] * vol; | ||||
| } | } | ||||
| @@ -422,7 +748,7 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| for(int nefxfrom = 0; nefxfrom < nefx; ++nefxfrom) | for(int nefxfrom = 0; nefxfrom < nefx; ++nefxfrom) | ||||
| if(Psysefxsend[nefxfrom][nefx] != 0) { | if(Psysefxsend[nefxfrom][nefx] != 0) { | ||||
| const float vol = sysefxsend[nefxfrom][nefx]; | const float vol = sysefxsend[nefxfrom][nefx]; | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| tmpmixl[i] += sysefx[nefxfrom]->efxoutl[i] * vol; | tmpmixl[i] += sysefx[nefxfrom]->efxoutl[i] * vol; | ||||
| tmpmixr[i] += sysefx[nefxfrom]->efxoutr[i] * vol; | tmpmixr[i] += sysefx[nefxfrom]->efxoutr[i] * vol; | ||||
| } | } | ||||
| @@ -432,7 +758,7 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| //Add the System Effect to sound output | //Add the System Effect to sound output | ||||
| const float outvol = sysefx[nefx]->sysefxgetvolume(); | const float outvol = sysefx[nefx]->sysefxgetvolume(); | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| outl[i] += tmpmixl[i] * outvol; | outl[i] += tmpmixl[i] * outvol; | ||||
| outr[i] += tmpmixr[i] * outvol; | outr[i] += tmpmixr[i] * outvol; | ||||
| } | } | ||||
| @@ -441,7 +767,7 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| //Mix all parts | //Mix all parts | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | ||||
| if(part[npart]->Penabled) //only mix active parts | if(part[npart]->Penabled) //only mix active parts | ||||
| for(int i = 0; i < synth->buffersize; ++i) { //the volume did not changed | |||||
| for(int i = 0; i < synth.buffersize; ++i) { //the volume did not changed | |||||
| outl[i] += part[npart]->partoutl[i]; | outl[i] += part[npart]->partoutl[i]; | ||||
| outr[i] += part[npart]->partoutr[i]; | outr[i] += part[npart]->partoutr[i]; | ||||
| } | } | ||||
| @@ -453,20 +779,17 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| //Master Volume | //Master Volume | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| outl[i] *= volume; | outl[i] *= volume; | ||||
| outr[i] *= volume; | outr[i] *= volume; | ||||
| } | } | ||||
| if(!pthread_mutex_trylock(&vumutex)) { | |||||
| vuUpdate(outl, outr); | |||||
| pthread_mutex_unlock(&vumutex); | |||||
| } | |||||
| vuUpdate(outl, outr); | |||||
| //Shutup if it is asked (with fade-out) | //Shutup if it is asked (with fade-out) | ||||
| if(shutup) { | if(shutup) { | ||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| float tmp = (synth->buffersize_f - i) / synth->buffersize_f; | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| float tmp = (synth.buffersize_f - i) / synth.buffersize_f; | |||||
| outl[i] *= tmp; | outl[i] *= tmp; | ||||
| outr[i] *= tmp; | outr[i] *= tmp; | ||||
| } | } | ||||
| @@ -475,8 +798,6 @@ void Master::AudioOut(float *outl, float *outr) | |||||
| //update the LFO's time | //update the LFO's time | ||||
| LFOParams::time++; | LFOParams::time++; | ||||
| dump.inctick(); | |||||
| } | } | ||||
| //TODO review the respective code from yoshimi for this | //TODO review the respective code from yoshimi for this | ||||
| @@ -489,8 +810,8 @@ void Master::GetAudioOutSamples(size_t nsamples, | |||||
| off_t out_off = 0; | off_t out_off = 0; | ||||
| //Fail when resampling rather than doing a poor job | //Fail when resampling rather than doing a poor job | ||||
| if(synth->samplerate != samplerate) { | |||||
| printf("darn it: %d vs %d\n", synth->samplerate, samplerate); | |||||
| if(synth.samplerate != samplerate) { | |||||
| printf("darn it: %d vs %d\n", synth.samplerate, samplerate); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -505,7 +826,7 @@ void Master::GetAudioOutSamples(size_t nsamples, | |||||
| AudioOut(bufl, bufr); | AudioOut(bufl, bufr); | ||||
| off = 0; | off = 0; | ||||
| out_off += smps; | out_off += smps; | ||||
| smps = synth->buffersize; | |||||
| smps = synth.buffersize; | |||||
| } | } | ||||
| else { //use some samples | else { //use some samples | ||||
| memcpy(outl + out_off, bufl + off, sizeof(float) * nsamples); | memcpy(outl + out_off, bufl + off, sizeof(float) * nsamples); | ||||
| @@ -530,9 +851,7 @@ Master::~Master() | |||||
| delete sysefx[nefx]; | delete sysefx[nefx]; | ||||
| delete fft; | delete fft; | ||||
| pthread_mutex_destroy(&mutex); | |||||
| pthread_mutex_destroy(&vumutex); | |||||
| delete memory; | |||||
| } | } | ||||
| @@ -588,28 +907,28 @@ void Master::ShutUp() | |||||
| */ | */ | ||||
| void Master::vuresetpeaks() | void Master::vuresetpeaks() | ||||
| { | { | ||||
| pthread_mutex_lock(&vumutex); | |||||
| vu.outpeakl = 1e-9; | vu.outpeakl = 1e-9; | ||||
| vu.outpeakr = 1e-9; | vu.outpeakr = 1e-9; | ||||
| vu.maxoutpeakl = 1e-9; | vu.maxoutpeakl = 1e-9; | ||||
| vu.maxoutpeakr = 1e-9; | vu.maxoutpeakr = 1e-9; | ||||
| vu.clipped = 0; | vu.clipped = 0; | ||||
| pthread_mutex_unlock(&vumutex); | |||||
| } | } | ||||
| vuData Master::getVuData() | |||||
| void Master::applyparameters(void) | |||||
| { | { | ||||
| vuData tmp; | |||||
| pthread_mutex_lock(&vumutex); | |||||
| tmp = vu; | |||||
| pthread_mutex_unlock(&vumutex); | |||||
| return tmp; | |||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | |||||
| part[npart]->applyparameters(); | |||||
| } | } | ||||
| void Master::applyparameters(bool lockmutex) | |||||
| void Master::initialize_rt(void) | |||||
| { | { | ||||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | |||||
| part[npart]->applyparameters(lockmutex); | |||||
| for(int i=0; i<NUM_SYS_EFX; ++i) | |||||
| sysefx[i]->init(); | |||||
| for(int i=0; i<NUM_INS_EFX; ++i) | |||||
| insefx[i]->init(); | |||||
| for(int i=0; i<NUM_MIDI_PARTS; ++i) | |||||
| part[i]->initialize_rt(); | |||||
| } | } | ||||
| void Master::add2XML(XMLwrapper *xml) | void Master::add2XML(XMLwrapper *xml) | ||||
| @@ -673,9 +992,7 @@ int Master::getalldata(char **data) | |||||
| xml->beginbranch("MASTER"); | xml->beginbranch("MASTER"); | ||||
| pthread_mutex_lock(&mutex); | |||||
| add2XML(xml); | add2XML(xml); | ||||
| pthread_mutex_unlock(&mutex); | |||||
| xml->endbranch(); | xml->endbranch(); | ||||
| @@ -695,9 +1012,7 @@ void Master::putalldata(char *data, int /*size*/) | |||||
| if(xml->enterbranch("MASTER") == 0) | if(xml->enterbranch("MASTER") == 0) | ||||
| return; | return; | ||||
| pthread_mutex_lock(&mutex); | |||||
| getfromXML(xml); | getfromXML(xml); | ||||
| pthread_mutex_unlock(&mutex); | |||||
| xml->exitbranch(); | xml->exitbranch(); | ||||
| @@ -733,6 +1048,7 @@ int Master::loadXML(const char *filename) | |||||
| xml->exitbranch(); | xml->exitbranch(); | ||||
| delete (xml); | delete (xml); | ||||
| initialize_rt(); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -23,23 +23,17 @@ | |||||
| #ifndef MASTER_H | #ifndef MASTER_H | ||||
| #define MASTER_H | #define MASTER_H | ||||
| #include <pthread.h> | |||||
| #include "../globals.h" | #include "../globals.h" | ||||
| #include "Microtonal.h" | #include "Microtonal.h" | ||||
| #include <rtosc/miditable.h> | |||||
| #include <rtosc/ports.h> | |||||
| #include "Bank.h" | #include "Bank.h" | ||||
| #include "Recorder.h" | #include "Recorder.h" | ||||
| #include "Dump.h" | |||||
| #include "XMLwrapper.h" | |||||
| #include "../Params/Controller.h" | #include "../Params/Controller.h" | ||||
| typedef enum { | |||||
| MUTEX_TRYLOCK, MUTEX_LOCK, MUTEX_UNLOCK | |||||
| } lockset; | |||||
| extern Dump dump; | |||||
| class Allocator; | |||||
| struct vuData { | struct vuData { | ||||
| vuData(void); | vuData(void); | ||||
| @@ -55,12 +49,11 @@ class Master | |||||
| { | { | ||||
| public: | public: | ||||
| /** Constructor TODO make private*/ | /** Constructor TODO make private*/ | ||||
| Master(); | |||||
| Master(const SYNTH_T &synth); | |||||
| /** Destructor*/ | /** Destructor*/ | ||||
| ~Master(); | ~Master(); | ||||
| static Master &getInstance(); | |||||
| static void deleteInstance(); | |||||
| void applyOscEvent(const char *event); | |||||
| /**Saves all settings to a XML file | /**Saves all settings to a XML file | ||||
| * @return 0 for ok or <0 if there is an error*/ | * @return 0 for ok or <0 if there is an error*/ | ||||
| @@ -75,28 +68,27 @@ class Master | |||||
| /**loads all settings from a XML file | /**loads all settings from a XML file | ||||
| * @return 0 for ok or -1 if there is an error*/ | * @return 0 for ok or -1 if there is an error*/ | ||||
| int loadXML(const char *filename); | int loadXML(const char *filename); | ||||
| void applyparameters(bool lockmutex = true); | |||||
| /**Regenerate PADsynth and other non-RT parameters | |||||
| * It is NOT SAFE to call this from a RT context*/ | |||||
| void applyparameters(void) NONREALTIME; | |||||
| //This must be called prior-to/at-the-time-of RT insertion | |||||
| void initialize_rt(void) REALTIME; | |||||
| void getfromXML(XMLwrapper *xml); | void getfromXML(XMLwrapper *xml); | ||||
| /**get all data to a newly allocated array (used for VST) | /**get all data to a newly allocated array (used for VST) | ||||
| * @return the datasize*/ | * @return the datasize*/ | ||||
| int getalldata(char **data); | |||||
| int getalldata(char **data) NONREALTIME; | |||||
| /**put all data from the *data array to zynaddsubfx parameters (used for VST)*/ | /**put all data from the *data array to zynaddsubfx parameters (used for VST)*/ | ||||
| void putalldata(char *data, int size); | void putalldata(char *data, int size); | ||||
| //Mutex control | |||||
| /**Control the Master's mutex state. | |||||
| * @param lockset either trylock, lock, or unlock. | |||||
| * @return true when successful false otherwise.*/ | |||||
| bool mutexLock(lockset request); | |||||
| //Midi IN | //Midi IN | ||||
| void noteOn(char chan, char note, char velocity); | void noteOn(char chan, char note, char velocity); | ||||
| void noteOff(char chan, char note); | void noteOff(char chan, char note); | ||||
| void polyphonicAftertouch(char chan, char note, char velocity); | void polyphonicAftertouch(char chan, char note, char velocity); | ||||
| void setController(char chan, int type, int par); | void setController(char chan, int type, int par); | ||||
| void setProgram(char chan, unsigned int pgm); | |||||
| //void NRPN... | //void NRPN... | ||||
| @@ -106,12 +98,12 @@ class Master | |||||
| void vuUpdate(const float *outl, const float *outr); | void vuUpdate(const float *outl, const float *outr); | ||||
| /**Audio Output*/ | /**Audio Output*/ | ||||
| void AudioOut(float *outl, float *outr); | |||||
| void AudioOut(float *outl, float *outr) REALTIME; | |||||
| /**Audio Output (for callback mode). This allows the program to be controled by an external program*/ | /**Audio Output (for callback mode). This allows the program to be controled by an external program*/ | ||||
| void GetAudioOutSamples(size_t nsamples, | void GetAudioOutSamples(size_t nsamples, | ||||
| unsigned samplerate, | unsigned samplerate, | ||||
| float *outl, | float *outl, | ||||
| float *outr); | |||||
| float *outr) REALTIME; | |||||
| void partonoff(int npart, int what); | void partonoff(int npart, int what); | ||||
| @@ -146,11 +138,8 @@ class Master | |||||
| //peaks for VU-meter | //peaks for VU-meter | ||||
| void vuresetpeaks(); | void vuresetpeaks(); | ||||
| //get VU-meter data | |||||
| vuData getVuData(); | |||||
| //peaks for part VU-meters | //peaks for part VU-meters | ||||
| /**\todo synchronize this with a mutex*/ | |||||
| float vuoutpeakpart[NUM_MIDI_PARTS]; | float vuoutpeakpart[NUM_MIDI_PARTS]; | ||||
| unsigned char fakepeakpart[NUM_MIDI_PARTS]; //this is used to compute the "peak" when the part is disabled | unsigned char fakepeakpart[NUM_MIDI_PARTS]; //this is used to compute the "peak" when the part is disabled | ||||
| @@ -159,16 +148,27 @@ class Master | |||||
| //other objects | //other objects | ||||
| Microtonal microtonal; | Microtonal microtonal; | ||||
| Bank bank; | |||||
| //Strictly Non-RT instrument bank object | |||||
| Bank bank; | |||||
| class FFTwrapper * fft; | class FFTwrapper * fft; | ||||
| pthread_mutex_t mutex; | |||||
| pthread_mutex_t vumutex; | |||||
| static const rtosc::Ports &ports; | |||||
| float volume; | |||||
| private: | |||||
| //Statistics on output levels | |||||
| vuData vu; | vuData vu; | ||||
| float volume; | |||||
| rtosc::MidiTable midi;//<1024,64> | |||||
| bool frozenState;//read-only parameters for threadsafe actions | |||||
| Allocator *memory; | |||||
| rtosc::ThreadLink *bToU; | |||||
| rtosc::ThreadLink *uToB; | |||||
| bool pendingMemory; | |||||
| const SYNTH_T &synth; | |||||
| private: | |||||
| float sysefxvol[NUM_SYS_EFX][NUM_MIDI_PARTS]; | float sysefxvol[NUM_SYS_EFX][NUM_MIDI_PARTS]; | ||||
| float sysefxsend[NUM_SYS_EFX][NUM_SYS_EFX]; | float sysefxsend[NUM_SYS_EFX][NUM_SYS_EFX]; | ||||
| int keyshift; | int keyshift; | ||||
| @@ -20,16 +20,63 @@ | |||||
| */ | */ | ||||
| #include <math.h> | |||||
| #include <string.h> | |||||
| #include <cmath> | |||||
| #include <cstring> | |||||
| #include <cstdio> | |||||
| #include <rtosc/ports.h> | |||||
| #include <rtosc/port-sugar.h> | |||||
| #include "XMLwrapper.h" | |||||
| #include "Util.h" | |||||
| #include "Microtonal.h" | #include "Microtonal.h" | ||||
| #define MAX_LINE_SIZE 80 | #define MAX_LINE_SIZE 80 | ||||
| #define rObject Microtonal | |||||
| using namespace rtosc; | |||||
| /** | |||||
| * TODO | |||||
| * Consider how much of this should really exist on the rt side of things. | |||||
| * All the rt side needs is a function to map notes at various keyshifts to | |||||
| * frequencies, which does not require this many parameters... | |||||
| * | |||||
| * A good lookup table should be a good finalization of this | |||||
| */ | |||||
| const rtosc::Ports Microtonal::ports = { | |||||
| rToggle(Pinvertupdown, "key mapping inverse"), | |||||
| rParamZyn(Pinvertupdowncenter, "center of the inversion"), | |||||
| rToggle(Penabled, "Enable for microtonal mode"), | |||||
| rParamZyn(PAnote, "The note for 'A'"), | |||||
| rParamF(PAfreq, "Frequency of the 'A' note"), | |||||
| rParamZyn(Pscaleshift, "UNDOCUMENTED"), | |||||
| rParamZyn(Pfirstkey, "First key to retune"), | |||||
| rParamZyn(Plastkey, "Last key to retune"), | |||||
| rParamZyn(Pmiddlenote, "Scale degree 0 note"), | |||||
| //TODO check to see if this should be exposed | |||||
| rParamZyn(Pmapsize, "UNDOCUMENTED"), | |||||
| rToggle(Pmappingenabled, "Mapping Enable"), | |||||
| rParams(Pmapping, "UNDOCUMENTED"), | |||||
| rParamZyn(Pglobalfinedetune, "Fine detune for all notes"), | |||||
| rString(Pname, MICROTONAL_MAX_NAME_LEN, "Microtonal Name"), | |||||
| rString(Pcomment, MICROTONAL_MAX_NAME_LEN, "Microtonal Name"), | |||||
| {"octavesize:", 0, 0, [](const char*, RtData &d) | |||||
| { | |||||
| Microtonal &m = *(Microtonal*)d.obj; | |||||
| d.reply(d.loc, "i", m.getoctavesize()); | |||||
| }}, | |||||
| }; | |||||
| Microtonal::Microtonal() | Microtonal::Microtonal() | ||||
| { | { | ||||
| Pname = new unsigned char[MICROTONAL_MAX_NAME_LEN]; | |||||
| Pcomment = new unsigned char[MICROTONAL_MAX_NAME_LEN]; | |||||
| defaults(); | defaults(); | ||||
| } | } | ||||
| @@ -76,10 +123,7 @@ void Microtonal::defaults() | |||||
| } | } | ||||
| Microtonal::~Microtonal() | Microtonal::~Microtonal() | ||||
| { | |||||
| delete [] Pname; | |||||
| delete [] Pcomment; | |||||
| } | |||||
| {} | |||||
| /* | /* | ||||
| * Get the size of the octave | * Get the size of the octave | ||||
| @@ -106,13 +150,13 @@ float Microtonal::getnotefreq(int note, int keyshift) const | |||||
| note = (int) Pinvertupdowncenter * 2 - note; | note = (int) Pinvertupdowncenter * 2 - note; | ||||
| //compute global fine detune | //compute global fine detune | ||||
| float globalfinedetunerap = powf(2.0f, | |||||
| (Pglobalfinedetune - 64.0f) / 1200.0f); //-64.0f .. 63.0f cents | |||||
| float globalfinedetunerap = | |||||
| powf(2.0f, (Pglobalfinedetune - 64.0f) / 1200.0f); //-64.0f .. 63.0f cents | |||||
| if(Penabled == 0) | |||||
| if(Penabled == 0) //12tET | |||||
| return powf(2.0f, | return powf(2.0f, | ||||
| (note - PAnote | (note - PAnote | ||||
| + keyshift) / 12.0f) * PAfreq * globalfinedetunerap; //12tET | |||||
| + keyshift) / 12.0f) * PAfreq * globalfinedetunerap; | |||||
| int scaleshift = | int scaleshift = | ||||
| ((int)Pscaleshift - 64 + (int) octavesize * 100) % octavesize; | ((int)Pscaleshift - 64 + (int) octavesize * 100) % octavesize; | ||||
| @@ -127,7 +171,7 @@ float Microtonal::getnotefreq(int note, int keyshift) const | |||||
| } | } | ||||
| //if the mapping is enabled | //if the mapping is enabled | ||||
| if(Pmappingenabled != 0) { | |||||
| if(Pmappingenabled) { | |||||
| if((note < Pfirstkey) || (note > Plastkey)) | if((note < Pfirstkey) || (note > Plastkey)) | ||||
| return -1.0f; | return -1.0f; | ||||
| //Compute how many mapped keys are from middle note to reference note | //Compute how many mapped keys are from middle note to reference note | ||||
| @@ -144,11 +188,11 @@ float Microtonal::getnotefreq(int note, int keyshift) const | |||||
| float rap_anote_middlenote = | float rap_anote_middlenote = | ||||
| (deltanote == | (deltanote == | ||||
| 0) ? (1.0f) : (octave[(deltanote - 1) % octavesize].tuning); | 0) ? (1.0f) : (octave[(deltanote - 1) % octavesize].tuning); | ||||
| if(deltanote != 0) | |||||
| if(deltanote) | |||||
| rap_anote_middlenote *= | rap_anote_middlenote *= | ||||
| powf(octave[octavesize - 1].tuning, | powf(octave[octavesize - 1].tuning, | ||||
| (deltanote - 1) / octavesize); | (deltanote - 1) / octavesize); | ||||
| if(minus != 0) | |||||
| if(minus) | |||||
| rap_anote_middlenote = 1.0f / rap_anote_middlenote; | rap_anote_middlenote = 1.0f / rap_anote_middlenote; | ||||
| //Convert from note (midi) to degree (note from the tunning) | //Convert from note (midi) to degree (note from the tunning) | ||||
| @@ -175,7 +219,7 @@ float Microtonal::getnotefreq(int note, int keyshift) const | |||||
| freq *= powf(octave[octavesize - 1].tuning, degoct); | freq *= powf(octave[octavesize - 1].tuning, degoct); | ||||
| freq *= PAfreq / rap_anote_middlenote; | freq *= PAfreq / rap_anote_middlenote; | ||||
| freq *= globalfinedetunerap; | freq *= globalfinedetunerap; | ||||
| if(scaleshift != 0) | |||||
| if(scaleshift) | |||||
| freq /= octave[scaleshift - 1].tuning; | freq /= octave[scaleshift - 1].tuning; | ||||
| return freq * rap_keyshift; | return freq * rap_keyshift; | ||||
| } | } | ||||
| @@ -189,11 +233,10 @@ float Microtonal::getnotefreq(int note, int keyshift) const | |||||
| octave[(ntkey + octavesize - 1) % octavesize].tuning * powf(oct, | octave[(ntkey + octavesize - 1) % octavesize].tuning * powf(oct, | ||||
| ntoct) | ntoct) | ||||
| * PAfreq; | * PAfreq; | ||||
| if(ntkey == 0) | |||||
| if(!ntkey) | |||||
| freq /= oct; | freq /= oct; | ||||
| if(scaleshift != 0) | |||||
| if(scaleshift) | |||||
| freq /= octave[scaleshift - 1].tuning; | freq /= octave[scaleshift - 1].tuning; | ||||
| // fprintf(stderr,"note=%d freq=%.3f cents=%d\n",note,freq,(int)floor(logf(freq/PAfreq)/logf(2.0f)*1200.0f+0.5f)); | |||||
| freq *= globalfinedetunerap; | freq *= globalfinedetunerap; | ||||
| return freq * rap_keyshift; | return freq * rap_keyshift; | ||||
| } | } | ||||
| @@ -460,77 +503,51 @@ int Microtonal::loadkbm(const char *filename) | |||||
| { | { | ||||
| FILE *file = fopen(filename, "r"); | FILE *file = fopen(filename, "r"); | ||||
| int x; | int x; | ||||
| float tmpPAfreq = 440.0f; | |||||
| char tmp[500]; | char tmp[500]; | ||||
| fseek(file, 0, SEEK_SET); | fseek(file, 0, SEEK_SET); | ||||
| //loads the mapsize | //loads the mapsize | ||||
| if(loadline(file, &tmp[0]) != 0) | |||||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0) | |||||
| return 2; | return 2; | ||||
| if(sscanf(&tmp[0], "%d", &x) == 0) | |||||
| return 2; | |||||
| if(x < 1) | |||||
| x = 0; | |||||
| if(x > 127) | |||||
| x = 127; //just in case... | |||||
| Pmapsize = x; | |||||
| Pmapsize = limit(x, 0, 127); | |||||
| //loads first MIDI note to retune | //loads first MIDI note to retune | ||||
| if(loadline(file, &tmp[0]) != 0) | |||||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0) | |||||
| return 2; | return 2; | ||||
| if(sscanf(&tmp[0], "%d", &x) == 0) | |||||
| return 2; | |||||
| if(x < 1) | |||||
| x = 0; | |||||
| if(x > 127) | |||||
| x = 127; //just in case... | |||||
| Pfirstkey = x; | |||||
| Pfirstkey = limit(x, 0, 127); | |||||
| //loads last MIDI note to retune | //loads last MIDI note to retune | ||||
| if(loadline(file, &tmp[0]) != 0) | |||||
| return 2; | |||||
| if(sscanf(&tmp[0], "%d", &x) == 0) | |||||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0) | |||||
| return 2; | return 2; | ||||
| if(x < 1) | |||||
| x = 0; | |||||
| if(x > 127) | |||||
| x = 127; //just in case... | |||||
| Plastkey = x; | |||||
| Plastkey = limit(x, 0, 127); | |||||
| //loads last the middle note where scale fro scale degree=0 | //loads last the middle note where scale fro scale degree=0 | ||||
| if(loadline(file, &tmp[0]) != 0) | |||||
| return 2; | |||||
| if(sscanf(&tmp[0], "%d", &x) == 0) | |||||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0) | |||||
| return 2; | return 2; | ||||
| if(x < 1) | |||||
| x = 0; | |||||
| if(x > 127) | |||||
| x = 127; //just in case... | |||||
| Pmiddlenote = x; | |||||
| Pmiddlenote = limit(x, 0, 127); | |||||
| //loads the reference note | //loads the reference note | ||||
| if(loadline(file, &tmp[0]) != 0) | |||||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0) | |||||
| return 2; | return 2; | ||||
| if(sscanf(&tmp[0], "%d", &x) == 0) | |||||
| return 2; | |||||
| if(x < 1) | |||||
| x = 0; | |||||
| if(x > 127) | |||||
| x = 127; //just in case... | |||||
| PAnote = x; | |||||
| PAnote = limit(x,0,127); | |||||
| //loads the reference freq. | //loads the reference freq. | ||||
| if(loadline(file, &tmp[0]) != 0) | |||||
| return 2; | |||||
| float tmpPAfreq = 440.0f; | |||||
| if(sscanf(&tmp[0], "%f", &tmpPAfreq) == 0) | |||||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%f", &tmpPAfreq) == 0) | |||||
| return 2; | return 2; | ||||
| PAfreq = tmpPAfreq; | PAfreq = tmpPAfreq; | ||||
| //the scale degree(which is the octave) is not loaded, it is obtained by the tunnings with getoctavesize() method | |||||
| //the scale degree(which is the octave) is not loaded, | |||||
| //it is obtained by the tunnings with getoctavesize() method | |||||
| if(loadline(file, &tmp[0]) != 0) | if(loadline(file, &tmp[0]) != 0) | ||||
| return 2; | return 2; | ||||
| //load the mappings | //load the mappings | ||||
| if(Pmapsize != 0) { | if(Pmapsize != 0) { | ||||
| for(int nline = 0; nline < Pmapsize; ++nline) { | for(int nline = 0; nline < Pmapsize; ++nline) { | ||||
| if(loadline(file, &tmp[0]) != 0) | |||||
| if(loadline(file, tmp) != 0) | |||||
| return 2; | return 2; | ||||
| if(sscanf(&tmp[0], "%d", &x) == 0) | |||||
| if(sscanf(tmp, "%d", &x) == 0) | |||||
| x = -1; | x = -1; | ||||
| Pmapping[nline] = x; | Pmapping[nline] = x; | ||||
| } | } | ||||
| @@ -23,13 +23,12 @@ | |||||
| #ifndef MICROTONAL_H | #ifndef MICROTONAL_H | ||||
| #define MICROTONAL_H | #define MICROTONAL_H | ||||
| #include <cstdio> | |||||
| #include "../globals.h" | #include "../globals.h" | ||||
| #include "XMLwrapper.h" | |||||
| #define MAX_OCTAVE_SIZE 128 | #define MAX_OCTAVE_SIZE 128 | ||||
| #define MICROTONAL_MAX_NAME_LEN 120 | #define MICROTONAL_MAX_NAME_LEN 120 | ||||
| #include <stdio.h> | |||||
| class XMLwrapper; | |||||
| /**Tuning settings and microtonal capabilities*/ | /**Tuning settings and microtonal capabilities*/ | ||||
| @@ -102,9 +101,9 @@ class Microtonal | |||||
| void texttomapping(const char *text); | void texttomapping(const char *text); | ||||
| /**Name of Microtonal tuning*/ | /**Name of Microtonal tuning*/ | ||||
| unsigned char *Pname; | |||||
| char Pname[MICROTONAL_MAX_NAME_LEN]; | |||||
| /**Comment about the tuning*/ | /**Comment about the tuning*/ | ||||
| unsigned char *Pcomment; | |||||
| char Pcomment[MICROTONAL_MAX_NAME_LEN]; | |||||
| void add2XML(XMLwrapper *xml) const; | void add2XML(XMLwrapper *xml) const; | ||||
| void getfromXML(XMLwrapper *xml); | void getfromXML(XMLwrapper *xml); | ||||
| @@ -115,9 +114,12 @@ class Microtonal | |||||
| bool operator==(const Microtonal µ) const; | bool operator==(const Microtonal µ) const; | ||||
| bool operator!=(const Microtonal µ) const; | bool operator!=(const Microtonal µ) const; | ||||
| static const rtosc::Ports ports; | |||||
| private: | private: | ||||
| int linetotunings(unsigned int nline, const char *line); | int linetotunings(unsigned int nline, const char *line); | ||||
| int loadline(FILE *file, char *line); //loads a line from the text file, while ignoring the lines beggining with "!" | |||||
| //loads a line from the text file, while ignoring the lines beggining with "!" | |||||
| int loadline(FILE *file, char *line); | |||||
| //Grab a 0..127 integer from the provided descriptor | |||||
| unsigned char octavesize; | unsigned char octavesize; | ||||
| struct { | struct { | ||||
| unsigned char type; //1 for cents or 2 for division | unsigned char type; //1 for cents or 2 for division | ||||
| @@ -0,0 +1,40 @@ | |||||
| #pragma once | |||||
| #include <functional> | |||||
| #include <cstdarg> | |||||
| #include <string> | |||||
| struct SYNTH_T; | |||||
| //Link between realtime and non-realtime layers | |||||
| class MiddleWare | |||||
| { | |||||
| public: | |||||
| MiddleWare(SYNTH_T synth, int prefered_port = -1); | |||||
| ~MiddleWare(void); | |||||
| //returns internal master pointer | |||||
| class Master *spawnMaster(void); | |||||
| //return UI interface | |||||
| class Fl_Osc_Interface *spawnUiApi(void); | |||||
| //Set callback to push UI events to | |||||
| void setUiCallback(void(*cb)(void*,const char *),void *ui); | |||||
| //Set callback to run while busy | |||||
| void setIdleCallback(void(*cb)(void)); | |||||
| //Handle events | |||||
| void tick(void); | |||||
| //Do A Readonly Operation (For Parameter Copy) | |||||
| void doReadOnlyOp(std::function<void()>); | |||||
| //Handle a rtosc Message uToB | |||||
| void transmitMsg(const char *); | |||||
| //Handle a rtosc Message uToB | |||||
| void transmitMsg(const char *, const char *args, ...); | |||||
| //Handle a rtosc Message uToB | |||||
| void transmitMsg(const char *, const char *args, va_list va); | |||||
| //Indicate that a program will be loaded on a known part | |||||
| void pendingSetProgram(int part, int program); | |||||
| //Get/Set the active bToU url | |||||
| std::string activeUrl(void); | |||||
| void activeUrl(std::string u); | |||||
| //View Synthesis Parameters | |||||
| const SYNTH_T &getSynth(void) const; | |||||
| private: | |||||
| class MiddleWareImpl *impl; | |||||
| }; | |||||
| @@ -27,18 +27,8 @@ | |||||
| #include "../globals.h" | #include "../globals.h" | ||||
| #include "../Params/Controller.h" | #include "../Params/Controller.h" | ||||
| #include "../Misc/Microtonal.h" | |||||
| #include <pthread.h> | |||||
| #include <list> // For the monomemnotes list. | |||||
| class EffectMgr; | |||||
| class ADnoteParameters; | |||||
| class SUBnoteParameters; | |||||
| class PADnoteParameters; | |||||
| class SynthNote; | |||||
| class XMLWrapper; | |||||
| class FFTwrapper; | |||||
| #include <functional> | |||||
| /** Part implementation*/ | /** Part implementation*/ | ||||
| class Part | class Part | ||||
| @@ -46,29 +36,29 @@ class Part | |||||
| public: | public: | ||||
| /**Constructor | /**Constructor | ||||
| * @param microtonal_ Pointer to the microtonal object | * @param microtonal_ Pointer to the microtonal object | ||||
| * @param fft_ Pointer to the FFTwrapper | |||||
| * @param mutex_ Pointer to the master pthread_mutex_t*/ | |||||
| Part(Microtonal *microtonal_, FFTwrapper *fft_, pthread_mutex_t *mutex_); | |||||
| * @param fft_ Pointer to the FFTwrapper*/ | |||||
| Part(Allocator &alloc, const SYNTH_T &synth, Microtonal *microtonal_, FFTwrapper *fft_); | |||||
| /**Destructor*/ | /**Destructor*/ | ||||
| ~Part(); | ~Part(); | ||||
| // Copy misc parameters not stored in .xiz format | |||||
| void cloneTraits(Part &part) const REALTIME; | |||||
| // Midi commands implemented | // Midi commands implemented | ||||
| void NoteOn(unsigned char note, | void NoteOn(unsigned char note, | ||||
| unsigned char velocity, | unsigned char velocity, | ||||
| int masterkeyshift); | |||||
| void NoteOff(unsigned char note); | |||||
| int masterkeyshift) REALTIME; | |||||
| void NoteOff(unsigned char note) REALTIME; | |||||
| void PolyphonicAftertouch(unsigned char note, | void PolyphonicAftertouch(unsigned char note, | ||||
| unsigned char velocity, | unsigned char velocity, | ||||
| int masterkeyshift); | |||||
| void AllNotesOff(); //panic | |||||
| void SetController(unsigned int type, int par); | |||||
| void RelaseSustainedKeys(); //this is called when the sustain pedal is relased | |||||
| void RelaseAllKeys(); //this is called on AllNotesOff controller | |||||
| int masterkeyshift) REALTIME; | |||||
| void AllNotesOff() REALTIME; //panic | |||||
| void SetController(unsigned int type, int par) REALTIME; | |||||
| void ReleaseSustainedKeys() REALTIME; //this is called when the sustain pedal is released | |||||
| void ReleaseAllKeys() REALTIME; //this is called on AllNotesOff controller | |||||
| /* The synthesizer part output */ | /* The synthesizer part output */ | ||||
| void ComputePartSmps(); //Part output | |||||
| //instrumentonly: 0 - save all, 1 - save only instrumnet, 2 - save only instrument without the name(used in bank) | |||||
| void ComputePartSmps() REALTIME; //Part output | |||||
| //saves the instrument settings to a XML file | //saves the instrument settings to a XML file | ||||
| @@ -82,7 +72,11 @@ class Part | |||||
| void defaults(); | void defaults(); | ||||
| void defaultsinstrument(); | void defaultsinstrument(); | ||||
| void applyparameters(bool lockmutex = true); | |||||
| void applyparameters(void) NONREALTIME; | |||||
| void applyparameters(std::function<bool()> do_abort) NONREALTIME; | |||||
| void initialize_rt(void) REALTIME; | |||||
| void kill_rt(void) REALTIME; | |||||
| void getfromXML(XMLwrapper *xml); | void getfromXML(XMLwrapper *xml); | ||||
| void getfromXMLinstrument(XMLwrapper *xml); | void getfromXMLinstrument(XMLwrapper *xml); | ||||
| @@ -90,22 +84,25 @@ class Part | |||||
| void cleanup(bool final = false); | void cleanup(bool final = false); | ||||
| //the part's kit | //the part's kit | ||||
| struct { | |||||
| unsigned char Penabled, Pmuted, Pminkey, Pmaxkey; | |||||
| unsigned char *Pname; | |||||
| unsigned char Padenabled, Psubenabled, Ppadenabled; | |||||
| struct Kit { | |||||
| bool Penabled, Pmuted; | |||||
| unsigned char Pminkey, Pmaxkey; | |||||
| char *Pname; | |||||
| bool Padenabled, Psubenabled, Ppadenabled; | |||||
| unsigned char Psendtoparteffect; | unsigned char Psendtoparteffect; | ||||
| ADnoteParameters *adpars; | ADnoteParameters *adpars; | ||||
| SUBnoteParameters *subpars; | SUBnoteParameters *subpars; | ||||
| PADnoteParameters *padpars; | PADnoteParameters *padpars; | ||||
| const static rtosc::Ports &ports; | |||||
| } kit[NUM_KIT_ITEMS]; | } kit[NUM_KIT_ITEMS]; | ||||
| //Part parameters | //Part parameters | ||||
| void setkeylimit(unsigned char Pkeylimit); | void setkeylimit(unsigned char Pkeylimit); | ||||
| void setkititemstatus(int kititem, int Penabled_); | |||||
| void setkititemstatus(unsigned kititem, bool Penabled_); | |||||
| unsigned char Penabled; /**<if the part is enabled*/ | |||||
| bool Penabled; /**<if the part is enabled*/ | |||||
| unsigned char Pvolume; /**<part volume*/ | unsigned char Pvolume; /**<part volume*/ | ||||
| unsigned char Pminkey; /**<the minimum key that the part receives noteon messages*/ | unsigned char Pminkey; /**<the minimum key that the part receives noteon messages*/ | ||||
| unsigned char Pmaxkey; //the maximum key that the part receives noteon messages | unsigned char Pmaxkey; //the maximum key that the part receives noteon messages | ||||
| @@ -116,19 +113,19 @@ class Part | |||||
| void setPpanning(char Ppanning); | void setPpanning(char Ppanning); | ||||
| unsigned char Pvelsns; //velocity sensing (amplitude velocity scale) | unsigned char Pvelsns; //velocity sensing (amplitude velocity scale) | ||||
| unsigned char Pveloffs; //velocity offset | unsigned char Pveloffs; //velocity offset | ||||
| unsigned char Pnoteon; //if the part receives NoteOn messages | |||||
| unsigned char Pkitmode; //if the kitmode is enabled | |||||
| unsigned char Pdrummode; //if all keys are mapped and the system is 12tET (used for drums) | |||||
| bool Pnoteon; //if the part receives NoteOn messages | |||||
| int Pkitmode; //if the kitmode is enabled | |||||
| bool Pdrummode; //if all keys are mapped and the system is 12tET (used for drums) | |||||
| unsigned char Ppolymode; //Part mode - 0=monophonic , 1=polyphonic | |||||
| unsigned char Plegatomode; // 0=normal, 1=legato | |||||
| unsigned char Pkeylimit; //how many keys are alowed to be played same time (0=off), the older will be relased | |||||
| bool Ppolymode; //Part mode - 0=monophonic , 1=polyphonic | |||||
| bool Plegatomode; // 0=normal, 1=legato | |||||
| unsigned char Pkeylimit; //how many keys are alowed to be played same time (0=off), the older will be released | |||||
| unsigned char *Pname; //name of the instrument | |||||
| char *Pname; //name of the instrument | |||||
| struct { //instrument additional information | struct { //instrument additional information | ||||
| unsigned char Ptype; | unsigned char Ptype; | ||||
| unsigned char Pauthor[MAX_INFO_TEXT_SIZE + 1]; | |||||
| unsigned char Pcomments[MAX_INFO_TEXT_SIZE + 1]; | |||||
| char Pauthor[MAX_INFO_TEXT_SIZE + 1]; | |||||
| char Pcomments[MAX_INFO_TEXT_SIZE + 1]; | |||||
| } info; | } info; | ||||
| @@ -139,7 +136,7 @@ class Part | |||||
| *partfxinputr[NUM_PART_EFX + 1]; //partfxinput l/r [NUM_PART_EFX] is for "no effect" buffer | *partfxinputr[NUM_PART_EFX + 1]; //partfxinput l/r [NUM_PART_EFX] is for "no effect" buffer | ||||
| enum NoteStatus { | enum NoteStatus { | ||||
| KEY_OFF, KEY_PLAYING, KEY_RELASED_AND_SUSTAINED, KEY_RELASED | |||||
| KEY_OFF, KEY_PLAYING, KEY_RELEASED_AND_SUSTAINED, KEY_RELEASED | |||||
| }; | }; | ||||
| float volume, oldvolumel, oldvolumer; //this is applied by Master | float volume, oldvolumel, oldvolumer; //this is applied by Master | ||||
| @@ -151,16 +148,14 @@ class Part | |||||
| unsigned char Pefxroute[NUM_PART_EFX]; //how the effect's output is routed(to next effect/to out) | unsigned char Pefxroute[NUM_PART_EFX]; //how the effect's output is routed(to next effect/to out) | ||||
| bool Pefxbypass[NUM_PART_EFX]; //if the effects are bypassed | bool Pefxbypass[NUM_PART_EFX]; //if the effects are bypassed | ||||
| pthread_mutex_t *mutex; | |||||
| pthread_mutex_t load_mutex; | |||||
| int lastnote; | int lastnote; | ||||
| const static rtosc::Ports &ports; | |||||
| private: | private: | ||||
| void RunNote(unsigned k); | void RunNote(unsigned k); | ||||
| void KillNotePos(int pos); | void KillNotePos(int pos); | ||||
| void RelaseNotePos(int pos); | |||||
| void ReleaseNotePos(int pos); | |||||
| void MonoMemRenote(); // MonoMem stuff. | void MonoMemRenote(); // MonoMem stuff. | ||||
| int killallnotes; //is set to 1 if I want to kill all notes | int killallnotes; //is set to 1 if I want to kill all notes | ||||
| @@ -170,9 +165,7 @@ class Part | |||||
| int note; //if there is no note playing, the "note"=-1 | int note; //if there is no note playing, the "note"=-1 | ||||
| int itemsplaying; | int itemsplaying; | ||||
| struct { | struct { | ||||
| SynthNote *adnote, | |||||
| *subnote, | |||||
| *padnote; | |||||
| SynthNote *adnote, *subnote, *padnote; | |||||
| int sendtoparteffect; | int sendtoparteffect; | ||||
| } kititem[NUM_KIT_ITEMS]; | } kititem[NUM_KIT_ITEMS]; | ||||
| int time; | int time; | ||||
| @@ -182,7 +175,13 @@ class Part | |||||
| bool lastlegatomodevalid; // To keep track of previous legatomodevalid. | bool lastlegatomodevalid; // To keep track of previous legatomodevalid. | ||||
| // MonoMem stuff | // MonoMem stuff | ||||
| std::list<unsigned char> monomemnotes; // A list to remember held notes. | |||||
| void monomemPush(char note); | |||||
| void monomemPop(char note); | |||||
| char monomemBack(void) const; | |||||
| bool monomemEmpty(void) const; | |||||
| void monomemClear(void); | |||||
| short monomemnotes[256]; // A list to remember held notes. | |||||
| struct { | struct { | ||||
| unsigned char velocity; | unsigned char velocity; | ||||
| int mkeyshift; // I'm not sure masterkeyshift should be remembered. | int mkeyshift; // I'm not sure masterkeyshift should be remembered. | ||||
| @@ -192,11 +191,13 @@ class Part | |||||
| store the velocity and masterkeyshift values of a given note (the list only store note values). | store the velocity and masterkeyshift values of a given note (the list only store note values). | ||||
| For example 'monomem[note].velocity' would be the velocity value of the note 'note'.*/ | For example 'monomem[note].velocity' would be the velocity value of the note 'note'.*/ | ||||
| PartNotes partnote[POLIPHONY]; | |||||
| PartNotes partnote[POLYPHONY]; | |||||
| float oldfreq; //this is used for portamento | float oldfreq; //this is used for portamento | ||||
| Microtonal *microtonal; | Microtonal *microtonal; | ||||
| FFTwrapper *fft; | FFTwrapper *fft; | ||||
| Allocator &memory; | |||||
| const SYNTH_T &synth; | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -0,0 +1,479 @@ | |||||
| /** | |||||
| * Extract Presets from realtime data | |||||
| */ | |||||
| #include "../Params/PresetsStore.h" | |||||
| #include "../Misc/Master.h" | |||||
| #include "../Misc/Util.h" | |||||
| #include "../Misc/Allocator.h" | |||||
| #include "../Effects/EffectMgr.h" | |||||
| #include "../Synth/OscilGen.h" | |||||
| #include "../Synth/Resonance.h" | |||||
| #include "../Params/ADnoteParameters.h" | |||||
| #include "../Params/EnvelopeParams.h" | |||||
| #include "../Params/FilterParams.h" | |||||
| #include "../Params/LFOParams.h" | |||||
| #include "../Params/PADnoteParameters.h" | |||||
| #include "../Params/Presets.h" | |||||
| #include "../Params/PresetsArray.h" | |||||
| #include "../Params/PresetsStore.h" | |||||
| #include "../Params/SUBnoteParameters.h" | |||||
| #include "../Misc/MiddleWare.h" | |||||
| #include "PresetExtractor.h" | |||||
| #include <rtosc/ports.h> | |||||
| #include <rtosc/port-sugar.h> | |||||
| #include <string> | |||||
| using std::string; | |||||
| static void dummy(const char *, rtosc::RtData&) {} | |||||
| const rtosc::Ports real_preset_ports = | |||||
| { | |||||
| {"scan-for-presets:", 0, 0, | |||||
| [](const char *msg, rtosc::RtData &d) { | |||||
| presetsstore.scanforpresets(); | |||||
| auto &pre = presetsstore.presets; | |||||
| d.reply(d.loc, "i", pre.size()); | |||||
| for(unsigned i=0; i<pre.size();++i) | |||||
| d.reply(d.loc, "isss", i, | |||||
| pre[i].file.c_str(), | |||||
| pre[i].name.c_str(), | |||||
| pre[i].type.c_str()); | |||||
| }}, | |||||
| {"copy:s:ss:si:ssi", 0, 0, | |||||
| [](const char *msg, rtosc::RtData &d) { | |||||
| MiddleWare &mw = *(MiddleWare*)d.obj; | |||||
| std::string args = rtosc_argument_string(msg); | |||||
| d.reply(d.loc, "s", "clipboard copy..."); | |||||
| printf("\nClipboard Copy...\n"); | |||||
| if(args == "s") | |||||
| presetCopy(mw, rtosc_argument(msg, 0).s, ""); | |||||
| else if(args == "ss") | |||||
| presetCopy(mw, rtosc_argument(msg, 0).s, | |||||
| rtosc_argument(msg, 1).s); | |||||
| else if(args == "si") | |||||
| presetCopyArray(mw, rtosc_argument(msg, 0).s, | |||||
| rtosc_argument(msg, 1).i, ""); | |||||
| else if(args == "ssi") | |||||
| presetCopyArray(mw, rtosc_argument(msg, 0).s, | |||||
| rtosc_argument(msg, 2).i, rtosc_argument(msg, 1).s); | |||||
| else | |||||
| assert(false && "bad arguments"); | |||||
| }}, | |||||
| {"paste:s:ss:si:ssi", 0, 0, | |||||
| [](const char *msg, rtosc::RtData &d) { | |||||
| MiddleWare &mw = *(MiddleWare*)d.obj; | |||||
| std::string args = rtosc_argument_string(msg); | |||||
| d.reply(d.loc, "s", "clipboard paste..."); | |||||
| printf("\nClipboard Paste...\n"); | |||||
| if(args == "s") | |||||
| presetPaste(mw, rtosc_argument(msg, 0).s, ""); | |||||
| else if(args == "ss") | |||||
| presetPaste(mw, rtosc_argument(msg, 0).s, | |||||
| rtosc_argument(msg, 1).s); | |||||
| else if(args == "si") | |||||
| presetPasteArray(mw, rtosc_argument(msg, 0).s, | |||||
| rtosc_argument(msg, 1).i, ""); | |||||
| else if(args == "ssi") | |||||
| presetPasteArray(mw, rtosc_argument(msg, 0).s, | |||||
| rtosc_argument(msg, 2).i, rtosc_argument(msg, 1).s); | |||||
| else | |||||
| assert(false && "bad arguments"); | |||||
| }}, | |||||
| {"clipboard-type:", 0, 0, | |||||
| [](const char *msg, rtosc::RtData &d) { | |||||
| d.reply(d.loc, "s", presetsstore.clipboard.type.c_str()); | |||||
| }}, | |||||
| {"delete:s", 0, 0, | |||||
| [](const char *msg, rtosc::RtData &d) { | |||||
| presetsstore.deletepreset(rtosc_argument(msg,0).s); | |||||
| }}, | |||||
| }; | |||||
| const rtosc::Ports preset_ports | |||||
| { | |||||
| {"scan-for-presets:", rDoc("Scan For Presets"), 0, dummy}, | |||||
| {"copy:s:ss:si:ssi", rDoc("Copy <s> URL to <s> Name/Clipboard from subfield <i>"), 0, dummy}, | |||||
| {"paste:s:ss:si:ssi", rDoc("Paste <s> URL to <s> File-Name/Clipboard from subfield <i>"), 0, dummy}, | |||||
| {"clipboard-type:", rDoc("Type Stored In Clipboard"), 0, dummy}, | |||||
| {"delete:s", rDoc("Delete the given preset file"), 0, dummy}, | |||||
| }; | |||||
| //Relevant types to keep in mind | |||||
| //Effects/EffectMgr.cpp: setpresettype("Peffect"); | |||||
| //Params/ADnoteParameters.cpp: setpresettype("Padsynth"); | |||||
| //Params/EnvelopeParams.cpp: //setpresettype("Penvamplitude"); | |||||
| //Params/EnvelopeParams.cpp: //setpresettype("Penvamplitude"); | |||||
| //Params/EnvelopeParams.cpp: //setpresettype("Penvfrequency"); | |||||
| //Params/EnvelopeParams.cpp: //setpresettype("Penvfilter"); | |||||
| //Params/EnvelopeParams.cpp: //setpresettype("Penvbandwidth"); | |||||
| //Params/FilterParams.cpp: //setpresettype("Pfilter"); | |||||
| //Params/LFOParams.cpp: // setpresettype("Plfofrequency"); | |||||
| //Params/LFOParams.cpp: // setpresettype("Plfoamplitude"); | |||||
| //Params/LFOParams.cpp: // setpresettype("Plfofilter"); | |||||
| //Params/PADnoteParameters.cpp: setpresettype("Ppadsynth"); | |||||
| //Params/SUBnoteParameters.cpp: setpresettype("Psubsynth"); | |||||
| //Synth/OscilGen.cpp: setpresettype("Poscilgen"); | |||||
| //Synth/Resonance.cpp: setpresettype("Presonance"); | |||||
| //Translate newer symbols to old preset types | |||||
| std::vector<string> translate_preset_types(std::string metatype) | |||||
| { | |||||
| std::vector<string> results; | |||||
| return results; | |||||
| } | |||||
| /***************************************************************************** | |||||
| * Implementation Methods * | |||||
| *****************************************************************************/ | |||||
| class Capture:public rtosc::RtData | |||||
| { | |||||
| public: | |||||
| Capture(void *obj_) | |||||
| { | |||||
| matches = 0; | |||||
| memset(locbuf, 0, sizeof(locbuf)); | |||||
| loc = locbuf; | |||||
| loc_size = sizeof(locbuf); | |||||
| obj = obj_; | |||||
| } | |||||
| virtual void reply(const char *path, const char *args, ...) | |||||
| { | |||||
| printf("reply(%p)(%s)(%s)...\n", msgbuf, path, args); | |||||
| //printf("size is %d\n", sizeof(msgbuf)); | |||||
| va_list va; | |||||
| va_start(va,args); | |||||
| char *buffer = msgbuf; | |||||
| rtosc_vmessage(buffer,sizeof(msgbuf),path,args,va); | |||||
| va_end(va); | |||||
| } | |||||
| char msgbuf[1024]; | |||||
| char locbuf[1024]; | |||||
| }; | |||||
| template <class T> | |||||
| T capture(Master *m, std::string url); | |||||
| template <> | |||||
| std::string capture(Master *m, std::string url) | |||||
| { | |||||
| Capture c(m); | |||||
| char query[1024]; | |||||
| rtosc_message(query, 1024, url.c_str(), ""); | |||||
| Master::ports.dispatch(query+1,c); | |||||
| if(rtosc_message_length(c.msgbuf, sizeof(c.msgbuf))) { | |||||
| if(rtosc_type(c.msgbuf, 0) == 's') | |||||
| return rtosc_argument(c.msgbuf,0).s; | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| template <> | |||||
| void *capture(Master *m, std::string url) | |||||
| { | |||||
| Capture c(m); | |||||
| char query[1024]; | |||||
| rtosc_message(query, 1024, url.c_str(), ""); | |||||
| Master::ports.dispatch(query+1,c); | |||||
| if(rtosc_message_length(c.msgbuf, sizeof(c.msgbuf))) { | |||||
| if(rtosc_type(c.msgbuf, 0) == 'b' && | |||||
| rtosc_argument(c.msgbuf, 0).b.len == sizeof(void*)) | |||||
| return *(void**)rtosc_argument(c.msgbuf,0).b.data; | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| template<class T> | |||||
| std::string doCopy(MiddleWare &mw, string url, string name) | |||||
| { | |||||
| XMLwrapper xml; | |||||
| mw.doReadOnlyOp([&xml, url, name, &mw](){ | |||||
| Master *m = mw.spawnMaster(); | |||||
| //Get the pointer | |||||
| T *t = (T*)capture<void*>(m, url+"self"); | |||||
| //Extract Via mxml | |||||
| //t->add2XML(&xml); | |||||
| t->copy(presetsstore, name.empty()? NULL:name.c_str()); | |||||
| }); | |||||
| return "";//xml.getXMLdata(); | |||||
| } | |||||
| template<class T, typename... Ts> | |||||
| void doPaste(MiddleWare &mw, string url, string type, XMLwrapper &xml, Ts&&... args) | |||||
| { | |||||
| //Generate a new object | |||||
| T *t = new T(std::forward<Ts>(args)...); | |||||
| if(xml.enterbranch(type) == 0) | |||||
| return; | |||||
| t->getfromXML(&xml); | |||||
| //Send the pointer | |||||
| string path = url+"paste"; | |||||
| char buffer[1024]; | |||||
| rtosc_message(buffer, 1024, path.c_str(), "b", sizeof(void*), &t); | |||||
| if(!Master::ports.apropos(path.c_str())) | |||||
| fprintf(stderr, "Warning: Missing Paste URL: '%s'\n", path.c_str()); | |||||
| printf("Sending info to '%s'\n", buffer); | |||||
| mw.transmitMsg(buffer); | |||||
| //Let the pointer be reclaimed later | |||||
| } | |||||
| template<class T> | |||||
| std::string doArrayCopy(MiddleWare &mw, int field, string url, string name) | |||||
| { | |||||
| XMLwrapper xml; | |||||
| printf("Getting info from '%s'<%d>\n", url.c_str(), field); | |||||
| mw.doReadOnlyOp([&xml, url, field, name, &mw](){ | |||||
| Master *m = mw.spawnMaster(); | |||||
| //Get the pointer | |||||
| T *t = (T*)capture<void*>(m, url+"self"); | |||||
| //Extract Via mxml | |||||
| t->copy(presetsstore, field, name.empty()?NULL:name.c_str()); | |||||
| }); | |||||
| return "";//xml.getXMLdata(); | |||||
| } | |||||
| template<class T, typename... Ts> | |||||
| void doArrayPaste(MiddleWare &mw, int field, string url, string type, | |||||
| XMLwrapper &xml, Ts&&... args) | |||||
| { | |||||
| //Generate a new object | |||||
| T *t = new T(std::forward<Ts>(args)...); | |||||
| if(xml.enterbranch(type+"n") == 0) { | |||||
| delete t; | |||||
| return; | |||||
| } | |||||
| t->defaults(field); | |||||
| t->getfromXMLsection(&xml, field); | |||||
| xml.exitbranch(); | |||||
| //Send the pointer | |||||
| string path = url+"paste-array"; | |||||
| char buffer[1024]; | |||||
| rtosc_message(buffer, 1024, path.c_str(), "bi", sizeof(void*), &t, field); | |||||
| if(!Master::ports.apropos(path.c_str())) | |||||
| fprintf(stderr, "Warning: Missing Paste URL: '%s'\n", path.c_str()); | |||||
| printf("Sending info to '%s'<%d>\n", buffer, field); | |||||
| mw.transmitMsg(buffer); | |||||
| //Let the pointer be reclaimed later | |||||
| } | |||||
| /* | |||||
| * Dispatch to class specific operators | |||||
| * | |||||
| * Oscilgen and PADnoteParameters have mixed RT/non-RT parameters and require | |||||
| * extra handling. | |||||
| * See MiddleWare.cpp for these specifics | |||||
| */ | |||||
| void doClassPaste(std::string type, std::string type_, MiddleWare &mw, string url, XMLwrapper &data) | |||||
| { | |||||
| printf("Class Paste\n"); | |||||
| if(type == "EnvelopeParams") | |||||
| doPaste<EnvelopeParams>(mw, url, type_, data); | |||||
| else if(type == "LFOParams") | |||||
| doPaste<LFOParams>(mw, url, type_, data); | |||||
| else if(type == "FilterParams") | |||||
| doPaste<FilterParams>(mw, url, type_, data); | |||||
| else if(type == "ADnoteParameters") | |||||
| doPaste<ADnoteParameters>(mw, url, type_, data, mw.getSynth(), (FFTwrapper*)NULL); | |||||
| else if(type == "PADnoteParameters") | |||||
| doPaste<PADnoteParameters>(mw, url, type_, data, mw.getSynth(), (FFTwrapper*)NULL); | |||||
| else if(type == "SUBnoteParameters") | |||||
| doPaste<SUBnoteParameters>(mw, url, type_, data); | |||||
| else if(type == "OscilGen") | |||||
| doPaste<OscilGen>(mw, url, type_, data, mw.getSynth(), (FFTwrapper*)NULL, (Resonance*)NULL); | |||||
| else if(type == "Resonance") | |||||
| doPaste<Resonance>(mw, url, type_, data); | |||||
| else if(type == "EffectMgr") | |||||
| doPaste<EffectMgr>(mw, url, type_, data, DummyAlloc, mw.getSynth(), false); | |||||
| else { | |||||
| fprintf(stderr, "Warning: Unknown type<%s> from url<%s>\n", type.c_str(), url.c_str()); | |||||
| } | |||||
| } | |||||
| std::string doClassCopy(std::string type, MiddleWare &mw, string url, string name) | |||||
| { | |||||
| if(type == "EnvelopeParams") | |||||
| return doCopy<EnvelopeParams>(mw, url, name); | |||||
| else if(type == "LFOParams") | |||||
| return doCopy<LFOParams>(mw, url, name); | |||||
| else if(type == "FilterParams") | |||||
| return doCopy<FilterParams>(mw, url, name); | |||||
| else if(type == "ADnoteParameters") | |||||
| return doCopy<ADnoteParameters>(mw, url, name); | |||||
| else if(type == "PADnoteParameters") | |||||
| return doCopy<PADnoteParameters>(mw, url, name); | |||||
| else if(type == "SUBnoteParameters") | |||||
| return doCopy<SUBnoteParameters>(mw, url, name); | |||||
| else if(type == "OscilGen") | |||||
| return doCopy<OscilGen>(mw, url, name); | |||||
| else if(type == "Resonance") | |||||
| return doCopy<Resonance>(mw, url, name); | |||||
| else if(type == "EffectMgr") | |||||
| doCopy<EffectMgr>(mw, url, name); | |||||
| return "UNDEF"; | |||||
| } | |||||
| void doClassArrayPaste(std::string type, std::string type_, int field, MiddleWare &mw, string url, | |||||
| XMLwrapper &data) | |||||
| { | |||||
| if(type == "FilterParams") | |||||
| doArrayPaste<FilterParams>(mw, field, url, type_, data); | |||||
| else if(type == "ADnoteParameters") | |||||
| doArrayPaste<ADnoteParameters>(mw, field, url, type_, data, mw.getSynth(), (FFTwrapper*)NULL); | |||||
| } | |||||
| std::string doClassArrayCopy(std::string type, int field, MiddleWare &mw, string url, string name) | |||||
| { | |||||
| if(type == "FilterParams") | |||||
| return doArrayCopy<FilterParams>(mw, field, url, name); | |||||
| else if(type == "ADnoteParameters") | |||||
| return doArrayCopy<ADnoteParameters>(mw, field, url, name); | |||||
| return "UNDEF"; | |||||
| } | |||||
| //This is an abuse of the readonly op, but one that might look reasonable from a | |||||
| //user perspective... | |||||
| std::string getUrlPresetType(std::string url, MiddleWare &mw) | |||||
| { | |||||
| std::string result; | |||||
| mw.doReadOnlyOp([url, &result, &mw](){ | |||||
| Master *m = mw.spawnMaster(); | |||||
| //Get the pointer | |||||
| result = capture<std::string>(m, url+"preset-type"); | |||||
| }); | |||||
| printf("preset type = %s\n", result.c_str()); | |||||
| return result; | |||||
| } | |||||
| std::string getUrlType(std::string url) | |||||
| { | |||||
| assert(!url.empty()); | |||||
| printf("Searching for '%s'\n", (url+"self").c_str()); | |||||
| auto self = Master::ports.apropos((url+"self").c_str()); | |||||
| if(!self) | |||||
| fprintf(stderr, "Warning: URL Metadata Not Found For '%s'\n", url.c_str()); | |||||
| if(self) | |||||
| return self->meta()["class"]; | |||||
| else | |||||
| return ""; | |||||
| } | |||||
| /***************************************************************************** | |||||
| * API Stubs * | |||||
| *****************************************************************************/ | |||||
| #if 0 | |||||
| Clipboard clipboardCopy(MiddleWare &mw, string url) | |||||
| { | |||||
| //Identify The Self Type of the Object | |||||
| string type = getUrlType(url); | |||||
| printf("Copying a '%s' object", type.c_str()); | |||||
| //Copy The Object | |||||
| string data = doClassCopy(type, mw, url); | |||||
| printf("Object Information '%s'\n", data.c_str()); | |||||
| return {type, data}; | |||||
| } | |||||
| void clipBoardPaste(const char *url, Clipboard clip) | |||||
| { | |||||
| (void) url; | |||||
| (void) clip; | |||||
| } | |||||
| #endif | |||||
| void presetCopy(MiddleWare &mw, std::string url, std::string name) | |||||
| { | |||||
| (void) name; | |||||
| doClassCopy(getUrlType(url), mw, url, name); | |||||
| printf("PresetCopy()\n"); | |||||
| } | |||||
| void presetPaste(MiddleWare &mw, std::string url, std::string name) | |||||
| { | |||||
| (void) name; | |||||
| printf("PresetPaste()\n"); | |||||
| string data = ""; | |||||
| XMLwrapper xml; | |||||
| if(name.empty()) { | |||||
| data = presetsstore.clipboard.data; | |||||
| if(data.length() < 20) | |||||
| return; | |||||
| if(!xml.putXMLdata(data.c_str())) | |||||
| return; | |||||
| } else { | |||||
| if(xml.loadXMLfile(name)) | |||||
| return; | |||||
| } | |||||
| doClassPaste(getUrlType(url), getUrlPresetType(url, mw), mw, url, xml); | |||||
| } | |||||
| void presetCopyArray(MiddleWare &mw, std::string url, int field, std::string name) | |||||
| { | |||||
| (void) name; | |||||
| printf("PresetArrayCopy()\n"); | |||||
| doClassArrayCopy(getUrlType(url), field, mw, url, name); | |||||
| } | |||||
| void presetPasteArray(MiddleWare &mw, std::string url, int field, std::string name) | |||||
| { | |||||
| (void) name; | |||||
| printf("PresetArrayPaste()\n"); | |||||
| string data = ""; | |||||
| XMLwrapper xml; | |||||
| if(name.empty()) { | |||||
| data = presetsstore.clipboard.data; | |||||
| if(data.length() < 20) | |||||
| return; | |||||
| if(!xml.putXMLdata(data.c_str())) | |||||
| return; | |||||
| } else { | |||||
| if(xml.loadXMLfile(name)) | |||||
| return; | |||||
| } | |||||
| printf("Performing Paste...\n"); | |||||
| doClassArrayPaste(getUrlType(url), getUrlPresetType(url, mw), field, mw, url, xml); | |||||
| } | |||||
| #if 0 | |||||
| void presetPaste(std::string url, int) | |||||
| { | |||||
| printf("PresetPaste()\n"); | |||||
| doClassPaste(getUrlType(url), *middlewarepointer, url, presetsstore.clipboard.data); | |||||
| } | |||||
| #endif | |||||
| void presetDelete(int) | |||||
| { | |||||
| printf("PresetDelete()\n"); | |||||
| } | |||||
| void presetRescan() | |||||
| { | |||||
| printf("PresetRescan()\n"); | |||||
| } | |||||
| std::string presetClipboardType() | |||||
| { | |||||
| printf("PresetClipboardType()\n"); | |||||
| return "dummy"; | |||||
| } | |||||
| bool presetCheckClipboardType() | |||||
| { | |||||
| printf("PresetCheckClipboardType()\n"); | |||||
| return true; | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| #pragma once | |||||
| #include <string> | |||||
| #include <rtosc/ports.h> | |||||
| extern const rtosc::Ports real_preset_ports; | |||||
| extern const rtosc::Ports preset_ports; | |||||
| struct Clipboard { | |||||
| std::string data; | |||||
| std::string type; | |||||
| }; | |||||
| Clipboard clipboardCopy(class MiddleWare &mw, std::string url); | |||||
| void presetCopy(MiddleWare &mw, std::string url, std::string name); | |||||
| void presetPaste(MiddleWare &mw, std::string url, std::string name); | |||||
| void presetCopyArray(MiddleWare &mw, std::string url, int field, std::string name); | |||||
| void presetPasteArray(MiddleWare &mw, std::string url, int field, std::string name); | |||||
| void presetPaste(std::string url, int); | |||||
| void presetDelete(int); | |||||
| void presetRescan(); | |||||
| std::string presetClipboardType(); | |||||
| bool presetCheckClipboardType(); | |||||
| @@ -23,10 +23,11 @@ | |||||
| #include <sys/stat.h> | #include <sys/stat.h> | ||||
| #include "Recorder.h" | #include "Recorder.h" | ||||
| #include "WavFile.h" | #include "WavFile.h" | ||||
| #include "../globals.h" | |||||
| #include "../Nio/Nio.h" | #include "../Nio/Nio.h" | ||||
| Recorder::Recorder() | |||||
| :status(0), notetrigger(0) | |||||
| Recorder::Recorder(const SYNTH_T &synth_) | |||||
| :status(0), notetrigger(0),synth(synth_) | |||||
| {} | {} | ||||
| Recorder::~Recorder() | Recorder::~Recorder() | ||||
| @@ -45,7 +46,7 @@ int Recorder::preparefile(std::string filename_, int overwrite) | |||||
| return 1; | return 1; | ||||
| } | } | ||||
| Nio::waveNew(new WavFile(filename_, synth->samplerate, 2)); | |||||
| Nio::waveNew(new WavFile(filename_, synth.samplerate, 2)); | |||||
| status = 1; //ready | status = 1; //ready | ||||
| @@ -23,14 +23,14 @@ | |||||
| #ifndef RECORDER_H | #ifndef RECORDER_H | ||||
| #define RECORDER_H | #define RECORDER_H | ||||
| #include <string> | #include <string> | ||||
| #include "../globals.h" | |||||
| struct SYNTH_T; | |||||
| /**Records sound to a file*/ | /**Records sound to a file*/ | ||||
| class Recorder | class Recorder | ||||
| { | { | ||||
| public: | public: | ||||
| Recorder(); | |||||
| Recorder(const SYNTH_T &synth); | |||||
| ~Recorder(); | ~Recorder(); | ||||
| /**Prepare the given file. | /**Prepare the given file. | ||||
| * @returns 1 if the file exists */ | * @returns 1 if the file exists */ | ||||
| @@ -49,6 +49,7 @@ class Recorder | |||||
| private: | private: | ||||
| int notetrigger; | int notetrigger; | ||||
| const SYNTH_T &synth; | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -23,11 +23,10 @@ | |||||
| #include "Util.h" | #include "Util.h" | ||||
| #include <vector> | #include <vector> | ||||
| #include <cassert> | #include <cassert> | ||||
| #include <math.h> | |||||
| #include <stdio.h> | |||||
| #ifndef CARLA_OS_WIN | |||||
| #include <cmath> | |||||
| #include <cstdio> | |||||
| #include <fstream> | |||||
| #include <err.h> | #include <err.h> | ||||
| #endif | |||||
| #include <sys/types.h> | #include <sys/types.h> | ||||
| #include <sys/stat.h> | #include <sys/stat.h> | ||||
| @@ -87,7 +86,7 @@ float getdetune(unsigned char type, | |||||
| findet = fabs(fdetune / 8192.0f) * 10.0f; | findet = fabs(fdetune / 8192.0f) * 10.0f; | ||||
| break; | break; | ||||
| case 3: | case 3: | ||||
| cdet = fabs(cdetune * 100); | |||||
| cdet = fabs(cdetune * 100.0f); | |||||
| findet = powf(10, fabs(fdetune / 8192.0f) * 3.0f) / 10.0f - 0.1f; | findet = powf(10, fabs(fdetune / 8192.0f) * 3.0f) / 10.0f - 0.1f; | ||||
| break; | break; | ||||
| case 4: | case 4: | ||||
| @@ -138,6 +137,43 @@ void os_sleep(long length) | |||||
| usleep(length); | usleep(length); | ||||
| } | } | ||||
| //!< maximum lenght a pid has on any POSIX system | |||||
| //!< this is an estimation, but more than 12 looks insane | |||||
| constexpr std::size_t max_pid_len = 12; | |||||
| //!< safe pid lenght guess, posix conform | |||||
| std::size_t os_guess_pid_length() | |||||
| { | |||||
| const char* pid_max_file = "/proc/sys/kernel/pid_max"; | |||||
| if(-1 == access(pid_max_file, R_OK)) { | |||||
| return max_pid_len; | |||||
| } | |||||
| else { | |||||
| std::ifstream is(pid_max_file); | |||||
| if(!is.good()) | |||||
| return max_pid_len; | |||||
| else { | |||||
| std::string s; | |||||
| is >> s; | |||||
| for(const auto& c : s) | |||||
| if(c < '0' || c > '9') | |||||
| return max_pid_len; | |||||
| return std::min(s.length(), max_pid_len); | |||||
| } | |||||
| } | |||||
| } | |||||
| //!< returns pid padded, posix conform | |||||
| std::string os_pid_as_padded_string() | |||||
| { | |||||
| char result_str[max_pid_len << 1]; | |||||
| std::fill_n(result_str, max_pid_len, '0'); | |||||
| std::size_t written = snprintf(result_str + max_pid_len, max_pid_len, | |||||
| "%d", (int)getpid()); | |||||
| // the below pointer should never cause segfaults: | |||||
| return result_str + max_pid_len + written - os_guess_pid_length(); | |||||
| } | |||||
| std::string legalizeFilename(std::string filename) | std::string legalizeFilename(std::string filename) | ||||
| { | { | ||||
| for(int i = 0; i < (int) filename.size(); ++i) { | for(int i = 0; i < (int) filename.size(); ++i) { | ||||
| @@ -26,9 +26,13 @@ | |||||
| #include <string> | #include <string> | ||||
| #include <sstream> | #include <sstream> | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include <algorithm> | |||||
| #include "Config.h" | #include "Config.h" | ||||
| #include "../globals.h" | #include "../globals.h" | ||||
| using std::min; | |||||
| using std::max; | |||||
| //Velocity Sensing function | //Velocity Sensing function | ||||
| extern float VelF(float velocity, unsigned char scaling); | extern float VelF(float velocity, unsigned char scaling); | ||||
| @@ -48,6 +52,9 @@ void set_realtime(); | |||||
| /**Os independent sleep in microsecond*/ | /**Os independent sleep in microsecond*/ | ||||
| void os_sleep(long length); | void os_sleep(long length); | ||||
| //! returns pid padded to maximum pid lenght, posix conform | |||||
| std::string os_pid_as_padded_string(); | |||||
| std::string legalizeFilename(std::string filename); | std::string legalizeFilename(std::string filename); | ||||
| extern float *denormalkillbuf; /**<the buffer to add noise in order to avoid denormalisation*/ | extern float *denormalkillbuf; /**<the buffer to add noise in order to avoid denormalisation*/ | ||||
| @@ -64,6 +71,12 @@ std::string stringFrom(T x) | |||||
| return ss.str(); | return ss.str(); | ||||
| } | } | ||||
| template<class T> | |||||
| std::string to_s(T x) | |||||
| { | |||||
| return stringFrom(x); | |||||
| } | |||||
| template<class T> | template<class T> | ||||
| T stringTo(const char *x) | T stringTo(const char *x) | ||||
| { | { | ||||
| @@ -74,12 +87,31 @@ T stringTo(const char *x) | |||||
| return ans; | return ans; | ||||
| } | } | ||||
| template<class T> | template<class T> | ||||
| T limit(T val, T min, T max) | T limit(T val, T min, T max) | ||||
| { | { | ||||
| return val < min ? min : (val > max ? max : val); | return val < min ? min : (val > max ? max : val); | ||||
| } | } | ||||
| template<class T> | |||||
| bool inRange(T val, T min, T max) | |||||
| { | |||||
| return val >= min && val <= max; | |||||
| } | |||||
| template<class T> | |||||
| T array_max(const T *data, size_t len) | |||||
| { | |||||
| T max = 0; | |||||
| for(unsigned i = 0; i < len; ++i) | |||||
| if(max < data[i]) | |||||
| max = data[i]; | |||||
| return max; | |||||
| } | |||||
| //Random number generator | //Random number generator | ||||
| typedef uint32_t prng_t; | typedef uint32_t prng_t; | ||||
| @@ -105,9 +137,9 @@ inline void sprng(prng_t p) | |||||
| * The random generator (0.0f..1.0f) | * The random generator (0.0f..1.0f) | ||||
| */ | */ | ||||
| #ifndef INT32_MAX | #ifndef INT32_MAX | ||||
| # define INT32_MAX (2147483647) | |||||
| #define INT32_MAX (2147483647) | |||||
| #endif | #endif | ||||
| #define RND (float(prng()) / float(INT32_MAX)) | |||||
| #define RND (prng() / (INT32_MAX * 1.0f)) | |||||
| //Linear Interpolation | //Linear Interpolation | ||||
| float interpolate(const float *data, size_t len, float pos); | float interpolate(const float *data, size_t len, float pos); | ||||
| @@ -115,4 +147,42 @@ float interpolate(const float *data, size_t len, float pos); | |||||
| //Linear circular interpolation | //Linear circular interpolation | ||||
| float cinterpolate(const float *data, size_t len, float pos); | float cinterpolate(const float *data, size_t len, float pos); | ||||
| template<class T> | |||||
| static inline void nullify(T &t) {delete t; t = NULL; } | |||||
| template<class T> | |||||
| static inline void arrayNullify(T &t) {delete [] t; t = NULL; } | |||||
| /** | |||||
| * Port macros - these produce easy and regular port definitions for common | |||||
| * types | |||||
| */ | |||||
| #define rParamZyn(name, ...) \ | |||||
| {STRINGIFY(name) "::i", rProp(parameter) rMap(min, 0) rMap(max, 127) DOC(__VA_ARGS__), NULL, rParamICb(name)} | |||||
| #define rSelf(type) \ | |||||
| {"self", rProp(internal) rMap(class, type) rDoc("port metadata"), 0, \ | |||||
| [](const char *, rtosc::RtData &d){ \ | |||||
| d.reply(d.loc, "b", sizeof(d.obj), &d.obj);}}\ | |||||
| #define rPaste \ | |||||
| {"preset-type", rProp(internal), 0, \ | |||||
| [](const char *, rtosc::RtData &d){ \ | |||||
| rObject *obj = (rObject*)d.obj; \ | |||||
| d.reply(d.loc, "s", obj->type);}},\ | |||||
| {"paste:b", rProp(internal) rDoc("paste port"), 0, \ | |||||
| [](const char *m, rtosc::RtData &d){ \ | |||||
| printf("rPaste...\n"); \ | |||||
| rObject &paste = **(rObject **)rtosc_argument(m,0).b.data; \ | |||||
| rObject &o = *(rObject*)d.obj;\ | |||||
| o.paste(paste);}} | |||||
| #define rArrayPaste \ | |||||
| {"paste-array:bi", rProp(internal) rDoc("array paste port"), 0, \ | |||||
| [](const char *m, rtosc::RtData &d){ \ | |||||
| printf("rArrayPaste...\n"); \ | |||||
| rObject &paste = **(rObject **)rtosc_argument(m,0).b.data; \ | |||||
| int field = rtosc_argument(m,1).i; \ | |||||
| rObject &o = *(rObject*)d.obj;\ | |||||
| o.pasteArray(paste,field);}} | |||||
| #endif | #endif | ||||
| @@ -97,8 +97,8 @@ const char *mxmlElementGetAttr(const mxml_node_t *node, const char *name) | |||||
| XMLwrapper::XMLwrapper() | XMLwrapper::XMLwrapper() | ||||
| { | { | ||||
| version.Major = 2; | version.Major = 2; | ||||
| version.Minor = 4; | |||||
| version.Revision = 4; | |||||
| version.Minor = 5; | |||||
| version.Revision = 0; | |||||
| minimal = true; | minimal = true; | ||||
| @@ -311,7 +311,7 @@ int XMLwrapper::loadXMLfile(const string &filename) | |||||
| mxmlDelete(tree); | mxmlDelete(tree); | ||||
| tree = NULL; | tree = NULL; | ||||
| const char *xmldata = doloadfile(filename.c_str()); | |||||
| const char *xmldata = doloadfile(filename); | |||||
| if(xmldata == NULL) | if(xmldata == NULL) | ||||
| return -1; //the file could not be loaded or uncompressed | return -1; //the file could not be loaded or uncompressed | ||||
| @@ -618,6 +618,7 @@ mxml_node_t *XMLwrapper::addparams(const char *name, unsigned int params, | |||||
| << ParamName << "=\"" << ParamValue << "\"" << endl; | << ParamName << "=\"" << ParamValue << "\"" << endl; | ||||
| mxmlElementSetAttr(element, ParamName, ParamValue); | mxmlElementSetAttr(element, ParamName, ParamValue); | ||||
| } | } | ||||
| va_end(variableList); | |||||
| } | } | ||||
| return element; | return element; | ||||
| } | } | ||||
| @@ -1,21 +1,22 @@ | |||||
| /* | /* | ||||
| AlsaEngine.cpp | |||||
| ZynAddSubFX - a software synthesizer | |||||
| AlsaEngine.cpp - ALSA Driver | |||||
| Copyright 2009, Alan Calvert | |||||
| 2010, Mark McCurry | |||||
| Copyright 2009, Alan Calvert | |||||
| 2014, Mark McCurry | |||||
| This file is part of ZynAddSubFX, which is free software: you can | |||||
| redistribute it and/or modify it under the terms of the GNU General | |||||
| Public License as published by the Free Software Foundation, either | |||||
| version 3 of the License, or (at your option) any later version. | |||||
| This program is free software; you can redistribute it and/or modify | |||||
| it under the terms of version 2 of the GNU General Public License | |||||
| as published by the Free Software Foundation. | |||||
| ZynAddSubFX is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License for more details. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License (version 2 or later) for more details. | |||||
| You should have received a copy of the GNU General Public License | |||||
| along with ZynAddSubFX. If not, see <http://www.gnu.org/licenses/>. | |||||
| You should have received a copy of the GNU General Public License (version 2) | |||||
| along with this program; if not, write to the Free Software Foundation, | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||||
| */ | */ | ||||
| #include <iostream> | #include <iostream> | ||||
| @@ -28,10 +29,10 @@ using namespace std; | |||||
| #include "InMgr.h" | #include "InMgr.h" | ||||
| #include "AlsaEngine.h" | #include "AlsaEngine.h" | ||||
| AlsaEngine::AlsaEngine() | |||||
| :AudioOut() | |||||
| AlsaEngine::AlsaEngine(const SYNTH_T &synth) | |||||
| :AudioOut(synth) | |||||
| { | { | ||||
| audio.buffer = new short[synth->buffersize * 2]; | |||||
| audio.buffer = new short[synth.buffersize * 2]; | |||||
| name = "ALSA"; | name = "ALSA"; | ||||
| audio.handle = NULL; | audio.handle = NULL; | ||||
| @@ -295,7 +296,7 @@ bool AlsaEngine::openAudio() | |||||
| /* Two channels (stereo) */ | /* Two channels (stereo) */ | ||||
| snd_pcm_hw_params_set_channels(audio.handle, audio.params, 2); | snd_pcm_hw_params_set_channels(audio.handle, audio.params, 2); | ||||
| audio.sampleRate = synth->samplerate; | |||||
| audio.sampleRate = synth.samplerate; | |||||
| snd_pcm_hw_params_set_rate_near(audio.handle, audio.params, | snd_pcm_hw_params_set_rate_near(audio.handle, audio.params, | ||||
| &audio.sampleRate, NULL); | &audio.sampleRate, NULL); | ||||
| @@ -320,7 +321,7 @@ bool AlsaEngine::openAudio() | |||||
| /* latency = periodsize * periods / (rate * bytes_per_frame) */ | /* latency = periodsize * periods / (rate * bytes_per_frame) */ | ||||
| snd_pcm_hw_params_set_buffer_size(audio.handle, | snd_pcm_hw_params_set_buffer_size(audio.handle, | ||||
| audio.params, | audio.params, | ||||
| synth->buffersize); | |||||
| synth.buffersize); | |||||
| //snd_pcm_hw_params_get_period_size(audio.params, &audio.frames, NULL); | //snd_pcm_hw_params_get_period_size(audio.params, &audio.frames, NULL); | ||||
| //snd_pcm_hw_params_get_period_time(audio.params, &val, NULL); | //snd_pcm_hw_params_get_period_time(audio.params, &val, NULL); | ||||
| @@ -352,7 +353,7 @@ void *AlsaEngine::processAudio() | |||||
| while(audio.handle) { | while(audio.handle) { | ||||
| audio.buffer = interleave(getNext()); | audio.buffer = interleave(getNext()); | ||||
| snd_pcm_t *handle = audio.handle; | snd_pcm_t *handle = audio.handle; | ||||
| int rc = snd_pcm_writei(handle, audio.buffer, synth->buffersize); | |||||
| int rc = snd_pcm_writei(handle, audio.buffer, synth.buffersize); | |||||
| if(rc == -EPIPE) { | if(rc == -EPIPE) { | ||||
| /* EPIPE means underrun */ | /* EPIPE means underrun */ | ||||
| cerr << "underrun occurred" << endl; | cerr << "underrun occurred" << endl; | ||||
| @@ -1,21 +1,22 @@ | |||||
| /* | /* | ||||
| AlsaEngine.h | |||||
| ZynAddSubFX - a software synthesizer | |||||
| AlsaEngine.h - ALSA Driver | |||||
| Copyright 2009, Alan Calvert | |||||
| 2010, Mark McCurry | |||||
| Copyright 2009, Alan Calvert | |||||
| 2014, Mark McCurry | |||||
| This file is part of ZynAddSubFX, which is free software: you can | |||||
| redistribute it and/or modify it under the terms of the GNU General | |||||
| Public License as published by the Free Software Foundation, either | |||||
| version 3 of the License, or (at your option) any later version. | |||||
| This program is free software; you can redistribute it and/or modify | |||||
| it under the terms of version 2 of the GNU General Public License | |||||
| as published by the Free Software Foundation. | |||||
| ZynAddSubFX is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License for more details. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License (version 2 or later) for more details. | |||||
| You should have received a copy of the GNU General Public License | |||||
| along with ZynAddSubFX. If not, see <http://www.gnu.org/licenses/>. | |||||
| You should have received a copy of the GNU General Public License (version 2) | |||||
| along with this program; if not, write to the Free Software Foundation, | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||||
| */ | */ | ||||
| #ifndef ALSA_ENGINE_H | #ifndef ALSA_ENGINE_H | ||||
| @@ -34,7 +35,7 @@ | |||||
| class AlsaEngine:public AudioOut, MidiIn | class AlsaEngine:public AudioOut, MidiIn | ||||
| { | { | ||||
| public: | public: | ||||
| AlsaEngine(); | |||||
| AlsaEngine(const SYNTH_T &synth); | |||||
| ~AlsaEngine(); | ~AlsaEngine(); | ||||
| bool Start(); | bool Start(); | ||||
| @@ -30,8 +30,8 @@ using namespace std; | |||||
| #include "../Misc/Master.h" | #include "../Misc/Master.h" | ||||
| #include "AudioOut.h" | #include "AudioOut.h" | ||||
| AudioOut::AudioOut() | |||||
| :samplerate(synth->samplerate), bufferSize(synth->buffersize) | |||||
| AudioOut::AudioOut(const SYNTH_T &synth_) | |||||
| :synth(synth_), samplerate(synth.samplerate), bufferSize(synth.buffersize) | |||||
| {} | {} | ||||
| AudioOut::~AudioOut() | AudioOut::~AudioOut() | ||||
| @@ -30,7 +30,7 @@ | |||||
| class AudioOut:public virtual Engine | class AudioOut:public virtual Engine | ||||
| { | { | ||||
| public: | public: | ||||
| AudioOut(); | |||||
| AudioOut(const SYNTH_T &synth); | |||||
| virtual ~AudioOut(); | virtual ~AudioOut(); | ||||
| /**Sets the Sample Rate of this Output | /**Sets the Sample Rate of this Output | ||||
| @@ -54,6 +54,7 @@ class AudioOut:public virtual Engine | |||||
| * (has nsamples sampled at a rate of samplerate)*/ | * (has nsamples sampled at a rate of samplerate)*/ | ||||
| const Stereo<float *> getNext(); | const Stereo<float *> getNext(); | ||||
| const SYNTH_T &synth; | |||||
| int samplerate; | int samplerate; | ||||
| int bufferSize; | int bufferSize; | ||||
| }; | }; | ||||
| @@ -15,19 +15,24 @@ set(zynaddsubfx_nio_SRCS | |||||
| Nio.cpp | Nio.cpp | ||||
| ) | ) | ||||
| set(zynaddsubfx_nio_lib ) | |||||
| set(zynaddsubfx_nio_lib) | |||||
| add_definitions(-DOUT_DEFAULT="${DefaultOutput}") | add_definitions(-DOUT_DEFAULT="${DefaultOutput}") | ||||
| add_definitions(-DIN_DEFAULT="${DefaultInput}") | add_definitions(-DIN_DEFAULT="${DefaultInput}") | ||||
| if(JackEnable) | if(JackEnable) | ||||
| include_directories(${JACK_INCLUDE_DIR}) | include_directories(${JACK_INCLUDE_DIR}) | ||||
| list(APPEND zynaddsubfx_nio_SRCS JackEngine.cpp) | |||||
| list(APPEND zynaddsubfx_nio_SRCS JackEngine.cpp JackMultiEngine.cpp) | |||||
| list(APPEND zynaddsubfx_nio_lib ${JACK_LIBRARIES}) | list(APPEND zynaddsubfx_nio_lib ${JACK_LIBRARIES}) | ||||
| CHECK_INCLUDE_FILES("jack/metadata.h" JACK_HAS_METADATA_API) | |||||
| if(JACK_HAS_METADATA_API) | |||||
| add_definitions(-DJACK_HAS_METADATA_API) | |||||
| endif(JACK_HAS_METADATA_API) | |||||
| endif(JackEnable) | endif(JackEnable) | ||||
| if(PaEnable) | if(PaEnable) | ||||
| include_directories(${PORTAUDIO_INCLUDE_DIR}) | |||||
| include_directories(${PORTAUDIO_INCLUDE_DIRS}) | |||||
| list(APPEND zynaddsubfx_nio_SRCS PaEngine.cpp) | list(APPEND zynaddsubfx_nio_SRCS PaEngine.cpp) | ||||
| list(APPEND zynaddsubfx_nio_lib ${PORTAUDIO_LIBRARIES}) | list(APPEND zynaddsubfx_nio_lib ${PORTAUDIO_LIBRARIES}) | ||||
| endif(PaEnable) | endif(PaEnable) | ||||
| @@ -38,7 +43,7 @@ if(AlsaEnable) | |||||
| endif(AlsaEnable) | endif(AlsaEnable) | ||||
| if(OssEnable) | if(OssEnable) | ||||
| list(APPEND zynaddsubfx_nio_SRCS OssEngine.cpp) | |||||
| list(APPEND zynaddsubfx_nio_SRCS OssEngine.cpp OssMultiEngine.cpp) | |||||
| endif(OssEnable) | endif(OssEnable) | ||||
| @@ -1,6 +1,7 @@ | |||||
| #include "EngineMgr.h" | #include "EngineMgr.h" | ||||
| #include <algorithm> | #include <algorithm> | ||||
| #include <iostream> | #include <iostream> | ||||
| #include <cassert> | |||||
| #include "Nio.h" | #include "Nio.h" | ||||
| #include "InMgr.h" | #include "InMgr.h" | ||||
| #include "OutMgr.h" | #include "OutMgr.h" | ||||
| @@ -9,12 +10,14 @@ | |||||
| #include "NulEngine.h" | #include "NulEngine.h" | ||||
| #if OSS | #if OSS | ||||
| #include "OssEngine.h" | #include "OssEngine.h" | ||||
| #include "OssMultiEngine.h" | |||||
| #endif | #endif | ||||
| #if ALSA | #if ALSA | ||||
| #include "AlsaEngine.h" | #include "AlsaEngine.h" | ||||
| #endif | #endif | ||||
| #if JACK | #if JACK | ||||
| #include "JackEngine.h" | #include "JackEngine.h" | ||||
| #include "JackMultiEngine.h" | |||||
| #endif | #endif | ||||
| #if PORTAUDIO | #if PORTAUDIO | ||||
| #include "PaEngine.h" | #include "PaEngine.h" | ||||
| @@ -22,29 +25,32 @@ | |||||
| using namespace std; | using namespace std; | ||||
| EngineMgr &EngineMgr::getInstance() | |||||
| EngineMgr &EngineMgr::getInstance(const SYNTH_T *synth) | |||||
| { | { | ||||
| static EngineMgr instance; | |||||
| static EngineMgr instance(synth); | |||||
| return instance; | return instance; | ||||
| } | } | ||||
| EngineMgr::EngineMgr() | |||||
| EngineMgr::EngineMgr(const SYNTH_T *synth) | |||||
| { | { | ||||
| Engine *defaultEng = new NulEngine(); | |||||
| assert(synth); | |||||
| Engine *defaultEng = new NulEngine(*synth); | |||||
| //conditional compiling mess (but contained) | //conditional compiling mess (but contained) | ||||
| engines.push_back(defaultEng); | engines.push_back(defaultEng); | ||||
| #if OSS | #if OSS | ||||
| engines.push_back(new OssEngine()); | |||||
| engines.push_back(new OssEngine(*synth)); | |||||
| engines.push_back(new OssMultiEngine(*synth)); | |||||
| #endif | #endif | ||||
| #if ALSA | #if ALSA | ||||
| engines.push_back(new AlsaEngine()); | |||||
| engines.push_back(new AlsaEngine(*synth)); | |||||
| #endif | #endif | ||||
| #if JACK | #if JACK | ||||
| engines.push_back(new JackEngine()); | |||||
| engines.push_back(new JackEngine(*synth)); | |||||
| engines.push_back(new JackMultiEngine(*synth)); | |||||
| #endif | #endif | ||||
| #if PORTAUDIO | #if PORTAUDIO | ||||
| engines.push_back(new PaEngine()); | |||||
| engines.push_back(new PaEngine(*synth)); | |||||
| #endif | #endif | ||||
| defaultOut = dynamic_cast<AudioOut *>(defaultEng); | defaultOut = dynamic_cast<AudioOut *>(defaultEng); | ||||
| @@ -9,11 +9,12 @@ | |||||
| class MidiIn; | class MidiIn; | ||||
| class AudioOut; | class AudioOut; | ||||
| class OutMgr; | class OutMgr; | ||||
| struct SYNTH_T; | |||||
| /**Container/Owner of the long lived Engines*/ | /**Container/Owner of the long lived Engines*/ | ||||
| class EngineMgr | class EngineMgr | ||||
| { | { | ||||
| public: | public: | ||||
| static EngineMgr &getInstance(); | |||||
| static EngineMgr &getInstance(const SYNTH_T *synth=NULL); | |||||
| ~EngineMgr(); | ~EngineMgr(); | ||||
| /**Gets requested engine | /**Gets requested engine | ||||
| @@ -38,6 +39,6 @@ class EngineMgr | |||||
| AudioOut *defaultOut; | AudioOut *defaultOut; | ||||
| MidiIn *defaultIn; | MidiIn *defaultIn; | ||||
| private: | private: | ||||
| EngineMgr(); | |||||
| EngineMgr(const SYNTH_T *synth); | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -2,10 +2,15 @@ | |||||
| #include "MidiIn.h" | #include "MidiIn.h" | ||||
| #include "EngineMgr.h" | #include "EngineMgr.h" | ||||
| #include "../Misc/Master.h" | #include "../Misc/Master.h" | ||||
| #include "../Misc/Part.h" | |||||
| #include "../Misc/MiddleWare.h" | |||||
| #include <rtosc/thread-link.h> | |||||
| #include <iostream> | #include <iostream> | ||||
| using namespace std; | using namespace std; | ||||
| extern MiddleWare *middleware; | |||||
| ostream &operator<<(ostream &out, const MidiEvent &ev) | ostream &operator<<(ostream &out, const MidiEvent &ev) | ||||
| { | { | ||||
| switch(ev.type) { | switch(ev.type) { | ||||
| @@ -41,7 +46,7 @@ InMgr &InMgr::getInstance() | |||||
| } | } | ||||
| InMgr::InMgr() | InMgr::InMgr() | ||||
| :queue(100), master(Master::getInstance()) | |||||
| :queue(100), master(NULL) | |||||
| { | { | ||||
| current = NULL; | current = NULL; | ||||
| work.init(PTHREAD_PROCESS_PRIVATE, 0); | work.init(PTHREAD_PROCESS_PRIVATE, 0); | ||||
| @@ -76,24 +81,27 @@ void InMgr::flush(unsigned frameStart, unsigned frameStop) | |||||
| switch(ev.type) { | switch(ev.type) { | ||||
| case M_NOTE: | case M_NOTE: | ||||
| dump.dumpnote(ev.channel, ev.num, ev.value); | |||||
| if(ev.value) | if(ev.value) | ||||
| master.noteOn(ev.channel, ev.num, ev.value); | |||||
| master->noteOn(ev.channel, ev.num, ev.value); | |||||
| else | else | ||||
| master.noteOff(ev.channel, ev.num); | |||||
| master->noteOff(ev.channel, ev.num); | |||||
| break; | break; | ||||
| case M_CONTROLLER: | case M_CONTROLLER: | ||||
| dump.dumpcontroller(ev.channel, ev.num, ev.value); | |||||
| master.setController(ev.channel, ev.num, ev.value); | |||||
| master->setController(ev.channel, ev.num, ev.value); | |||||
| break; | break; | ||||
| case M_PGMCHANGE: | case M_PGMCHANGE: | ||||
| master.setProgram(ev.channel, ev.num); | |||||
| for(int i=0; i < NUM_MIDI_PARTS; ++i) { | |||||
| //set the program of the parts assigned to the midi channel | |||||
| if(master->part[i]->Prcvchn == ev.channel) { | |||||
| middleware->pendingSetProgram(i, ev.num); | |||||
| } | |||||
| } | |||||
| break; | break; | ||||
| case M_PRESSURE: | case M_PRESSURE: | ||||
| master.polyphonicAftertouch(ev.channel, ev.num, ev.value); | |||||
| master->polyphonicAftertouch(ev.channel, ev.num, ev.value); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -136,6 +144,11 @@ string InMgr::getSource() const | |||||
| MidiIn *InMgr::getIn(string name) | MidiIn *InMgr::getIn(string name) | ||||
| { | { | ||||
| EngineMgr &eng = EngineMgr::getInstance(); | |||||
| EngineMgr &eng = EngineMgr::getInstance(NULL); | |||||
| return dynamic_cast<MidiIn *>(eng.getEng(name)); | return dynamic_cast<MidiIn *>(eng.getEng(name)); | ||||
| } | } | ||||
| void InMgr::setMaster(Master *master_) | |||||
| { | |||||
| master = master_; | |||||
| } | |||||
| @@ -40,6 +40,8 @@ class InMgr | |||||
| std::string getSource() const; | std::string getSource() const; | ||||
| void setMaster(class Master *master); | |||||
| friend class EngineMgr; | friend class EngineMgr; | ||||
| private: | private: | ||||
| InMgr(); | InMgr(); | ||||
| @@ -49,7 +51,7 @@ class InMgr | |||||
| class MidiIn * current; | class MidiIn * current; | ||||
| /**the link to the rest of zyn*/ | /**the link to the rest of zyn*/ | ||||
| class Master & master; | |||||
| class Master *master; | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -1,32 +1,42 @@ | |||||
| /* | /* | ||||
| JackEngine.cpp | |||||
| ZynAddSubFX - a software synthesizer | |||||
| JackEngine.cpp - Jack Driver | |||||
| Copyright 2009, Alan Calvert | |||||
| Copyright 2009, Alan Calvert | |||||
| 2014, Mark McCurry | |||||
| This file is part of yoshimi, which is free software: you can | |||||
| redistribute it and/or modify it under the terms of the GNU General | |||||
| Public License as published by the Free Software Foundation, either | |||||
| version 3 of the License, or (at your option) any later version. | |||||
| This program is free software; you can redistribute it and/or modify | |||||
| it under the terms of version 2 of the GNU General Public License | |||||
| as published by the Free Software Foundation. | |||||
| yoshimi is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License for more details. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License (version 2 or later) for more details. | |||||
| You should have received a copy of the GNU General Public License | |||||
| along with yoshimi. If not, see <http://www.gnu.org/licenses/>. | |||||
| You should have received a copy of the GNU General Public License (version 2) | |||||
| along with this program; if not, write to the Free Software Foundation, | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||||
| */ | */ | ||||
| #include <iostream> | #include <iostream> | ||||
| #include <jack/midiport.h> | #include <jack/midiport.h> | ||||
| #ifdef JACK_HAS_METADATA_API | |||||
| # include <jack/metadata.h> | |||||
| #endif // JACK_HAS_METADATA_API | |||||
| #include "jack_osc.h" | |||||
| #include <fcntl.h> | #include <fcntl.h> | ||||
| #include <sys/stat.h> | #include <sys/stat.h> | ||||
| #include <cassert> | #include <cassert> | ||||
| #include <cstring> | #include <cstring> | ||||
| #include <unistd.h> // access() | |||||
| #include <fstream> // std::istream | |||||
| #include "Nio.h" | #include "Nio.h" | ||||
| #include "OutMgr.h" | |||||
| #include "InMgr.h" | #include "InMgr.h" | ||||
| #include "Misc/Util.h" | |||||
| #include "JackEngine.h" | #include "JackEngine.h" | ||||
| @@ -34,8 +44,8 @@ using namespace std; | |||||
| extern char *instance_name; | extern char *instance_name; | ||||
| JackEngine::JackEngine() | |||||
| :AudioOut(), jackClient(NULL) | |||||
| JackEngine::JackEngine(const SYNTH_T &synth) | |||||
| :AudioOut(synth), jackClient(NULL) | |||||
| { | { | ||||
| name = "JACK"; | name = "JACK"; | ||||
| audio.jackSamplerate = 0; | audio.jackSamplerate = 0; | ||||
| @@ -58,6 +68,9 @@ bool JackEngine::connectServer(string server) | |||||
| string postfix = Nio::getPostfix(); | string postfix = Nio::getPostfix(); | ||||
| if(!postfix.empty()) | if(!postfix.empty()) | ||||
| clientname += "_" + postfix; | clientname += "_" + postfix; | ||||
| if(Nio::pidInClientName) | |||||
| clientname += "_" + os_pid_as_padded_string(); | |||||
| jack_status_t jackstatus; | jack_status_t jackstatus; | ||||
| bool use_server_name = server.size() && server.compare("default") != 0; | bool use_server_name = server.size() && server.compare("default") != 0; | ||||
| jack_options_t jopts = (jack_options_t) | jack_options_t jopts = (jack_options_t) | ||||
| @@ -93,12 +106,11 @@ bool JackEngine::connectJack() | |||||
| connectServer(""); | connectServer(""); | ||||
| if(NULL != jackClient) { | if(NULL != jackClient) { | ||||
| setBufferSize(jack_get_buffer_size(jackClient)); | setBufferSize(jack_get_buffer_size(jackClient)); | ||||
| int chk; | |||||
| jack_set_error_function(_errorCallback); | jack_set_error_function(_errorCallback); | ||||
| jack_set_info_function(_infoCallback); | jack_set_info_function(_infoCallback); | ||||
| if(jack_set_buffer_size_callback(jackClient, _bufferSizeCallback, this)) | if(jack_set_buffer_size_callback(jackClient, _bufferSizeCallback, this)) | ||||
| cerr << "Error setting the bufferSize callback" << endl; | cerr << "Error setting the bufferSize callback" << endl; | ||||
| if((chk = jack_set_xrun_callback(jackClient, _xrunCallback, this))) | |||||
| if((jack_set_xrun_callback(jackClient, _xrunCallback, this))) | |||||
| cerr << "Error setting jack xrun callback" << endl; | cerr << "Error setting jack xrun callback" << endl; | ||||
| if(jack_set_process_callback(jackClient, _processCallback, this)) { | if(jack_set_process_callback(jackClient, _processCallback, this)) { | ||||
| cerr << "Error, JackEngine failed to set process callback" << endl; | cerr << "Error, JackEngine failed to set process callback" << endl; | ||||
| @@ -213,6 +225,12 @@ bool JackEngine::openAudio() | |||||
| cerr << "Warning, No outputs to autoconnect to" << endl; | cerr << "Warning, No outputs to autoconnect to" << endl; | ||||
| } | } | ||||
| midi.jack_sync = true; | midi.jack_sync = true; | ||||
| osc.oscport = jack_port_register(jackClient, "osc", | |||||
| JACK_DEFAULT_OSC_TYPE, JackPortIsInput, 0); | |||||
| #ifdef JACK_HAS_METADATA_API | |||||
| jack_uuid_t uuid = jack_port_uuid(osc.oscport); | |||||
| jack_set_property(jackClient, uuid, "http://jackaudio.org/metadata/event-types", JACK_EVENT_TYPE__OSC, "text/plain"); | |||||
| #endif // JACK_HAS_METADATA_API | |||||
| return true; | return true; | ||||
| } | } | ||||
| else | else | ||||
| @@ -226,10 +244,19 @@ void JackEngine::stopAudio() | |||||
| for(int i = 0; i < 2; ++i) { | for(int i = 0; i < 2; ++i) { | ||||
| jack_port_t *port = audio.ports[i]; | jack_port_t *port = audio.ports[i]; | ||||
| audio.ports[i] = NULL; | audio.ports[i] = NULL; | ||||
| if(NULL != port) | |||||
| if(jackClient != NULL && NULL != port) | |||||
| jack_port_unregister(jackClient, port); | jack_port_unregister(jackClient, port); | ||||
| } | } | ||||
| midi.jack_sync = false; | midi.jack_sync = false; | ||||
| if(osc.oscport) { | |||||
| if (jackClient != NULL) { | |||||
| jack_port_unregister(jackClient, osc.oscport); | |||||
| #ifdef JACK_HAS_METADATA_API | |||||
| jack_uuid_t uuid = jack_port_uuid(osc.oscport); | |||||
| jack_remove_property(jackClient, uuid, "http://jackaudio.org/metadata/event-types"); | |||||
| #endif // JACK_HAS_METADATA_API | |||||
| } | |||||
| } | |||||
| if(!getMidiEn()) | if(!getMidiEn()) | ||||
| disconnectJack(); | disconnectJack(); | ||||
| } | } | ||||
| @@ -293,6 +320,20 @@ int JackEngine::processCallback(jack_nframes_t nframes) | |||||
| bool JackEngine::processAudio(jack_nframes_t nframes) | bool JackEngine::processAudio(jack_nframes_t nframes) | ||||
| { | { | ||||
| //handle rt osc events first | |||||
| void *oscport = jack_port_get_buffer(osc.oscport, nframes); | |||||
| size_t osc_packets = jack_osc_get_event_count(oscport); | |||||
| for(size_t i = 0; i < osc_packets; ++i) { | |||||
| jack_osc_event_t event; | |||||
| if(jack_osc_event_get(&event, oscport, i)) | |||||
| continue; | |||||
| if(*event.buffer!='/') //Bundles are unhandled | |||||
| continue; | |||||
| //TODO validate message length | |||||
| OutMgr::getInstance().applyOscEventRt((char*)event.buffer); | |||||
| } | |||||
| for(int port = 0; port < 2; ++port) { | for(int port = 0; port < 2; ++port) { | ||||
| audio.portBuffs[port] = | audio.portBuffs[port] = | ||||
| (jsample_t *)jack_port_get_buffer(audio.ports[port], nframes); | (jsample_t *)jack_port_get_buffer(audio.ports[port], nframes); | ||||
| @@ -1,20 +1,22 @@ | |||||
| /* | /* | ||||
| JackEngine.h | |||||
| ZynAddSubFX - a software synthesizer | |||||
| JackEngine.h - Jack Driver | |||||
| Copyright 2009, Alan Calvert | |||||
| Copyright 2009, Alan Calvert | |||||
| 2014, Mark McCurry | |||||
| This file is part of yoshimi, which is free software: you can | |||||
| redistribute it and/or modify it under the terms of the GNU General | |||||
| Public License as published by the Free Software Foundation, either | |||||
| version 3 of the License, or (at your option) any later version. | |||||
| This program is free software; you can redistribute it and/or modify | |||||
| it under the terms of version 2 of the GNU General Public License | |||||
| as published by the Free Software Foundation. | |||||
| yoshimi is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License for more details. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License (version 2 or later) for more details. | |||||
| You should have received a copy of the GNU General Public License | |||||
| along with yoshimi. If not, see <http://www.gnu.org/licenses/>. | |||||
| You should have received a copy of the GNU General Public License (version 2) | |||||
| along with this program; if not, write to the Free Software Foundation, | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||||
| */ | */ | ||||
| #ifndef JACK_ENGINE_H | #ifndef JACK_ENGINE_H | ||||
| @@ -34,7 +36,7 @@ typedef jack_default_audio_sample_t jsample_t; | |||||
| class JackEngine:public AudioOut, MidiIn | class JackEngine:public AudioOut, MidiIn | ||||
| { | { | ||||
| public: | public: | ||||
| JackEngine(); | |||||
| JackEngine(const SYNTH_T &synth); | |||||
| ~JackEngine() { } | ~JackEngine() { } | ||||
| bool Start(); | bool Start(); | ||||
| @@ -78,6 +80,9 @@ class JackEngine:public AudioOut, MidiIn | |||||
| jack_port_t *ports[2]; | jack_port_t *ports[2]; | ||||
| jsample_t *portBuffs[2]; | jsample_t *portBuffs[2]; | ||||
| } audio; | } audio; | ||||
| struct osc { | |||||
| jack_port_t *oscport; | |||||
| } osc; | |||||
| struct midi { | struct midi { | ||||
| jack_port_t *inport; | jack_port_t *inport; | ||||
| bool jack_sync; | bool jack_sync; | ||||
| @@ -0,0 +1,176 @@ | |||||
| /* | |||||
| ZynAddSubFX - a software synthesizer | |||||
| JackMultiEngine.cpp - Channeled Audio output JACK | |||||
| Copyright (C) 2012-2012 Mark McCurry | |||||
| Author: Mark McCurry | |||||
| This program is free software; you can redistribute it and/or modify | |||||
| it under the terms of version 2 of the GNU General Public License | |||||
| as published by the Free Software Foundation. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License (version 2 or later) for more details. | |||||
| You should have received a copy of the GNU General Public License (version 2) | |||||
| along with this program; if not, write to the Free Software Foundation, | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||||
| */ | |||||
| #include <jack/jack.h> | |||||
| #include <string> | |||||
| #include <cstring> | |||||
| #include <err.h> | |||||
| #include <cstdio> | |||||
| #include <cassert> | |||||
| #include "Nio.h" | |||||
| #include "../Misc/Util.h" | |||||
| #include "../Misc/Master.h" | |||||
| #include "../Misc/Part.h" | |||||
| #include "../Misc/MiddleWare.h" | |||||
| #include "JackMultiEngine.h" | |||||
| extern MiddleWare *middleware; | |||||
| using std::string; | |||||
| struct jack_multi | |||||
| { | |||||
| jack_port_t *ports[NUM_MIDI_PARTS * 2 + 2]; | |||||
| jack_client_t *client; | |||||
| bool running; | |||||
| }; | |||||
| JackMultiEngine::JackMultiEngine(const SYNTH_T &synth) | |||||
| :AudioOut(synth), impl(new jack_multi()) | |||||
| { | |||||
| impl->running = false; | |||||
| impl->client = NULL; | |||||
| name = "JACK-MULTI"; | |||||
| } | |||||
| JackMultiEngine::~JackMultiEngine(void) | |||||
| { | |||||
| delete impl; | |||||
| } | |||||
| void JackMultiEngine::setAudioEn(bool nval) | |||||
| { | |||||
| if(nval) | |||||
| Start(); | |||||
| else | |||||
| Stop(); | |||||
| } | |||||
| bool JackMultiEngine::getAudioEn() const | |||||
| { | |||||
| return impl->running; | |||||
| } | |||||
| bool JackMultiEngine::Start(void) | |||||
| { | |||||
| if(impl->client) | |||||
| return true; | |||||
| string clientname = "zynaddsubfx"; | |||||
| string postfix = Nio::getPostfix(); | |||||
| if(!postfix.empty()) | |||||
| clientname += "_" + postfix; | |||||
| if(Nio::pidInClientName) | |||||
| clientname += "_" + os_pid_as_padded_string(); | |||||
| jack_status_t jackstatus; | |||||
| impl->client = jack_client_open(clientname.c_str(), JackNullOption, &jackstatus); | |||||
| if(!impl->client) | |||||
| errx(1, "failed to connect to jack..."); | |||||
| //Create the set of jack ports | |||||
| char portName[20]; | |||||
| memset(portName,0,sizeof(portName)); | |||||
| #define JACK_REGISTER(x) jack_port_register(impl->client, x, \ | |||||
| JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0) | |||||
| //Create the master wet port | |||||
| impl->ports[0] = JACK_REGISTER("out-L"); | |||||
| impl->ports[1] = JACK_REGISTER("out-R"); | |||||
| //Create all part's outputs | |||||
| for(int i = 0; i < NUM_MIDI_PARTS * 2; i += 2) { | |||||
| snprintf(portName, 19, "part%d/out-L", i / 2); | |||||
| impl->ports[2 + i] = JACK_REGISTER(portName); | |||||
| snprintf(portName, 19, "part%d/out-R", i / 2); | |||||
| impl->ports[3 + i] = JACK_REGISTER(portName); | |||||
| } | |||||
| //verify that all sample rate and buffer_size are the same in jack. | |||||
| //This insures that the connection can be made with no resampling or | |||||
| //buffering | |||||
| if(synth.samplerate != jack_get_sample_rate(impl->client)) | |||||
| errx(1, "jack must have the same sample rate!"); | |||||
| if(synth.buffersize != (int) jack_get_buffer_size(impl->client)) | |||||
| errx(1, "jack must have the same buffer size"); | |||||
| jack_set_process_callback(impl->client, _processCallback, this); | |||||
| //run | |||||
| if(jack_activate(impl->client)) | |||||
| errx(1, "failed at starting the jack client"); | |||||
| impl->running = true; | |||||
| return true; | |||||
| } | |||||
| int JackMultiEngine::_processCallback(jack_nframes_t nframes, void *arg) | |||||
| { | |||||
| return static_cast<JackMultiEngine *>(arg)->processAudio(nframes); | |||||
| } | |||||
| int JackMultiEngine::processAudio(jack_nframes_t nframes) | |||||
| { | |||||
| //Gather all buffers | |||||
| float *buffers[NUM_MIDI_PARTS * 2 + 2]; | |||||
| for(int i = 0; i < NUM_MIDI_PARTS * 2 + 2; ++i) { | |||||
| buffers[i] = | |||||
| (float *)jack_port_get_buffer(impl->ports[i], nframes); | |||||
| assert(buffers[i]); | |||||
| } | |||||
| //Get the wet samples from OutMgr | |||||
| Stereo<float *> smp = getNext(); | |||||
| memcpy(buffers[0], smp.l, synth.bufferbytes); | |||||
| memcpy(buffers[1], smp.r, synth.bufferbytes); | |||||
| //Gather other samples from individual parts | |||||
| Master &master = *middleware->spawnMaster(); | |||||
| for(int i = 0; i < NUM_MIDI_PARTS; ++i) { | |||||
| memcpy(buffers[2*i + 2], master.part[i]->partoutl, synth.bufferbytes); | |||||
| memcpy(buffers[2*i + 3], master.part[i]->partoutr, synth.bufferbytes); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| void JackMultiEngine::Stop() | |||||
| { | |||||
| for(int i = 0; i < NUM_MIDI_PARTS * 2 + 2; ++i) { | |||||
| jack_port_t *port = impl->ports[i]; | |||||
| impl->ports[i] = NULL; | |||||
| if(port) | |||||
| jack_port_unregister(impl->client, port); | |||||
| } | |||||
| jack_client_close(impl->client); | |||||
| impl->client = NULL; | |||||
| impl->running = false; | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| /* | |||||
| ZynAddSubFX - a software synthesizer | |||||
| JackMultiEngine.h - Channeled Audio output JACK | |||||
| Copyright (C) 2012-2012 Mark McCurry | |||||
| Author: Mark McCurry | |||||
| This program is free software; you can redistribute it and/or modify | |||||
| it under the terms of version 2 of the GNU General Public License | |||||
| as published by the Free Software Foundation. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License (version 2 or later) for more details. | |||||
| You should have received a copy of the GNU General Public License (version 2) | |||||
| along with this program; if not, write to the Free Software Foundation, | |||||
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||||
| */ | |||||
| #ifndef JACK_MULTI_ENGINE | |||||
| #define JACK_MULTI_ENGINE | |||||
| #include "AudioOut.h" | |||||
| class JackMultiEngine:public AudioOut | |||||
| { | |||||
| public: | |||||
| JackMultiEngine(const SYNTH_T &synth); | |||||
| ~JackMultiEngine(void); | |||||
| void setAudioEn(bool nval); | |||||
| bool getAudioEn() const; | |||||
| bool Start(void); | |||||
| void Stop(void); | |||||
| private: | |||||
| static int _processCallback(unsigned nframes, void *arg); | |||||
| int processAudio(unsigned nframes); | |||||
| struct jack_multi *impl; | |||||
| }; | |||||
| #endif | |||||
| @@ -12,26 +12,39 @@ using std::set; | |||||
| using std::cerr; | using std::cerr; | ||||
| using std::endl; | using std::endl; | ||||
| #ifndef IN_DEFAULT | |||||
| #define IN_DEFAULT "NULL" | |||||
| #endif | |||||
| #ifndef OUT_DEFAULT | |||||
| #define OUT_DEFAULT "NULL" | |||||
| #endif | |||||
| InMgr *in = NULL; | InMgr *in = NULL; | ||||
| OutMgr *out = NULL; | OutMgr *out = NULL; | ||||
| EngineMgr *eng = NULL; | EngineMgr *eng = NULL; | ||||
| string postfix; | string postfix; | ||||
| bool Nio::autoConnect = false; | |||||
| string Nio::defaultSource = IN_DEFAULT; | |||||
| string Nio::defaultSink = OUT_DEFAULT; | |||||
| bool Nio::autoConnect = false; | |||||
| bool Nio::pidInClientName = false; | |||||
| string Nio::defaultSource = IN_DEFAULT; | |||||
| string Nio::defaultSink = OUT_DEFAULT; | |||||
| void Nio::init(void) | |||||
| void Nio::init(const SYNTH_T &synth, class Master *master) | |||||
| { | { | ||||
| in = &InMgr::getInstance(); //Enable input wrapper | in = &InMgr::getInstance(); //Enable input wrapper | ||||
| out = &OutMgr::getInstance(); //Initialize the Output Systems | |||||
| eng = &EngineMgr::getInstance(); //Initialize The Engines | |||||
| out = &OutMgr::getInstance(&synth); //Initialize the Output Systems | |||||
| eng = &EngineMgr::getInstance(&synth); //Initialize The Engines | |||||
| in->setMaster(master); | |||||
| out->setMaster(master); | |||||
| } | } | ||||
| bool Nio::start() | bool Nio::start() | ||||
| { | { | ||||
| init(); | |||||
| return eng->start(); | |||||
| if(eng) | |||||
| return eng->start(); | |||||
| else | |||||
| return false; | |||||
| } | } | ||||
| void Nio::stop() | void Nio::stop() | ||||
| @@ -105,6 +118,10 @@ string Nio::getSink() | |||||
| #include <jack/jack.h> | #include <jack/jack.h> | ||||
| void Nio::preferedSampleRate(unsigned &rate) | void Nio::preferedSampleRate(unsigned &rate) | ||||
| { | { | ||||
| //XXX hello non portable code | |||||
| if(system("ps aux | grep jack")) | |||||
| return; | |||||
| jack_client_t *client = jack_client_open("temp-client", | jack_client_t *client = jack_client_open("temp-client", | ||||
| JackNoStartServer, 0); | JackNoStartServer, 0); | ||||
| if(client) { | if(client) { | ||||
| @@ -117,6 +134,12 @@ void Nio::preferedSampleRate(unsigned &) | |||||
| {} | {} | ||||
| #endif | #endif | ||||
| void Nio::masterSwap(Master *master) | |||||
| { | |||||
| in->setMaster(master); | |||||
| out->setMaster(master); | |||||
| } | |||||
| void Nio::waveNew(class WavFile *wave) | void Nio::waveNew(class WavFile *wave) | ||||
| { | { | ||||
| out->wave->newFile(wave); | out->wave->newFile(wave); | ||||
| @@ -4,13 +4,15 @@ | |||||
| #include <set> | #include <set> | ||||
| class WavFile; | class WavFile; | ||||
| class Master; | |||||
| struct SYNTH_T; | |||||
| /**Interface to Nio Subsystem | /**Interface to Nio Subsystem | ||||
| * | * | ||||
| * Should be only externally included header */ | * Should be only externally included header */ | ||||
| namespace Nio | namespace Nio | ||||
| { | { | ||||
| void init(void); | |||||
| void init(const SYNTH_T &synth, Master *master); | |||||
| bool start(void); | bool start(void); | ||||
| void stop(void); | void stop(void); | ||||
| @@ -32,6 +34,8 @@ namespace Nio | |||||
| //Get the prefered sample rate from jack (if running) | //Get the prefered sample rate from jack (if running) | ||||
| void preferedSampleRate(unsigned &rate); | void preferedSampleRate(unsigned &rate); | ||||
| //Complete Master Swaps to ONLY BE CALLED FROM RT CONTEXT | |||||
| void masterSwap(Master *master); | |||||
| //Wave writing | //Wave writing | ||||
| void waveNew(class WavFile *wave); | void waveNew(class WavFile *wave); | ||||
| @@ -40,6 +44,7 @@ namespace Nio | |||||
| void waveEnd(void); | void waveEnd(void); | ||||
| extern bool autoConnect; | extern bool autoConnect; | ||||
| extern bool pidInClientName; | |||||
| extern std::string defaultSource; | extern std::string defaultSource; | ||||
| extern std::string defaultSink; | extern std::string defaultSink; | ||||
| }; | }; | ||||
| @@ -28,8 +28,8 @@ | |||||
| using namespace std; | using namespace std; | ||||
| NulEngine::NulEngine() | |||||
| :AudioOut(), pThread(NULL) | |||||
| NulEngine::NulEngine(const SYNTH_T &synth_) | |||||
| :AudioOut(synth_), pThread(NULL) | |||||
| { | { | ||||
| name = "NULL"; | name = "NULL"; | ||||
| playing_until.tv_sec = 0; | playing_until.tv_sec = 0; | ||||
| @@ -62,8 +62,8 @@ void *NulEngine::AudioThread() | |||||
| if(remaining < 0) | if(remaining < 0) | ||||
| cerr << "WARNING - too late" << endl; | cerr << "WARNING - too late" << endl; | ||||
| } | } | ||||
| playing_until.tv_usec += synth->buffersize * 1000000 | |||||
| / synth->samplerate; | |||||
| playing_until.tv_usec += synth.buffersize * 1000000 | |||||
| / synth.samplerate; | |||||
| if(remaining < 0) | if(remaining < 0) | ||||
| playing_until.tv_usec -= remaining; | playing_until.tv_usec -= remaining; | ||||
| playing_until.tv_sec += playing_until.tv_usec / 1000000; | playing_until.tv_sec += playing_until.tv_usec / 1000000; | ||||
| @@ -32,7 +32,7 @@ | |||||
| class NulEngine:public AudioOut, MidiIn | class NulEngine:public AudioOut, MidiIn | ||||
| { | { | ||||
| public: | public: | ||||
| NulEngine(); | |||||
| NulEngine(const SYNTH_T &synth_); | |||||
| ~NulEngine(); | ~NulEngine(); | ||||
| bool Start(); | bool Start(); | ||||
| @@ -28,71 +28,254 @@ | |||||
| #include <stdlib.h> | #include <stdlib.h> | ||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #include <fcntl.h> | #include <fcntl.h> | ||||
| #include <errno.h> | |||||
| #include <sys/soundcard.h> | #include <sys/soundcard.h> | ||||
| #include <sys/stat.h> | #include <sys/stat.h> | ||||
| #include <sys/ioctl.h> | #include <sys/ioctl.h> | ||||
| #include <unistd.h> | #include <unistd.h> | ||||
| #include <iostream> | #include <iostream> | ||||
| #include <signal.h> | |||||
| #include "InMgr.h" | #include "InMgr.h" | ||||
| using namespace std; | using namespace std; | ||||
| OssEngine::OssEngine() | |||||
| :AudioOut(), engThread(NULL) | |||||
| /* | |||||
| * The following statemachine converts MIDI commands to USB MIDI | |||||
| * packets, derived from Linux's usbmidi.c, which was written by | |||||
| * "Clemens Ladisch". It is used to figure out when a MIDI command is | |||||
| * complete, without having to read the first byte of the next MIDI | |||||
| * command. This is useful when connecting to so-called system PIPEs | |||||
| * and FIFOs. See "man mkfifo". | |||||
| * | |||||
| * Return values: | |||||
| * 0: No command | |||||
| * Else: Command is complete | |||||
| */ | |||||
| static unsigned char | |||||
| OssMidiParse(struct OssMidiParse &midi_parse, | |||||
| unsigned char cn, unsigned char b) | |||||
| { | |||||
| unsigned char p0 = (cn << 4); | |||||
| if(b >= 0xf8) { | |||||
| midi_parse.temp_0[0] = p0 | 0x0f; | |||||
| midi_parse.temp_0[1] = b; | |||||
| midi_parse.temp_0[2] = 0; | |||||
| midi_parse.temp_0[3] = 0; | |||||
| midi_parse.temp_cmd = midi_parse.temp_0; | |||||
| return (1); | |||||
| } else if(b >= 0xf0) { | |||||
| switch (b) { | |||||
| case 0xf0: /* system exclusive begin */ | |||||
| midi_parse.temp_1[1] = b; | |||||
| midi_parse.state = OSSMIDI_ST_SYSEX_1; | |||||
| break; | |||||
| case 0xf1: /* MIDI time code */ | |||||
| case 0xf3: /* song select */ | |||||
| midi_parse.temp_1[1] = b; | |||||
| midi_parse.state = OSSMIDI_ST_1PARAM; | |||||
| break; | |||||
| case 0xf2: /* song position pointer */ | |||||
| midi_parse.temp_1[1] = b; | |||||
| midi_parse.state = OSSMIDI_ST_2PARAM_1; | |||||
| break; | |||||
| case 0xf4: /* unknown */ | |||||
| case 0xf5: /* unknown */ | |||||
| midi_parse.state = OSSMIDI_ST_UNKNOWN; | |||||
| break; | |||||
| case 0xf6: /* tune request */ | |||||
| midi_parse.temp_1[0] = p0 | 0x05; | |||||
| midi_parse.temp_1[1] = 0xf6; | |||||
| midi_parse.temp_1[2] = 0; | |||||
| midi_parse.temp_1[3] = 0; | |||||
| midi_parse.temp_cmd = midi_parse.temp_1; | |||||
| midi_parse.state = OSSMIDI_ST_UNKNOWN; | |||||
| return (1); | |||||
| case 0xf7: /* system exclusive end */ | |||||
| switch (midi_parse.state) { | |||||
| case OSSMIDI_ST_SYSEX_0: | |||||
| midi_parse.temp_1[0] = p0 | 0x05; | |||||
| midi_parse.temp_1[1] = 0xf7; | |||||
| midi_parse.temp_1[2] = 0; | |||||
| midi_parse.temp_1[3] = 0; | |||||
| midi_parse.temp_cmd = midi_parse.temp_1; | |||||
| midi_parse.state = OSSMIDI_ST_UNKNOWN; | |||||
| return (1); | |||||
| case OSSMIDI_ST_SYSEX_1: | |||||
| midi_parse.temp_1[0] = p0 | 0x06; | |||||
| midi_parse.temp_1[2] = 0xf7; | |||||
| midi_parse.temp_1[3] = 0; | |||||
| midi_parse.temp_cmd = midi_parse.temp_1; | |||||
| midi_parse.state = OSSMIDI_ST_UNKNOWN; | |||||
| return (1); | |||||
| case OSSMIDI_ST_SYSEX_2: | |||||
| midi_parse.temp_1[0] = p0 | 0x07; | |||||
| midi_parse.temp_1[3] = 0xf7; | |||||
| midi_parse.temp_cmd = midi_parse.temp_1; | |||||
| midi_parse.state = OSSMIDI_ST_UNKNOWN; | |||||
| return (1); | |||||
| } | |||||
| midi_parse.state = OSSMIDI_ST_UNKNOWN; | |||||
| break; | |||||
| } | |||||
| } else if(b >= 0x80) { | |||||
| midi_parse.temp_1[1] = b; | |||||
| if((b >= 0xc0) && (b <= 0xdf)) { | |||||
| midi_parse.state = OSSMIDI_ST_1PARAM; | |||||
| } else { | |||||
| midi_parse.state = OSSMIDI_ST_2PARAM_1; | |||||
| } | |||||
| } else { /* b < 0x80 */ | |||||
| switch (midi_parse.state) { | |||||
| case OSSMIDI_ST_1PARAM: | |||||
| if(midi_parse.temp_1[1] < 0xf0) { | |||||
| p0 |= midi_parse.temp_1[1] >> 4; | |||||
| } else { | |||||
| p0 |= 0x02; | |||||
| midi_parse.state = OSSMIDI_ST_UNKNOWN; | |||||
| } | |||||
| midi_parse.temp_1[0] = p0; | |||||
| midi_parse.temp_1[2] = b; | |||||
| midi_parse.temp_1[3] = 0; | |||||
| midi_parse.temp_cmd = midi_parse.temp_1; | |||||
| return (1); | |||||
| case OSSMIDI_ST_2PARAM_1: | |||||
| midi_parse.temp_1[2] = b; | |||||
| midi_parse.state = OSSMIDI_ST_2PARAM_2; | |||||
| break; | |||||
| case OSSMIDI_ST_2PARAM_2: | |||||
| if(midi_parse.temp_1[1] < 0xf0) { | |||||
| p0 |= midi_parse.temp_1[1] >> 4; | |||||
| midi_parse.state = OSSMIDI_ST_2PARAM_1; | |||||
| } else { | |||||
| p0 |= 0x03; | |||||
| midi_parse.state = OSSMIDI_ST_UNKNOWN; | |||||
| } | |||||
| midi_parse.temp_1[0] = p0; | |||||
| midi_parse.temp_1[3] = b; | |||||
| midi_parse.temp_cmd = midi_parse.temp_1; | |||||
| return (1); | |||||
| case OSSMIDI_ST_SYSEX_0: | |||||
| midi_parse.temp_1[1] = b; | |||||
| midi_parse.state = OSSMIDI_ST_SYSEX_1; | |||||
| break; | |||||
| case OSSMIDI_ST_SYSEX_1: | |||||
| midi_parse.temp_1[2] = b; | |||||
| midi_parse.state = OSSMIDI_ST_SYSEX_2; | |||||
| break; | |||||
| case OSSMIDI_ST_SYSEX_2: | |||||
| midi_parse.temp_1[0] = p0 | 0x04; | |||||
| midi_parse.temp_1[3] = b; | |||||
| midi_parse.temp_cmd = midi_parse.temp_1; | |||||
| midi_parse.state = OSSMIDI_ST_SYSEX_0; | |||||
| return (1); | |||||
| default: | |||||
| break; | |||||
| } | |||||
| } | |||||
| return (0); | |||||
| } | |||||
| OssEngine::OssEngine(const SYNTH_T &synth) | |||||
| :AudioOut(synth), audioThread(NULL), midiThread(NULL) | |||||
| { | { | ||||
| name = "OSS"; | name = "OSS"; | ||||
| midi.handle = -1; | midi.handle = -1; | ||||
| audio.handle = -1; | audio.handle = -1; | ||||
| audio.smps = new short[synth->buffersize * 2]; | |||||
| memset(audio.smps, 0, synth->bufferbytes); | |||||
| /* allocate worst case audio buffer */ | |||||
| audio.smps.ps32 = new int[synth.buffersize * 2]; | |||||
| memset(audio.smps.ps32, 0, sizeof(int) * synth.buffersize * 2); | |||||
| memset(&midi.state, 0, sizeof(midi.state)); | |||||
| } | } | ||||
| OssEngine::~OssEngine() | OssEngine::~OssEngine() | ||||
| { | { | ||||
| Stop(); | Stop(); | ||||
| delete [] audio.smps; | |||||
| delete [] audio.smps.ps32; | |||||
| } | } | ||||
| bool OssEngine::openAudio() | bool OssEngine::openAudio() | ||||
| { | { | ||||
| int x; | |||||
| if(audio.handle != -1) | if(audio.handle != -1) | ||||
| return true; //already open | return true; //already open | ||||
| int snd_bitsize = 16; | |||||
| int snd_fragment = 0x00080009; //fragment size (?); | |||||
| int snd_fragment; | |||||
| int snd_stereo = 1; //stereo; | int snd_stereo = 1; //stereo; | ||||
| int snd_format = AFMT_S16_LE; | |||||
| int snd_samplerate = synth->samplerate; | |||||
| int snd_samplerate = synth.samplerate; | |||||
| const char *device = config.cfg.LinuxOSSWaveOutDev; | |||||
| if(getenv("DSP_DEVICE")) | |||||
| device = getenv("DSP_DEVICE"); | |||||
| const char *device = getenv("DSP_DEVICE"); | |||||
| if(device == NULL) | |||||
| device = config.cfg.LinuxOSSWaveOutDev; | |||||
| audio.handle = open(device, O_WRONLY, 0); | |||||
| /* NOTE: PIPEs and FIFOs can block when opening them */ | |||||
| audio.handle = open(device, O_WRONLY, O_NONBLOCK); | |||||
| if(audio.handle == -1) { | if(audio.handle == -1) { | ||||
| cerr << "ERROR - I can't open the " | cerr << "ERROR - I can't open the " | ||||
| << device << '.' << endl; | << device << '.' << endl; | ||||
| return false; | return false; | ||||
| } | } | ||||
| ioctl(audio.handle, SNDCTL_DSP_RESET, NULL); | ioctl(audio.handle, SNDCTL_DSP_RESET, NULL); | ||||
| ioctl(audio.handle, SNDCTL_DSP_SETFMT, &snd_format); | |||||
| /* Figure out the correct format first */ | |||||
| int snd_format16 = AFMT_S16_NE; | |||||
| #ifdef AFMT_S32_NE | |||||
| int snd_format32 = AFMT_S32_NE; | |||||
| if (ioctl(audio.handle, SNDCTL_DSP_SETFMT, &snd_format32) == 0) { | |||||
| audio.is32bit = true; | |||||
| } else | |||||
| #endif | |||||
| if (ioctl(audio.handle, SNDCTL_DSP_SETFMT, &snd_format16) == 0) { | |||||
| audio.is32bit = false; | |||||
| } else { | |||||
| cerr << "ERROR - I cannot set DSP format for " | |||||
| << device << '.' << endl; | |||||
| goto error; | |||||
| } | |||||
| ioctl(audio.handle, SNDCTL_DSP_STEREO, &snd_stereo); | ioctl(audio.handle, SNDCTL_DSP_STEREO, &snd_stereo); | ||||
| ioctl(audio.handle, SNDCTL_DSP_SPEED, &snd_samplerate); | ioctl(audio.handle, SNDCTL_DSP_SPEED, &snd_samplerate); | ||||
| ioctl(audio.handle, SNDCTL_DSP_SAMPLESIZE, &snd_bitsize); | |||||
| ioctl(audio.handle, SNDCTL_DSP_SETFRAGMENT, &snd_fragment); | |||||
| if(!getMidiEn()) { | |||||
| pthread_attr_t attr; | |||||
| pthread_attr_init(&attr); | |||||
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); | |||||
| engThread = new pthread_t; | |||||
| pthread_create(engThread, &attr, _thread, this); | |||||
| if (snd_samplerate != (int)synth.samplerate) { | |||||
| cerr << "ERROR - Cannot set samplerate for " | |||||
| << device << ". " << snd_samplerate | |||||
| << " != " << synth.samplerate << endl; | |||||
| goto error; | |||||
| } | } | ||||
| /* compute buffer size for 16-bit stereo samples */ | |||||
| audio.buffersize = 4 * synth.buffersize; | |||||
| if (audio.is32bit) | |||||
| audio.buffersize *= 2; | |||||
| for (x = 4; x < 20; x++) { | |||||
| if ((1 << x) >= audio.buffersize) | |||||
| break; | |||||
| } | |||||
| snd_fragment = 0x20000 | x; /* 2x buffer */ | |||||
| ioctl(audio.handle, SNDCTL_DSP_SETFRAGMENT, &snd_fragment); | |||||
| pthread_attr_t attr; | |||||
| pthread_attr_init(&attr); | |||||
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); | |||||
| audioThread = new pthread_t; | |||||
| pthread_create(audioThread, &attr, _audioThreadCb, this); | |||||
| return true; | return true; | ||||
| error: | |||||
| close(audio.handle); | |||||
| audio.handle = -1; | |||||
| return false; | |||||
| } | } | ||||
| void OssEngine::stopAudio() | void OssEngine::stopAudio() | ||||
| @@ -102,12 +285,12 @@ void OssEngine::stopAudio() | |||||
| return; | return; | ||||
| audio.handle = -1; | audio.handle = -1; | ||||
| if(!getMidiEn() && engThread) | |||||
| pthread_join(*engThread, NULL); | |||||
| delete engThread; | |||||
| engThread = NULL; | |||||
| /* close handle first, so that write() exits */ | |||||
| close(handle); | close(handle); | ||||
| pthread_join(*audioThread, NULL); | |||||
| delete audioThread; | |||||
| audioThread = NULL; | |||||
| } | } | ||||
| bool OssEngine::Start() | bool OssEngine::Start() | ||||
| @@ -165,19 +348,22 @@ bool OssEngine::openMidi() | |||||
| if(handle != -1) | if(handle != -1) | ||||
| return true; //already open | return true; //already open | ||||
| handle = open(config.cfg.LinuxOSSSeqInDev, O_RDONLY, 0); | |||||
| const char *device = getenv("MIDI_DEVICE"); | |||||
| if(device == NULL) | |||||
| device = config.cfg.LinuxOSSSeqInDev; | |||||
| /* NOTE: PIPEs and FIFOs can block when opening them */ | |||||
| handle = open(device, O_RDONLY, O_NONBLOCK); | |||||
| if(-1 == handle) | if(-1 == handle) | ||||
| return false; | return false; | ||||
| midi.handle = handle; | midi.handle = handle; | ||||
| if(!getAudioEn()) { | |||||
| pthread_attr_t attr; | |||||
| pthread_attr_init(&attr); | |||||
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); | |||||
| engThread = new pthread_t; | |||||
| pthread_create(engThread, &attr, _thread, this); | |||||
| } | |||||
| pthread_attr_t attr; | |||||
| pthread_attr_init(&attr); | |||||
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); | |||||
| midiThread = new pthread_t; | |||||
| pthread_create(midiThread, &attr, _midiThreadCb, this); | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -190,96 +376,110 @@ void OssEngine::stopMidi() | |||||
| midi.handle = -1; | midi.handle = -1; | ||||
| if(!getAudioEn() && engThread) { | |||||
| pthread_join(*engThread, NULL); | |||||
| delete engThread; | |||||
| engThread = NULL; | |||||
| } | |||||
| /* close handle first, so that read() exits */ | |||||
| close(handle); | close(handle); | ||||
| pthread_join(*midiThread, NULL); | |||||
| delete midiThread; | |||||
| midiThread = NULL; | |||||
| } | } | ||||
| void *OssEngine::_thread(void *arg) | |||||
| void *OssEngine::_audioThreadCb(void *arg) | |||||
| { | { | ||||
| return (static_cast<OssEngine *>(arg))->thread(); | |||||
| return (static_cast<OssEngine *>(arg))->audioThreadCb(); | |||||
| } | } | ||||
| void *OssEngine::thread() | |||||
| void *OssEngine::_midiThreadCb(void *arg) | |||||
| { | { | ||||
| unsigned char tmp[4] = {0, 0, 0, 0}; | |||||
| return (static_cast<OssEngine *>(arg))->midiThreadCb(); | |||||
| } | |||||
| void *OssEngine::audioThreadCb() | |||||
| { | |||||
| /* | |||||
| * In case the audio device is a PIPE/FIFO, | |||||
| * we need to ignore any PIPE signals: | |||||
| */ | |||||
| signal(SIGPIPE, SIG_IGN); | |||||
| set_realtime(); | set_realtime(); | ||||
| while(getAudioEn() || getMidiEn()) { | |||||
| if(getAudioEn()) { | |||||
| const Stereo<float *> smps = getNext(); | |||||
| float l, r; | |||||
| for(int i = 0; i < synth->buffersize; ++i) { | |||||
| l = smps.l[i]; | |||||
| r = smps.r[i]; | |||||
| if(l < -1.0f) | |||||
| l = -1.0f; | |||||
| else | |||||
| while(getAudioEn()) { | |||||
| const Stereo<float *> smps = getNext(); | |||||
| float l, r; | |||||
| for(int i = 0; i < synth.buffersize; ++i) { | |||||
| l = smps.l[i]; | |||||
| r = smps.r[i]; | |||||
| if(l < -1.0f) | |||||
| l = -1.0f; | |||||
| else | |||||
| if(l > 1.0f) | if(l > 1.0f) | ||||
| l = 1.0f; | l = 1.0f; | ||||
| if(r < -1.0f) | |||||
| r = -1.0f; | |||||
| else | |||||
| if(r < -1.0f) | |||||
| r = -1.0f; | |||||
| else | |||||
| if(r > 1.0f) | if(r > 1.0f) | ||||
| r = 1.0f; | r = 1.0f; | ||||
| audio.smps[i * 2] = (short int) (l * 32767.0f); | |||||
| audio.smps[i * 2 + 1] = (short int) (r * 32767.0f); | |||||
| if (audio.is32bit) { | |||||
| audio.smps.ps32[i * 2] = (int) (l * 2147483647.0f); | |||||
| audio.smps.ps32[i * 2 + 1] = (int) (r * 2147483647.0f); | |||||
| } else {/* 16bit */ | |||||
| audio.smps.ps16[i * 2] = (short int) (l * 32767.0f); | |||||
| audio.smps.ps16[i * 2 + 1] = (short int) (r * 32767.0f); | |||||
| } | } | ||||
| int handle = audio.handle; | |||||
| if(handle != -1) | |||||
| write(handle, audio.smps, synth->buffersize * 4); // *2 because is 16 bit, again * 2 because is stereo | |||||
| else | |||||
| break; | |||||
| } | } | ||||
| //Collect up to 30 midi events | |||||
| for(int k = 0; k < 30 && getMidiEn(); ++k) { | |||||
| static char escaped; | |||||
| memset(tmp, 0, 4); | |||||
| int error; | |||||
| do { | |||||
| /* make a copy of handle, in case of OSS audio disable */ | |||||
| int handle = audio.handle; | |||||
| if(handle == -1) | |||||
| goto done; | |||||
| error = write(handle, audio.smps.ps32, audio.buffersize); | |||||
| } while (error == -1 && errno == EINTR); | |||||
| if(escaped) { | |||||
| tmp[0] = escaped; | |||||
| escaped = 0; | |||||
| } | |||||
| else { | |||||
| getMidi(tmp); | |||||
| if(!(tmp[0] & 0x80)) | |||||
| continue; | |||||
| } | |||||
| getMidi(tmp + 1); | |||||
| if(tmp[1] & 0x80) { | |||||
| escaped = tmp[1]; | |||||
| tmp[1] = 0; | |||||
| } | |||||
| else { | |||||
| getMidi(tmp + 2); | |||||
| if(tmp[2] & 0x80) { | |||||
| escaped = tmp[2]; | |||||
| tmp[2] = 0; | |||||
| } | |||||
| else { | |||||
| getMidi(tmp + 3); | |||||
| if(tmp[3] & 0x80) { | |||||
| escaped = tmp[3]; | |||||
| tmp[3] = 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| midiProcess(tmp[0], tmp[1], tmp[2]); | |||||
| } | |||||
| if(error == -1) | |||||
| goto done; | |||||
| } | } | ||||
| done: | |||||
| pthread_exit(NULL); | pthread_exit(NULL); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| void OssEngine::getMidi(unsigned char *midiPtr) | |||||
| void *OssEngine::midiThreadCb() | |||||
| { | { | ||||
| read(midi.handle, midiPtr, 1); | |||||
| /* | |||||
| * In case the MIDI device is a PIPE/FIFO, | |||||
| * we need to ignore any PIPE signals: | |||||
| */ | |||||
| signal(SIGPIPE, SIG_IGN); | |||||
| set_realtime(); | |||||
| while(getMidiEn()) { | |||||
| unsigned char tmp; | |||||
| int error; | |||||
| do { | |||||
| /* make a copy of handle, in case of OSS MIDI disable */ | |||||
| int handle = midi.handle; | |||||
| if(handle == -1) | |||||
| goto done; | |||||
| error = read(handle, &tmp, 1); | |||||
| } while (error == -1 && errno == EINTR); | |||||
| /* check that we got one byte */ | |||||
| if(error != 1) | |||||
| goto done; | |||||
| /* feed MIDI byte into statemachine */ | |||||
| if(OssMidiParse(midi.state, 0, tmp)) { | |||||
| /* we got a complete MIDI command */ | |||||
| midiProcess(midi.state.temp_cmd[1], | |||||
| midi.state.temp_cmd[2], | |||||
| midi.state.temp_cmd[3]); | |||||
| } | |||||
| } | |||||
| done: | |||||
| pthread_exit(NULL); | |||||
| return NULL; | |||||
| } | } | ||||